Compare commits

...

219 Commits

Author SHA1 Message Date
Andras Bacsai
671b22e964 Merge pull request #2941 from coollabsio/next
v4.0.0-beta.320
2024-08-08 11:27:29 +02:00
Andras Bacsai
777bff6553 refactor: Update Traefik labels on init and cleanup unnecessary dynamic proxy configuration 2024-08-08 11:15:33 +02:00
Andras Bacsai
d3b3c2f7fd fix: Add missing middleware for server check job 2024-08-08 10:28:34 +02:00
Andras Bacsai
0b000919cf feat: able to generate only the required labels for resources 2024-08-07 18:27:50 +02:00
Andras Bacsai
92f90d4e52 Merge pull request #2863 from lorenzomigliorero/feat/labels-by-proxy-type
Generate labels by proxy type
2024-08-07 18:06:51 +02:00
Andras Bacsai
59702c6dbc refactor: Update ProxyTypes enum values to use TRAEFIK instead of TRAEFIK_V2 2024-08-07 17:52:51 +02:00
Andras Bacsai
13e8d3c17c fix: update Traefik labels on init 2024-08-07 17:52:43 +02:00
Andras Bacsai
5d384b1149 feat: Support map fields in Directus 2024-08-07 17:43:41 +02:00
Andras Bacsai
7b31955409 Merge pull request #2906 from Loque-/Loque--patch-1
Support map fields in Directus
2024-08-07 17:43:03 +02:00
Andras Bacsai
efb4049966 feat: Improve homepage service template 2024-08-07 12:58:36 +02:00
Andras Bacsai
6da359bc60 fix: empty string content should be saved as a file 2024-08-07 12:58:32 +02:00
Andras Bacsai
f5f3c77d9e Merge pull request #3010 from Telokis/improvate-homepage-template
feat: Improve homepage service template
2024-08-07 12:17:46 +02:00
Andras Bacsai
dc5c324f9c feat: Update Upgrade component to check for latest version of Coolify 2024-08-07 12:16:06 +02:00
Andras Bacsai
d283be4917 disable php code styling for now 2024-08-07 12:12:09 +02:00
Andras Bacsai
f0278bc33d fix: show latest version on manual update view 2024-08-07 12:10:31 +02:00
Andras Bacsai
052565f4e8 Merge pull request #3011 from stooit/feat/enhance-services-api
Return applications & databases in service API.
2024-08-07 12:02:18 +02:00
Andras Bacsai
64146a46fc chore: Update UpdateCoolifyJob to dispatch CheckForUpdatesJob synchronously 2024-08-07 11:58:53 +02:00
Andras Bacsai
5a82395bb7 feat: Update auto update and update check frequencies in settings 2024-08-07 11:55:16 +02:00
Andras Bacsai
d3085e1ade feat: Add manual update check functionality to settings page 2024-08-07 11:42:55 +02:00
Andras Bacsai
af41ed26ba chore: Update UpdateCoolifyJob timeout to 10 minutes 2024-08-07 11:10:02 +02:00
Andras Bacsai
961ba49d89 fix: Handle null and empty disk usage in DockerCleanupJob 2024-08-07 10:54:22 +02:00
Andras Bacsai
a2150f2f7d refactor: Remove unused code and optimize CheckForUpdatesJob 2024-08-07 10:50:12 +02:00
Andras Bacsai
46b549ab8d chore: Update workflows to include PR build and merge manifest steps 2024-08-07 10:02:01 +02:00
Andras Bacsai
e4c3d61b76 fixes 2024-08-07 09:50:29 +02:00
Andras Bacsai
869f0878c2 fix (cloud): cloud instance should get latest servics on update (all servers) 2024-08-07 09:50:12 +02:00
Stuart Rowlands
dfb6d4da3d Return applications & databases in service API. 2024-08-07 17:20:55 +10:00
Telokis
5d4a379e8d Improve homepage service template 2024-08-07 01:06:54 +02:00
Andras Bacsai
73a265107a Merge branch 'next' of github.com:coollabsio/coolify into next 2024-08-06 14:38:41 +02:00
Andras Bacsai
b77171d2f2 fix: settings view
feat: add separate views for settings
2024-08-06 14:36:56 +02:00
andrasbacsai
8b817dad87 Fix styling 2024-08-06 12:04:23 +00:00
Andras Bacsai
c4436aadfa Merge pull request #3000 from ayntk-ai/update-settings-improvements
Feat: Updated Check Frequency and Auto Update Frequency settings
2024-08-06 14:03:41 +02:00
Andras Bacsai
c9a7af0ffa chore: Update navbar and configuration to improve service status check functionality 2024-08-06 14:02:24 +02:00
Andras Bacsai
c648e0dff9 chore: Update navbar to include service status check 2024-08-06 13:50:32 +02:00
Andras Bacsai
e897eb2999 fix: Stop resources gracefully 2024-08-06 13:27:06 +02:00
Andras Bacsai
35e62a3003 chore: Update server form to use password input for IP Address/Domain field 2024-08-06 13:18:57 +02:00
Andras Bacsai
b66c511160 refactor: Update timeout for graceful_shutdown_container in ApplicationDeploymentJob 2024-08-06 13:07:32 +02:00
Andras Bacsai
a866bf437d Merge branch 'next' of github.com:coollabsio/coolify into next 2024-08-06 13:07:03 +02:00
Andras Bacsai
a4f107e191 fix: graceful shutdown 2024-08-06 13:05:34 +02:00
andrasbacsai
f22d7741a3 Fix styling 2024-08-06 10:13:34 +00:00
Andras Bacsai
f4d64e121c Merge pull request #2969 from alexzvn/feat/graceful-shutdown
feat: add graceful shutdown
2024-08-06 12:13:00 +02:00
Andras Bacsai
818d5e1159 fix: minecraft server template fixed 2024-08-06 12:10:29 +02:00
ayntk-ai
7eacdf23f9 fix database migration 2024-08-06 12:00:34 +02:00
ayntk-ai
0459baa55e Update Kernel.php 2024-08-06 11:48:43 +02:00
ayntk-ai
a426c00a03 should work :) 2024-08-06 11:47:48 +02:00
ayntk-ai
fd36e143e0 final conflict hopfully :) 2024-08-06 11:46:26 +02:00
ayntk-ai
2243a3304b remove duplicates 2024-08-06 11:43:55 +02:00
ayntk-ai
bb23141138 add backup line twice 2024-08-06 11:42:01 +02:00
ayntk-ai
5f1e1c0ac4 fix another conflict 2024-08-06 11:41:24 +02:00
Andras Bacsai
ef8be5f133 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-08-06 11:40:16 +02:00
Andras Bacsai
f205c0ab9e Merge pull request #2981 from kunumigab/main
Added minecraft-server (java) as template
2024-08-06 11:40:30 +02:00
ayntk-ai
c4cf116e6e more fixes 2024-08-06 11:38:30 +02:00
ayntk-ai
f7b1aaca92 fix fix 2024-08-06 11:35:22 +02:00
Andras Bacsai
0ce41d2c1c chore: Update registration view to display a notice for first user that it will be an admin 2024-08-06 11:34:51 +02:00
ayntk-ai
d9edb1c72f fix 2024-08-06 11:32:37 +02:00
ayntk-ai
93322dc3cf updated helper text 2024-08-06 11:29:02 +02:00
Andras Bacsai
22f04e4708 refactor: Remove unnecessary debug statement in ApplicationDeploymentJob 2024-08-06 11:27:10 +02:00
ayntk-ai
d9a079c289 fix conflict in kernel.php 2024-08-06 11:25:57 +02:00
Andras Bacsai
f4690bf15a Merge pull request #2958 from filiabel/formbricks-template-update
chore: update Formbricks template with additional env variables
2024-08-06 11:25:46 +02:00
Andras Bacsai
a81faa68b8 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-08-06 11:16:55 +02:00
Andras Bacsai
4b388b463d Merge pull request #2960 from J-o-n-a-t-h-a-n-M-u-e-l-l-e-r/fix-plausible-totp-key
Fixes Plausible TOTP key by changing it from BASE64 to REALBASE64_32
2024-08-06 11:16:58 +02:00
Andras Bacsai
3c98b558f6 fix: do not use port exposes on docker compose buildpacks 2024-08-06 11:16:49 +02:00
Andras Bacsai
e3c7c615c6 refactor: Cleanup unnecessary dynamic proxy configuration in Init command 2024-08-06 10:53:13 +02:00
Andras Bacsai
74e8a4a703 refactor: Simplify log drain installation and stop log drain if necessary 2024-08-06 10:52:47 +02:00
Andras Bacsai
c5de1a25c3 refactor: Remove unnecessary debug statement in ServerCheckJob 2024-08-06 09:45:48 +02:00
ayntk-ai
d4cb7e25dc renamed database migration file 2024-08-05 21:04:47 +02:00
ayntk-ai
50ede5cab9 remove comments and remove duplicated scheduling 2024-08-05 20:57:27 +02:00
ayntk-ai
b64d4881cb made helper more clear 2024-08-05 20:33:20 +02:00
ayntk-ai
4dfec6771c added defaults, remove duplicated cron validation 2024-08-05 20:31:06 +02:00
Andras Bacsai
92ebc3f0c6 refactor 2024-08-05 20:08:37 +02:00
Andras Bacsai
3e0821e471 refactor 2024-08-05 20:07:08 +02:00
ayntk-ai
38976dac12 fixes and check for valid cron expressions 2024-08-05 20:05:38 +02:00
Andras Bacsai
ea5101c814 fix: file storages (dir/file mount) handled properly 2024-08-05 20:00:57 +02:00
Andras Bacsai
8133a8b770 fix: dir mounts should have proper dirs 2024-08-05 18:38:13 +02:00
Andras Bacsai
3269ca3eb8 chore: Add comment explaining the purpose of disconnecting the network in cleanup_unused_network_from_coolify_proxy() 2024-08-05 18:27:54 +02:00
Andras Bacsai
012d660886 fix: remove network if it is only connected to coolify proxy itself 2024-08-05 18:16:29 +02:00
Andras Bacsai
ddb4b4d215 refactor: Simplify log drain installation in ServerCheckJob 2024-08-05 16:45:30 +02:00
Andras Bacsai
2725a93bfd fix: only run logdrain if necessary 2024-08-05 16:44:50 +02:00
ayntk-ai
27e82f0bde implement logic, jobs and add DB migrate 2024-08-05 16:31:41 +02:00
Andras Bacsai
5755965b4f chore: add pull_request image builds to GH actions 2024-08-05 16:09:49 +02:00
Andras Bacsai
e4b92bb660 feat: new server checking job
feat: show if the server  has problems on ui
2024-08-05 15:48:15 +02:00
Andras Bacsai
bc48b42ff1 refactor: Remove unused code for checking server status in Heading.php 2024-08-05 15:02:33 +02:00
Andras Bacsai
94b2d67a6e refactor: Update livewire polling interval in heading.blade.php 2024-08-05 15:02:23 +02:00
ayntk-ai
7d6a895449 Updated Configuration Settings 2024-08-05 14:44:20 +02:00
Andras Bacsai
39e3ea9f07 fix: async remote command 2024-08-05 13:51:34 +02:00
Andras Bacsai
465b254813 fix: if usagebefore cannot be determined, cleanup docker with force 2024-08-05 13:45:53 +02:00
Andras Bacsai
e2bc3f4841 refactor: Remove commented out code for cleaning up networks in CleanupDocker.php 2024-08-05 13:45:33 +02:00
Andras Bacsai
b38a651a08 feat: coolify init should cleanup stuck networks in proxy 2024-08-05 13:45:24 +02:00
Andras Bacsai
76c39a987c fix: service_url should not have a trailing slash 2024-08-05 13:42:51 +02:00
Andras Bacsai
1d8c496906 feat: delete team in cloud without subscription 2024-08-05 12:03:36 +02:00
Andras Bacsai
820693ac22 fix: plausible template 2024-08-05 11:50:52 +02:00
Andras Bacsai
d7b45a6dd2 fix: remove lazy load from scheduled tasks 2024-08-05 11:30:48 +02:00
Gabriel Peralta
cb2fc68dde Update minecraft.yaml 2024-08-03 21:13:42 -04:00
Andras Bacsai
e6e48c5812 fix: only append docker network if service/app is running 2024-08-01 13:47:58 +02:00
Andras Bacsai
fd855847ff refactor: Adjust keep_days in CleanupDatabase.php based on environment 2024-08-01 09:07:54 +02:00
Andras Bacsai
35dbced3c5 refactor: Update CleanupDatabase.php to adjust keep_days based on environment 2024-08-01 08:52:58 +02:00
Andras Bacsai
e8b2d8bf03 chore: Update version to 4.0.0-beta.320 2024-08-01 08:52:13 +02:00
Gabriel Peralta
9887d5eedd Added minecraft-server (java) as template 2024-07-31 00:50:13 -04:00
Alexzvn
1feb8488a3 cleanup 2024-07-29 08:09:27 +00:00
Alexzvn
342ef4d367 add graceful shutdown 2024-07-29 07:57:13 +00:00
Andras Bacsai
85d080a042 update servicetemplates 2024-07-26 20:32:29 +02:00
Andras Bacsai
69c48a511e Merge branch 'main' into next 2024-07-26 20:31:48 +02:00
Andras Bacsai
10b9c4bcfa Merge pull request #2961 from coollabsio/fixservicesenvparse
v4.0.0-beta.319
2024-07-26 20:28:43 +02:00
Andras Bacsai
38d914076e fix: update env on ui 2024-07-26 20:17:40 +02:00
Andras Bacsai
102dd6bfb1 fix: activity type invalid 2024-07-26 20:07:39 +02:00
Andras Bacsai
04379b76f2 chore: collect/create/update volumes in parseDockerComposeFile function 2024-07-26 20:04:41 +02:00
Andras Bacsai
d6fb54f3c3 fix: service env variables 2024-07-26 20:01:23 +02:00
Andras Bacsai
1d419c6ab8 fix: service env parsing 2024-07-26 20:00:37 +02:00
Andras Bacsai
8a4e958663 fix: parse docker composer 2024-07-26 19:58:52 +02:00
Andras Bacsai
b04f7686fd update servicetemplates 2024-07-26 19:54:29 +02:00
Andras Bacsai
5aabdefaa7 quickfix: service env parsing 2024-07-26 19:53:40 +02:00
Jonathan Müller
a2e439686d Fixes Plausible TOTP key by changing it from BASE64 to REALBASE64_32 2024-07-26 16:30:36 +00:00
Filip Gornitzka Abelson
219b21767b chore: update formbricks template 2024-07-26 17:27:20 +02:00
Andras Bacsai
281c6e39a5 fix: members of root team should not see instance admin stuff 2024-07-26 14:54:24 +02:00
Andras Bacsai
95548b9d9b enhance: Add required CRON_SECRET to Formbricks template 2024-07-26 14:05:48 +02:00
Andras Bacsai
2ed1529b95 Merge pull request #2954 from LaurenceJJones/formbricks_env_var_required
enhance: Formbricks template add required CRON_SECRET
2024-07-26 14:05:34 +02:00
Laurence Jones
85f037702a Update formbricks.yaml 2024-07-26 13:03:28 +01:00
Laurence Jones
2caa91d772 enhance: Formbricks template add required CRON_SECRET
formbricks/formbricks#2874

A new required env var has been added, using special magic to generate a random password as the value to abstract away from user.
2024-07-26 12:25:12 +01:00
Andras Bacsai
d7350fad76 fix: several shared environment variables in one value, parsed correctly 2024-07-26 13:22:44 +02:00
Andras Bacsai
dcfb716711 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-07-26 11:58:43 +02:00
Andras Bacsai
0e0c81c32b fix: update SERVICE_FQDN/URL env variables when you change the domain 2024-07-26 11:58:40 +02:00
Andras Bacsai
802cab4a70 fix: domain update on services on the UI 2024-07-26 11:58:14 +02:00
Andras Bacsai
2c0b40ac8c fix: by default volumes that we cannot determine if they are directories or files are treated as directories 2024-07-26 11:57:49 +02:00
Andras Bacsai
001e8493ae Merge pull request #2946 from vitalybaev/fixes-link-to-feature-requests
Fixes link to feature requests in GitHub's issue template
2024-07-26 10:01:30 +02:00
Vitaly Baev
e9256152e8 Fixes link to feature requests in GitHub's issue template 2024-07-26 00:06:12 +02:00
Andras Bacsai
25410cb31a workaround for null bytes 2024-07-25 21:51:53 +02:00
Andras Bacsai
3dedf5548f feat: new server check job 2024-07-25 20:30:22 +02:00
Andras Bacsai
2d2bb23708 Merge pull request #2879 from lorenzomigliorero/fix/bitbucket-repository-url
fix: bitbucket repos url
2024-07-25 14:23:58 +02:00
Andras Bacsai
6afc0b6303 fix: gitlab logo 2024-07-25 14:17:26 +02:00
Andras Bacsai
f558f01ffd fix: gitlab service 2024-07-25 14:14:00 +02:00
Andras Bacsai
47fa955e71 Merge pull request #2638 from GunniBusch/main
[TEMPLATE] Add GitLab template
2024-07-25 13:49:40 +02:00
andrasbacsai
96c4f5b8da Fix styling 2024-07-25 11:31:59 +00:00
Andras Bacsai
6a4aa492c0 fix: random generated uuid will be full length (not 7 characters) 2024-07-25 13:31:01 +02:00
Andras Bacsai
debd2a3433 fix: link in task failed job notifications 2024-07-25 13:30:38 +02:00
Andras Bacsai
192bc0f13b fix: handle array env vars 2024-07-25 12:38:31 +02:00
Andras Bacsai
8756141f1d fix: remove pull always when uploading backup to s3 2024-07-25 12:11:47 +02:00
Andras Bacsai
b97abc600e chore: remove commented out code for docker image pruning 2024-07-25 12:00:14 +02:00
Andras Bacsai
4b29636b42 fix: api -> application patch endpoint 2024-07-25 11:53:57 +02:00
Andras Bacsai
321c51f8ed feat: check custom internal name through server's applications. 2024-07-25 11:30:31 +02:00
Andras Bacsai
69d8f706cf fix: raw compose deployment .env not found 2024-07-25 11:07:32 +02:00
Andras Bacsai
98f67c5c6c feat: update Caddy button label in proxy.blade.php 2024-07-25 11:07:18 +02:00
Andras Bacsai
1cf8d0b886 chore: update version numbers to 4.0.0-beta.319 2024-07-24 21:12:22 +02:00
andrasbacsai
613830e6a6 Fix styling 2024-07-24 19:11:12 +00:00
Andras Bacsai
2ea146333e fix: uuid in api docs type 2024-07-24 21:10:32 +02:00
andrasbacsai
f76d45b826 Fix styling 2024-07-24 12:27:21 +00:00
Andras Bacsai
6cc86a3c82 Merge pull request #2881 from coollabsio/next
v4.0.0-beta.318
2024-07-24 14:26:39 +02:00
Andras Bacsai
f1e5b61970 feat: update API endpoint summaries 2024-07-23 14:36:44 +02:00
Andras Bacsai
65380646f7 feat: add branddev logo to README.md 2024-07-23 14:23:58 +02:00
Andras Bacsai
189a8347ed feat: add server api endpoints 2024-07-23 14:20:53 +02:00
Andras Bacsai
e96e8f6fec feat: add patch request to projects 2024-07-23 11:48:38 +02:00
Andras Bacsai
38299ab507 feat: create/delete project endpoints 2024-07-23 11:36:05 +02:00
Andras Bacsai
f134171855 fix: restart proxy does not work + status indicator on the UI 2024-07-23 11:11:54 +02:00
Andras Bacsai
320204d854 fix: directory will be created by default for compose host mounts 2024-07-22 15:10:07 +02:00
Andras Bacsai
b68199a482 fix: Fix issue with deployment start command in ApplicationDeploymentJob 2024-07-22 15:09:50 +02:00
Andras Bacsai
6f4436fd5e fix: plane service images 2024-07-22 14:32:58 +02:00
Andras Bacsai
0d8cc19698 fix: deleting application should delete preview deployments 2024-07-22 14:13:56 +02:00
Andras Bacsai
a3a1ff69e1 fix: preview deployments should be stopped properly via gh webhook 2024-07-22 13:06:03 +02:00
Andras Bacsai
5df7e23aa4 chore: Update resource-limits.blade.php with improved input field helpers 2024-07-22 11:33:25 +02:00
Andras Bacsai
35d9691b3f chore: Update APP_BASE_URL to use SERVICE_FQDN_PLANE 2024-07-22 11:16:50 +02:00
Andras Bacsai
465f649641 Merge pull request #2903 from MrAlexand0r/bugfix/2860_plane_images
#2860 - [PLANE] Fixed image uploads not working
2024-07-22 10:30:49 +02:00
Andras Bacsai
d909e7d802 update service template 2024-07-22 10:02:28 +02:00
Andras Bacsai
06db6b8502 Merge branch 'main' into next 2024-07-22 10:02:03 +02:00
Andras Bacsai
12261b9082 chore: Remove commented out code for sending internal notification 2024-07-22 09:50:49 +02:00
Andras Bacsai
583ec432e8 Merge branch 'next' into bugfix/2860_plane_images 2024-07-22 09:43:16 +02:00
Andras Bacsai
8ffbccf7db fix: create file storage even if content is empty 2024-07-22 09:18:15 +02:00
Toby Brancher
097ca209bc Support map fields in Directus
To support the map fields in Directus with Postgres you need the Postgis extension installed, with docker we can just change the image to use the equivalent alpine postgis image, source latest stable alpine from here (perhaps there is a better ref to use than one with a specific version number?): https://registry.hub.docker.com/r/postgis/postgis/

If you are already running Directus without postgis, and would like to make use of the mapping fields after updating the docker compose file you will need to go to `execute command`, select the postgres container and run the following command filling in the appropriate env vars;

`psql -U ${SERVICE_USER_POSTGRESQL} -d ${POSTGRESQL_DATABASE} -c "CREATE EXTENSION postgis;"`
2024-07-21 23:39:22 +01:00
Alexander G.
439fe43a04 #2860 - plane service: fixed image uploads not working because credentials were unset 2024-07-21 15:54:42 +02:00
Andras Bacsai
7fd9a799b5 Update BUG_REPORT.yml 2024-07-20 14:17:51 +02:00
Andras Bacsai
7459ab22d1 remove file 2024-07-20 13:17:09 +02:00
Andras Bacsai
133a68f3eb update suapbase 2024-07-20 12:31:05 +02:00
Andras Bacsai
3224110583 fix: supabase 2024-07-20 12:30:32 +02:00
Andras Bacsai
810488b115 fix: volume detection (dir or file) is fixed 2024-07-19 17:06:30 +02:00
Andras Bacsai
b2276147ad chore: Disable health check by default 2024-07-19 15:40:44 +02:00
Andras Bacsai
6c1293c63e chore: Update helper message with link to documentation 2024-07-19 15:40:36 +02:00
Andras Bacsai
526d675272 refactor: Disable health check for Rust applications during deployment 2024-07-19 15:40:33 +02:00
Andras Bacsai
14b2442d40 chore: Update version to 4.0.0-beta.318 2024-07-19 15:04:18 +02:00
Andras Bacsai
d6d194d414 Merge pull request #2880 from coollabsio/next
v4.0.0-beta.317
2024-07-19 14:57:29 +02:00
Andras Bacsai
0e99f97855 oops 2024-07-19 14:56:18 +02:00
Andras Bacsai
14dc933219 fix: missing input for api endpoint 2024-07-19 14:40:01 +02:00
Andras Bacsai
9497f123b4 revert: advanced dropdown 2024-07-19 14:38:47 +02:00
Andras Bacsai
6feb439d0a chore: Update version to 4.0.0-beta.317 2024-07-19 14:34:21 +02:00
Andras Bacsai
e4ca5ee5f5 chore: Update Traefik image version to v2.11 2024-07-19 14:34:19 +02:00
Andras Bacsai
f21c12f39b Merge pull request #2856 from coollabsio/next
v4.0.0-beta.316
2024-07-19 13:45:51 +02:00
Andras Bacsai
6c1e50a914 fix: backup downloads 2024-07-19 13:45:04 +02:00
Lorenzo Migliorero
2dbba366b7 fix: bitbucket repository url 2024-07-19 13:41:01 +02:00
Andras Bacsai
da064def7a update service-templates 2024-07-19 10:06:26 +02:00
Andras Bacsai
3af3fa5773 refactor: Update DockerCleanupJob to use server settings for force cleanup 2024-07-19 09:59:09 +02:00
Andras Bacsai
005bd55fb2 refactor: Update DockerCleanupJob to use server settings for force cleanup 2024-07-18 15:12:52 +02:00
Andras Bacsai
82a5b4c55d refactor: server status job and docker cleanup job 2024-07-18 14:43:21 +02:00
Andras Bacsai
b8e95b2099 feat: force cleanup server 2024-07-18 14:38:56 +02:00
Andras Bacsai
8ea50dc029 refactor: Update DockerCleanupJob to handle nullable usageBefore property 2024-07-18 14:28:33 +02:00
Andras Bacsai
ec191af874 chore: Handle JSON parsing errors in format_docker_command_output_to_json 2024-07-18 14:23:15 +02:00
Andras Bacsai
d98c742aff chore: update general page of apps 2024-07-18 14:20:22 +02:00
Andras Bacsai
2529496594 feat: preserve git repository 2024-07-18 13:14:07 +02:00
Andras Bacsai
1b6114036a chore: Update checkbox labels in general.blade.php 2024-07-18 12:40:17 +02:00
Andras Bacsai
b33fb6c39a chore: Update width of container in general.blade.php 2024-07-18 12:39:49 +02:00
Andras Bacsai
0a6826af58 remove ray 2024-07-18 12:32:33 +02:00
Andras Bacsai
1c7034ff78 fix: if git limit reached, ignore it and continue with a default selection 2024-07-18 12:30:45 +02:00
Andras Bacsai
7e11698c55 chore: Update repository form with simplified URL input field 2024-07-18 12:13:23 +02:00
Andras Bacsai
1c4eb31d59 fix: handle custom_internal_name check in ApplicationDeploymentJob.php 2024-07-18 12:10:59 +02:00
Andras Bacsai
b4b6a4294a chore: Update bug report template
Update the bug report template to include a checkbox for indicating whether the user is using the cloud version of Coolify.
2024-07-18 12:07:44 +02:00
Andras Bacsai
4c031a7c05 fix: handle / in preselecting branches 2024-07-18 12:03:48 +02:00
Lorenzo Migliorero
1b4a8aa58f Merge branch 'feat/labels-by-proxy-type' of github.com:lorenzomigliorero/coolify into feat/labels-by-proxy-type 2024-07-18 11:42:52 +02:00
Lorenzo Migliorero
be6d74a6a3 style: linting 2024-07-18 11:42:41 +02:00
Andras Bacsai
997a262b6c Merge pull request #2840 from Pjort/next
Update supabase.yaml
2024-07-18 10:38:36 +02:00
Lorenzo Migliorero
5aae65f62f Merge branch 'next' into feat/labels-by-proxy-type 2024-07-17 21:21:09 +02:00
Lorenzo Migliorero
3be06ced92 feat: labels by proxy type 2024-07-17 21:06:56 +02:00
Andras Bacsai
c0e88df3e8 feat: add readonly labels 2024-07-17 14:52:40 +02:00
Andras Bacsai
85e1cbad53 chore: Update version to 4.0.0-beta.316 2024-07-17 09:17:02 +02:00
Andras Bacsai
c37398af72 Merge pull request #2853 from coollabsio/next
v4.0.0-beta.315
2024-07-17 08:45:33 +02:00
Andras Bacsai
19cfe4e514 fix: new docker compose parsing 2024-07-17 08:09:33 +02:00
Andras Bacsai
23a1b1925f fix: tag deployments 2024-07-17 07:59:12 +02:00
Andras Bacsai
1fb8d1e14c revert: pull policy 2024-07-17 07:59:06 +02:00
Andras Bacsai
804c70b575 chore: Update version to 4.0.0-beta.315 2024-07-17 07:58:45 +02:00
Pjort
548c4a4c64 Update supabase.yaml
Fixes problem related to emails sent for invite and forgotten password, that then doesn't actually use the external URL instead uses the hardcoded: http://supabase-kong:8000
2024-07-15 17:47:35 +02:00
Leon Adomaitis
53032469e7 added smtp config 2024-06-23 21:43:14 +02:00
Leon Adomaitis
768e27d68c Add metadata 2024-06-23 04:12:03 +02:00
Leon Adomaitis
691ae04ca9 Add gitlab.svg 2024-06-23 04:02:46 +02:00
Leon Adomaitis
6585db1f9c Merge branch 'next' into main 2024-06-23 03:56:50 +02:00
Leon Adomaitis
42aa2d0088 add first outline of the gitlab compose file 2024-06-23 03:52:29 +02:00
209 changed files with 4730 additions and 2006 deletions

View File

@@ -1,6 +1,6 @@
name: Bug report
description: 'Create a new bug report.'
title: '[Bug]: '
description: "Create a new bug report."
title: "[Bug]: "
body:
- type: markdown
attributes:
@@ -35,3 +35,12 @@ body:
description: Coolify's version (see top of your screen).
validations:
required: true
- type: checkboxes
attributes:
label: Cloud?
description: "Are you using the cloud version of Coolify?"
options:
- label: 'Yes'
required: false
- label: 'No'
required: false

View File

@@ -4,5 +4,5 @@ contact_links:
url: https://coollabs.io/discord
about: Reach out to us on Discord.
- name: 🙋‍♂️ Feature Requests
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
url: https://github.com/coollabsio/coolify/discussions/categories/new-features
about: All feature requests will be discussed here.

81
.github/workflows/pr-build.yml vendored Normal file
View File

@@ -0,0 +1,81 @@
name: PR Build (v4)
on:
pull_request:
types:
- opened
branches-ignore: ["main", "v3"]
paths-ignore:
- .github/workflows/coolify-helper.yml
- docker/coolify-helper/Dockerfile
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify"
jobs:
amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
aarch64:
runs-on: [self-hosted, arm64]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64
merge-manifest:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: [amd64, aarch64]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}

View File

@@ -48,6 +48,7 @@ Special thanks to our biggest sponsors!
<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>
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
## Github Sponsors ($40+)
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>

View File

@@ -9,7 +9,7 @@ class StopApplication
{
use AsAction;
public function handle(Application $application)
public function handle(Application $application, bool $previewDeployments = false)
{
if ($application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
@@ -26,15 +26,18 @@ class StopApplication
if (! $server->isFunctional()) {
return 'Server is not functional';
}
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
if ($previewDeployments) {
$containers = getCurrentApplicationContainerStatus($server, $application->id, includePullrequests: true);
} else {
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
}
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(
["docker rm -f {$containerName}"],
$server
);
instant_remote_process(command: ["docker stop --time=30 $containerName"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm $containerName"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$containerName}"], server: $server, throwError: false);
}
}
}

View File

@@ -22,10 +22,11 @@ class StopDatabase
if (! $server->isFunctional()) {
return 'Server is not functional';
}
instant_remote_process(
["docker rm -f {$database->uuid}"],
$server
);
instant_remote_process(command: ["docker stop --time=30 $database->uuid"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm $database->uuid"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f $database->uuid"], server: $server, throwError: false);
if ($database->is_public) {
StopDatabaseProxy::run($database);
}

View File

@@ -12,6 +12,7 @@ use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Lorisleiva\Actions\Concerns\AsAction;
class GetContainersStatus
@@ -20,13 +21,16 @@ class GetContainersStatus
public $applications;
public ?Collection $containers;
public ?Collection $containerReplicates;
public $server;
public function handle(Server $server)
public function handle(Server $server, ?Collection $containers = null, ?Collection $containerReplicates = null)
{
// if (isDev()) {
// $server = Server::find(0);
// }
$this->containers = $containers;
$this->containerReplicates = $containerReplicates;
$this->server = $server;
if (! $this->server->isFunctional()) {
return 'Server is not ready.';
@@ -66,322 +70,312 @@ class GetContainersStatus
// }
}
private function sentinel()
{
try {
$containers = $this->server->getContainers();
if ($containers->count() === 0) {
return;
}
$databases = $this->server->databases();
$services = $this->server->services()->get();
$previews = $this->server->previews();
$foundApplications = [];
$foundApplicationPreviews = [];
$foundDatabases = [];
$foundServices = [];
// private function sentinel()
// {
// try {
// $this->containers = $this->server->getContainersWithSentinel();
// if ($this->containers->count() === 0) {
// return;
// }
// $databases = $this->server->databases();
// $services = $this->server->services()->get();
// $previews = $this->server->previews();
// $foundApplications = [];
// $foundApplicationPreviews = [];
// $foundDatabases = [];
// $foundServices = [];
foreach ($containers as $container) {
$labels = Arr::undot(data_get($container, 'labels'));
$containerStatus = data_get($container, 'state');
$containerHealth = data_get($container, 'health_status', 'unhealthy');
$containerStatus = "$containerStatus ($containerHealth)";
$applicationId = data_get($labels, 'coolify.applicationId');
if ($applicationId) {
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
if ($pullRequestId) {
if (str($applicationId)->contains('-')) {
$applicationId = str($applicationId)->before('-');
}
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
if ($preview) {
$foundApplicationPreviews[] = $preview->id;
$statusFromDb = $preview->status;
if ($statusFromDb !== $containerStatus) {
$preview->update(['status' => $containerStatus]);
}
} else {
//Notify user that this container should not be there.
}
} else {
$application = $this->applications->where('id', $applicationId)->first();
if ($application) {
$foundApplications[] = $application->id;
$statusFromDb = $application->status;
if ($statusFromDb !== $containerStatus) {
$application->update(['status' => $containerStatus]);
}
} else {
//Notify user that this container should not be there.
}
}
} else {
$uuid = data_get($labels, 'com.docker.compose.service');
$type = data_get($labels, 'coolify.type');
if ($uuid) {
if ($type === 'service') {
$database_id = data_get($labels, 'coolify.service.subId');
if ($database_id) {
$service_db = ServiceDatabase::where('id', $database_id)->first();
if ($service_db) {
$uuid = $service_db->service->uuid;
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
// TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'name') === "$uuid-proxy";
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($service_db);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
}
}
}
} else {
$database = $databases->where('uuid', $uuid)->first();
if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id;
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
}
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
// TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'name') === "$uuid-proxy";
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else {
// Notify user that this container should not be there.
}
}
}
if (data_get($container, 'name') === 'coolify-db') {
$foundDatabases[] = 0;
}
}
$serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) {
$subType = data_get($labels, 'coolify.service.subType');
$subId = data_get($labels, 'coolify.service.subId');
$service = $services->where('id', $serviceLabelId)->first();
if (! $service) {
continue;
}
if ($subType === 'application') {
$service = $service->applications()->where('id', $subId)->first();
} else {
$service = $service->databases()->where('id', $subId)->first();
}
if ($service) {
$foundServices[] = "$service->id-$service->name";
$statusFromDb = $service->status;
if ($statusFromDb !== $containerStatus) {
// ray('Updating status: ' . $containerStatus);
$service->update(['status' => $containerStatus]);
}
}
}
}
$exitedServices = collect([]);
foreach ($services as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
if (in_array("$app->id-$app->name", $foundServices)) {
continue;
} else {
$exitedServices->push($app);
}
}
foreach ($dbs as $db) {
if (in_array("$db->id-$db->name", $foundServices)) {
continue;
} else {
$exitedServices->push($db);
}
}
}
$exitedServices = $exitedServices->unique('id');
foreach ($exitedServices as $exitedService) {
if (str($exitedService->status)->startsWith('exited')) {
continue;
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
if ($name) {
if ($fqdn) {
$containerName = "$name, available at $fqdn";
} else {
$containerName = $name;
}
} else {
if ($fqdn) {
$containerName = $fqdn;
} else {
$containerName = null;
}
}
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');
// foreach ($this->containers as $container) {
// $labels = Arr::undot(data_get($container, 'labels'));
// $containerStatus = data_get($container, 'state');
// $containerHealth = data_get($container, 'health_status', 'unhealthy');
// $containerStatus = "$containerStatus ($containerHealth)";
// $applicationId = data_get($labels, 'coolify.applicationId');
// if ($applicationId) {
// $pullRequestId = data_get($labels, 'coolify.pullRequestId');
// if ($pullRequestId) {
// if (str($applicationId)->contains('-')) {
// $applicationId = str($applicationId)->before('-');
// }
// $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
// if ($preview) {
// $foundApplicationPreviews[] = $preview->id;
// $statusFromDb = $preview->status;
// if ($statusFromDb !== $containerStatus) {
// $preview->update(['status' => $containerStatus]);
// }
// } else {
// //Notify user that this container should not be there.
// }
// } else {
// $application = $this->applications->where('id', $applicationId)->first();
// if ($application) {
// $foundApplications[] = $application->id;
// $statusFromDb = $application->status;
// if ($statusFromDb !== $containerStatus) {
// $application->update(['status' => $containerStatus]);
// }
// } else {
// //Notify user that this container should not be there.
// }
// }
// } else {
// $uuid = data_get($labels, 'com.docker.compose.service');
// $type = data_get($labels, 'coolify.type');
// if ($uuid) {
// if ($type === 'service') {
// $database_id = data_get($labels, 'coolify.service.subId');
// if ($database_id) {
// $service_db = ServiceDatabase::where('id', $database_id)->first();
// if ($service_db) {
// $uuid = $service_db->service->uuid;
// $isPublic = data_get($service_db, 'is_public');
// if ($isPublic) {
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
// if ($this->server->isSwarm()) {
// // TODO: fix this with sentinel
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
// } else {
// return data_get($value, 'name') === "$uuid-proxy";
// }
// })->first();
// if (! $foundTcpProxy) {
// StartDatabaseProxy::run($service_db);
// // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
// }
// }
// }
// }
// } else {
// $database = $databases->where('uuid', $uuid)->first();
// if ($database) {
// $isPublic = data_get($database, 'is_public');
// $foundDatabases[] = $database->id;
// $statusFromDb = $database->status;
// if ($statusFromDb !== $containerStatus) {
// $database->update(['status' => $containerStatus]);
// }
// if ($isPublic) {
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
// if ($this->server->isSwarm()) {
// // TODO: fix this with sentinel
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
// } else {
// return data_get($value, 'name') === "$uuid-proxy";
// }
// })->first();
// if (! $foundTcpProxy) {
// StartDatabaseProxy::run($database);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
// }
// }
// } else {
// // Notify user that this container should not be there.
// }
// }
// }
// if (data_get($container, 'name') === 'coolify-db') {
// $foundDatabases[] = 0;
// }
// }
// $serviceLabelId = data_get($labels, 'coolify.serviceId');
// if ($serviceLabelId) {
// $subType = data_get($labels, 'coolify.service.subType');
// $subId = data_get($labels, 'coolify.service.subId');
// $service = $services->where('id', $serviceLabelId)->first();
// if (! $service) {
// continue;
// }
// if ($subType === 'application') {
// $service = $service->applications()->where('id', $subId)->first();
// } else {
// $service = $service->databases()->where('id', $subId)->first();
// }
// if ($service) {
// $foundServices[] = "$service->id-$service->name";
// $statusFromDb = $service->status;
// if ($statusFromDb !== $containerStatus) {
// // ray('Updating status: ' . $containerStatus);
// $service->update(['status' => $containerStatus]);
// }
// }
// }
// }
// $exitedServices = collect([]);
// foreach ($services as $service) {
// $apps = $service->applications()->get();
// $dbs = $service->databases()->get();
// foreach ($apps as $app) {
// if (in_array("$app->id-$app->name", $foundServices)) {
// continue;
// } else {
// $exitedServices->push($app);
// }
// }
// foreach ($dbs as $db) {
// if (in_array("$db->id-$db->name", $foundServices)) {
// continue;
// } else {
// $exitedServices->push($db);
// }
// }
// }
// $exitedServices = $exitedServices->unique('id');
// foreach ($exitedServices as $exitedService) {
// if (str($exitedService->status)->startsWith('exited')) {
// continue;
// }
// $name = data_get($exitedService, 'name');
// $fqdn = data_get($exitedService, 'fqdn');
// if ($name) {
// if ($fqdn) {
// $containerName = "$name, available at $fqdn";
// } else {
// $containerName = $name;
// }
// } else {
// if ($fqdn) {
// $containerName = $fqdn;
// } else {
// $containerName = null;
// }
// }
// $projectUuid = data_get($service, 'environment.project.uuid');
// $serviceUuid = data_get($service, 'uuid');
// $environmentName = data_get($service, 'environment.name');
if ($projectUuid && $serviceUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
$exitedService->update(['status' => 'exited']);
}
// if ($projectUuid && $serviceUuid && $environmentName) {
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
// } else {
// $url = null;
// }
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $exitedService->update(['status' => 'exited']);
// }
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
foreach ($notRunningApplications as $applicationId) {
$application = $this->applications->where('id', $applicationId)->first();
if (str($application->status)->startsWith('exited')) {
continue;
}
$application->update(['status' => 'exited']);
// $notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
// foreach ($notRunningApplications as $applicationId) {
// $application = $this->applications->where('id', $applicationId)->first();
// if (str($application->status)->startsWith('exited')) {
// continue;
// }
// $application->update(['status' => 'exited']);
$name = data_get($application, 'name');
$fqdn = data_get($application, 'fqdn');
// $name = data_get($application, 'name');
// $fqdn = data_get($application, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
$projectUuid = data_get($application, 'environment.project.uuid');
$applicationUuid = data_get($application, 'uuid');
$environment = data_get($application, 'environment.name');
// $projectUuid = data_get($application, 'environment.project.uuid');
// $applicationUuid = data_get($application, 'uuid');
// $environment = data_get($application, 'environment.name');
if ($projectUuid && $applicationUuid && $environment) {
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
} else {
$url = null;
}
// if ($projectUuid && $applicationUuid && $environment) {
// $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
// } else {
// $url = null;
// }
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) {
$preview = $previews->where('id', $previewId)->first();
if (str($preview->status)->startsWith('exited')) {
continue;
}
$preview->update(['status' => 'exited']);
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// }
// $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
// foreach ($notRunningApplicationPreviews as $previewId) {
// $preview = $previews->where('id', $previewId)->first();
// if (str($preview->status)->startsWith('exited')) {
// continue;
// }
// $preview->update(['status' => 'exited']);
$name = data_get($preview, 'name');
$fqdn = data_get($preview, 'fqdn');
// $name = data_get($preview, 'name');
// $fqdn = data_get($preview, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
$projectUuid = data_get($preview, 'application.environment.project.uuid');
$environmentName = data_get($preview, 'application.environment.name');
$applicationUuid = data_get($preview, 'application.uuid');
// $projectUuid = data_get($preview, 'application.environment.project.uuid');
// $environmentName = data_get($preview, 'application.environment.name');
// $applicationUuid = data_get($preview, 'application.uuid');
if ($projectUuid && $applicationUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
} else {
$url = null;
}
// if ($projectUuid && $applicationUuid && $environmentName) {
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
// } else {
// $url = null;
// }
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) {
$database = $databases->where('id', $database)->first();
if (str($database->status)->startsWith('exited')) {
continue;
}
$database->update(['status' => 'exited']);
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// }
// $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
// foreach ($notRunningDatabases as $database) {
// $database = $databases->where('id', $database)->first();
// if (str($database->status)->startsWith('exited')) {
// continue;
// }
// $database->update(['status' => 'exited']);
$name = data_get($database, 'name');
$fqdn = data_get($database, 'fqdn');
// $name = data_get($database, 'name');
// $fqdn = data_get($database, 'fqdn');
$containerName = $name;
// $containerName = $name;
$projectUuid = data_get($database, 'environment.project.uuid');
$environmentName = data_get($database, 'environment.name');
$databaseUuid = data_get($database, 'uuid');
// $projectUuid = data_get($database, 'environment.project.uuid');
// $environmentName = data_get($database, 'environment.name');
// $databaseUuid = data_get($database, 'uuid');
if ($projectUuid && $databaseUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// if ($projectUuid && $databaseUuid && $environmentName) {
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
// } else {
// $url = null;
// }
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// }
// Check if proxy is running
$this->server->proxyType();
$foundProxyContainer = $containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
// TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {
return data_get($value, 'name') === 'coolify-proxy';
}
})->first();
if (! $foundProxyContainer) {
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
}
} catch (\Throwable $e) {
ray($e);
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'state');
$this->server->save();
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
} catch (\Exception $e) {
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
ray($e->getMessage());
// // Check if proxy is running
// $this->server->proxyType();
// $foundProxyContainer = $this->containers->filter(function ($value, $key) {
// if ($this->server->isSwarm()) {
// // TODO: fix this with sentinel
// return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
// } else {
// return data_get($value, 'name') === 'coolify-proxy';
// }
// })->first();
// if (! $foundProxyContainer) {
// try {
// $shouldStart = CheckProxy::run($this->server);
// if ($shouldStart) {
// StartProxy::run($this->server, false);
// $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
// }
// } catch (\Throwable $e) {
// ray($e);
// }
// } else {
// $this->server->proxy->status = data_get($foundProxyContainer, 'state');
// $this->server->save();
// $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
// instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
// }
// } catch (\Exception $e) {
// // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
// ray($e->getMessage());
return handleError($e);
}
}
// return handleError($e);
// }
// }
private function old_way()
{
if ($this->server->isSwarm()) {
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
} else {
// Precheck for containers
$containers = instant_remote_process(['docker container ls -q'], $this->server, false);
if (! $containers) {
return;
}
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
$containerReplicates = null;
if ($this->containers === null) {
['containers' => $this->containers,'containerReplicates' => $this->containerReplicates] = $this->server->getContainers();
}
if (is_null($containers)) {
if (is_null($this->containers)) {
return;
}
$containers = format_docker_command_output_to_json($containers);
if ($containerReplicates) {
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
foreach ($containerReplicates as $containerReplica) {
if ($this->containerReplicates) {
foreach ($this->containerReplicates as $containerReplica) {
$name = data_get($containerReplica, 'Name');
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
$this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
if (data_get($container, 'Spec.Name') === $name) {
$replicas = data_get($containerReplica, 'Replicas');
$running = str($replicas)->explode('/')[0];
@@ -407,7 +401,7 @@ class GetContainersStatus
$foundDatabases = [];
$foundServices = [];
foreach ($containers as $container) {
foreach ($this->containers as $container) {
if ($this->server->isSwarm()) {
$labels = data_get($container, 'Spec.Labels');
$uuid = data_get($labels, 'coolify.name');
@@ -461,7 +455,7 @@ class GetContainersStatus
if ($uuid) {
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
@@ -486,7 +480,7 @@ class GetContainersStatus
$database->update(['status' => $containerStatus]);
}
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
@@ -659,7 +653,7 @@ class GetContainersStatus
// Check if proxy is running
$this->server->proxyType();
$foundProxyContainer = $containers->filter(function ($value, $key) {
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {

View File

@@ -21,7 +21,6 @@ class CheckConfiguration
"cat $proxy_path/docker-compose.yml",
];
$proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
}

View File

@@ -11,7 +11,6 @@ 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);
@@ -22,15 +21,5 @@ 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

@@ -24,12 +24,7 @@ class InstallLogDrain
}
try {
if ($type === 'none') {
$command = [
"echo 'Stopping old Fluent Bit'",
'docker rm -f coolify-log-drain || true',
];
return instant_remote_process($command, $server);
return 'No log drain is enabled.';
} elseif ($type === 'newrelic') {
if (! $server->settings->is_logdrain_newrelic_enabled) {
throw new \Exception('New Relic log drain is not enabled.');

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class StopLogDrain
{
use AsAction;
public function handle(Server $server)
{
try {
return instant_remote_process(['docker rm -f coolify-log-drain || true'], $server);
} catch (\Throwable $e) {
return handleError($e);
}
}
}

View File

@@ -20,7 +20,6 @@ class UpdateCoolify
{
try {
$settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob');
$this->server = Server::find(0);
if (! $this->server) {
return;
@@ -48,7 +47,6 @@ class UpdateCoolify
private function update()
{
if (isDev()) {
ray('Running in dev mode');
remote_process([
'sleep 10',
], $this->server);

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class ValidateServer
{
use AsAction;
public ?string $uptime = null;
public ?string $error = null;
public ?string $supported_os_type = null;
public ?string $docker_installed = null;
public ?string $docker_compose_installed = null;
public ?string $docker_version = null;
public function handle(Server $server)
{
$server->update([
'validation_logs' => null,
]);
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
if (! $this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
$server->update([
'validation_logs' => $this->error,
]);
throw new \Exception($this->error);
}
$this->supported_os_type = $server->validateOS();
if (! $this->supported_os_type) {
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$server->update([
'validation_logs' => $this->error,
]);
throw new \Exception($this->error);
}
$this->docker_installed = $server->validateDockerEngine();
$this->docker_compose_installed = $server->validateDockerCompose();
if (! $this->docker_installed || ! $this->docker_compose_installed) {
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$server->update([
'validation_logs' => $this->error,
]);
throw new \Exception($this->error);
}
$this->docker_version = $server->validateDockerEngineVersion();
if ($this->docker_version) {
return 'OK';
} else {
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$server->update([
'validation_logs' => $this->error,
]);
throw new \Exception($this->error);
}
}
}

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 --policy always';
$commands[] = 'docker compose pull';
$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,21 @@ 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, false);
instant_remote_process(command: ["docker stop --time=30 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: 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, false);
instant_remote_process(command: ["docker stop --time=30 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
$db->update(['status' => 'exited']);
}
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());
return $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 --policy always';
$commands[] = 'docker compose pull';
$server = data_get($resource, 'server');

View File

@@ -18,7 +18,12 @@ class CleanupDatabase extends Command
} else {
echo "Running database cleanup in dry-run mode...\n";
}
$keep_days = 60;
if (isCloud()) {
// Later on we can increase this to 180 days or dynamically set
$keep_days = 60;
} else {
$keep_days = 60;
}
echo "Keep days: $keep_days\n";
// Cleanup failed jobs table
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));

View File

@@ -18,7 +18,7 @@ class CleanupUnreachableServers extends Command
if ($servers->count() > 0) {
foreach ($servers as $server) {
echo "Cleanup unreachable server ($server->id) with name $server->name";
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
// send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
$server->update([
'ip' => '1.2.3.4',
]);

View File

@@ -81,7 +81,7 @@ class Emails extends Command
}
set_transanctional_email_settings();
$this->mail = new MailMessage();
$this->mail = new MailMessage;
$this->mail->subject('Test Email');
switch ($type) {
case 'updates':
@@ -107,7 +107,7 @@ class Emails extends Command
$confirmed = confirm('Are you sure?');
if ($confirmed) {
foreach ($emails as $email) {
$this->mail = new MailMessage();
$this->mail = new MailMessage;
$this->mail->subject('One-click Services, Docker Compose support');
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
'token' => encrypt($email),
@@ -118,7 +118,7 @@ class Emails extends Command
}
break;
case 'emails-test':
$this->mail = (new Test())->toMail();
$this->mail = (new Test)->toMail();
$this->sendEmail();
break;
case 'database-backup-statuses-daily':
@@ -224,7 +224,7 @@ class Emails extends Command
// $this->sendEmail();
// break;
case 'waitlist-invitation-link':
$this->mail = new MailMessage();
$this->mail = new MailMessage;
$this->mail->view('emails.waitlist-invitation', [
'loginLink' => 'https://coolify.io',
]);
@@ -241,7 +241,7 @@ class Emails extends Command
break;
case 'realusers-before-trial':
$this->mail = new MailMessage();
$this->mail = new MailMessage;
$this->mail->view('emails.before-trial-conversion');
$this->mail->subject('Trial period has been added for all subscription plans.');
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
@@ -287,7 +287,7 @@ class Emails extends Command
foreach ($admins as $admin) {
$this->info($admin);
}
$this->mail = new MailMessage();
$this->mail = new MailMessage;
$this->mail->view('emails.server-lost-connection', [
'name' => $server->name,
]);

View File

@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Actions\Server\StopSentinel;
use App\Enums\ActivityTypes;
use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\ApplicationDeploymentQueue;
@@ -12,22 +13,27 @@ use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class Init extends Command
{
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments} {--cleanup-proxy-networks}';
protected $description = 'Cleanup instance related stuffs';
public $servers = null;
public function handle()
{
$this->servers = Server::all();
$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]);
foreach ($this->servers as $server) {
if ($server->settings->is_metrics_enabled === true) {
$server->settings->update(['is_metrics_enabled' => false]);
}
if ($server->isFunctional()) {
StopSentinel::dispatch($server);
}
@@ -36,7 +42,7 @@ class Init extends Command
$full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments');
$cleanup_proxy_networks = $this->option('cleanup-proxy-networks');
$this->replace_slash_in_environment_name();
if ($cleanup_deployments) {
echo "Running cleanup deployments.\n";
@@ -44,17 +50,26 @@ class Init extends Command
return;
}
if ($cleanup_proxy_networks) {
echo "Running cleanup proxy networks.\n";
$this->cleanup_unused_network_from_coolify_proxy();
return;
}
if ($full_cleanup) {
// Required for falsely deleted coolify db
$this->restore_coolify_db_backup();
$this->update_traefik_labels();
$this->cleanup_unused_network_from_coolify_proxy();
$this->cleanup_unnecessary_dynamic_proxy_configuration();
$this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:queue');
$this->call('cleanup:stucked-resources');
if (! isCloud()) {
try {
$server = Server::find(0)->first();
$server->setupDynamicProxyConfiguration();
$localhost = $this->servers->where('id', 0)->first();
$localhost->setupDynamicProxyConfiguration();
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
@@ -68,6 +83,13 @@ class Init extends Command
$settings->update(['is_auto_update_enabled' => false]);
}
}
if (isCloud()) {
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
if ($response->successful()) {
$services = $response->json();
File::put(base_path('templates/service-templates.json'), json_encode($services));
}
}
return;
}
@@ -75,6 +97,79 @@ class Init extends Command
$this->call('cleanup:stucked-resources');
}
private function update_traefik_labels()
{
try {
Server::where('proxy->type', 'TRAEFIK_V2')->update(['proxy->type' => 'TRAEFIK']);
} catch (\Throwable $e) {
echo "Error in updating traefik labels: {$e->getMessage()}\n";
}
}
private function cleanup_unnecessary_dynamic_proxy_configuration()
{
if (isCloud()) {
foreach ($this->servers as $server) {
try {
if (! $server->isFunctional()) {
continue;
}
if ($server->id === 0) {
continue;
}
$file = $server->proxyPath().'/dynamic/coolify.yaml';
return instant_remote_process([
"rm -f $file",
], $server, false);
} catch (\Throwable $e) {
echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
}
}
}
}
private function cleanup_unused_network_from_coolify_proxy()
{
foreach ($this->servers as $server) {
if (! $server->isFunctional()) {
continue;
}
if (! $server->isProxyShouldRun()) {
continue;
}
try {
['networks' => $networks, 'allNetworks' => $allNetworks] = collectDockerNetworksByServer($server);
$removeNetworks = $allNetworks->diff($networks);
$commands = collect();
foreach ($removeNetworks as $network) {
$out = instant_remote_process(["docker network inspect -f json $network | jq '.[].Containers | if . == {} then null else . end'"], $server, false);
if (empty($out)) {
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
} else {
$data = collect(json_decode($out, true));
if ($data->count() === 1) {
// If only coolify-proxy itself is connected to that network (it should not be possible, but who knows)
$isCoolifyProxyItself = data_get($data->first(), 'Name') === 'coolify-proxy';
if ($isCoolifyProxyItself) {
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
}
}
}
}
if ($commands->isNotEmpty()) {
echo "Cleaning up unused networks from coolify proxy\n";
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
}
} catch (\Throwable $e) {
echo "Error in cleaning up unused networks from coolify proxy: {$e->getMessage()}\n";
}
}
}
private function restore_coolify_db_backup()
{
try {
@@ -102,8 +197,7 @@ class Init extends Command
private function cleanup_stucked_helper_containers()
{
$servers = Server::all();
foreach ($servers as $server) {
foreach ($this->servers as $server) {
if ($server->isFunctional()) {
CleanupHelperContainersJob::dispatch($server);
}
@@ -148,7 +242,6 @@ class Init extends Command
private function cleanup_in_progress_application_deployments()
{
// Cleanup any failed deployments
try {
if (isCloud()) {
return;

View File

@@ -103,7 +103,7 @@ class WaitlistInvite extends Command
{
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
$loginLink = route('auth.link', ['token' => $token]);
$mail = new MailMessage();
$mail = new MailMessage;
$mail->view('emails.waitlist-invitation', [
'loginLink' => $loginLink,
]);

View File

@@ -2,16 +2,18 @@
namespace App\Console;
use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CheckForUpdatesJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\PullCoolifyImageJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\ServerStatusJob;
use App\Jobs\ServerCheckJob;
use App\Jobs\UpdateCoolifyJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
@@ -26,25 +28,26 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
$this->all_servers = Server::all();
$settings = InstanceSettings::get();
if (isDev()) {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
// Server Jobs
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily();
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
$schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
$this->schedule_updates($schedule);
// Server Jobs
$this->check_scheduled_backups($schedule);
@@ -59,12 +62,26 @@ class Kernel extends ConsoleKernel
private function pull_images($schedule)
{
$settings = InstanceSettings::get();
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
if ($server->isSentinelEnabled()) {
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
$schedule->job(new PullSentinelImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
}
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
$schedule->job(new PullHelperImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
}
}
private function schedule_updates($schedule)
{
$settings = InstanceSettings::get();
$updateCheckFrequency = $settings->update_check_frequency;
$schedule->job(new CheckForUpdatesJob)->cron($updateCheckFrequency)->onOneServer();
if ($settings->is_auto_update_enabled) {
$autoUpdateFrequency = $settings->auto_update_frequency;
$schedule->job(new UpdateCoolifyJob)->cron($autoUpdateFrequency)->onOneServer();
}
}
@@ -74,19 +91,12 @@ class Kernel extends ConsoleKernel
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
$own = Team::find(0)->servers;
$servers = $servers->merge($own);
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} else {
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
}
foreach ($containerServers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
if ($server->isLogDrainEnabled()) {
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
}
}
foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
}
}

View File

@@ -5,7 +5,7 @@ namespace App\Enums;
enum ProxyTypes: string
{
case NONE = 'NONE';
case TRAEFIK_V2 = 'TRAEFIK_V2';
case TRAEFIK = 'TRAEFIK';
case NGINX = 'NGINX';
case CADDY = 'CADDY';
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class FileStorageChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
ray($teamId);
if (is_null($teamId)) {
throw new \Exception('Team id is null');
}
$this->teamId = $teamId;
}
public function broadcastOn(): array
{
return [
new PrivateChannel("team.{$this->teamId}"),
];
}
}

View File

@@ -620,7 +620,7 @@ class ApplicationsController extends Controller
private function create_application(Request $request, $type)
{
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
@@ -708,7 +708,7 @@ class ApplicationsController extends Controller
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$application = new Application();
$application = new Application;
removeUnnecessaryFieldsFromRequest($request);
$application->fill($request->all());
@@ -732,12 +732,14 @@ class ApplicationsController extends Controller
$application->environment_id = $environment->id;
$application->save();
$application->refresh();
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
if (! $application->settings->is_container_label_readonly_enabled) {
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
}
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -794,7 +796,7 @@ class ApplicationsController extends Controller
if (str($gitRepository)->startsWith('http') || str($gitRepository)->contains('github.com')) {
$gitRepository = str($gitRepository)->replace('https://', '')->replace('http://', '')->replace('github.com/', '');
}
$application = new Application();
$application = new Application;
removeUnnecessaryFieldsFromRequest($request);
$application->fill($request->all());
@@ -826,12 +828,14 @@ class ApplicationsController extends Controller
$application->source_id = $githubApp->id;
$application->save();
$application->refresh();
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
if (! $application->settings->is_container_label_readonly_enabled) {
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
}
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -886,7 +890,7 @@ class ApplicationsController extends Controller
return response()->json(['message' => 'Private Key not found.'], 404);
}
$application = new Application();
$application = new Application;
removeUnnecessaryFieldsFromRequest($request);
$application->fill($request->all());
@@ -916,12 +920,14 @@ class ApplicationsController extends Controller
$application->environment_id = $environment->id;
$application->save();
$application->refresh();
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
if (! $application->settings->is_container_label_readonly_enabled) {
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
}
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -941,7 +947,7 @@ class ApplicationsController extends Controller
]));
} elseif ($type === 'dockerfile') {
if (! $request->has('name')) {
$request->offsetSet('name', 'dockerfile-'.new Cuid2(7));
$request->offsetSet('name', 'dockerfile-'.new Cuid2);
}
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -982,7 +988,7 @@ class ApplicationsController extends Controller
$port = 80;
}
$application = new Application();
$application = new Application;
$application->fill($request->all());
$application->fqdn = $fqdn;
$application->ports_exposes = $port;
@@ -996,12 +1002,14 @@ class ApplicationsController extends Controller
$application->git_branch = 'main';
$application->save();
$application->refresh();
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
if (! $application->settings->is_container_label_readonly_enabled) {
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
}
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -1017,7 +1025,7 @@ class ApplicationsController extends Controller
]));
} elseif ($type === 'dockerimage') {
if (! $request->has('name')) {
$request->offsetSet('name', 'docker-image-'.new Cuid2(7));
$request->offsetSet('name', 'docker-image-'.new Cuid2);
}
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -1038,7 +1046,7 @@ class ApplicationsController extends Controller
if (! $request->docker_registry_image_tag) {
$request->offsetSet('docker_registry_image_tag', 'latest');
}
$application = new Application();
$application = new Application;
removeUnnecessaryFieldsFromRequest($request);
$application->fill($request->all());
@@ -1052,12 +1060,14 @@ class ApplicationsController extends Controller
$application->git_branch = 'main';
$application->save();
$application->refresh();
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
if (! $application->settings->is_container_label_readonly_enabled) {
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
}
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -1089,7 +1099,7 @@ class ApplicationsController extends Controller
], 422);
}
if (! $request->has('name')) {
$request->offsetSet('name', 'service'.new Cuid2(7));
$request->offsetSet('name', 'service'.new Cuid2);
}
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -1130,7 +1140,7 @@ class ApplicationsController extends Controller
// return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
// }
$service = new Service();
$service = new Service;
removeUnnecessaryFieldsFromRequest($request);
$service->fill($request->all());
@@ -1310,7 +1320,7 @@ class ApplicationsController extends Controller
#[OA\Patch(
summary: 'Update',
description: 'Update application by UUID.',
path: '/applications',
path: '/applications/{uuid}',
security: [
['bearerAuth' => []],
],
@@ -1494,8 +1504,10 @@ class ApplicationsController extends Controller
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
$application->fqdn = $fqdn;
$customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->custom_labels = base64_encode($customLabels);
if (! $application->settings->is_container_label_readonly_enabled) {
$customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->custom_labels = base64_encode($customLabels);
}
$request->offsetUnset('domains');
}
@@ -2310,7 +2322,7 @@ class ApplicationsController extends Controller
return response()->json(['message' => 'Application not found.'], 404);
}
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -2467,7 +2479,7 @@ class ApplicationsController extends Controller
return response()->json(['message' => 'Application not found.'], 404);
}
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,

View File

@@ -84,7 +84,7 @@ class DeployController extends Controller
],
tags: ['Deployments'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -290,7 +290,7 @@ class DeployController extends Controller
}
switch ($resource?->getMorphClass()) {
case 'App\Models\Application':
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $resource,
deployment_uuid: $deployment_uuid,

View File

@@ -5,7 +5,7 @@ namespace App\Http\Controllers\Api;
use OpenApi\Attributes as OA;
#[OA\Info(title: 'Coolify', version: '0.1')]
#[OA\Server(url: 'https://app.coolify.io/api/v1')]
#[OA\Server(url: 'https://app.coolify.io/api/v1', description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.')]
#[OA\SecurityScheme(
type: 'http',
scheme: 'bearer',

View File

@@ -61,7 +61,7 @@ class ProjectController extends Controller
],
tags: ['Projects'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -107,7 +107,7 @@ class ProjectController extends Controller
],
tags: ['Projects'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
],
responses: [
@@ -135,8 +135,14 @@ class ProjectController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
$environment = $project->environments()->whereName(request()->environment_name)->first();
if (! $request->uuid) {
return response()->json(['message' => 'Uuid is required.'], 422);
}
if (! $request->environment_name) {
return response()->json(['message' => 'Environment name is required.'], 422);
}
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
$environment = $project->environments()->whereName($request->environment_name)->first();
if (! $environment) {
return response()->json(['message' => 'Environment not found.'], 404);
}
@@ -144,4 +150,276 @@ class ProjectController extends Controller
return response()->json(serializeApiResponse($environment));
}
#[OA\Post(
summary: 'Create',
description: 'Create Project.',
path: '/projects',
security: [
['bearerAuth' => []],
],
tags: ['Projects'],
requestBody: new OA\RequestBody(
required: true,
description: 'Project created.',
content: new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'uuid' => ['type' => 'string', 'description' => 'The name of the project.'],
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
],
),
),
),
responses: [
new OA\Response(
response: 201,
description: 'Project created.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the project.'],
]
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 404,
ref: '#/components/responses/404',
),
]
)]
public function create_project(Request $request)
{
$allowedFields = ['name', 'description'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'name' => 'string|max:255|required',
'description' => 'string|nullable',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
$errors = $validator->errors();
if (! empty($extraFields)) {
foreach ($extraFields as $field) {
$errors->add($field, 'This field is not allowed.');
}
}
return response()->json([
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
$project = Project::create([
'name' => $request->name,
'description' => $request->description,
'team_id' => $teamId,
]);
return response()->json([
'uuid' => $project->uuid,
])->setStatusCode(201);
}
#[OA\Patch(
summary: 'Update',
description: 'Update Project.',
path: '/projects/{uuid}',
security: [
['bearerAuth' => []],
],
tags: ['Projects'],
requestBody: new OA\RequestBody(
required: true,
description: 'Project updated.',
content: new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'name' => ['type' => 'string', 'description' => 'The name of the project.'],
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
],
),
),
),
responses: [
new OA\Response(
response: 201,
description: 'Project updated.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'uuid' => ['type' => 'string', 'example' => 'og888os'],
'name' => ['type' => 'string', 'example' => 'Project Name'],
'description' => ['type' => 'string', 'example' => 'Project Description'],
]
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 404,
ref: '#/components/responses/404',
),
]
)]
public function update_project(Request $request)
{
$allowedFields = ['name', 'description'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'name' => 'string|max:255|nullable',
'description' => 'string|nullable',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
$errors = $validator->errors();
if (! empty($extraFields)) {
foreach ($extraFields as $field) {
$errors->add($field, 'This field is not allowed.');
}
}
return response()->json([
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
$uuid = $request->uuid;
if (! $uuid) {
return response()->json(['message' => 'Uuid is required.'], 422);
}
$project = Project::whereTeamId($teamId)->whereUuid($uuid)->first();
if (! $project) {
return response()->json(['message' => 'Project not found.'], 404);
}
$project->update($request->only($allowedFields));
return response()->json([
'uuid' => $project->uuid,
'name' => $project->name,
'description' => $project->description,
])->setStatusCode(201);
}
#[OA\Delete(
summary: 'Delete',
description: 'Delete project by UUID.',
path: '/projects/{uuid}',
security: [
['bearerAuth' => []],
],
tags: ['Projects'],
parameters: [
new OA\Parameter(
name: 'uuid',
in: 'path',
description: 'UUID of the application.',
required: true,
schema: new OA\Schema(
type: 'string',
format: 'uuid',
)
),
],
responses: [
new OA\Response(
response: 200,
description: 'Project deleted.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Project deleted.'],
]
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 404,
ref: '#/components/responses/404',
),
]
)]
public function delete_project(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if (! $request->uuid) {
return response()->json(['message' => 'Uuid is required.'], 422);
}
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
if (! $project) {
return response()->json(['message' => 'Project not found.'], 404);
}
if ($project->resource_count() > 0) {
return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400);
}
$project->delete();
return response()->json(['message' => 'Project deleted.']);
}
}

View File

@@ -73,7 +73,7 @@ class SecurityController extends Controller
],
tags: ['Private Keys'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -318,7 +318,7 @@ class SecurityController extends Controller
],
tags: ['Private Keys'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(

View File

@@ -2,8 +2,12 @@
namespace App\Http\Controllers\Api;
use App\Actions\Server\ValidateServer;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server as ModelsServer;
use Illuminate\Http\Request;
@@ -75,7 +79,7 @@ class ServersController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port', 'description')->get()->load(['settings'])->map(function ($server) {
$server['is_reachable'] = $server->settings->is_reachable;
$server['is_usable'] = $server->settings->is_usable;
@@ -101,7 +105,7 @@ class ServersController extends Controller
],
tags: ['Servers'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -178,7 +182,7 @@ class ServersController extends Controller
],
tags: ['Servers'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -255,7 +259,7 @@ class ServersController extends Controller
],
tags: ['Servers'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -392,4 +396,390 @@ class ServersController extends Controller
return response()->json(serializeApiResponse($domains));
}
#[OA\Post(
summary: 'Create',
description: 'Create Server.',
path: '/servers',
security: [
['bearerAuth' => []],
],
tags: ['Servers'],
requestBody: new OA\RequestBody(
required: true,
description: 'Server created.',
content: new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'name' => ['type' => 'string', 'example' => 'My Server', 'description' => 'The name of the server.'],
'description' => ['type' => 'string', 'example' => 'My Server Description', 'description' => 'The description of the server.'],
'ip' => ['type' => 'string', 'example' => '127.0.0.1', 'description' => 'The IP of the server.'],
'port' => ['type' => 'integer', 'example' => 22, 'description' => 'The port of the server.'],
'user' => ['type' => 'string', 'example' => 'root', 'description' => 'The user of the server.'],
'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'],
'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'],
'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'],
],
),
),
),
responses: [
new OA\Response(
response: 201,
description: 'Server created.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the server.'],
]
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 404,
ref: '#/components/responses/404',
),
]
)]
public function create_server(Request $request)
{
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'name' => 'string|max:255',
'description' => 'string|nullable',
'ip' => 'string|required',
'port' => 'integer|nullable',
'private_key_uuid' => 'string|required',
'user' => 'string|nullable',
'is_build_server' => 'boolean|nullable',
'instant_validate' => 'boolean|nullable',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
$errors = $validator->errors();
if (! empty($extraFields)) {
foreach ($extraFields as $field) {
$errors->add($field, 'This field is not allowed.');
}
}
return response()->json([
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
if (! $request->name) {
$request->offsetSet('name', generate_random_name());
}
if (! $request->user) {
$request->offsetSet('user', 'root');
}
if (is_null($request->port)) {
$request->offsetSet('port', 22);
}
if (is_null($request->is_build_server)) {
$request->offsetSet('is_build_server', false);
}
if (is_null($request->instant_validate)) {
$request->offsetSet('instant_validate', false);
}
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
if (! $privateKey) {
return response()->json(['message' => 'Private key not found.'], 404);
}
$allServers = ModelsServer::whereIp($request->ip)->get();
if ($allServers->count() > 0) {
return response()->json(['message' => 'Server with this IP already exists.'], 400);
}
$server = ModelsServer::create([
'name' => $request->name,
'description' => $request->description,
'ip' => $request->ip,
'port' => $request->port,
'user' => $request->user,
'private_key_id' => $privateKey->id,
'team_id' => $teamId,
'proxy' => [
'type' => ProxyTypes::TRAEFIK->value,
'status' => ProxyStatus::EXITED->value,
],
]);
$server->settings()->update([
'is_build_server' => $request->is_build_server,
]);
if ($request->instant_validate) {
ValidateServer::dispatch($server);
}
return response()->json([
'uuid' => $server->uuid,
])->setStatusCode(201);
}
#[OA\Patch(
summary: 'Update',
description: 'Update Server.',
path: '/servers/{uuid}',
security: [
['bearerAuth' => []],
],
tags: ['Servers'],
requestBody: new OA\RequestBody(
required: true,
description: 'Server updated.',
content: new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'name' => ['type' => 'string', 'description' => 'The name of the server.'],
'description' => ['type' => 'string', 'description' => 'The description of the server.'],
'ip' => ['type' => 'string', 'description' => 'The IP of the server.'],
'port' => ['type' => 'integer', 'description' => 'The port of the server.'],
'user' => ['type' => 'string', 'description' => 'The user of the server.'],
'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'],
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
],
),
),
),
responses: [
new OA\Response(
response: 201,
description: 'Server updated.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'array',
items: new OA\Items(ref: '#/components/schemas/Server')
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 404,
ref: '#/components/responses/404',
),
]
)]
public function update_server(Request $request)
{
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'name' => 'string|max:255|nullable',
'description' => 'string|nullable',
'ip' => 'string|nullable',
'port' => 'integer|nullable',
'private_key_uuid' => 'string|nullable',
'user' => 'string|nullable',
'is_build_server' => 'boolean|nullable',
'instant_validate' => 'boolean|nullable',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
$errors = $validator->errors();
if (! empty($extraFields)) {
foreach ($extraFields as $field) {
$errors->add($field, 'This field is not allowed.');
}
}
return response()->json([
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
}
$server->update($request->only(['name', 'description', 'ip', 'port', 'user']));
if ($request->is_build_server) {
$server->settings()->update([
'is_build_server' => $request->is_build_server,
]);
}
if ($request->instant_validate) {
ValidateServer::dispatch($server);
}
return response()->json(serializeApiResponse($server))->setStatusCode(201);
}
#[OA\Delete(
summary: 'Delete',
description: 'Delete server by UUID.',
path: '/servers/{uuid}',
security: [
['bearerAuth' => []],
],
tags: ['Servers'],
parameters: [
new OA\Parameter(
name: 'uuid',
in: 'path',
description: 'UUID of the server.',
required: true,
schema: new OA\Schema(
type: 'string',
format: 'uuid',
)
),
],
responses: [
new OA\Response(
response: 200,
description: 'Server deleted.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Server deleted.'],
]
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 404,
ref: '#/components/responses/404',
),
]
)]
public function delete_server(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if (! $request->uuid) {
return response()->json(['message' => 'Uuid is required.'], 422);
}
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
}
if ($server->definedResources()->count() > 0) {
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
}
$server->delete();
return response()->json(['message' => 'Server deleted.']);
}
#[OA\Get(
summary: 'Validate',
description: 'Validate server by UUID.',
path: '/servers/{uuid}/validate',
security: [
['bearerAuth' => []],
],
tags: ['Servers'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
response: 201,
description: 'Server validation started.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Validation started.'],
]
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 404,
ref: '#/components/responses/404',
),
]
)]
public function validate_server(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if (! $request->uuid) {
return response()->json(['message' => 'Uuid is required.'], 422);
}
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
}
ValidateServer::dispatch($server);
return response()->json(['message' => 'Validation started.']);
}
}

View File

@@ -413,6 +413,8 @@ class ServicesController extends Controller
return response()->json(['message' => 'Service not found.'], 404);
}
$service = $service->load(['applications', 'databases']);
return response()->json($this->removeSensitiveData($service));
}

View File

@@ -21,7 +21,7 @@ class UploadController extends BaseController
$receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request));
if ($receiver->isUploaded() === false) {
throw new UploadMissingFileException();
throw new UploadMissingFileException;
}
$save = $receiver->receive();

View File

@@ -103,7 +103,7 @@ class Bitbucket extends Controller
if ($x_bitbucket_event === 'repo:push') {
if ($application->isDeployable()) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -127,7 +127,7 @@ class Bitbucket extends Controller
if ($x_bitbucket_event === 'pullrequest:created') {
if ($application->isPRDeployable()) {
ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {

View File

@@ -123,7 +123,7 @@ class Gitea extends Controller
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -162,7 +162,7 @@ class Gitea extends Controller
if ($x_gitea_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {

View File

@@ -128,7 +128,7 @@ class Github extends Controller
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -167,7 +167,7 @@ class Github extends Controller
if ($x_github_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
@@ -340,7 +340,6 @@ class Github extends Controller
return response("Nothing to do. No applications found with branch '$base_branch'.");
}
}
foreach ($applications as $application) {
$isFunctional = $application->destination->server->isFunctional();
if (! $isFunctional) {
@@ -358,7 +357,7 @@ class Github extends Controller
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -397,7 +396,7 @@ class Github extends Controller
if ($x_github_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
ApplicationPreview::create([
@@ -432,8 +431,13 @@ class Github extends Controller
if ($action === 'closed' || $action === 'close') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$container_name = generateApplicationContainerName($application, $pull_request_id);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
$containers = getCurrentApplicationContainerStatus($application->destination->server, $application->id, $pull_request_id);
if ($containers->isNotEmpty()) {
$containers->each(function ($container) use ($application) {
$container_name = data_get($container, 'Names');
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
});
}
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
$found->delete();

View File

@@ -137,7 +137,7 @@ class Gitlab extends Controller
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -177,7 +177,7 @@ class Gitlab extends Controller
if ($x_gitlab_event === 'merge_request') {
if ($action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {

View File

@@ -157,6 +157,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private ?string $coolify_variables = null;
private bool $preserveRepository = true;
public $tries = 1;
public function __construct(int $application_deployment_queue_id)
@@ -187,6 +189,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->server = $this->mainServer = $this->destination->server;
$this->serverUser = $this->server->user;
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
$this->preserveRepository = $this->application->settings->is_preserve_repository_enabled;
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
$this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/');
@@ -304,14 +307,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
]
);
// $this->execute_remote_command(
// [
// "docker image prune -f >/dev/null 2>&1",
// "hidden" => true,
// "ignore_errors" => true,
// ]
// );
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
}
@@ -487,40 +482,41 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
// Start compose file
if ($this->application->settings->is_raw_compose_deployment_enabled) {
if ($this->docker_compose_custom_start_command) {
$this->write_deployment_configurations();
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
);
$this->write_deployment_configurations();
} else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
$this->docker_compose_location = '/docker-compose.yaml';
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
$command .= " --env-file {$server_workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
['command' => $command, 'hidden' => true],
);
}
} else {
if ($this->docker_compose_custom_start_command) {
$this->write_deployment_configurations();
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
);
$this->write_deployment_configurations();
} else {
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->write_deployment_configurations();
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
);
$this->write_deployment_configurations();
}
}
@@ -605,26 +601,53 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function write_deployment_configurations()
{
if ($this->preserveRepository) {
if ($this->use_build_server) {
$this->server = $this->original_server;
}
if (str($this->configuration_dir)->isNotEmpty()) {
$this->execute_remote_command(
[
"mkdir -p $this->configuration_dir",
],
// removing this now as we are using docker cp
// [
// "rm -rf $this->configuration_dir/{*,.*}",
// ],
[
"docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}",
],
);
}
if ($this->use_build_server) {
$this->server = $this->build_server;
}
}
if (isset($this->docker_compose_base64)) {
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
$mainDir = $this->configuration_dir;
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$mainDir = $this->application->workdir();
}
if ($this->pull_request_id === 0) {
$composeFileName = "$this->configuration_dir/docker-compose.yaml";
$composeFileName = "$mainDir/docker-compose.yaml";
} else {
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yaml";
$composeFileName = "$mainDir/docker-compose-pr-{$this->pull_request_id}.yaml";
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml";
}
$this->execute_remote_command(
[
"mkdir -p $this->configuration_dir",
"mkdir -p $mainDir",
],
[
"echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
"echo '{$readme}' > $mainDir/README.md",
]
);
if ($this->use_build_server) {
@@ -848,8 +871,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$envs->push($env->key.'='.$real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}");
if ($this->build_pack !== 'dockercompose') {
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
}
// Add HOST if not exists
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
@@ -892,15 +917,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$envs->push($env->key.'='.$real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}");
if ($this->build_pack !== 'dockercompose') {
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
}
// Add HOST if not exists
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
$envs->push('HOST=0.0.0.0');
}
}
if ($envs->isEmpty()) {
$this->env_filename = null;
if ($this->use_build_server) {
@@ -965,7 +991,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
}
if (! $nixpacks_php_fallback_path) {
$nixpacks_php_fallback_path = new EnvironmentVariable();
$nixpacks_php_fallback_path = new EnvironmentVariable;
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
$nixpacks_php_fallback_path->value = '/index.php';
$nixpacks_php_fallback_path->is_build_time = false;
@@ -973,7 +999,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$nixpacks_php_fallback_path->save();
}
if (! $nixpacks_php_root_dir) {
$nixpacks_php_root_dir = new EnvironmentVariable();
$nixpacks_php_root_dir = new EnvironmentVariable;
$nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
$nixpacks_php_root_dir->value = '/app/public';
$nixpacks_php_root_dir->is_build_time = false;
@@ -999,7 +1025,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name) || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty() || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
$this->application_deployment_queue->addLogEntry('----------------------------------------');
if (count($this->application->ports_mappings_array) > 0) {
$this->application_deployment_queue->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
@@ -1007,7 +1033,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
$this->application_deployment_queue->addLogEntry('Consistent container name feature enabled, rolling update is not supported.');
}
if (isset($this->application->settings->custom_internal_name)) {
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
$this->application_deployment_queue->addLogEntry('Custom internal name is set, rolling update is not supported.');
}
if ($this->pull_request_id !== 0) {
@@ -1247,7 +1273,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
continue;
}
// ray('Deploying to additional destination: ', $server->name);
$deployment_uuid = new Cuid2();
$deployment_uuid = new Cuid2;
queue_application_deployment(
deployment_uuid: $deployment_uuid,
application: $this->application,
@@ -1421,6 +1447,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
if ($this->nixpacks_type === 'rust') {
// temporary: disable healthcheck for rust because the start phase does not have curl/wget
$this->application->health_check_enabled = false;
$this->application->save();
}
}
}
}
@@ -1523,7 +1554,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application->custom_labels = base64_encode($labels->implode("\n"));
$this->application->save();
} else {
$labels = collect(generateLabelsApplication($this->application, $this->preview));
if (! $this->application->settings->is_container_label_readonly_enabled) {
$labels = collect(generateLabelsApplication($this->application, $this->preview));
}
}
if ($this->pull_request_id !== 0) {
$labels = collect(generateLabelsApplication($this->application, $this->preview));
@@ -1994,27 +2027,43 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
}
/**
* @param int $timeout in seconds
*/
private function graceful_shutdown_container(string $containerName, int $timeout = 30)
{
try {
$this->execute_remote_command(
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
["docker rm $containerName", 'hidden' => true, 'ignore_errors' => true]
);
} catch (\Exception $error) {
// report error if needed
}
$this->execute_remote_command(
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
);
}
private function stop_running_container(bool $force = false)
{
$this->application_deployment_queue->addLogEntry('Removing old containers.');
if ($this->newVersionIsHealthy || $force) {
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
if ($this->pull_request_id === 0) {
$containers = $containers->filter(function ($container) {
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
$this->graceful_shutdown_container($this->container_name);
} else {
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
if ($this->pull_request_id === 0) {
$containers = $containers->filter(function ($container) {
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
});
}
$containers->each(function ($container) {
$this->graceful_shutdown_container(data_get($container, 'Names'));
});
}
$containers->each(function ($container) {
$containerName = data_get($container, 'Names');
$this->execute_remote_command(
["docker rm -f $containerName >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
);
});
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
$this->execute_remote_command(
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
);
}
} else {
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
$this->application_deployment_queue->addLogEntry('----------------------------------------');
@@ -2025,9 +2074,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::FAILED->value,
]);
$this->execute_remote_command(
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
);
$this->graceful_shutdown_container($this->container_name);
}
}
@@ -2202,7 +2249,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
ray($code);
if ($code !== 69420) {
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
// do not remove already running container
} else {
$this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Jobs;
use App\Models\InstanceSettings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle(): void
{
try {
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
$latest_version = data_get($versions, 'coolify.v4.version');
$current_version = config('version');
if (version_compare($latest_version, $current_version, '>')) {
// New version available
$settings->update(['new_version_available' => true]);
} else {
$settings->update(['new_version_available' => false]);
}
}
} catch (\Throwable $e) {
// Consider implementing a notification to administrators
}
}
}

View File

@@ -90,6 +90,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
BackupCreated::dispatch($this->team->id);
// Check if team is exists
if (is_null($this->team)) {
$this->backup->update(['status' => 'failed']);
@@ -476,7 +477,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
} else {
$network = $this->database->destination->network;
}
$commands[] = "docker run --pull=always -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
instant_remote_process($commands, $this->server);

View File

@@ -40,7 +40,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
switch ($this->resource->type()) {
case 'application':
$persistentStorages = $this->resource?->persistentStorages()?->get();
StopApplication::run($this->resource);
StopApplication::run($this->resource, previewDeployments: true);
break;
case 'standalone-postgresql':
case 'standalone-redis':

View File

@@ -12,7 +12,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use RuntimeException;
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -20,47 +19,43 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
public $timeout = 300;
public ?int $usageBefore = null;
public int|string|null $usageBefore = null;
public function __construct(public Server $server) {}
public function handle(): void
{
try {
$isInprogress = false;
$this->server->applications()->each(function ($application) use (&$isInprogress) {
if ($application->isDeploymentInprogress()) {
$isInprogress = true;
return;
}
});
// if ($isInprogress) {
// throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
// }
if (! $this->server->isFunctional()) {
return;
}
if ($this->server->settings->is_force_cleanup_enabled) {
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
CleanupDocker::run(server: $this->server, force: true);
return;
}
$this->usageBefore = $this->server->getDiskUsage();
ray('Usage before: '.$this->usageBefore);
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
CleanupDocker::run(server: $this->server, force: true);
return;
}
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
ray('Cleaning up '.$this->server->name);
CleanupDocker::run($this->server);
CleanupDocker::run(server: $this->server, force: false);
$usageAfter = $this->server->getDiskUsage();
if ($usageAfter < $this->usageBefore) {
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
// ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
// send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name);
} else {
Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name);
}
} else {
ray('No need to clean up '.$this->server->name);
Log::info('No need to clean up '.$this->server->name);
}
} catch (\Throwable $e) {
// send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
ray($e->getMessage());
throw $e;
}

View File

@@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -16,16 +17,13 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 1000;
public function __construct() {}
public function handle(): void
{
try {
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();
$server = Server::findOrFail(0);
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
@@ -35,7 +33,6 @@ 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 = \App\Models\InstanceSettings::get();
$current_version = config('version');
if (! $settings->is_auto_update_enabled) {
return;
@@ -46,10 +43,6 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
if (version_compare($latest_version, $current_version, '<')) {
return;
}
instant_remote_process([
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
"bash /data/coolify/source/upgrade.sh $latest_version",
], $server);
} catch (\Throwable $e) {
throw $e;
}

View File

@@ -22,15 +22,16 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
public function handle(): void
{
try {
if (! isDev()) {
ray('PullTemplatesAndVersions service-templates');
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
if ($response->successful()) {
$services = $response->json();
File::put(base_path('templates/service-templates.json'), json_encode($services));
} else {
send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
}
if (isDev() || isCloud()) {
return;
}
ray('PullTemplatesAndVersions service-templates');
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
if ($response->successful()) {
$services = $response->json();
File::put(base_path('templates/service-templates.json'), json_encode($services));
} else {
send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
}
} catch (\Throwable $e) {
send_internal_notification('PullTemplatesAndVersions failed with: '.$e->getMessage());

View File

@@ -19,7 +19,7 @@ class SendConfirmationForWaitlistJob implements ShouldBeEncrypted, ShouldQueue
public function handle()
{
try {
$mail = new MailMessage();
$mail = new MailMessage;
$confirmation_url = base_url().'/webhooks/waitlist/confirm?email='.$this->email.'&confirmation_code='.$this->uuid;
$cancel_url = base_url().'/webhooks/waitlist/cancel?email='.$this->email.'&confirmation_code='.$this->uuid;
$mail->view('emails.waitlist-confirmation',

440
app/Jobs/ServerCheckJob.php Normal file
View File

@@ -0,0 +1,440 @@
<?php
namespace App\Jobs;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Docker\GetContainersStatus;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Actions\Server\InstallLogDrain;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $containers;
public $applications;
public $databases;
public $services;
public $previews;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): int
{
return $this->server->uuid;
}
public function handle()
{
try {
$this->applications = $this->server->applications();
$this->databases = $this->server->databases();
$this->services = $this->server->services()->get();
$this->previews = $this->server->previews();
$up = $this->serverStatus();
if (! $up) {
ray('Server is not reachable.');
return 'Server is not reachable.';
}
if (! $this->server->isFunctional()) {
ray('Server is not ready.');
return 'Server is not ready.';
}
if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
if (is_null($this->containers)) {
return 'No containers found.';
}
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
$this->checkLogDrainContainer();
$this->checkSentinel();
}
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
}
private function checkSentinel()
{
if ($this->server->isSentinelEnabled()) {
$sentinelContainerFound = $this->containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-sentinel';
})->first();
if ($sentinelContainerFound) {
$status = data_get($sentinelContainerFound, 'State.Status');
if ($status !== 'running') {
PullSentinelImageJob::dispatch($this);
}
}
}
}
private function serverStatus()
{
['uptime' => $uptime] = $this->server->validateConnection();
if ($uptime) {
if ($this->server->unreachable_notification_sent === true) {
$this->server->update(['unreachable_notification_sent' => false]);
}
} else {
// $this->server->team?->notify(new Unreachable($this->server));
foreach ($this->applications as $application) {
$application->update(['status' => 'exited']);
}
foreach ($this->databases as $database) {
$database->update(['status' => 'exited']);
}
foreach ($this->services as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
$app->update(['status' => 'exited']);
}
foreach ($dbs as $db) {
$db->update(['status' => 'exited']);
}
}
return false;
}
return true;
}
private function checkLogDrainContainer()
{
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-log-drain';
})->first();
if ($foundLogDrainContainer) {
$status = data_get($foundLogDrainContainer, 'State.Status');
if ($status !== 'running') {
InstallLogDrain::dispatch($this->server);
}
} else {
InstallLogDrain::dispatch($this->server);
}
}
private function containerStatus()
{
$foundApplications = [];
$foundApplicationPreviews = [];
$foundDatabases = [];
$foundServices = [];
foreach ($this->containers as $container) {
if ($this->server->isSwarm()) {
$labels = data_get($container, 'Spec.Labels');
$uuid = data_get($labels, 'coolify.name');
} else {
$labels = data_get($container, 'Config.Labels');
}
$containerStatus = data_get($container, 'State.Status');
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
$containerStatus = "$containerStatus ($containerHealth)";
$labels = Arr::undot(format_docker_labels_to_json($labels));
$applicationId = data_get($labels, 'coolify.applicationId');
if ($applicationId) {
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
if ($pullRequestId) {
if (str($applicationId)->contains('-')) {
$applicationId = str($applicationId)->before('-');
}
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
if ($preview) {
$foundApplicationPreviews[] = $preview->id;
$statusFromDb = $preview->status;
if ($statusFromDb !== $containerStatus) {
$preview->update(['status' => $containerStatus]);
}
} else {
//Notify user that this container should not be there.
}
} else {
$application = $this->applications->where('id', $applicationId)->first();
if ($application) {
$foundApplications[] = $application->id;
$statusFromDb = $application->status;
if ($statusFromDb !== $containerStatus) {
$application->update(['status' => $containerStatus]);
}
} else {
//Notify user that this container should not be there.
}
}
} else {
$uuid = data_get($labels, 'com.docker.compose.service');
$type = data_get($labels, 'coolify.type');
if ($uuid) {
if ($type === 'service') {
$database_id = data_get($labels, 'coolify.service.subId');
if ($database_id) {
$service_db = ServiceDatabase::where('id', $database_id)->first();
if ($service_db) {
$uuid = data_get($service_db, 'service.uuid');
if ($uuid) {
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($service_db);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
}
}
}
}
} else {
$database = $this->databases->where('uuid', $uuid)->first();
if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id;
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
}
if ($isPublic) {
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (! $foundTcpProxy) {
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else {
// Notify user that this container should not be there.
}
}
}
if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0;
}
}
$serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) {
$subType = data_get($labels, 'coolify.service.subType');
$subId = data_get($labels, 'coolify.service.subId');
$service = $this->services->where('id', $serviceLabelId)->first();
if (! $service) {
continue;
}
if ($subType === 'application') {
$service = $service->applications()->where('id', $subId)->first();
} else {
$service = $service->databases()->where('id', $subId)->first();
}
if ($service) {
$foundServices[] = "$service->id-$service->name";
$statusFromDb = $service->status;
if ($statusFromDb !== $containerStatus) {
// ray('Updating status: ' . $containerStatus);
$service->update(['status' => $containerStatus]);
}
}
}
}
$exitedServices = collect([]);
foreach ($this->services as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
if (in_array("$app->id-$app->name", $foundServices)) {
continue;
} else {
$exitedServices->push($app);
}
}
foreach ($dbs as $db) {
if (in_array("$db->id-$db->name", $foundServices)) {
continue;
} else {
$exitedServices->push($db);
}
}
}
$exitedServices = $exitedServices->unique('id');
foreach ($exitedServices as $exitedService) {
if (str($exitedService->status)->startsWith('exited')) {
continue;
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
if ($name) {
if ($fqdn) {
$containerName = "$name, available at $fqdn";
} else {
$containerName = $name;
}
} else {
if ($fqdn) {
$containerName = $fqdn;
} else {
$containerName = null;
}
}
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');
if ($projectUuid && $serviceUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
$exitedService->update(['status' => 'exited']);
}
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
foreach ($notRunningApplications as $applicationId) {
$application = $this->applications->where('id', $applicationId)->first();
if (str($application->status)->startsWith('exited')) {
continue;
}
$application->update(['status' => 'exited']);
$name = data_get($application, 'name');
$fqdn = data_get($application, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$projectUuid = data_get($application, 'environment.project.uuid');
$applicationUuid = data_get($application, 'uuid');
$environment = data_get($application, 'environment.name');
if ($projectUuid && $applicationUuid && $environment) {
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningApplicationPreviews = $this->previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) {
$preview = $this->previews->where('id', $previewId)->first();
if (str($preview->status)->startsWith('exited')) {
continue;
}
$preview->update(['status' => 'exited']);
$name = data_get($preview, 'name');
$fqdn = data_get($preview, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$projectUuid = data_get($preview, 'application.environment.project.uuid');
$environmentName = data_get($preview, 'application.environment.name');
$applicationUuid = data_get($preview, 'application.uuid');
if ($projectUuid && $applicationUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningDatabases = $this->databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) {
$database = $this->databases->where('id', $database)->first();
if (str($database->status)->startsWith('exited')) {
continue;
}
$database->update(['status' => 'exited']);
$name = data_get($database, 'name');
$fqdn = data_get($database, 'fqdn');
$containerName = $name;
$projectUuid = data_get($database, 'environment.project.uuid');
$environmentName = data_get($database, 'environment.name');
$databaseUuid = data_get($database, 'uuid');
if ($projectUuid && $databaseUuid && $environmentName) {
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
} else {
$url = null;
}
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// Check if proxy is running
$this->server->proxyType();
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {
return data_get($value, 'Name') === '/coolify-proxy';
}
})->first();
if (! $foundProxyContainer) {
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
}
} catch (\Throwable $e) {
ray($e);
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
$this->server->save();
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
}
}

View File

@@ -3,7 +3,6 @@
namespace App\Jobs;
use App\Models\Server;
use App\Notifications\Server\HighDiskUsage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -44,7 +43,6 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
}
try {
if ($this->server->isFunctional()) {
$this->cleanup(notify: false);
$this->remove_unnecessary_coolify_yaml();
if ($this->server->isSentinelEnabled()) {
$this->server->checkSentinel();
@@ -56,45 +54,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
return handleError($e);
}
try {
// $this->check_docker_engine();
} catch (\Throwable $e) {
// Do nothing
}
}
private function check_docker_engine()
{
$version = instant_remote_process([
'docker info',
], $this->server, false);
if (is_null($version)) {
$os = instant_remote_process([
'cat /etc/os-release | grep ^ID=',
], $this->server, false);
$os = str($os)->after('ID=')->trim();
if ($os === 'ubuntu') {
try {
instant_remote_process([
'systemctl start docker',
], $this->server);
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
} else {
try {
instant_remote_process([
'service docker start',
], $this->server);
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
}
}
}
private function remove_unnecessary_coolify_yaml()
@@ -108,28 +68,4 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
], $this->server, false);
}
}
public function cleanup(bool $notify = false): void
{
$this->disk_usage = $this->server->getDiskUsage();
if ($this->disk_usage >= $this->server->settings->cleanup_after_percentage) {
if ($notify) {
if ($this->server->high_disk_usage_notification_sent) {
ray('high disk usage notification already sent');
return;
} else {
$this->server->high_disk_usage_notification_sent = true;
$this->server->save();
$this->server->team?->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
}
} else {
DockerCleanupJob::dispatchSync($this->server);
$this->cleanup(notify: true);
}
} else {
$this->server->high_disk_usage_notification_sent = false;
$this->server->save();
}
}
}

View File

@@ -21,7 +21,7 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$session = getStripeCustomerPortalSession($this->team);
$mail = new MailMessage();
$mail = new MailMessage;
$mail->view('emails.subscription-invoice-failed', [
'stripeCustomerPortal' => $session->url,
]);

View File

@@ -23,7 +23,7 @@ class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$session = getStripeCustomerPortalSession($this->team);
$mail = new MailMessage();
$mail = new MailMessage;
$mail->subject('Action required: You trial in Coolify Cloud ended.');
$mail->view('emails.trial-ended', [
'stripeCustomerPortal' => $session->url,

View File

@@ -23,7 +23,7 @@ class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$session = getStripeCustomerPortalSession($this->team);
$mail = new MailMessage();
$mail = new MailMessage;
$mail->subject('You trial in Coolify Cloud ends soon.');
$mail->view('emails.trial-ends-soon', [
'stripeCustomerPortal' => $session->url,

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Jobs;
use App\Actions\Server\UpdateCoolify;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 600;
public function handle(): void
{
try {
CheckForUpdatesJob::dispatchSync();
$settings = InstanceSettings::get();
if (! $settings->new_version_available) {
Log::info('No new version available. Skipping update.');
return;
}
$server = Server::findOrFail(0);
if (! $server) {
Log::error('Server not found. Cannot proceed with update.');
return;
}
Log::info('Starting Coolify update process...');
UpdateCoolify::run(false); // false means it's not a manual update
$settings->update(['new_version_available' => false]);
Log::info('Coolify update completed successfully.');
} catch (\Throwable $e) {
Log::error('UpdateCoolifyJob failed: '.$e->getMessage());
// Consider implementing a notification to administrators
}
}
}

View File

@@ -38,7 +38,7 @@ class MaintenanceModeDisabledNotification
$class = "App\Http\Controllers\Webhook\\".ucfirst(str($endpoint)->before('::')->value());
$method = str($endpoint)->after('::')->value();
try {
$instance = new $class();
$instance = new $class;
$instance->$method($request);
} catch (\Throwable $th) {
ray($th);

View File

@@ -179,7 +179,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function getProxyType()
{
// Set Default Proxy Type
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
$this->selectProxy(ProxyTypes::TRAEFIK->value);
// $proxyTypeSet = $this->createdServer->proxy->type;
// if (!$proxyTypeSet) {
// $this->currentState = 'select-proxy';
@@ -257,7 +257,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
$this->createdServer->settings->save();
$this->createdServer->addInitialNetwork();
$this->selectedExistingServer = $this->createdServer->id;
$this->currentState = 'validate-server';
}

View File

@@ -52,7 +52,7 @@ class Docker extends Component
if (request()->query('network_name')) {
$this->network = request()->query('network_name');
} else {
$this->network = new Cuid2(7);
$this->network = new Cuid2;
}
if ($this->servers->count() > 0) {
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();

View File

@@ -38,7 +38,7 @@ class Help extends Component
$this->rateLimit(3, 30);
$this->validate();
$debug = "Route: {$this->path}";
$mail = new MailMessage();
$mail = new MailMessage;
$mail->view(
'emails.help',
[

View File

@@ -39,7 +39,7 @@ class MonacoEditor extends Component
public function render()
{
if (is_null($this->id)) {
$this->id = new Cuid2(7);
$this->id = new Cuid2;
}
if (is_null($this->name)) {

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Livewire;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class NavbarDeleteTeam extends Component
{
public function delete()
{
$currentTeam = currentTeam();
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) {
return;
}
$user->teams()->detach($currentTeam);
$session = DB::table('sessions')->where('user_id', $user->id)->first();
if ($session) {
DB::table('sessions')->where('id', $session->id)->delete();
}
});
refreshSession();
return redirect()->route('team.index');
}
public function render()
{
return view('livewire.navbar-delete-team');
}
}

View File

@@ -56,7 +56,7 @@ class Discord extends Component
public function sendTestNotification()
{
$this->team?->notify(new Test());
$this->team?->notify(new Test);
$this->dispatch('success', 'Test notification sent.');
}

View File

@@ -63,7 +63,7 @@ class Telegram extends Component
public function sendTestNotification()
{
$this->team?->notify(new Test());
$this->team?->notify(new Test);
$this->dispatch('success', 'Test notification sent.');
}

View File

@@ -91,11 +91,25 @@ class Advanced extends Component
public function saveCustomName()
{
if (isset($this->application->settings->custom_internal_name)) {
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
} else {
$this->application->settings->custom_internal_name = null;
}
$customInternalName = $this->application->settings->custom_internal_name;
$server = $this->application->destination->server;
$allApplications = $server->applications();
$foundSameInternalName = $allApplications->filter(function ($application) {
return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->application->settings->custom_internal_name;
});
if ($foundSameInternalName->isNotEmpty()) {
$this->dispatch('error', 'This custom container name is already in use by another application on this server.');
$this->application->settings->custom_internal_name = $customInternalName;
$this->application->settings->refresh();
return;
}
$this->application->settings->save();
$this->dispatch('success', 'Custom name saved.');
}

View File

@@ -84,6 +84,8 @@ class General extends Component
'application.settings.is_static' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
'application.settings.is_preserve_repository_enabled' => 'boolean|required',
'application.watch_paths' => 'nullable',
'application.redirect' => 'string|required',
];
@@ -119,6 +121,8 @@ class General extends Component
'application.settings.is_static' => 'Is static',
'application.settings.is_build_server_enabled' => 'Is build server enabled',
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly',
'application.settings.is_preserve_repository_enabled' => 'Is preserve repository enabled',
'application.watch_paths' => 'Watch paths',
'application.redirect' => 'Redirect',
];
@@ -143,7 +147,7 @@ class General extends Component
$this->ports_exposes = $this->application->ports_exposes;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
$this->customLabels = $this->application->parseContainerLabels();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
@@ -210,7 +214,7 @@ class General extends Component
}
$this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded');
$this->dispatch('refresh_storages');
$this->dispatch('refreshStorages');
$this->dispatch('refreshEnvs');
} catch (\Throwable $e) {
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
@@ -224,7 +228,7 @@ class General extends Component
public function generateDomain(string $serviceName)
{
$uuid = new Cuid2(7);
$uuid = new Cuid2;
$domain = generateFqdn($this->application->destination->server, $uuid);
$this->parsedServiceDomains[$serviceName]['domain'] = $domain;
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
@@ -290,6 +294,9 @@ class General extends Component
public function resetDefaultLabels()
{
if ($this->application->settings->is_container_label_readonly_enabled) {
return;
}
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
$this->ports_exposes = $this->application->ports_exposes;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
@@ -350,7 +357,7 @@ class General extends Component
$this->checkFqdns();
$this->application->save();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();

View File

@@ -5,8 +5,6 @@ namespace App\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Actions\Docker\GetContainersStatus;
use App\Events\ApplicationStatusChanged;
use App\Jobs\ContainerStatusJob;
use App\Jobs\ServerStatusJob;
use App\Models\Application;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -46,11 +44,7 @@ class Heading extends Component
{
if ($this->application->destination->server->isFunctional()) {
GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
// dispatch(new ContainerStatusJob($this->application->destination->server));
} else {
dispatch(new ServerStatusJob($this->application->destination->server));
}
if ($showNotification) {
$this->dispatch('success', 'Success', 'Application status updated.');
}
@@ -102,7 +96,7 @@ class Heading extends Component
protected function setDeploymentUuid()
{
$this->deploymentUuid = new Cuid2(7);
$this->deploymentUuid = new Cuid2;
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
}

View File

@@ -85,7 +85,7 @@ class Previews extends Component
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $preview->pull_request_id, $preview_fqdn);
@@ -170,7 +170,7 @@ class Previews extends Component
protected function setDeploymentUuid()
{
$this->deployment_uuid = new Cuid2(7);
$this->deployment_uuid = new Cuid2;
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
}

View File

@@ -44,7 +44,7 @@ class PreviewsCompose extends Component
$template = $this->preview->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn);

View File

@@ -23,7 +23,7 @@ class Rollback extends Component
public function rollbackImage($commit)
{
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $this->application,

View File

@@ -47,7 +47,7 @@ class CloneMe extends Component
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
$this->project_id = $this->project->id;
$this->servers = currentTeam()->servers;
$this->newName = str($this->project->name.'-clone-'.(string) new Cuid2(7))->slug();
$this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug();
}
public function render()
@@ -106,7 +106,7 @@ class CloneMe extends Component
$databases = $this->environment->databases();
$services = $this->environment->services;
foreach ($applications as $application) {
$uuid = (string) new Cuid2(7);
$uuid = (string) new Cuid2;
$newApplication = $application->replicate()->fill([
'uuid' => $uuid,
'fqdn' => generateFqdn($this->server, $uuid),
@@ -133,7 +133,7 @@ class CloneMe extends Component
}
}
foreach ($databases as $database) {
$uuid = (string) new Cuid2(7);
$uuid = (string) new Cuid2;
$newDatabase = $database->replicate()->fill([
'uuid' => $uuid,
'status' => 'exited',
@@ -161,7 +161,7 @@ class CloneMe extends Component
}
}
foreach ($services as $service) {
$uuid = (string) new Cuid2(7);
$uuid = (string) new Cuid2;
$newService = $service->replicate()->fill([
'uuid' => $uuid,
'environment_id' => $environment->id,

View File

@@ -48,7 +48,7 @@ class DockerImage extends Component
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
ray($image, $tag);
$application = Application::create([
'name' => 'docker-image-'.new Cuid2(7),
'name' => 'docker-image-'.new Cuid2,
'repository_project_id' => 0,
'git_repository' => 'coollabsio/coolify',
'git_branch' => 'main',

View File

@@ -25,11 +25,11 @@ class PublicGitRepository extends Component
public $query;
public bool $branch_found = false;
public bool $branchFound = false;
public string $selected_branch = 'main';
public string $selectedBranch = 'main';
public bool $is_static = false;
public bool $isStatic = false;
public ?string $publish_directory = null;
@@ -62,7 +62,7 @@ class PublicGitRepository extends Component
protected $rules = [
'repository_url' => 'required|url',
'port' => 'required|numeric',
'is_static' => 'required|boolean',
'isStatic' => 'required|boolean',
'publish_directory' => 'nullable|string',
'build_pack' => 'required|string',
'base_directory' => 'nullable|string',
@@ -72,7 +72,7 @@ class PublicGitRepository extends Component
protected $validationAttributes = [
'repository_url' => 'repository',
'port' => 'port',
'is_static' => 'static',
'isStatic' => 'static',
'publish_directory' => 'publish directory',
'build_pack' => 'build pack',
'base_directory' => 'base directory',
@@ -106,17 +106,17 @@ class PublicGitRepository extends Component
$this->port = 3000;
} elseif ($this->build_pack === 'static') {
$this->show_is_static = false;
$this->is_static = false;
$this->isStatic = false;
$this->port = 80;
} else {
$this->show_is_static = false;
$this->is_static = false;
$this->isStatic = false;
}
}
public function instantSave()
{
if ($this->is_static) {
if ($this->isStatic) {
$this->port = 80;
$this->publish_directory = '/dist';
} else {
@@ -126,12 +126,7 @@ class PublicGitRepository extends Component
$this->dispatch('success', 'Application settings updated!');
}
public function load_any_git()
{
$this->branch_found = true;
}
public function load_branch()
public function loadBranch()
{
try {
if (str($this->repository_url)->startsWith('git@')) {
@@ -155,15 +150,21 @@ class PublicGitRepository extends Component
return handleError($e, $this);
}
try {
$this->branch_found = false;
$this->get_git_source();
$this->get_branch();
$this->selected_branch = $this->git_branch;
$this->branchFound = false;
$this->getGitSource();
$this->getBranch();
$this->selectedBranch = $this->git_branch;
} catch (\Throwable $e) {
if (! $this->branch_found && $this->git_branch == 'main') {
if ($this->rate_limit_remaining == 0) {
$this->selectedBranch = $this->git_branch;
$this->branchFound = true;
return;
}
if (! $this->branchFound && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
$this->get_branch();
$this->getBranch();
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -173,13 +174,16 @@ class PublicGitRepository extends Component
}
}
private function get_git_source()
private function getGitSource()
{
$this->repository_url_parsed = Url::fromString($this->repository_url);
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
if ($this->repository_url_parsed->getSegment(3) === 'tree') {
$this->git_branch = str($this->repository_url_parsed->getPath())->after('tree/')->value();
} else {
$this->git_branch = 'main';
}
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
@@ -189,17 +193,17 @@ class PublicGitRepository extends Component
$this->git_source = 'other';
}
private function get_branch()
private function getBranch()
{
if ($this->git_source === 'other') {
$this->branch_found = true;
$this->branchFound = true;
return;
}
if ($this->git_source->getMorphClass() === 'App\Models\GithubApp') {
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int) $this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branch_found = true;
$this->branchFound = true;
}
}
@@ -287,7 +291,7 @@ class PublicGitRepository extends Component
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;
$application->settings->is_static = $this->isStatic;
$application->settings->save();
$fqdn = generateFqdn($destination->server, $application->uuid);

View File

@@ -53,7 +53,7 @@ CMD ["nginx", "-g", "daemon off;"]
$port = 80;
}
$application = Application::create([
'name' => 'dockerfile-'.new Cuid2(7),
'name' => 'dockerfile-'.new Cuid2,
'repository_project_id' => 0,
'git_repository' => 'coollabsio/coolify',
'git_branch' => 'main',

View File

@@ -25,7 +25,6 @@ class Configuration extends Component
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
'check_status',
'refresh' => '$refresh',
];
}
@@ -76,8 +75,7 @@ class Configuration extends Component
{
try {
GetContainersStatus::run($this->service->server);
// dispatch_sync(new ContainerStatusJob($this->service->server));
$this->dispatch('refresh')->self();
$this->dispatch('$refresh');
} catch (\Exception $e) {
return handleError($e, $this);
}

View File

@@ -26,6 +26,8 @@ class FileStorage extends Component
public ?string $workdir = null;
public bool $permanently_delete = true;
protected $rules = [
'fileStorage.is_directory' => 'required',
'fileStorage.fs_path' => 'required',
@@ -56,7 +58,7 @@ class FileStorage extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
$this->dispatch('refreshStorages');
}
}
@@ -71,20 +73,27 @@ class FileStorage extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
$this->dispatch('refreshStorages');
}
}
public function delete()
{
try {
$this->fileStorage->deleteStorageOnServer();
$message = 'File deleted.';
if ($this->fileStorage->is_directory) {
$message = 'Directory deleted.';
}
if ($this->permanently_delete) {
$message = 'Directory deleted from the server.';
$this->fileStorage->deleteStorageOnServer();
}
$this->fileStorage->delete();
$this->dispatch('success', 'File deleted.');
$this->dispatch('success', $message);
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
$this->dispatch('refreshStorages');
}
}

View File

@@ -49,6 +49,11 @@ class Navbar extends Component
}
}
public function check_status_without_notification()
{
$this->dispatch('check_status');
}
public function check_status()
{
$this->dispatch('check_status');
@@ -62,11 +67,17 @@ class Navbar extends Component
public function checkDeployments()
{
$activity = Activity::where('properties->type_uuid', $this->service->uuid)->latest()->first();
$status = data_get($activity, 'properties.status');
if ($status === 'queued' || $status === 'in_progress') {
$this->isDeploymentProgress = true;
} else {
try {
// TODO: This is a temporary solution. We need to refactor this.
// We need to delete null bytes somehow.
$activity = Activity::where('properties->type_uuid', $this->service->uuid)->latest()->first();
$status = data_get($activity, 'properties.status');
if ($status === 'queued' || $status === 'in_progress') {
$this->isDeploymentProgress = true;
} else {
$this->isDeploymentProgress = false;
}
} catch (\Throwable $e) {
$this->isDeploymentProgress = false;
}
}

View File

@@ -9,14 +9,36 @@ class Storage extends Component
{
public $resource;
public $fileStorage;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},FileStorageChanged" => 'refreshStoragesFromEvent',
'refreshStorages',
'addNewVolume',
'refresh_storages' => '$refresh',
];
}
public function mount()
{
$this->refreshStorages();
}
public function refreshStoragesFromEvent()
{
$this->refreshStorages();
$this->dispatch('warning', 'File storage changed. Usually it means that the file / directory is already defined on the server, so Coolify set it up for you properly on the UI.');
}
public function refreshStorages()
{
$this->fileStorage = $this->resource->fileStorages()->get();
$this->dispatch('$refresh');
}
public function addNewVolume($data)
{
try {
@@ -30,7 +52,7 @@ class Storage extends Component
$this->resource->refresh();
$this->dispatch('success', 'Storage added successfully');
$this->dispatch('clearAddStorage');
$this->dispatch('refresh_storages');
$this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -22,7 +22,7 @@ class Danger extends Component
public function mount()
{
$this->modalId = new Cuid2(7);
$this->modalId = new Cuid2;
$parameters = get_route_parameters();
$this->projectUuid = data_get($parameters, 'project_uuid');
$this->environmentName = data_get($parameters, 'environment_name');

View File

@@ -67,7 +67,7 @@ class Destination extends Component
return;
}
$deployment_uuid = new Cuid2(7);
$deployment_uuid = new Cuid2;
$server = Server::find($server_id);
$destination = StandaloneDocker::find($network_id);
queue_application_deployment(

View File

@@ -48,14 +48,14 @@ class Add extends Component
public function submit()
{
$this->validate();
if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
$type = str($this->value)->after('{{')->before('.')->value;
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
$this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
// $type = str($this->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
return;
}
}
// return;
// }
// }
$this->dispatch('saveKey', [
'key' => $this->key,
'value' => $this->value,

View File

@@ -39,7 +39,7 @@ class All extends Component
if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) {
$this->showPreview = true;
}
$this->modalId = new Cuid2(7);
$this->modalId = new Cuid2;
$this->sortMe();
$this->getDevView();
}
@@ -125,29 +125,29 @@ class All extends Component
continue;
}
$found->value = $variable;
if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
$type = str($found->value)->after('{{')->before('.')->value;
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
$this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
// $type = str($found->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
return;
}
}
// return;
// }
// }
$found->save();
continue;
} else {
$environment = new EnvironmentVariable();
$environment = new EnvironmentVariable;
$environment->key = $key;
$environment->value = $variable;
if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
$type = str($environment->value)->after('{{')->before('.')->value;
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
$this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
// $type = str($environment->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
return;
}
}
// return;
// }
// }
$environment->is_build_time = false;
$environment->is_multiline = false;
$environment->is_preview = $isPreview ? true : false;
@@ -209,7 +209,7 @@ class All extends Component
return;
}
$environment = new EnvironmentVariable();
$environment = new EnvironmentVariable;
$environment->key = $data['key'];
$environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time'];

View File

@@ -24,6 +24,7 @@ class Show extends Component
public string $type;
protected $listeners = [
'refresh' => 'refresh',
'compose_loaded' => '$refresh',
];
@@ -46,12 +47,18 @@ class Show extends Component
'env.is_shown_once' => 'Shown Once',
];
public function refresh()
{
$this->env->refresh();
$this->checkEnvs();
}
public function mount()
{
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
$this->isSharedVariable = true;
}
$this->modalId = new Cuid2(7);
$this->modalId = new Cuid2;
$this->parameters = get_route_parameters();
$this->checkEnvs();
}
@@ -101,14 +108,14 @@ class Show extends Component
} else {
$this->validate();
}
if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
$type = str($this->env->value)->after('{{')->before('.')->value;
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
$this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
// $type = str($this->env->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
return;
}
}
// return;
// }
// }
$this->serialize();
$this->env->save();
$this->dispatch('success', 'Environment variable updated.');

View File

@@ -39,7 +39,7 @@ class ResourceOperations extends Component
if (! $new_destination) {
return $this->addError('destination_id', 'Destination not found.');
}
$uuid = (string) new Cuid2(7);
$uuid = (string) new Cuid2;
$server = $new_destination->server;
if ($this->resource->getMorphClass() === 'App\Models\Application') {
$new_resource = $this->resource->replicate()->fill([
@@ -87,7 +87,7 @@ class ResourceOperations extends Component
$this->resource->getMorphClass() === 'App\Models\StandaloneDragonfly' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneClickhouse'
) {
$uuid = (string) new Cuid2(7);
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
@@ -121,7 +121,7 @@ class ResourceOperations extends Component
return redirect()->to($route);
} elseif ($this->resource->type() === 'service') {
$uuid = (string) new Cuid2(7);
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,

View File

@@ -43,7 +43,7 @@ class All extends Component
public function submit($data)
{
try {
$task = new ScheduledTask();
$task = new ScheduledTask;
$task->name = $data['name'];
$task->command = $data['command'];
$task->frequency = $data['frequency'];

View File

@@ -47,7 +47,7 @@ class Show extends Component
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
}
$this->modalId = new Cuid2(7);
$this->modalId = new Cuid2;
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
}

View File

@@ -54,7 +54,11 @@ class Add extends Component
public function mount()
{
$this->file_storage_directory_source = application_configuration_dir()."/{$this->resource->uuid}";
if (str($this->resource->getMorphClass())->contains('Standalone')) {
$this->file_storage_directory_source = database_configuration_dir()."/{$this->resource->uuid}";
} else {
$this->file_storage_directory_source = application_configuration_dir()."/{$this->resource->uuid}";
}
$this->uuid = $this->resource->uuid;
$this->parameters = get_route_parameters();
if (data_get($this->parameters, 'application_uuid')) {
@@ -92,7 +96,7 @@ class Add extends Component
'resource_type' => get_class($this->resource),
],
);
$this->dispatch('refresh_storages');
$this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -119,7 +123,7 @@ class Add extends Component
'resource_type' => get_class($this->resource),
],
);
$this->dispatch('refresh_storages');
$this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -8,5 +8,5 @@ class All extends Component
{
public $resource;
protected $listeners = ['refresh_storages' => '$refresh'];
protected $listeners = ['refreshStorages' => '$refresh'];
}

View File

@@ -39,6 +39,6 @@ class Show extends Component
public function delete()
{
$this->storage->delete();
$this->dispatch('refresh_storages');
$this->dispatch('refreshStorages');
}
}

View File

@@ -37,6 +37,7 @@ class Form extends Component
'server.settings.is_swarm_manager' => 'required|boolean',
'server.settings.is_swarm_worker' => 'required|boolean',
'server.settings.is_build_server' => 'required|boolean',
'server.settings.is_force_cleanup_enabled' => 'required|boolean',
'server.settings.concurrent_builds' => 'required|integer|min:1',
'server.settings.dynamic_timeout' => 'required|integer|min:1',
'server.settings.is_metrics_enabled' => 'required|boolean',
@@ -163,6 +164,9 @@ class Form extends Component
public function validateServer($install = true)
{
$this->server->update([
'validation_logs' => null,
]);
$this->dispatch('init', $install);
}

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Server;
use App\Actions\Server\InstallLogDrain;
use App\Actions\Server\StopLogDrain;
use App\Models\Server;
use Livewire\Component;
@@ -132,6 +133,9 @@ class LogDrains extends Component
'is_logdrain_axiom_enabled' => false,
]);
}
if (! $this->server->isLogDrainEnabled()) {
StopLogDrain::dispatch($this->server);
}
$this->server->settings->save();
$this->dispatch('success', 'Settings saved.');

View File

@@ -104,7 +104,7 @@ class ByIp extends Component
'private_key_id' => $this->private_key_id,
'proxy' => [
// set default proxy type to traefik v2
'type' => ProxyTypes::TRAEFIK_V2->value,
'type' => ProxyTypes::TRAEFIK->value,
'status' => ProxyStatus::EXITED->value,
],
];
@@ -124,7 +124,6 @@ class ByIp extends Component
}
$server->settings->is_build_server = $this->is_build_server;
$server->settings->save();
$server->addInitialNetwork();
return redirect()->route('server.show', $server->uuid);
} catch (\Throwable $e) {

View File

@@ -20,6 +20,10 @@ class Proxy extends Component
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
protected $rules = [
'server.settings.generate_exact_labels' => 'required|boolean',
];
public function mount()
{
$this->selectedProxy = $this->server->proxyType();
@@ -31,13 +35,13 @@ class Proxy extends Component
$this->dispatch('refresh')->self();
}
public function change_proxy()
public function changeProxy()
{
$this->server->proxy = null;
$this->server->save();
}
public function select_proxy($proxy_type)
public function selectProxy($proxy_type)
{
$this->server->proxy->set('status', 'exited');
$this->server->proxy->set('type', $proxy_type);
@@ -49,6 +53,17 @@ class Proxy extends Component
$this->dispatch('proxyStatusUpdated');
}
public function instantSave()
{
try {
$this->validate();
$this->server->settings->save();
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
try {

View File

@@ -50,7 +50,7 @@ class Deploy extends Component
public function proxyStarted()
{
CheckProxy::run($this->server, true);
$this->dispatch('success', 'Proxy started.');
$this->dispatch('proxyStatusUpdated');
}
public function proxyStatusUpdated()
@@ -61,7 +61,7 @@ class Deploy extends Component
public function restart()
{
try {
$this->stop();
$this->stop(forceStop: false);
$this->dispatch('checkProxy');
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -91,7 +91,7 @@ class Deploy extends Component
}
}
public function stop()
public function stop(bool $forceStop = true)
{
try {
if ($this->server->isSwarm()) {
@@ -104,7 +104,7 @@ class Deploy extends Component
], $this->server);
}
$this->server->proxy->status = 'exited';
$this->server->proxy->force_stop = true;
$this->server->proxy->force_stop = $forceStop;
$this->server->save();
$this->dispatch('proxyStatusUpdated');
} catch (\Throwable $e) {

View File

@@ -21,7 +21,6 @@ class DynamicConfigurations extends Component
return [
"echo-private:team.{$teamId},ProxyStatusChanged" => 'loadDynamicConfigurations',
'loadDynamicConfigurations',
'refresh' => '$refresh',
];
}
@@ -42,7 +41,7 @@ class DynamicConfigurations extends Component
$contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
}
$this->contents = $contents;
$this->dispatch('refresh');
$this->dispatch('$refresh');
}
public function mount()

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Server\Proxy;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Livewire\Component;
use Symfony\Component\Yaml\Yaml;
@@ -45,7 +46,7 @@ class NewDynamicConfiguration extends Component
return redirect()->route('server.index');
}
$proxy_type = $this->server->proxyType();
if ($proxy_type === 'TRAEFIK_V2') {
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
if (! str($this->fileName)->endsWith('.yaml') && ! str($this->fileName)->endsWith('.yml')) {
$this->fileName = "{$this->fileName}.yaml";
}
@@ -69,7 +70,7 @@ class NewDynamicConfiguration extends Component
return;
}
}
if ($proxy_type === 'TRAEFIK_V2') {
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
$yaml = Yaml::parse($this->value);
$yaml = Yaml::dump($yaml, 10, 2);
$this->value = $yaml;

View File

@@ -16,7 +16,10 @@ class Status extends Component
public int $numberOfPolls = 0;
protected $listeners = ['proxyStatusUpdated' => '$refresh', 'startProxyPolling'];
protected $listeners = [
'proxyStatusUpdated',
'startProxyPolling',
];
public function startProxyPolling()
{

View File

@@ -87,7 +87,10 @@ class ValidateAndInstall extends Component
{
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (! $this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error;
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
$this->server->update([
'validation_logs' => $this->error,
]);
return;
}
@@ -99,6 +102,9 @@ class ValidateAndInstall extends Component
$this->supported_os_type = $this->server->validateOS();
if (! $this->supported_os_type) {
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$this->server->update([
'validation_logs' => $this->error,
]);
return;
}
@@ -113,6 +119,9 @@ class ValidateAndInstall extends Component
if ($this->install) {
if ($this->number_of_tries == $this->max_tries) {
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$this->server->update([
'validation_logs' => $this->error,
]);
return;
} else {
@@ -126,6 +135,9 @@ class ValidateAndInstall extends Component
}
} else {
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$this->server->update([
'validation_logs' => $this->error,
]);
return;
}
@@ -148,6 +160,9 @@ class ValidateAndInstall extends Component
$this->dispatch('success', 'Server validated.');
} else {
$this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
$this->server->update([
'validation_logs' => $this->error,
]);
return;
}

View File

@@ -1,111 +0,0 @@
<?php
namespace App\Livewire\Settings;
use App\Models\InstanceSettings as ModelsInstanceSettings;
use App\Models\Server;
use Livewire\Component;
class Configuration extends Component
{
public ModelsInstanceSettings $settings;
public bool $do_not_track;
public bool $is_auto_update_enabled;
public bool $is_registration_enabled;
public bool $is_dns_validation_enabled;
public bool $is_api_enabled;
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
protected Server $server;
protected $rules = [
'settings.fqdn' => 'nullable',
'settings.resale_license' => 'nullable',
'settings.public_port_min' => 'required',
'settings.public_port_max' => 'required',
'settings.custom_dns_servers' => 'nullable',
'settings.instance_name' => 'nullable',
'settings.allowed_ips' => 'nullable',
];
protected $validationAttributes = [
'settings.fqdn' => 'FQDN',
'settings.resale_license' => 'Resale License',
'settings.public_port_min' => 'Public port min',
'settings.public_port_max' => 'Public port max',
'settings.custom_dns_servers' => 'Custom DNS servers',
'settings.allowed_ips' => 'Allowed IPs',
];
public function mount()
{
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
$this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
$this->is_api_enabled = $this->settings->is_api_enabled;
}
public function instantSave()
{
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
$this->settings->is_api_enabled = $this->is_api_enabled;
$this->settings->save();
$this->dispatch('success', 'Settings updated!');
}
public function submit()
{
try {
$error_show = false;
$this->server = Server::findOrFail(0);
$this->resetErrorBag();
if ($this->settings->public_port_min > $this->settings->public_port_max) {
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
return;
}
$this->validate();
if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
if (! validate_dns_entry($this->settings->fqdn, $this->server)) {
$this->dispatch('error', "Validating DNS failed.<br><br>Make sure you have added the DNS records correctly.<br><br>{$this->settings->fqdn}->{$this->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$error_show = true;
}
}
if ($this->settings->fqdn) {
check_domain_usage(domain: $this->settings->fqdn);
}
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
return str($dns)->trim()->lower();
});
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
$this->settings->allowed_ips = str($this->settings->allowed_ips)->replaceEnd(',', '')->trim();
$this->settings->allowed_ips = str($this->settings->allowed_ips)->trim()->explode(',')->map(function ($ip) {
return str($ip)->trim();
});
$this->settings->allowed_ips = $this->settings->allowed_ips->unique();
$this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
$this->settings->save();
$this->server->setupDynamicProxyConfiguration();
if (! $error_show) {
$this->dispatch('success', 'Instance settings updated successfully!');
}
} catch (\Exception $e) {
return handleError($e, $this);
}
}
}

View File

@@ -2,39 +2,170 @@
namespace App\Livewire\Settings;
use App\Jobs\CheckForUpdatesJob;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\StandalonePostgresql;
use App\Models\Server;
use Livewire\Component;
class Index extends Component
{
public InstanceSettings $settings;
public StandalonePostgresql $database;
public bool $do_not_track;
public $s3s;
public bool $is_auto_update_enabled;
public bool $is_registration_enabled;
public bool $is_dns_validation_enabled;
public bool $is_api_enabled;
public string $auto_update_frequency;
public string $update_check_frequency;
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
protected Server $server;
protected $rules = [
'settings.fqdn' => 'nullable',
'settings.resale_license' => 'nullable',
'settings.public_port_min' => 'required',
'settings.public_port_max' => 'required',
'settings.custom_dns_servers' => 'nullable',
'settings.instance_name' => 'nullable',
'settings.allowed_ips' => 'nullable',
'settings.is_auto_update_enabled' => 'boolean',
'auto_update_frequency' => 'string',
'update_check_frequency' => 'string',
];
protected $validationAttributes = [
'settings.fqdn' => 'FQDN',
'settings.resale_license' => 'Resale License',
'settings.public_port_min' => 'Public port min',
'settings.public_port_max' => 'Public port max',
'settings.custom_dns_servers' => 'Custom DNS servers',
'settings.allowed_ips' => 'Allowed IPs',
'settings.is_auto_update_enabled' => 'Auto Update Enabled',
'auto_update_frequency' => 'Auto Update Frequency',
'update_check_frequency' => 'Update Check Frequency',
];
public function mount()
{
if (isInstanceAdmin()) {
$settings = \App\Models\InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($database) {
if ($database->status !== 'running') {
$database->status = 'running';
$database->save();
}
$this->database = $database;
}
$this->settings = $settings;
$this->s3s = $s3s;
$this->settings = InstanceSettings::get();
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
$this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
$this->is_api_enabled = $this->settings->is_api_enabled;
$this->auto_update_frequency = $this->settings->auto_update_frequency;
$this->update_check_frequency = $this->settings->update_check_frequency;
} else {
return redirect()->route('dashboard');
}
}
public function instantSave()
{
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
$this->settings->is_api_enabled = $this->is_api_enabled;
$this->settings->auto_update_frequency = $this->auto_update_frequency;
$this->settings->update_check_frequency = $this->update_check_frequency;
$this->settings->save();
$this->dispatch('success', 'Settings updated!');
}
public function submit()
{
try {
$error_show = false;
$this->server = Server::findOrFail(0);
$this->resetErrorBag();
if ($this->settings->public_port_min > $this->settings->public_port_max) {
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
return;
}
$this->validate();
if ($this->is_auto_update_enabled && ! validate_cron_expression($this->auto_update_frequency)) {
$this->dispatch('error', 'Invalid Cron / Human expression for Auto Update Frequency.');
if (empty($this->auto_update_frequency)) {
$this->auto_update_frequency = '0 0 * * *';
}
return;
}
if (! validate_cron_expression($this->update_check_frequency)) {
$this->dispatch('error', 'Invalid Cron / Human expression for Update Check Frequency.');
if (empty($this->update_check_frequency)) {
$this->update_check_frequency = '0 * * * *';
}
return;
}
if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
if (! validate_dns_entry($this->settings->fqdn, $this->server)) {
$this->dispatch('error', "Validating DNS failed.<br><br>Make sure you have added the DNS records correctly.<br><br>{$this->settings->fqdn}->{$this->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$error_show = true;
}
}
if ($this->settings->fqdn) {
check_domain_usage(domain: $this->settings->fqdn);
}
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
return str($dns)->trim()->lower();
});
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
$this->settings->allowed_ips = str($this->settings->allowed_ips)->replaceEnd(',', '')->trim();
$this->settings->allowed_ips = str($this->settings->allowed_ips)->trim()->explode(',')->map(function ($ip) {
return str($ip)->trim();
});
$this->settings->allowed_ips = $this->settings->allowed_ips->unique();
$this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
$this->settings->is_api_enabled = $this->is_api_enabled;
$this->settings->auto_update_frequency = $this->auto_update_frequency;
$this->settings->update_check_frequency = $this->update_check_frequency;
$this->settings->save();
$this->server->setupDynamicProxyConfiguration();
if (! $error_show) {
$this->dispatch('success', 'Instance settings updated successfully!');
}
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function checkManually()
{
CheckForUpdatesJob::dispatchSync();
$this->dispatch('updateAvailable');
$settings = InstanceSettings::get();
if ($settings->new_version_available) {
$this->dispatch('success', 'New version available!');
} else {
$this->dispatch('success', 'No new version available.');
}
}
public function render()
{
return view('livewire.settings.index');

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Livewire\Settings;
namespace App\Livewire;
use App\Jobs\DatabaseBackupJob;
use App\Models\InstanceSettings;
@@ -10,7 +10,7 @@ use App\Models\Server;
use App\Models\StandalonePostgresql;
use Livewire\Component;
class Backup extends Component
class SettingsBackup extends Component
{
public InstanceSettings $settings;
@@ -41,8 +41,24 @@ class Backup extends Component
public function mount()
{
$this->backup = $this->database?->scheduledBackups->first() ?? null;
$this->executions = $this->backup?->executions ?? [];
if (isInstanceAdmin()) {
$settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($database) {
if ($database->status !== 'running') {
$database->status = 'running';
$database->save();
}
$this->database = $database;
}
$this->settings = $settings;
$this->s3s = $s3s;
$this->backup = $this->database?->scheduledBackups?->first() ?? null;
$this->executions = $this->backup?->executions ?? [];
} else {
return redirect()->route('dashboard');
}
}
public function add_coolify_database()

View File

@@ -1,12 +1,12 @@
<?php
namespace App\Livewire\Settings;
namespace App\Livewire;
use App\Models\InstanceSettings;
use App\Notifications\TransactionalEmails\Test;
use Livewire\Component;
class Email extends Component
class SettingsEmail extends Component
{
public InstanceSettings $settings;
@@ -42,7 +42,13 @@ class Email extends Component
public function mount()
{
$this->emails = auth()->user()->email;
if (isInstanceAdmin()) {
$this->settings = InstanceSettings::get();
$this->emails = auth()->user()->email;
} else {
return redirect()->route('dashboard');
}
}
public function submitFromFields()

View File

@@ -1,11 +1,11 @@
<?php
namespace App\Livewire\Settings;
namespace App\Livewire;
use App\Models\OauthSetting;
use Livewire\Component;
class Auth extends Component
class SettingsOauth extends Component
{
public $oauth_settings_map;

View File

@@ -59,7 +59,7 @@ class Create extends Component
{
try {
$this->validate();
$this->storage = new S3Storage();
$this->storage = new S3Storage;
$this->storage->name = $this->name;
$this->storage->description = $this->description ?? null;
$this->storage->region = $this->region;

View File

@@ -2,7 +2,7 @@
namespace App\Livewire\Tags;
use App\Http\Controllers\Api\Deploy;
use App\Http\Controllers\Api\DeployController;
use App\Models\Tag;
use Illuminate\Support\Collection;
use Livewire\Attributes\Url;
@@ -51,11 +51,11 @@ class Index extends Component
{
try {
$this->applications->each(function ($resource) {
$deploy = new Deploy();
$deploy = new DeployController;
$deploy->deploy_resource($resource);
});
$this->services->each(function ($resource) {
$deploy = new Deploy();
$deploy = new DeployController;
$deploy->deploy_resource($resource);
});
$this->dispatch('success', 'Mass deployment started.');

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