Compare commits

...

168 Commits

Author SHA1 Message Date
Andras Bacsai
bb8e0eb7bf Merge pull request #2232 from coollabsio/next
quickfixes
2024-05-17 13:44:12 +02:00
Andras Bacsai
bb451ac3b5 Refactor BackupExecutions.php to use optional chaining for deleting failed backup executions 2024-05-17 13:43:36 +02:00
Andras Bacsai
b0b7842f9c Refactor BackupEdit component to handle null s3_storage_id 2024-05-17 13:43:21 +02:00
Andras Bacsai
aad661cb65 Merge pull request #2231 from coollabsio/next
v4.0.0-beta.282
2024-05-17 13:41:33 +02:00
Andras Bacsai
ed9b63520d Refactor gitCommitLink method in Application model 2024-05-17 13:40:28 +02:00
Andras Bacsai
5de1246827 Merge pull request #2230 from coollabsio/next
v4.0.0-beta.281
2024-05-17 12:45:26 +02:00
Andras Bacsai
edc3b014cd fix: telegram group chat notifications 2024-05-17 12:09:22 +02:00
Andras Bacsai
fec98f45ce feat: Improve sorting of environment variables in the All component 2024-05-17 11:40:32 +02:00
Andras Bacsai
94810d5066 fix: use rc in hc 2024-05-17 11:39:26 +02:00
Andras Bacsai
431cc796d8 feat: sort envs alphabetically and creation date 2024-05-17 11:10:57 +02:00
Andras Bacsai
e9d2dbcc92 Refactor Upgrade.php and add isDev condition for upgrade availability 2024-05-17 10:12:17 +02:00
Andras Bacsai
7a618ef89c feat: Add lastDeploymentInfo and lastDeploymentLink props to breadcrumbs and status components 2024-05-17 10:12:13 +02:00
Andras Bacsai
5b56249d12 Refactor gitCommitLink method in Application model 2024-05-17 10:12:05 +02:00
Andras Bacsai
e2ba5abe76 fix: hc from localhost to 127.0.0.1 2024-05-17 10:11:55 +02:00
Andras Bacsai
70a4b7c863 feat: new manual update process + remove next_channel 2024-05-17 09:52:19 +02:00
Andras Bacsai
10fde1b1ef feat: shows the latest deployment commit + message on status 2024-05-17 08:53:25 +02:00
Andras Bacsai
6131746180 Add all_servers property to Kernel class for better code organization 2024-05-16 21:36:01 +02:00
Andras Bacsai
bf953bf1b5 Update version numbers 2024-05-16 21:35:57 +02:00
Andras Bacsai
d81da10dfa Merge pull request #2225 from coollabsio/next
v4.0.0-beta.280
2024-05-16 17:25:36 +02:00
Andras Bacsai
ed9cdc1ab7 Fix issue with parsing services in ScheduledTask/All.php 2024-05-16 17:24:57 +02:00
Andras Bacsai
9f506eb83a fix: commit message length 2024-05-16 17:22:32 +02:00
Andras Bacsai
36e8c52b01 Update version numbers to 4.0.0-beta.280 2024-05-16 17:21:53 +02:00
Andras Bacsai
e6d1233bfe Merge pull request #2220 from coollabsio/next
v4.0.0-beta.279
2024-05-16 13:34:33 +02:00
Andras Bacsai
6847459022 chore: Limit commit message length to 50 characters in ApplicationDeploymentJob 2024-05-16 13:33:35 +02:00
Andras Bacsai
fbf64f8037 Refactor ApplicationDeploymentJob to conditionally notify team on DeploymentSuccess 2024-05-16 11:25:58 +02:00
Andras Bacsai
2b74ca2746 Refactor scheduling of container status and log drain checks 2024-05-16 11:23:31 +02:00
Andras Bacsai
b106a82308 chore: Update version numbers to 4.0.0-beta.279 2024-05-16 11:23:26 +02:00
Andras Bacsai
5700f2f78a Merge pull request #2205 from coollabsio/next
v4.0.0-beta.278
2024-05-16 10:20:00 +02:00
Andras Bacsai
c5ca6abb90 Refactor sleep duration in check_resources method 2024-05-16 10:08:13 +02:00
Andras Bacsai
6826b6e1f8 Refactor container status and log drain checks scheduling 2024-05-16 10:08:00 +02:00
Andras Bacsai
27c4fa2fcf Refactor boarding index.blade.php for improved code structure and readability 2024-05-16 10:05:28 +02:00
Andras Bacsai
6ef1aff991 Refactor scheduling of container status and log drain checks 2024-05-16 09:56:38 +02:00
Andras Bacsai
57a026a7a1 Refactor modal input button title for dynamic configuration navbar 2024-05-16 09:39:29 +02:00
Andras Bacsai
cfc785358e Refactor README.md to add new sponsor logos 2024-05-16 09:11:52 +02:00
Andras Bacsai
2446dc6950 feat: toggle label escaping mechanism 2024-05-15 17:52:14 +02:00
Andras Bacsai
f98405188d Refactor shared.php to escape dollar signs in service labels 2024-05-15 15:45:56 +02:00
Andras Bacsai
a5cf24773c Refactor pricing-plans.blade.php for improved code structure and readability 2024-05-15 14:43:11 +02:00
Andras Bacsai
01c1e4f8cb chore: Update Docker and Docker Compose versions in Dockerfiles 2024-05-15 14:32:10 +02:00
Andras Bacsai
0759fb6436 chore: Update DOCKER_VERSION to 26.0 in install.sh script 2024-05-15 14:32:01 +02:00
Andras Bacsai
ed5188069b Refactor destination/all.blade.php for improved code structure and readability 2024-05-15 14:10:44 +02:00
Andras Bacsai
aa0a9bde76 Refactor storage create form for better usability and validation 2024-05-15 14:10:03 +02:00
Andras Bacsai
56a450a936 Refactor commit_message column length to 50 characters 2024-05-15 12:35:28 +02:00
Andras Bacsai
1e01106b94 chore: Remove unnecessary code for saving commit message 2024-05-15 11:53:28 +02:00
Andras Bacsai
b9a755d6d3 chore: Update ServerLimitCheckJob.php to handle missing serverLimit value 2024-05-15 11:44:42 +02:00
Andras Bacsai
1d9d6c899d Refactor User model role() method to use data_get() for better readability 2024-05-15 11:41:11 +02:00
Andras Bacsai
444dffb458 chore: Refactor GitHub app selection UI in project creation form 2024-05-15 11:34:59 +02:00
Andras Bacsai
3f1b7192ff feat: save commit message and better view on deployments 2024-05-15 11:31:03 +02:00
Andras Bacsai
b992b19c66 feat: adding new COOLIFY_ variables 2024-05-15 11:30:35 +02:00
Andras Bacsai
e341121f61 chore: Refactor deployment index.blade.php for improved readability and rollback handling 2024-05-15 10:47:57 +02:00
Andras Bacsai
cd3e2963b3 Refactor gitCommitLink method to handle different git repository formats 2024-05-15 10:45:08 +02:00
Andras Bacsai
346faf1d07 chore: Refactor applications.php to remove unused imports and improve code readability 2024-05-15 10:45:01 +02:00
Andras Bacsai
1e09b2bbd8 fix: use commit hash on webhooks 2024-05-15 10:44:45 +02:00
Andras Bacsai
0ffba45517 chore: Update twenty CRM template with environment variables and dependencies 2024-05-15 09:46:31 +02:00
Andras Bacsai
32ff346154 chore: Refactor Service.php to handle missing admin user in extraFields() method 2024-05-15 09:46:28 +02:00
Andras Bacsai
0008f44255 Merge pull request #2098 from jonkristian/main
Added twenty crm template
2024-05-15 09:11:04 +02:00
Andras Bacsai
a3c519a061 Merge pull request #2183 from Lxcasx/main
Correct repository links in source view for git SSH URLs
2024-05-15 09:08:22 +02:00
Andras Bacsai
5b8a923cb5 Merge pull request #2206 from maurobender/fix_schduled_tasks_executing_using_host_environment
Fix scheduled tasks being executed using host environment variables
2024-05-15 08:58:50 +02:00
Andras Bacsai
576cbc0b90 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-05-14 15:40:47 +02:00
Andras Bacsai
e95e2cf152 feat: Add AdminRemoveUser command to remove users from the database 2024-05-14 15:40:45 +02:00
Andras Bacsai
317dc10af4 fix: improve scheduled task adding/removing 2024-05-14 15:19:28 +02:00
Andras Bacsai
f06065337c chore: Handle invalid cron strings in Kernel.php 2024-05-14 15:18:59 +02:00
Mauro E. Bender
cc870ca302 Fix scheduled tasks being executed using host environment variables 2024-05-14 14:29:27 +02:00
Andras Bacsai
80f5506b18 Merge pull request #2204 from TorstenDittmann/fix-compose-dependencies-on-previews
fix: docker compose dependencies for pr previews
2024-05-14 13:41:39 +02:00
Torsten Dittmann
5321a75272 fix: properly populating dependencies 2024-05-14 13:33:29 +02:00
Andras Bacsai
b70a78b7aa chore: Remove debug logging statements in Kernel.php 2024-05-14 13:04:17 +02:00
Andras Bacsai
5ad08791ea chore: Skip scheduled tasks if application or service is not running 2024-05-14 12:52:01 +02:00
Andras Bacsai
9dc3ec0bf8 chore: Refactor scheduled task view to improve code readability and maintainability 2024-05-14 12:45:21 +02:00
Andras Bacsai
69dd9d0cac chore: Update hover behavior and cursor style in scheduled task executions view 2024-05-14 12:45:17 +02:00
Torsten Dittmann
24f923e88e fix: docker compose dependencies for pr previews 2024-05-14 12:19:33 +02:00
Andras Bacsai
b5552a216d fix: only allow push and mr gitlab events 2024-05-14 11:55:20 +02:00
Lucas Heinschke
1988c617a0 Correct repository links in source view for git SSH URLs 2024-05-10 16:28:14 +02:00
Andras Bacsai
5e531d6f96 fix: only show realtime error on non-cloud instances 2024-05-10 12:50:39 +02:00
Andras Bacsai
1fb7e97700 Fix error handling in GetContainersStatus.php and increase length of stripe_comment field in migrations 2024-05-10 12:10:47 +02:00
Andras Bacsai
64d27156f5 chore: Update version numbers to 4.0.0-beta.278 2024-05-10 12:10:35 +02:00
Andras Bacsai
b528a0f4ec Merge pull request #2164 from coollabsio/next
v4.0.0-beta.277
2024-05-10 10:50:40 +02:00
Andras Bacsai
7f265a6692 Merge pull request #2178 from wutangpaul/main
Fix silent failing of install script on manjaro, make it behave as arch
2024-05-10 10:01:25 +02:00
Andras Bacsai
55e00e35c1 Merge pull request #2175 from chz/fix/2174
Fix: Resource Operations page: incorrect color for server and project name
2024-05-10 09:59:18 +02:00
Andras Bacsai
d94e1ba55b ui: fix a few boxes here and there 2024-05-10 09:56:39 +02:00
Andras Bacsai
00e7167174 Merge pull request #2173 from maurobender/fix_one_shot_commands_executing_using_host_environment
Fix one-shot commands for a specific container being executed using host environment variables
2024-05-10 09:45:54 +02:00
Andras Bacsai
9d7b69fc0e Merge pull request #2159 from snekROmonoro/patch-1
"Inprogress" -> "In progress"
2024-05-10 09:43:59 +02:00
Andras Bacsai
db9a68e9c9 make sentinel enabled env var 2024-05-10 09:21:19 +02:00
Andras Bacsai
b5d9d6e268 chore: Comment out server sentinel check in ServerStatusJob 2024-05-10 09:12:19 +02:00
Andras Bacsai
5ff0c563ec disable sentinel for now 2024-05-10 08:42:17 +02:00
Paul McClean
e2131523ec Force manjaro to be arch 2024-05-10 00:30:31 +01:00
Andras Bacsai
1f5f51e3e5 Refactor metrics retrieval and update chart visualization 2024-05-09 15:44:32 +02:00
Andras Bacsai
1bb0d54dce Update image pull schedule to every five minutes 2024-05-09 15:27:14 +02:00
Andras Bacsai
094bb37049 Update sentinel version to 0.0.4 2024-05-09 15:26:39 +02:00
Andras Bacsai
58601db5ef feat: init metrics 2024-05-09 13:25:18 +02:00
Andras Bacsai
b5bef98a9b chore: Update permissions on metrics and logs directories 2024-05-09 12:35:49 +02:00
Andras Bacsai
1026f1efa5 refactor: add SCHEDULER environment variable to StartSentinel.php 2024-05-09 12:30:51 +02:00
Andras Bacsai
0c673fb524 chore: update sentinel version to 0.0.2 in versions.json 2024-05-09 12:23:01 +02:00
Andras Bacsai
7ee2c9478d chore: add metrics and logs directories to installation script 2024-05-09 12:22:54 +02:00
Andras Bacsai
4b51f8251b fix: change permissions on newly created dirs 2024-05-09 12:22:49 +02:00
Andras Bacsai
e91a64b1cc Refactor GetContainersStatus.php for improved readability and maintainability 2024-05-09 12:10:06 +02:00
Chingiz Mammadov
8920762fc5 fix: Color for resource operation server and project name 2024-05-09 01:48:44 +04:00
Andras Bacsai
ba40f93386 do not use sentinel for container details for now 2024-05-08 20:59:58 +02:00
Andras Bacsai
781bf52a8e Refactor PullSentinelImageJob.php to only start Sentinel if local version is outdated 2024-05-08 19:32:13 +02:00
Mauro E. Bender
cca1a9832e Fix one-shot commands for a specific container being executed using host environment variables 2024-05-08 19:20:21 +02:00
Andras Bacsai
1a152a5597 feat: pull new sentinel image and restart container 2024-05-08 19:19:32 +02:00
Andras Bacsai
76a5290351 Refactor StartSentinel.php for improved readability and maintainability 2024-05-08 15:29:59 +02:00
Andras Bacsai
829e17ef2b Refactor GetContainersStatus.php for improved readability and maintainability 2024-05-08 15:19:12 +02:00
Andras Bacsai
bc5d3bea14 Refactor GetContainersStatus.php for improved readability and maintainability 2024-05-08 15:04:13 +02:00
Andras Bacsai
edf2a2e68d Refactor ContainerStatusJob.php for improved readability and maintainability 2024-05-08 14:53:48 +02:00
Andras Bacsai
5df443a016 Refactor StartSentinel.php for improved readability and maintainability 2024-05-08 14:48:04 +02:00
Andras Bacsai
f6396f2e74 fix: turn off hc for dockerimage/docker base deployments by default
fix: loading github app
2024-05-08 14:42:45 +02:00
Andras Bacsai
c618e58a11 feat: start Sentinel on servers. 2024-05-08 14:22:35 +02:00
Andras Bacsai
2ea27acdde add cloud scripts 2024-05-08 12:29:36 +02:00
Andras Bacsai
331cad276e chore: Refactor ApplicationDeploymentJob.php for improved readability and maintainability 2024-05-08 10:36:38 +02:00
Andras Bacsai
b74eab8377 ui: fix tag view 2024-05-08 10:36:30 +02:00
Andras Bacsai
fb80318553 Refactor docker-compose files to remove version numbers 2024-05-08 09:23:36 +02:00
Andras Bacsai
3eb4aed867 chore: Refactor GetContainersStatus.php for improved readability and maintainability 2024-05-08 09:23:32 +02:00
Andras Bacsai
f6f959a897 feat: experimental sentinel 2024-05-07 15:41:50 +02:00
Andras Bacsai
2b422a542a fix: empty db conf
feat: add listen_addresses to postgresql if its missing in the custom conf
2024-05-07 12:35:24 +02:00
Andras Bacsai
d0e9d58a43 chore: Add Listmonk service template and logo 2024-05-07 10:22:02 +02:00
Andras Bacsai
8a1933b9b2 chore: remove docker compose versions 2024-05-07 09:43:51 +02:00
Andras Bacsai
96a587f343 Update version to 4.0.0-beta.277 2024-05-07 09:43:41 +02:00
snekROmonoro
2bb6a71874 "Inprogress" -> "In progress" 2024-05-06 15:42:01 +02:00
Andras Bacsai
94acd12f1c Merge pull request #2158 from coollabsio/next
v4.0.0-beta.276
2024-05-06 14:34:30 +02:00
Andras Bacsai
5e44a61068 chore: Improve menu item styling and spacing in project index and show views 2024-05-06 14:33:45 +02:00
Andras Bacsai
a54f0ed94d chore: Improve menu item styling and spacing in service configuration and index views 2024-05-06 14:30:50 +02:00
Andras Bacsai
662c6f3cc2 chore: Improve menu item styling and spacing in service configuration and index views 2024-05-06 14:28:16 +02:00
Andras Bacsai
d93c635a0a Update version to 4.0.0-beta.276 2024-05-06 14:28:13 +02:00
Andras Bacsai
17b73aaf91 Merge pull request #2155 from coollabsio/next
v4.0.0-beta.275
2024-05-06 14:03:15 +02:00
Andras Bacsai
c194911458 revert 2024-05-06 14:01:07 +02:00
Andras Bacsai
92e99e3fb4 chore: dark mode should be the default 2024-05-06 14:00:20 +02:00
Andras Bacsai
848e6102a1 fix css here and there 2024-05-06 13:58:19 +02:00
Andras Bacsai
ef37bf9b1a Merge branch 'next' of github.com:coollabsio/coolify into next 2024-05-06 13:31:13 +02:00
Andras Bacsai
eb41e023c7 feat: Add lazy loading for images in General.php and improve Docker Compose file handling in Application.php 2024-05-06 13:03:55 +02:00
Andras Bacsai
6b4987bf39 fix: confirmation for custom labels 2024-05-06 12:52:06 +02:00
Andras Bacsai
d46ff76887 feat: Add container name to network aliases in ApplicationDeploymentJob 2024-05-06 12:47:49 +02:00
Andras Bacsai
d53a9e672c fix: Comment out internal notification in email_verify method 2024-05-06 12:39:47 +02:00
Andras Bacsai
80ef99a24b Merge branch 'next' of github.com:coollabsio/coolify into next 2024-05-06 12:33:24 +02:00
Andras Bacsai
6062a1f8c7 chore: Update DNS server validation helper text 2024-05-06 12:33:22 +02:00
Andras Bacsai
d974bd9a07 Merge pull request #2120 from bitsnaps/gitpod
fixt: env file example
2024-05-06 12:31:43 +02:00
Andras Bacsai
fb8c9566d5 Merge pull request #2148 from SIPC/Add-Chinese-Simplified
Add Chinese Simplified lang
2024-05-06 12:31:24 +02:00
Andras Bacsai
7431bd69e9 Merge pull request #2149 from SIPC/fix-install.sh-error
fix: install.sh error
2024-05-06 12:31:06 +02:00
Andras Bacsai
5d7f393e94 Merge pull request #2139 from eltociear/patch-3
fix: typo in tags.blade.php
2024-05-06 12:29:59 +02:00
Andras Bacsai
ef25d100e1 Merge pull request #2138 from chikof/patch-1
Change of wording
2024-05-06 12:29:45 +02:00
Andras Bacsai
17f82c8972 Merge pull request #2137 from LucianoLaratelli/main
Don't append '.git' for sr.ht repositories
2024-05-06 12:29:13 +02:00
Andras Bacsai
05c937743c feat: custom internal container names
fix: add warning if hc fails with dockerfile/dockerimage based deployments
2024-05-06 11:45:22 +02:00
Andras Bacsai
bf2e7ff130 chore: update version to 4.0.0-beta.275 2024-05-06 11:44:27 +02:00
Francesco Bruno
1b30ee606f improved responsivness in case the service/app/database is deployed 2024-05-05 18:14:14 +00:00
Francesco Bruno
3235907266 Refactor settings page layout for better responsiveness 2024-05-05 17:18:48 +00:00
Francesco Bruno
99c7e417d6 Global setting's input text uses flex wrap for better spacing on small screens 2024-05-05 17:17:54 +00:00
Francesco Bruno
d81906d348 assing a min height to navbar list 2024-05-05 17:09:22 +00:00
Francesco Bruno
296872d2e4 database configuration input boxes use wrap in small screens 2024-05-05 17:04:59 +00:00
Francesco Bruno
7cd02b4916 moved width class on parent 2024-05-05 16:58:55 +00:00
Francesco Bruno
0e217f48be Improved input text spacing with small screens 2024-05-05 16:58:12 +00:00
Francesco Bruno
61fdf4b6c7 use muni-item-active for submenus for services and database 2024-05-05 16:41:15 +00:00
Francesco Bruno
91dbf1f01a navbar changed in order top move as first element the button for start/redeploy 2024-05-05 16:33:52 +00:00
Francesco Bruno
d52aac76c0 Changed the breadcrumbs for let it using flex-wrap 2024-05-05 15:55:58 +00:00
Francesco Bruno
6102e441d6 In small screen the submenu is moved up, under the navbar-main 2024-05-05 15:23:25 +00:00
sipc.ink
1a5fec39c0 fix: install.sh error 2024-05-05 21:11:44 +08:00
sipc.ink
efa5091b98 Create zh-cn.json 2024-05-05 21:07:49 +08:00
Ikko Eltociear Ashimine
be4386658a fix: typo in tags.blade.php
seperated -> separated
2024-05-04 01:23:31 +09:00
Francesco Bruno
0cddce7a37 Changed the navbar-main class to utilize overflow-x-scroll, preventing the page from overflowing entirely. 2024-05-03 14:51:08 +00:00
Francesco Bruno
a86d13632e Format foar a correct tailwind positioning of classes and eliminated usless spaces. 2024-05-03 14:49:41 +00:00
Chiko
d71682a3f7 Change of wording 2024-05-03 15:06:06 +01:00
Luciano Laratelli
c901ace21a don't append '.git' for sr.ht repositories 2024-05-03 09:31:05 -04:00
Andras Bacsai
418398a870 Merge pull request #2133 from coollabsio/next
Fix server status check in ServerStatusJob.php
2024-05-03 13:46:06 +02:00
Andras Bacsai
baca57062e Fix server status check in ServerStatusJob.php 2024-05-03 13:45:42 +02:00
Andras Bacsai
52df8e6e8b Fix server status check in ServerStatusJob.php 2024-05-03 13:43:54 +02:00
Andras Bacsai
b51747378a Merge pull request #2132 from coollabsio/next
v4.0.0-beta.274
2024-05-03 13:35:26 +02:00
Andras Bacsai
424a6b0428 revert server checking fn 2024-05-03 13:31:35 +02:00
Jon Kristian Nilsen
4549223d6d Storage type should be exposed. Fixed healthcheck test. 2024-05-03 12:56:49 +02:00
Jon Kristian Nilsen
91dd3468d5 Storage type should be exposed. Fixed healthcheck test. 2024-05-03 12:55:05 +02:00
Ibrahim H
692047e4c8 fixt: env file 2024-05-02 19:11:46 +01:00
Jon Kristian Nilsen
9cb981f068 Added twenty crm template + logo. 2024-04-29 13:29:42 +02:00
209 changed files with 2824 additions and 1410 deletions

View File

@@ -3,7 +3,7 @@ tasks:
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614
before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
init: |
cp .env.example .env &&
cp .env.development.example .env &&
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
sed -i "s#USERID=#USERID=33333#g" .env
sed -i "s#GROUPID=#GROUPID=33333#g" .env
@@ -20,7 +20,7 @@ tasks:
echo "Waiting for Sail environment to boot up."
gp sync-await spin-is-ready
./vendor/bin/spin exec vite npm install
./vendor/bin/spin exec vite npm run dev
./vendor/bin/spin exec vite npm run dev -- --host
- name: Laravel Queue Worker, listening to code changes
command: |

View File

@@ -35,6 +35,10 @@ Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
## Github Sponsors ($40+)
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
<a href="https://www.quantcdn.io/?utm_source=coolify.io"><img src="https://github.com/quantcdn.png" width="60px" alt="QuantCDN"/></a>
<a href="https://www.runpod.io/?utm_source=coolify.io">
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
@@ -62,6 +66,7 @@ Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
## Individuals
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>

View File

@@ -33,7 +33,6 @@ class StartClickhouse
$environment_variables = $this->generate_environment_variables();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,

View File

@@ -107,7 +107,6 @@ class StartDatabaseProxy
COPY nginx.conf /etc/nginx/nginx.conf
EOF;
$docker_compose = [
'version' => '3.8',
'services' => [
$proxyContainerName => [
'build' => [

View File

@@ -36,7 +36,6 @@ class StartDragonfly
$environment_variables = $this->generate_environment_variables();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,

View File

@@ -37,7 +37,6 @@ class StartKeydb
$this->add_custom_keydb();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
@@ -96,7 +95,7 @@ class StartKeydb
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->keydb_conf)) {
if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/keydb.conf',
@@ -162,7 +161,7 @@ class StartKeydb
}
private function add_custom_keydb()
{
if (is_null($this->database->keydb_conf)) {
if (is_null($this->database->keydb_conf) || empty($this->database->keydb_conf)) {
return;
}
$filename = 'keydb.conf';

View File

@@ -32,7 +32,6 @@ class StartMariadb
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
@@ -90,7 +89,7 @@ class StartMariadb
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->mariadb_conf)) {
if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/custom-config.cnf',
@@ -165,7 +164,7 @@ class StartMariadb
}
private function add_custom_mysql()
{
if (is_null($this->database->mariadb_conf)) {
if (is_null($this->database->mariadb_conf) || empty($this->database->mariadb_conf)) {
return;
}
$filename = 'custom-config.cnf';

View File

@@ -35,7 +35,6 @@ class StartMongodb
$this->add_custom_mongo_conf();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
@@ -97,7 +96,7 @@ class StartMongodb
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->mongo_conf)) {
if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/mongod.conf',
@@ -178,7 +177,7 @@ class StartMongodb
}
private function add_custom_mongo_conf()
{
if (is_null($this->database->mongo_conf)) {
if (is_null($this->database->mongo_conf) || empty($this->database->mongo_conf)) {
return;
}
$filename = 'mongod.conf';

View File

@@ -32,7 +32,6 @@ class StartMysql
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
@@ -90,7 +89,7 @@ class StartMysql
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->mysql_conf)) {
if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/custom-config.cnf',
@@ -165,7 +164,7 @@ class StartMysql
}
private function add_custom_mysql()
{
if (is_null($this->database->mysql_conf)) {
if (is_null($this->database->mysql_conf) || empty($this->database->mysql_conf)) {
return;
}
$filename = 'custom-config.cnf';

View File

@@ -35,7 +35,6 @@ class StartPostgresql
$this->add_custom_conf();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
@@ -78,7 +77,6 @@ class StartPostgresql
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
ray('Log Drain Enabled');
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
@@ -107,7 +105,7 @@ class StartPostgresql
];
}
}
if (!is_null($this->database->postgres_conf)) {
if (!is_null($this->database->postgres_conf) && !empty($this->database->postgres_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/custom-postgres.conf',
@@ -165,8 +163,6 @@ class StartPostgresql
private function generate_environment_variables()
{
$environment_variables = collect();
ray('Generate Environment Variables')->green();
ray($this->database->runtime_environment_variables)->green();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value");
}
@@ -203,11 +199,16 @@ class StartPostgresql
}
private function add_custom_conf()
{
if (is_null($this->database->postgres_conf)) {
if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) {
return;
}
$filename = 'custom-postgres.conf';
$content = $this->database->postgres_conf;
if (!str($content)->contains('listen_addresses')) {
$content .= "\nlisten_addresses = '*'";
$this->database->postgres_conf = $content;
$this->database->save();
}
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}

View File

@@ -37,7 +37,6 @@ class StartRedis
$this->add_custom_redis();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
@@ -100,7 +99,7 @@ class StartRedis
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->redis_conf)) {
if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/redis.conf',
@@ -166,7 +165,7 @@ class StartRedis
}
private function add_custom_redis()
{
if (is_null($this->database->redis_conf)) {
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {
return;
}
$filename = 'redis.conf';

View File

@@ -0,0 +1,657 @@
<?php
namespace App\Actions\Docker;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction;
class GetContainersStatus
{
use AsAction;
public $applications;
public $server;
public function handle(Server $server)
{
if (isDev()) {
$server = Server::find(0);
}
$this->server = $server;
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
$this->applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($this->applications as $application) {
if ($application->additional_servers->count() > 0) {
$skip_these_applications->push($application);
ComplexStatusCheck::run($application);
$this->applications = $this->applications->filter(function ($value, $key) use ($application) {
return $value->id !== $application->id;
});
}
}
$this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) {
return !$skip_these_applications->pluck('id')->contains($value->id);
});
$this->old_way();
// if ($this->server->isSwarm()) {
// $this->old_way();
// } else {
// if (!$this->server->is_metrics_enabled) {
// $this->old_way();
// return;
// }
// $sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this->server, false);
// $sentinel_found = json_decode($sentinel_found, true);
// $status = data_get($sentinel_found, '0.State.Status', 'exited');
// if ($status === 'running') {
// ray('Checking with Sentinel');
// $this->sentinel();
// } else {
// ray('Checking the Old way');
// $this->old_way();
// }
// }
}
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 = [];
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');
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
$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 = $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');
$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 = $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');
$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 = $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);
}
}
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 (is_null($containers)) {
return;
}
$containers = format_docker_command_output_to_json($containers);
if ($containerReplicates) {
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
foreach ($containerReplicates as $containerReplica) {
$name = data_get($containerReplica, 'Name');
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
if (data_get($container, 'Spec.Name') === $name) {
$replicas = data_get($containerReplica, 'Replicas');
$running = str($replicas)->explode('/')[0];
$total = str($replicas)->explode('/')[1];
if ($running === $total) {
data_set($container, 'State.Status', 'running');
data_set($container, 'State.Health.Status', 'healthy');
} else {
data_set($container, 'State.Status', 'starting');
data_set($container, 'State.Health.Status', 'unhealthy');
}
}
return $container;
});
}
}
$databases = $this->server->databases();
$services = $this->server->services()->get();
$previews = $this->server->previews();
$foundApplications = [];
$foundApplicationPreviews = [];
$foundDatabases = [];
$foundServices = [];
foreach ($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 = $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()) {
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()) {
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');
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
$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 = $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');
$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 = $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');
$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 = $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

@@ -0,0 +1,22 @@
<?php
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
class StartSentinel
{
use AsAction;
public function handle(Server $server, $version = 'latest', bool $restart = false)
{
if ($restart) {
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
}
instant_remote_process([
"docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
"chown -R 9999:root /data/coolify/metrics /data/coolify/logs",
"chmod -R 700 /data/coolify/metrics /data/coolify/logs"
], $server, false);
}
}

View File

@@ -27,10 +27,10 @@ class UpdateCoolify
CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
if ($settings->next_channel) {
ray('next channel enabled');
$this->latestVersion = 'next';
}
// if ($settings->next_channel) {
// ray('next channel enabled');
// $this->latestVersion = 'next';
// }
if ($force) {
$this->update();
} else {

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use App\Models\User;
use Illuminate\Console\Command;
class AdminRemoveUser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'admin:remove-user {email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Remove User from database';
/**
* Execute the console command.
*/
public function handle()
{
try {
$email = $this->argument('email');
$confirm = $this->confirm('Are you sure you want to remove user with email: ' . $email . '?');
if (!$confirm) {
$this->info('User removal cancelled.');
return;
}
$this->info("Removing user with email: $email");
$user = User::whereEmail($email)->firstOrFail();
$teams = $user->teams;
foreach ($teams as $team) {
if ($team->members->count() > 1) {
$this->error('User is a member of a team with more than one member. Please remove user from team first.');
return;
}
$team->delete();
}
$user->delete();
} catch (\Exception $e) {
$this->error('Failed to remove user.');
$this->error($e->getMessage());
return;
}
}
}

View File

@@ -1,33 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
class Cloud extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cloud:unused-servers';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get Unused Servers from Cloud';
/**
* Execute the console command.
*/
public function handle()
{
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
$this->info($server->name);
});
}
}

View File

@@ -29,7 +29,6 @@ class RootResetPassword extends Command
*/
public function handle()
{
//
$this->info('You are about to reset the root password.');
$password = password('Give me a new password for root user: ');
$passwordAgain = password('Again');

View File

@@ -9,6 +9,7 @@ use App\Jobs\ScheduledTaskJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
@@ -20,8 +21,10 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
private $all_servers;
protected function schedule(Schedule $schedule): void
{
$this->all_servers = Server::all();
if (isDev()) {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
@@ -55,20 +58,23 @@ class Kernel extends ConsoleKernel
}
private function pull_helper_image($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
if (config('coolify.is_sentinel_enabled')) {
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
}
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
}
}
private function check_resources($schedule)
{
if (isCloud()) {
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
$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 = Server::all()->where('ip', '!=', '1.2.3.4');
$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) {
@@ -134,7 +140,16 @@ class Kernel extends ConsoleKernel
$scheduled_task->delete();
continue;
}
if ($application) {
if (str($application->status)->contains('running') === false) {
continue;
}
}
if ($service) {
if (str($service->status())->contains('running') === false) {
continue;
}
}
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
}

View File

@@ -37,7 +37,7 @@ class Controller extends BaseController
public function email_verify(EmailVerificationRequest $request) {
$request->fulfill();
$name = request()->user()?->name;
send_internal_notification("User {$name} verified their email address.");
// send_internal_notification("User {$name} verified their email address.");
return redirect(RouteServiceProvider::HOME);
}
public function forgot_password(Request $request) {

View File

@@ -47,7 +47,7 @@ class Bitbucket extends Controller
if ($x_bitbucket_event === 'repo:push') {
$branch = data_get($payload, 'push.changes.0.new.name');
$full_name = data_get($payload, 'repository.full_name');
$commit = data_get($payload, 'push.changes.0.new.target.hash');
if (!$branch) {
return response([
'status' => 'failed',
@@ -104,6 +104,7 @@ class Bitbucket extends Controller
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
commit: $commit,
force_rebuild: false,
is_webhook: true
);

View File

@@ -129,6 +129,7 @@ class Github extends Controller
application: $application,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: data_get($payload, 'after', 'HEAD'),
is_webhook: true,
);
$return_payloads->push([
@@ -177,6 +178,7 @@ class Github extends Controller
pull_request_id: $pull_request_id,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: data_get($payload, 'head.sha', 'HEAD'),
is_webhook: true,
git_type: 'github'
);
@@ -338,6 +340,7 @@ class Github extends Controller
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
commit: data_get($payload, 'after', 'HEAD'),
force_rebuild: false,
is_webhook: true,
);
@@ -387,6 +390,7 @@ class Github extends Controller
pull_request_id: $pull_request_id,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: data_get($payload, 'head.sha', 'HEAD'),
is_webhook: true,
git_type: 'github'
);

View File

@@ -38,6 +38,15 @@ class Gitlab extends Controller
$headers = $request->headers->all();
$x_gitlab_token = data_get($headers, 'x-gitlab-token.0');
$x_gitlab_event = data_get($payload, 'object_kind');
$allowed_events = ['push', 'merge_request'];
if (!in_array($x_gitlab_event, $allowed_events)) {
$return_payloads->push([
'status' => 'failed',
'message' => 'Event not allowed. Only push and merge_request events are allowed.',
]);
return response($return_payloads);
}
if ($x_gitlab_event === 'push') {
$branch = data_get($payload, 'ref');
$full_name = data_get($payload, 'project.path_with_namespace');
@@ -124,6 +133,7 @@ class Gitlab extends Controller
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
commit: data_get($payload, 'after', 'HEAD'),
force_rebuild: false,
is_webhook: true,
);
@@ -173,6 +183,7 @@ class Gitlab extends Controller
application: $application,
pull_request_id: $pull_request_id,
deployment_uuid: $deployment_uuid,
commit: data_get($payload, 'object_attributes.last_commit.id', 'HEAD'),
force_rebuild: false,
is_webhook: true,
git_type: 'gitlab'

View File

@@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Actions\Docker\GetContainersStatus;
use App\Enums\ApplicationDeploymentStatus;
use App\Enums\ProcessStatus;
use App\Events\ApplicationStatusChanged;
@@ -106,6 +107,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private ?string $fullRepoUrl = null;
private ?string $branch = null;
private ?string $coolify_variables = null;
public $tries = 1;
public function __construct(int $application_deployment_queue_id)
{
@@ -302,7 +305,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{
if ($this->server->isProxyShouldRun()) {
dispatch(new ContainerStatusJob($this->server));
GetContainersStatus::dispatch($this->server);
// dispatch(new ContainerStatusJob($this->server));
}
$this->next(ApplicationDeploymentStatus::FINISHED->value);
if ($this->pull_request_id !== 0) {
@@ -404,7 +408,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
);
}
@@ -434,9 +438,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
ray("{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
$this->execute_remote_command(
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
["{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
);
}
} else {
@@ -447,7 +451,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->write_deployment_configurations();
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
);
$this->write_deployment_configurations();
}
@@ -709,10 +713,36 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function save_environment_variables()
{
$envs = collect([]);
$sort = $this->application->settings->is_env_sorting_enabled;
if ($sort) {
$sorted_environment_variables = $this->application->environment_variables->sortBy('key');
$sorted_environment_variables_preview = $this->application->environment_variables_preview->sortBy('key');
} else {
$sorted_environment_variables = $this->application->environment_variables->sortBy('id');
$sorted_environment_variables_preview = $this->application->environment_variables_preview->sortBy('id');
}
$ports = $this->application->main_port();
if ($this->pull_request_id !== 0) {
$this->env_filename = ".env-pr-$this->pull_request_id";
foreach ($this->application->environment_variables_preview as $env) {
// Add SOURCE_COMMIT if not exists
if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) {
$envs->push("COOLIFY_FQDN={$this->preview->fqdn}");
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
$envs->push("COOLIFY_URL={$url}");
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$this->application->git_branch}");
}
foreach ($sorted_environment_variables_preview as $env) {
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
@@ -733,20 +763,27 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
$envs->push("HOST=0.0.0.0");
}
} else {
$this->env_filename = ".env";
// Add SOURCE_COMMIT if not exists
if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) {
if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
$envs = $envs->sort(function ($a, $b) {
return strpos($a, '$') === false ? -1 : 1;
});
} else {
$this->env_filename = ".env";
foreach ($this->application->environment_variables as $env) {
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
}
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
$envs->push("COOLIFY_URL={$url}");
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$this->application->git_branch}");
}
foreach ($sorted_environment_variables as $env) {
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
@@ -767,17 +804,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
$envs->push("HOST=0.0.0.0");
}
// Add SOURCE_COMMIT if not exists
if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
$envs = $envs->sort(function ($a, $b) {
return strpos($a, '$') === false ? -1 : 1;
});
}
if ($envs->isEmpty()) {
@@ -869,7 +895,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$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 || $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 || 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')) {
$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.");
@@ -877,6 +903,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
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)) {
$this->application_deployment_queue->addLogEntry("Custom internal name is set, rolling update is not supported.");
}
if ($this->pull_request_id !== 0) {
$this->application->settings->is_consistent_container_name_enabled = true;
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
@@ -1017,7 +1046,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"command" => "docker rm -f {$this->deployment_uuid}",
"ignore_errors" => true,
"hidden" => true
],
]
);
$this->execute_remote_command(
[
$runCommand,
"hidden" => true,
@@ -1073,6 +1104,23 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{
$this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}.");
}
private function set_coolify_variables()
{
$this->coolify_variables = "SOURCE_COMMIT={$this->commit} ";
if ($this->pull_request_id === 0) {
$fqdn = $this->application->fqdn;
} else {
$fqdn = $this->preview->fqdn;
}
if (isset($fqdn)) {
$this->coolify_variables .= "COOLIFY_FQDN={$fqdn} ";
$url = str($fqdn)->replace('http://', '')->replace('https://', '');
$this->coolify_variables .= "COOLIFY_URL={$url} ";
}
if (isset($this->application->git_branch)) {
$this->coolify_variables .= "COOLIFY_BRANCH={$this->application->git_branch} ";
}
}
private function check_git_if_build_needed()
{
$this->generate_git_import_commands();
@@ -1106,7 +1154,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) {
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
$this->application_deployment_queue->commit = $this->commit;
$this->application_deployment_queue->save();
}
$this->set_coolify_variables();
}
private function clone_repository()
{
@@ -1122,6 +1173,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
);
$this->create_workdir();
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git log -1 {$this->commit} --pretty=%B"),
"hidden" => true,
"save" => "commit_message"
]
);
if ($this->saved_outputs->get('commit_message')) {
$commit_message = str($this->saved_outputs->get('commit_message'))->limit(47);
$this->application_deployment_queue->commit_message = $commit_message->value();
ApplicationDeploymentQueue::whereCommit($this->commit)->whereApplicationId($this->application->id)->update(
['commit_message' => $commit_message->value()]
);
}
}
private function generate_git_import_commands()
@@ -1209,6 +1274,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function generate_env_variables()
{
$this->env_args = collect([]);
$this->env_args->put('SOURCE_COMMIT', $this->commit);
if ($this->pull_request_id === 0) {
foreach ($this->application->build_environment_variables as $env) {
if (!is_null($env->real_value)) {
@@ -1222,7 +1288,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
}
}
$this->env_args->put('SOURCE_COMMIT', $this->commit);
}
private function generate_compose_file()
@@ -1270,9 +1335,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->pull_request_id !== 0) {
$labels = collect(generateLabelsApplication($this->application, $this->preview));
}
$labels = $labels->map(function ($value, $key) {
return escapeDollarSign($value);
});
if ($this->application->settings->is_container_label_escape_enabled) {
$labels = $labels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
// Check for custom HEALTHCHECK
@@ -1284,7 +1351,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application->parseHealthcheckFromDockerfile($dockerfile);
}
$docker_compose = [
'version' => '3.8',
'services' => [
$this->container_name => [
'image' => $this->production_image_name,
@@ -1292,7 +1358,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'restart' => RESTART_MODE,
'expose' => $ports,
'networks' => [
$this->destination->network,
$this->destination->network => [
'aliases' => [
$this->container_name
]
]
],
'mem_limit' => $this->application->limits_memory,
'memswap_limit' => $this->application->limits_memory_swap,
@@ -1310,6 +1380,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
]
];
if (isset($this->application->settings->custom_internal_name)) {
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name;
}
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
@@ -1519,95 +1592,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
return $local_persistent_volumes_names;
}
/*private function generate_environment_variables($ports)
{
$environment_variables = collect();
if ($this->pull_request_id === 0) {
foreach ($this->application->runtime_environment_variables as $env) {
// This is necessary because we have to escape the value of the environment variable
// but only if the environment variable is created after 4.0.0-beta.240
// when I implemented the escaping feature.
// Old environment variables are not escaped, because it could break the application
// as the application could expect the unescaped value.
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
foreach ($this->application->nixpacks_environment_variables as $env) {
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
} else {
foreach ($this->application->runtime_environment_variables_preview as $env) {
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
}
// Add PORT if not exists, use the first port as default
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$environment_variables->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
$environment_variables->push("HOST=0.0.0.0");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$environment_variables->push("SOURCE_COMMIT={$this->commit}");
} else {
$environment_variables->push("SOURCE_COMMIT=unknown");
}
}
ray($environment_variables->all());
return $environment_variables->all();
}*/
private function generate_healthcheck_commands()
{
// if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
// // TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
// return 'exit 0';
// }
if (!$this->application->health_check_port) {
$health_check_port = $this->application->ports_exposes_array[0];
} else {
@@ -1619,12 +1605,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->health_check_path) {
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}";
$generated_healthchecks_commands = [
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null"
"curl -o /dev/null -w \"%{http_code}\" -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} | grep -q \"{$this->application->health_check_return_code}\" || wget --save-headers -O - {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} 2>/dev/null |grep HTTP/ |grep -q \"{$this->application->health_check_return_code}\" || exit 1"
];
} else {
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/";
$generated_healthchecks_commands = [
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/"
"curl -o /dev/null -w \"%{http_code}\" -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ | grep -q \"{$this->application->health_check_return_code}\" || wget --save-headers -O - {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ 2>/dev/null |grep HTTP/ |grep -q \"{$this->application->health_check_return_code}\" || exit 1"
];
}
return implode(' ', $generated_healthchecks_commands);
@@ -1813,12 +1799,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
["docker rm -f $containerName >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
);
});
if ($this->application->settings->is_consistent_container_name_enabled) {
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("----------------------------------------");
$this->application_deployment_queue->addLogEntry("WARNING: Dockerfile or Docker Image based deployment detected. The healthcheck needs a curl or wget command to check the health of the application. Please make sure that it is available in the image or turn off healthcheck on Coolify's UI.");
$this->application_deployment_queue->addLogEntry("----------------------------------------");
}
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::FAILED->value,
@@ -1836,11 +1827,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} build"), "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
);
}
$this->application_deployment_queue->addLogEntry("New images built.");
@@ -1852,16 +1843,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
);
} else {
if ($this->use_build_server) {
$this->execute_remote_command(
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
["{$this->coolify_variables} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
);
}
}

View File

@@ -2,15 +2,8 @@
namespace App\Jobs;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck;
use App\Models\ApplicationPreview;
use App\Actions\Docker\GetContainersStatus;
use App\Models\Server;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -18,7 +11,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
@@ -44,335 +36,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function handle()
{
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
$applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($applications as $application) {
if ($application->additional_servers->count() > 0) {
$skip_these_applications->push($application);
ComplexStatusCheck::run($application);
$applications = $applications->filter(function ($value, $key) use ($application) {
return $value->id !== $application->id;
});
}
}
$applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
return !$skip_these_applications->pluck('id')->contains($value->id);
});
try {
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 (is_null($containers)) {
return;
}
$containers = format_docker_command_output_to_json($containers);
if ($containerReplicates) {
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
foreach ($containerReplicates as $containerReplica) {
$name = data_get($containerReplica, 'Name');
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
if (data_get($container, 'Spec.Name') === $name) {
$replicas = data_get($containerReplica, 'Replicas');
$running = str($replicas)->explode('/')[0];
$total = str($replicas)->explode('/')[1];
if ($running === $total) {
data_set($container, 'State.Status', 'running');
data_set($container, 'State.Health.Status', 'healthy');
} else {
data_set($container, 'State.Status', 'starting');
data_set($container, 'State.Health.Status', 'unhealthy');
}
}
return $container;
});
}
}
$databases = $this->server->databases();
$services = $this->server->services()->get();
$previews = $this->server->previews();
$foundApplications = [];
$foundApplicationPreviews = [];
$foundDatabases = [];
$foundServices = [];
foreach ($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 = $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()) {
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()) {
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');
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
$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 = $applications->pluck('id')->diff($foundApplications);
foreach ($notRunningApplications as $applicationId) {
$application = $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 = $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');
$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 = $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');
$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 = $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);
}
} catch (\Throwable $e) {
send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
ray($e->getMessage());
return handleError($e);
}
GetContainersStatus::run($this->server);
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Jobs;
use App\Actions\Server\StartSentinel;
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\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class PullSentinelImageJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 1000;
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): string
{
return $this->server->uuid;
}
public function __construct(public Server $server)
{
}
public function handle(): void
{
try {
$version = get_latest_sentinel_version();
if (!$version) {
ray('Failed to get latest Sentinel version');
return;
}
$local_version = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false);
if (empty($local_version)) {
$local_version = '0.0.0';
}
if (version_compare($local_version, $version, '<')) {
StartSentinel::run($this->server, $version, true);
return;
}
ray('Sentinel image is up to date');
} catch (\Throwable $e) {
send_internal_notification('PullSentinelImageJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

View File

@@ -77,8 +77,12 @@ class ScheduledTaskJob implements ShouldQueue
$this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
}
});
$this->resource->databases()->get()->each(function ($database) {
if (str(data_get($database, 'status'))->contains('running')) {
$this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid');
}
});
}
if (count($this->containers) == 0) {
throw new \Exception('ScheduledTaskJob failed: No containers running.');
}
@@ -89,7 +93,7 @@ class ScheduledTaskJob implements ShouldQueue
foreach ($this->containers as $containerName) {
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) {
$cmd = 'sh -c "' . str_replace('"', '\"', $this->task->command) . '"';
$cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'";
$exec = "docker exec {$containerName} {$cmd}";
$this->task_output = instant_remote_process([$exec], $this->server, true);
$this->task_log->update([

View File

@@ -40,7 +40,7 @@ class ServerLimitCheckJob implements ShouldQueue, ShouldBeEncrypted
try {
$servers = $this->team->servers;
$servers_count = $servers->count();
$limit = $this->team->limits['serverLimit'];
$limit = data_get($this->team->limits, 'serverLimit', 2);
$number_of_servers_to_disable = $servers_count - $limit;
ray('ServerLimitCheckJob', $this->team->uuid, $servers_count, $limit, $number_of_servers_to_disable);
if ($number_of_servers_to_disable > 0) {

View File

@@ -17,7 +17,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int|string|null $disk_usage = null;
public $tries = 4;
public $tries = 3;
public function backoff(): int
{
return isDev() ? 1 : 3;
@@ -38,12 +38,15 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function handle()
{
if (!$this->server->isServerReady($this->tries)) {
return "Server is not ready yet.";
throw new \RuntimeException('Server is not ready.');
};
try {
if ($this->server->isFunctional()) {
$this->cleanup(notify: false);
$this->removeCoolifyYaml();
if (config('coolify.is_sentinel_enabled')) {
$this->server->checkSentinel();
}
}
} catch (\Throwable $e) {
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());

View File

@@ -21,6 +21,7 @@ class Advanced extends Component
'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
'application.settings.custom_internal_name' => 'string|nullable',
'application.settings.is_gzip_enabled' => 'boolean|required',
'application.settings.is_stripprefix_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required',
@@ -30,7 +31,8 @@ class Advanced extends Component
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.connect_to_docker_network' => 'boolean|required',
];
public function mount() {
public function mount()
{
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
$this->is_gzip_enabled = $this->application->isGzipEnabled();
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
@@ -65,7 +67,8 @@ class Advanced extends Component
$this->dispatch('success', 'Settings saved.');
$this->dispatch('configurationChanged');
}
public function submit() {
public function submit()
{
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
$this->application->settings->gpu_count = null;
@@ -76,6 +79,16 @@ class Advanced extends Component
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
}
public function saveCustomName()
{
if (isset($this->application->settings->custom_internal_name)) {
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
} else {
$this->application->settings->custom_internal_name = null;
}
$this->application->settings->save();
$this->dispatch('success', 'Custom name saved.');
}
public function render()
{
return view('livewire.project.application.advanced');

View File

@@ -22,6 +22,7 @@ class General extends Component
public ?string $git_commit_sha = null;
public string $build_pack;
public ?string $ports_exposes = null;
public bool $is_container_label_escape_enabled = true;
public $customLabels;
public bool $labelsChanged = false;
@@ -74,6 +75,7 @@ class General extends Component
'application.post_deployment_command_container' => 'nullable',
'application.settings.is_static' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
'application.watch_paths' => 'nullable',
];
protected $validationAttributes = [
@@ -109,6 +111,7 @@ class General extends Component
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
'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.watch_paths' => 'Watch paths',
];
public function mount()
@@ -124,6 +127,7 @@ class General extends Component
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$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') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
@@ -145,7 +149,7 @@ class General extends Component
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->application->refresh();
if ($this->ports_exposes !== $this->application->ports_exposes) {
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
$this->resetDefaultLabels(false);
}
}
@@ -204,6 +208,9 @@ class General extends Component
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->application->save();
$this->dispatch('success', 'Domain generated.');
if ($this->application->build_pack === 'dockercompose') {
$this->loadComposeFile();
}
return $domain;
}
public function updatedApplicationBaseDirectory()
@@ -255,12 +262,14 @@ class General extends Component
}
public function resetDefaultLabels()
{
ray('resetDefaultLabels');
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->ports_exposes = $this->application->ports_exposes;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
if ($this->application->build_pack === 'dockercompose') {
$this->loadComposeFile();
}
}
public function checkFqdns($showToaster = true)
@@ -299,10 +308,13 @@ class General extends Component
}
if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) {
$this->loadComposeFile();
$compose_return = $this->loadComposeFile();
if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) {
return;
}
}
$this->validate();
if ($this->ports_exposes !== $this->application->ports_exposes) {
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
$this->resetDefaultLabels();
}
if (data_get($this->application, 'build_pack') === 'dockerimage') {

View File

@@ -3,8 +3,8 @@
namespace App\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Actions\Docker\GetContainersStatus;
use App\Events\ApplicationStatusChanged;
use App\Jobs\ComplexContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\ServerStatusJob;
use App\Models\Application;
@@ -14,6 +14,8 @@ use Visus\Cuid2\Cuid2;
class Heading extends Component
{
public Application $application;
public ?string $lastDeploymentInfo = null;
public ?string $lastDeploymentLink = null;
public array $parameters;
protected string $deploymentUuid;
@@ -28,12 +30,16 @@ class Heading extends Component
public function mount()
{
$this->parameters = get_route_parameters();
$lastDeployment = $this->application->get_last_successful_deployment();
$this->lastDeploymentInfo = data_get_str($lastDeployment, 'commit')->limit(7) . ' ' . data_get($lastDeployment, 'commit_message');
$this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit'));
}
public function check_status($showNotification = false)
{
if ($this->application->destination->server->isFunctional()) {
dispatch(new ContainerStatusJob($this->application->destination->server));
GetContainersStatus::dispatch($this->application->destination->server);
// dispatch(new ContainerStatusJob($this->application->destination->server));
} else {
dispatch(new ServerStatusJob($this->application->destination->server));
}

View File

@@ -35,7 +35,7 @@ class BackupEdit extends Component
public function mount()
{
$this->parameters = get_route_parameters();
if (is_null($this->backup->s3_storage_id)) {
if (is_null(data_get($this->backup, 's3_storage_id'))) {
$this->backup->s3_storage_id = 'default';
}
}

View File

@@ -20,7 +20,7 @@ class BackupExecutions extends Component
public function cleanupFailed()
{
$this->backup->executions()->where('status', 'failed')->delete();
$this->backup?->executions()->where('status', 'failed')->delete();
$this->refreshBackupExecutions();
}
public function deleteBackup($exeuctionId)

View File

@@ -11,6 +11,7 @@ use App\Actions\Database\StartMysql;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Database\StopDatabase;
use App\Actions\Docker\GetContainersStatus;
use App\Jobs\ContainerStatusJob;
use Livewire\Component;
@@ -44,7 +45,8 @@ class Heading extends Component
public function check_status($showNotification = false)
{
dispatch_sync(new ContainerStatusJob($this->database->destination->server));
GetContainersStatus::run($this->database->destination->server);
// dispatch_sync(new ContainerStatusJob($this->database->destination->server));
$this->database->refresh();
if ($showNotification) $this->dispatch('success', 'Database status updated.');
}

View File

@@ -150,7 +150,7 @@ class GithubPrivateRepository extends Component
'repository_project_id' => $this->selected_repository_id,
'git_repository' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}",
'git_branch' => $this->selected_branch_name,
'build_pack' => 'nixpacks',
'build_pack' => $this->build_pack,
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
@@ -162,6 +162,9 @@ class GithubPrivateRepository extends Component
$application->settings->is_static = $this->is_static;
$application->settings->save();
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
$application->health_check_enabled = false;
}
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;

View File

@@ -19,7 +19,7 @@ class GithubPrivateRepositoryDeployKey extends Component
public $current_step = 'private_keys';
public $parameters;
public $query;
public $private_keys =[];
public $private_keys = [];
public int $private_key_id;
public int $port = 3000;
@@ -125,7 +125,7 @@ class GithubPrivateRepositoryDeployKey extends Component
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->branch,
'build_pack' => 'nixpacks',
'build_pack' => $this->build_pack,
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
@@ -138,7 +138,7 @@ class GithubPrivateRepositoryDeployKey extends Component
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->branch,
'build_pack' => 'nixpacks',
'build_pack' => $this->build_pack,
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
@@ -149,7 +149,9 @@ class GithubPrivateRepositoryDeployKey extends Component
'source_type' => $this->git_source->getMorphClass()
];
}
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
$application_init['health_check_enabled'] = false;
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;
$application->settings->save();

View File

@@ -98,7 +98,8 @@ class PublicGitRepository extends Component
(str($this->repository_url)->startsWith('https://') ||
str($this->repository_url)->startsWith('http://')) &&
!str($this->repository_url)->endsWith('.git') &&
!str($this->repository_url)->contains('github.com')
(!str($this->repository_url)->contains('github.com') ||
!str($this->repository_url)->contains('git.sr.ht'))
) {
$this->repository_url = $this->repository_url . '.git';
}
@@ -204,6 +205,9 @@ class PublicGitRepository extends Component
];
}
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
$application_init['health_check_enabled'] = false;
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;

View File

@@ -2,7 +2,7 @@
namespace App\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Actions\Docker\GetContainersStatus;
use App\Models\Service;
use Livewire\Component;
@@ -64,7 +64,8 @@ class Configuration extends Component
public function check_status()
{
try {
dispatch_sync(new ContainerStatusJob($this->service->server));
GetContainersStatus::run($this->service->server);
// dispatch_sync(new ContainerStatusJob($this->service->server));
$this->dispatch('refresh')->self();
$this->dispatch('updateStatus');
} catch (\Exception $e) {

View File

@@ -12,6 +12,7 @@ class EditCompose extends Component
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
'service.is_container_label_escape_enabled' => 'required',
];
public function mount()
{
@@ -23,6 +24,14 @@ class EditCompose extends Component
$this->dispatch('info', "Saving new docker compose...");
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
}
public function instantSave()
{
$this->validate([
'service.is_container_label_escape_enabled' => 'required',
]);
$this->service->save(['is_container_label_escape_enabled' => $this->service->is_container_label_escape_enabled]);
$this->dispatch('success', "Service updated successfully");
}
public function render()
{
return view('livewire.project.service.edit-compose');

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Shared;
use App\Actions\Application\StopApplicationOneServer;
use App\Actions\Docker\GetContainersStatus;
use App\Events\ApplicationStatusChanged;
use App\Jobs\ContainerStatusJob;
use App\Models\Server;
@@ -90,7 +91,8 @@ class Destination extends Component
}
public function refreshServers()
{
ContainerStatusJob::dispatchSync($this->resource->destination->server);
GetContainersStatus::run($this->resource->destination->server);
// ContainerStatusJob::dispatchSync($this->resource->destination->server);
$this->loadData();
$this->dispatch('refresh');
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));

View File

@@ -5,11 +5,11 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class All extends Component
{
public $resource;
public string $resourceClass;
public bool $showPreview = false;
public ?string $modalId = null;
public ?string $variables = null;
@@ -19,17 +19,44 @@ class All extends Component
'refreshEnvs',
'saveKey' => 'submit',
];
protected $rules = [
'resource.settings.is_env_sorting_enabled' => 'required|boolean',
];
public function mount()
{
$resourceClass = get_class($this->resource);
$this->resourceClass = get_class($this->resource);
$resourceWithPreviews = ['App\Models\Application'];
$simpleDockerfile = !is_null(data_get($this->resource, 'dockerfile'));
if (Str::of($resourceClass)->contains($resourceWithPreviews) && !$simpleDockerfile) {
if (str($this->resourceClass)->contains($resourceWithPreviews) && !$simpleDockerfile) {
$this->showPreview = true;
}
$this->modalId = new Cuid2(7);
$this->sortMe();
$this->getDevView();
}
public function sortMe()
{
if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') {
if ($this->resource->settings->is_env_sorting_enabled) {
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('key');
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('key');
} else {
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('id');
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('id');
}
}
$this->getDevView();
}
public function instantSave()
{
if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') {
$this->resource->settings->save();
$this->dispatch('success', 'Environment variable settings updated.');
$this->sortMe();
}
}
public function getDevView()
{
$this->variables = $this->resource->environment_variables->map(function ($item) {
@@ -40,7 +67,7 @@ class All extends Component
return "$item->key=(multiline, edit in normal view)";
}
return "$item->key=$item->value";
})->sort()->join('
})->join('
');
if ($this->showPreview) {
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
@@ -51,13 +78,18 @@ class All extends Component
return "$item->key=(multiline, edit in normal view)";
}
return "$item->key=$item->value";
})->sort()->join('
})->join('
');
}
}
public function switch()
{
$this->view = $this->view === 'normal' ? 'dev' : 'normal';
if ($this->view === 'normal') {
$this->view = 'dev';
} else {
$this->view = 'normal';
}
$this->sortMe();
}
public function saveVariables($isPreview)
{
@@ -66,6 +98,7 @@ class All extends Component
$this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete();
} else {
$variables = parseEnvFormatToArray($this->variables);
ray($variables, $this->variables);
$this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete();
}
foreach ($variables as $key => $variable) {

View File

@@ -122,7 +122,7 @@ class ExecuteContainerCommand extends Component
if ($server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
$cmd = 'sh -c "if [ -f ~/.profile ]; then . ~/.profile; fi; ' . str_replace('"', '\"', $this->command) . '"';
$cmd = "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; " . str_replace("'", "'\''", $this->command) . "'";
if (!empty($this->workDir)) {
$exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}";
} else {

View File

@@ -27,7 +27,7 @@ class Logs extends Component
public $query;
public $status;
public $serviceSubType;
public $cpu;
public function loadContainers($server_id)
{
try {
@@ -49,6 +49,14 @@ class Logs extends Component
return handleError($e, $this);
}
}
public function loadMetrics()
{
return;
$server = data_get($this->resource, 'destination.server');
if ($server->isFunctional()) {
$this->cpu = $server->getMetrics();
}
}
public function mount()
{
try {
@@ -95,6 +103,7 @@ class Logs extends Component
}
}
$this->containers = $this->containers->sort();
$this->loadMetrics();
} catch (\Exception $e) {
return handleError($e, $this);
}

View File

@@ -2,11 +2,14 @@
namespace App\Livewire\Project\Shared\ScheduledTask;
use Illuminate\Support\Collection;
use Livewire\Component;
class Add extends Component
{
public $parameters;
public string $type;
public Collection $containerNames;
public string $name;
public string $command;
public string $frequency;
@@ -29,6 +32,9 @@ class Add extends Component
public function mount()
{
$this->parameters = get_route_parameters();
if ($this->containerNames->count() > 0) {
$this->container = $this->containerNames->first();
}
}
public function submit()
@@ -40,6 +46,11 @@ class Add extends Component
$this->dispatch('error', 'Invalid Cron / Human expression.');
return;
}
if (empty($this->container) || $this->container == 'null') {
if ($this->type == 'service') {
$this->container = $this->subServiceName;
}
}
$this->dispatch('saveScheduledTask', [
'name' => $this->name,
'command' => $this->command,

View File

@@ -3,14 +3,13 @@
namespace App\Livewire\Project\Shared\ScheduledTask;
use App\Models\ScheduledTask;
use Illuminate\Support\Collection;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class All extends Component
{
public $resource;
public string|null $modalId = null;
public Collection $containerNames;
public ?string $variables = null;
public array $parameters;
protected $listeners = ['refreshTasks', 'saveScheduledTask' => 'submit'];
@@ -18,7 +17,18 @@ class All extends Component
public function mount()
{
$this->parameters = get_route_parameters();
$this->modalId = new Cuid2(7);
if ($this->resource->type() == 'service') {
$this->containerNames = $this->resource->applications()->pluck('name');
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
} elseif ($this->resource->type() == 'application') {
if ($this->resource->build_pack === 'dockercompose') {
$parsed = $this->resource->parseCompose();
$containers = collect(data_get($parsed,'services'))->keys();
$this->containerNames = $containers;
} else {
$this->containerNames = collect([]);
}
}
}
public function refreshTasks()
{

View File

@@ -17,6 +17,7 @@ class Show extends Component
public string $type;
protected $rules = [
'task.enabled' => 'required|boolean',
'task.name' => 'required|string',
'task.command' => 'required|string',
'task.frequency' => 'required|string',
@@ -45,9 +46,18 @@ class Show extends Component
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
}
public function instantSave()
{
$this->validateOnly('task.enabled');
$this->task->save(['enabled' => $this->task->enabled]);
$this->dispatch('success', 'Scheduled task updated.');
$this->dispatch('refreshTasks');
}
public function submit()
{
$this->validate();
$this->task->name = str($this->task->name)->trim()->value();
$this->task->container = str($this->task->container)->trim()->value();
$this->task->save();
$this->dispatch('success', 'Scheduled task updated.');
$this->dispatch('refreshTasks');
@@ -60,11 +70,9 @@ class Show extends Component
if ($this->type == 'application') {
return redirect()->route('project.application.configuration', $this->parameters);
}
else {
} else {
return redirect()->route('project.service.configuration', $this->parameters);
}
} catch (\Exception $e) {
return handleError($e);
}

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Server\Proxy;
use App\Actions\Docker\GetContainersStatus;
use App\Actions\Proxy\CheckProxy;
use App\Jobs\ContainerStatusJob;
use App\Models\Server;
@@ -49,7 +50,8 @@ class Status extends Component
public function getProxyStatus()
{
try {
dispatch_sync(new ContainerStatusJob($this->server));
GetContainersStatus::run($this->server);
// dispatch_sync(new ContainerStatusJob($this->server));
$this->dispatch('proxyStatusUpdated');
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -13,7 +13,7 @@ class Configuration extends Component
public bool $is_auto_update_enabled;
public bool $is_registration_enabled;
public bool $is_dns_validation_enabled;
public bool $next_channel;
// public bool $next_channel;
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
protected Server $server;
@@ -37,7 +37,7 @@ class Configuration extends Component
$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->next_channel = $this->settings->next_channel;
// $this->next_channel = $this->settings->next_channel;
$this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
}
@@ -47,12 +47,12 @@ class Configuration extends Component
$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;
if ($this->next_channel) {
$this->settings->next_channel = false;
$this->next_channel = false;
} else {
$this->settings->next_channel = $this->next_channel;
}
// if ($this->next_channel) {
// $this->settings->next_channel = false;
// $this->next_channel = false;
// } else {
// $this->settings->next_channel = $this->next_channel;
// }
$this->settings->save();
$this->dispatch('success', 'Settings updated!');
}

View File

@@ -26,6 +26,7 @@ class Deployments extends Component
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
$this->dispatch('deployments', $this->deployments_per_tag_per_server);
} catch (\Exception $e) {
return handleError($e, $this);
}

View File

@@ -20,6 +20,12 @@ class Index extends Component
public $webhook = null;
public $deployments_per_tag_per_server = [];
protected $listeners = ['deployments' => 'update_deployments'];
public function update_deployments($deployments)
{
$this->deployments_per_tag_per_server = $deployments;
}
public function tag_updated()
{
if ($this->tag == "") {
@@ -39,14 +45,13 @@ class Index extends Component
public function redeploy_all()
{
try {
$message = collect([]);
$this->applications->each(function ($resource) use ($message) {
$this->applications->each(function ($resource){
$deploy = new Deploy();
$message->push($deploy->deploy_resource($resource));
$deploy->deploy_resource($resource);
});
$this->services->each(function ($resource) use ($message) {
$this->services->each(function ($resource) {
$deploy = new Deploy();
$message->push($deploy->deploy_resource($resource));
$deploy->deploy_resource($resource);
});
$this->dispatch('success', 'Mass deployment started.');
} catch (\Exception $e) {

View File

@@ -3,7 +3,7 @@
namespace App\Livewire;
use App\Actions\Server\UpdateCoolify;
use App\Models\InstanceSettings;
use Livewire\Component;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
@@ -11,6 +11,7 @@ class Upgrade extends Component
{
use WithRateLimiting;
public bool $showProgress = false;
public bool $updateInProgress = false;
public bool $isUpgradeAvailable = false;
public string $latestVersion = '';
@@ -22,23 +23,17 @@ class Upgrade extends Component
if (isDev()) {
$this->isUpgradeAvailable = true;
}
$settings = InstanceSettings::get();
if ($settings->next_channel) {
$this->isUpgradeAvailable = true;
$this->latestVersion = 'next';
}
}
public function upgrade()
{
try {
if ($this->showProgress) {
if ($this->updateInProgress) {
return;
}
$this->rateLimit(1, 30);
$this->showProgress = true;
$this->rateLimit(1, 60);
$this->updateInProgress = true;
UpdateCoolify::run(force: true, async: true);
$this->dispatch('success', "Updating Coolify to {$this->latestVersion} version...");
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -146,9 +146,13 @@ class Application extends BaseModel
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}";
}
// Convert the SSH URL to HTTPS URL
if (strpos($this->git_repository, 'git@') === 0) {
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
return "https://{$git_repository}/tree/{$this->git_branch}";
}
return $this->git_repository;
}
);
}
@@ -159,6 +163,11 @@ class Application extends BaseModel
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
}
// Convert the SSH URL to HTTPS URL
if (strpos($this->git_repository, 'git@') === 0) {
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
return "https://{$git_repository}/settings/hooks";
}
return $this->git_repository;
}
);
@@ -171,10 +180,29 @@ class Application extends BaseModel
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
}
// Convert the SSH URL to HTTPS URL
if (strpos($this->git_repository, 'git@') === 0) {
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
return "https://{$git_repository}/commits/{$this->git_branch}";
}
return $this->git_repository;
}
);
}
public function gitCommitLink($link): string
{
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
if (str($this->source->html_url)->contains('bitbucket')) {
return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}";
}
return "{$this->source->html_url}/{$this->git_repository}/commit/{$link}";
}
if (strpos($this->git_repository, 'git@') === 0) {
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
return "https://{$git_repository}/commit/{$link}";
}
return $this->git_repository;
}
public function dockerfileLocation(): Attribute
{
return Attribute::make(
@@ -429,6 +457,10 @@ class Application extends BaseModel
}
return false;
}
public function get_last_successful_deployment()
{
return ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'finished')->orderBy('created_at', 'desc')->first();
}
public function get_last_days_deployments()
{
return ApplicationDeploymentQueue::where('application_id', $this->id)->where('created_at', '>=', now()->subDays(7))->orderBy('created_at', 'desc')->get();
@@ -847,7 +879,7 @@ class Application extends BaseModel
if (!$composeFileContent) {
$this->docker_compose_location = $initialDockerComposeLocation;
$this->save();
throw new \RuntimeException("Could not load base compose file from $workdir$composeFile");
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile");
} else {
$this->docker_compose_raw = $composeFileContent;
$this->save();
@@ -964,7 +996,8 @@ class Application extends BaseModel
getFilesystemVolumesFromServer($this, $isInit);
}
public function parseHealthcheckFromDockerfile($dockerfile, bool $isInit = false) {
public function parseHealthcheckFromDockerfile($dockerfile, bool $isInit = false)
{
if (str($dockerfile)->contains('HEALTHCHECK') && ($this->isHealthcheckDisabled() || $isInit)) {
$healthcheckCommand = null;
$lines = $dockerfile->toArray();

View File

@@ -9,7 +9,8 @@ class ApplicationDeploymentQueue extends Model
{
protected $guarded = [];
public function setStatus(string $status) {
public function setStatus(string $status)
{
$this->update([
'status' => $status,
]);
@@ -21,7 +22,13 @@ class ApplicationDeploymentQueue extends Model
}
return collect(json_decode($this->logs))->where('name', $name)->first()?->output ?? null;
}
public function commitMessage()
{
if (empty($this->commit_message) || is_null($this->commit_message)) {
return null;
}
return str($this->commit_message)->trim()->limit(50)->value();
}
public function addLogEntry(string $message, string $type = 'stdout', bool $hidden = false)
{
if ($type === 'error') {

View File

@@ -3,14 +3,16 @@
namespace App\Models;
use App\Actions\Server\InstallDocker;
use App\Actions\Server\StartSentinel;
use App\Enums\ProxyTypes;
use App\Jobs\PullSentinelImageJob;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Sleep;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
use Illuminate\Support\Str;
@@ -463,58 +465,98 @@ $schema://$host {
Storage::disk('ssh-keys')->delete($sshKeyFileLocation);
Storage::disk('ssh-mux')->delete($this->muxFilename());
}
public function checkSentinel()
{
ray("Checking sentinel on server: {$this->name}");
if ($this->is_metrics_enabled) {
$sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this, false);
$sentinel_found = json_decode($sentinel_found, true);
$status = data_get($sentinel_found, '0.State.Status', 'exited');
if ($status !== 'running') {
ray('Sentinel is not running, starting it...');
PullSentinelImageJob::dispatch($this);
} else {
ray('Sentinel is running');
}
}
}
public function getMetrics()
{
if ($this->is_metrics_enabled) {
$from = now()->subMinutes(5)->toIso8601ZuluString();
$cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
$cpu = str($cpu)->explode("\n")->skip(1)->all();
$parsedCollection = collect($cpu)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
list($time, $value) = explode(',', trim($line));
return [(int) $time, (float) $value];
});
})->toArray();
return $parsedCollection;
}
}
public function isServerReady(int $tries = 3)
{
if ($this->skipServer()) {
return false;
}
$checkIteration = 1;
$isServerReady = false;
while ($checkIteration < $tries) {
['uptime' => $uptime] = $this->validateConnection();
if ($uptime) {
if ($this->unreachable_notification_sent === true) {
$this->update(['unreachable_notification_sent' => false]);
$serverUptimeCheckNumber = $this->unreachable_count;
if ($this->unreachable_count < $tries) {
$serverUptimeCheckNumber = $this->unreachable_count + 1;
}
if ($this->unreachable_count > $tries) {
$serverUptimeCheckNumber = $tries;
}
$serverUptimeCheckNumberMax = $tries;
// ray('server: ' . $this->name);
// ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
// ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
['uptime' => $uptime] = $this->validateConnection();
if ($uptime) {
if ($this->unreachable_notification_sent === true) {
$this->update(['unreachable_notification_sent' => false]);
}
return true;
} else {
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
// Reached max number of retries
if ($this->unreachable_notification_sent === false) {
ray('Server unreachable, sending notification...');
$this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]);
}
if ($this->settings->is_reachable === true) {
$this->settings()->update([
'is_reachable' => false,
]);
}
foreach ($this->applications() as $application) {
$application->update(['status' => 'exited']);
}
foreach ($this->databases() as $database) {
$database->update(['status' => 'exited']);
}
foreach ($this->services()->get() 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']);
}
}
$this->settings()->update([
'is_reachable' => true,
]);
$isServerReady = true;
break;
} else {
ray('Server is not ready yet.');
$checkIteration++;
Sleep::for(10)->seconds();
$this->update([
'unreachable_count' => $this->unreachable_count + 1,
]);
}
return false;
}
if ($isServerReady) {
return $isServerReady;
}
if ($this->unreachable_notification_sent === false) {
ray('Server unreachable, sending notification...');
$this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]);
}
$this->settings()->update([
'is_reachable' => false,
]);
foreach ($this->applications() as $application) {
$application->update(['status' => 'exited']);
}
foreach ($this->databases() as $database) {
$database->update(['status' => 'exited']);
}
foreach ($this->services()->get() 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;
}
public function getDiskUsage()
{
@@ -539,7 +581,36 @@ $schema://$host {
{
return instant_remote_process(["docker start $id"], $this);
}
public function loadUnmanagedContainers()
public function getContainers(): Collection
{
$sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this, false);
$sentinel_found = json_decode($sentinel_found, true);
$status = data_get($sentinel_found, '0.State.Status', 'exited');
if ($status === 'running') {
$containers = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/containers"'], $this, false);
if (is_null($containers)) {
return collect([]);
}
$containers = data_get(json_decode($containers, true), 'containers', []);
return collect($containers);
} else {
if ($this->isSwarm()) {
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false);
} else {
$containers = instant_remote_process(["docker container ls -q"], $this, false);
if (!$containers) {
return collect([]);
}
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
}
if (is_null($containers)) {
return collect([]);
}
return format_docker_command_output_to_json($containers);
}
}
public function loadUnmanagedContainers(): Collection
{
if ($this->isFunctional()) {
$containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
@@ -762,11 +833,6 @@ $schema://$host {
$server->settings()->update([
'is_reachable' => false,
]);
if (data_get($server, 'unreachable_notification_sent') === false) {
ray('Server unreachable, sending notification...');
$this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]);
}
return ['uptime' => false, 'error' => $e->getMessage()];
}
}

View File

@@ -171,7 +171,7 @@ class Service extends BaseModel
],
]);
}
$fields->put('Tolgee', $data);
$fields->put('Tolgee', $data->toArray());
break;
case str($image)?->contains('logto'):
$data = collect([]);
@@ -195,7 +195,7 @@ class Service extends BaseModel
],
]);
}
$fields->put('Logto', $data);
$fields->put('Logto', $data->toArray());
break;
case str($image)?->contains('unleash-server'):
$data = collect([]);
@@ -218,7 +218,7 @@ class Service extends BaseModel
],
]);
}
$fields->put('Unleash', $data);
$fields->put('Unleash', $data->toArray());
break;
case str($image)?->contains('grafana'):
$data = collect([]);
@@ -241,7 +241,7 @@ class Service extends BaseModel
],
]);
}
$fields->put('Grafana', $data);
$fields->put('Grafana', $data->toArray());
break;
case str($image)?->contains('directus'):
$data = collect([]);
@@ -267,7 +267,7 @@ class Service extends BaseModel
],
]);
}
$fields->put('Directus', $data);
$fields->put('Directus', $data->toArray());
break;
case str($image)?->contains('kong'):
$data = collect([]);
@@ -370,7 +370,7 @@ class Service extends BaseModel
],
]);
}
$fields->put('Weblate', $data);
$fields->put('Weblate', $data->toArray());
break;
case str($image)?->contains('meilisearch'):
$data = collect([]);
@@ -384,7 +384,7 @@ class Service extends BaseModel
],
]);
}
$fields->put('Meilisearch', $data);
$fields->put('Meilisearch', $data->toArray());
break;
case str($image)?->contains('ghost'):
$data = collect([]);
@@ -444,7 +444,33 @@ class Service extends BaseModel
]);
}
$fields->put('Ghost', $data);
$fields->put('Ghost', $data->toArray());
break;
default:
$data = collect([]);
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
if ($admin_user) {
$data = $data->merge([
'User' => [
'key' => 'SERVICE_USER_ADMIN',
'value' => data_get($admin_user, 'value', 'admin'),
'readonly' => true,
'rules' => 'required',
],
]);
}
if ($admin_password) {
$data = $data->merge([
'Password' => [
'key' => 'SERVICE_PASSWORD_ADMIN',
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Admin', $data->toArray());
break;
}
}
@@ -653,6 +679,17 @@ class Service extends BaseModel
{
return $this->belongsTo(Server::class);
}
public function byUuid(string $uuid) {
$app = $this->applications()->whereUuid($uuid)->first();
if ($app) {
return $app;
}
$db = $this->databases()->whereUuid($uuid)->first();
if ($db) {
return $db;
}
return null;
}
public function byName(string $name)
{
$app = $this->applications()->whereName($name)->first();

View File

@@ -183,6 +183,7 @@ class User extends Authenticatable implements SendsEmail
if (data_get($this, 'pivot')) {
return $this->pivot->role;
}
return auth()->user()->teams->where('id', currentTeam()->id)->first()->pivot->role;
$user = auth()->user()->teams->where('id', currentTeam()->id)->first();
return data_get($user, 'pivot.role');
}
}

View File

@@ -43,7 +43,12 @@ class DeploymentSuccess extends Notification implements ShouldQueue
public function via(object $notifiable): array
{
return setNotificationChannels($notifiable, 'deployments');
$channels = setNotificationChannels($notifiable, 'deployments');
if (isCloud()) {
// TODO: Make batch notifications work with email
$channels = array_diff($channels, ['App\Notifications\Channels\EmailChannel']);
}
return $channels;
}
public function toMail(): MailMessage
{

View File

@@ -14,20 +14,22 @@ class TelegramChannel
$buttons = data_get($data, 'buttons', []);
$telegramToken = data_get($telegramData, 'token');
$chatId = data_get($telegramData, 'chat_id');
$topicId = null;
$topicId = null;
$topicsInstance = get_class($notification);
switch ($topicsInstance) {
case 'App\Notifications\StatusChange':
$topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
break;
case 'App\Notifications\Test':
$topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id');
break;
case 'App\Notifications\Deployment':
case 'App\Notifications\Application\StatusChanged':
$topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
break;
case 'App\Notifications\Application\DeploymentSuccess':
case 'App\Notifications\Application\DeploymentFailed':
$topicId = data_get($notifiable, 'telegram_notifications_deployments_message_thread_id');
break;
case 'App\Notifications\DatabaseBackup':
case 'App\Notifications\Database\BackupSuccess':
case 'App\Notifications\Database\BackupFailed':
$topicId = data_get($notifiable, 'telegram_notifications_database_backups_message_thread_id');
break;
}

View File

@@ -2,6 +2,7 @@
namespace App\Notifications\Server;
use App\Actions\Docker\GetContainersStatus;
use App\Jobs\ContainerStatusJob;
use App\Models\Server;
use Illuminate\Bus\Queueable;
@@ -22,7 +23,8 @@ class Revived extends Notification implements ShouldQueue
if ($this->server->unreachable_notification_sent === false) {
return;
}
dispatch(new ContainerStatusJob($server));
GetContainersStatus::dispatch($server);
// dispatch(new ContainerStatusJob($server));
}
public function via(object $notifiable): array

View File

@@ -6,7 +6,6 @@ use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Models\StandaloneDocker;
use Illuminate\Support\Collection;
use Spatie\Url\Url;
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false)

View File

@@ -147,6 +147,18 @@ function get_route_parameters(): array
return Route::current()->parameters();
}
function get_latest_sentinel_version(): string
{
try {
$response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
$versions = $response->json();
return data_get($versions, 'coolify.sentinel.version');
} catch (\Throwable $e) {
//throw $e;
ray($e->getMessage());
return '0.0.0';
}
}
function get_latest_version_of_coolify(): string
{
try {
@@ -637,7 +649,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$allServices = getServiceTemplates();
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
$services = data_get($yaml, 'services');
$generatedServiceFQDNS = collect([]);
@@ -1163,6 +1174,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]
]);
}
if ($serviceLabels->count() > 0) {
if ($resource->is_container_label_escape_enabled) {
$serviceLabels = $serviceLabels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
}
data_set($service, 'labels', $serviceLabels->toArray());
data_forget($service, 'is_database');
if (!data_get($service, 'restart')) {
@@ -1192,7 +1210,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $service;
});
$finalServices = [
'version' => $dockerComposeVersion,
'services' => $services->toArray(),
'volumes' => $topLevelVolumes->toArray(),
'networks' => $topLevelNetworks->toArray(),
@@ -1230,7 +1247,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$topLevelVolumes = collect([]);
}
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
$services = data_get($yaml, 'services');
$generatedServiceFQDNS = collect([]);
@@ -1250,6 +1266,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$servicePorts = collect(data_get($service, 'ports', []));
$serviceNetworks = collect(data_get($service, 'networks', []));
$serviceVariables = collect(data_get($service, 'environment', []));
$serviceDependencies = collect(data_get($service, 'depends_on', []));
$serviceLabels = collect(data_get($service, 'labels', []));
$serviceBuildVariables = collect(data_get($service, 'build.args', []));
$serviceVariables = $serviceVariables->merge($serviceBuildVariables);
@@ -1266,11 +1283,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceLabels->push("$removedLabelName=$removedLabel");
}
}
if ($serviceLabels->count() > 0) {
$serviceLabels = $serviceLabels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
$baseName = generateApplicationContainerName($resource, $pull_request_id);
$containerName = "$serviceName-$baseName";
if (count($serviceVolumes) > 0) {
@@ -1361,6 +1374,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
data_set($service, 'volumes', $serviceVolumes->toArray());
}
if ($pull_request_id !== 0 && count($serviceDependencies) > 0) {
$serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) {
return $dependency . "-pr-$pull_request_id";
});
data_set($service, 'depends_on', $serviceDependencies->toArray());
}
// Decide if the service is a database
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
data_set($service, 'is_database', $isDatabase);
@@ -1643,6 +1663,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]
]);
}
if ($serviceLabels->count() > 0) {
if ($resource->settings->is_container_label_escape_enabled) {
$serviceLabels = $serviceLabels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
}
data_set($service, 'labels', $serviceLabels->toArray());
data_forget($service, 'is_database');
if (!data_get($service, 'restart')) {
@@ -1661,7 +1688,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
});
}
$finalServices = [
'version' => $dockerComposeVersion,
'services' => $services->toArray(),
'volumes' => $topLevelVolumes->toArray(),
'networks' => $topLevelNetworks->toArray(),
@@ -1841,7 +1867,7 @@ function validate_dns_entry(string $fqdn, Server $server)
$dns_servers = data_get($settings, 'custom_dns_servers');
$dns_servers = str($dns_servers)->explode(',');
if ($server->id === 0) {
$ip = data_get($settings, 'public_ipv4') || data_get($settings, 'public_ipv6') || $server->ip;
$ip = data_get($settings, 'public_ipv4', data_get($settings, 'public_ipv6', $server->ip));
} else {
$ip = $server->ip;
}
@@ -1920,7 +1946,6 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
if (data_get($resource, 'uuid')) {
ray($resource->uuid, $app->uuid);
if ($resource->uuid !== $app->uuid) {
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
}

View File

@@ -14,4 +14,5 @@ return [
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
'is_horizon_enabled' => env('HORIZON_ENABLED', true),
'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
'is_sentinel_enabled' => env('SENTINEL_ENABLED', false),
];

View File

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

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.273';
return '4.0.0-beta.282';

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->string('custom_internal_name')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('custom_internal_name');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->boolean('is_metrics_enabled')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->dropColumn('is_metrics_enabled');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('subscriptions', function (Blueprint $table) {
$table->longText('stripe_comment')->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('subscriptions', function (Blueprint $table) {
$table->string('stripe_comment')->change();
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->string('commit_message', 50)->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->dropColumn('commit_message');
});
}
};

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->boolean('is_container_label_escape_enabled')->default(true);
});
Schema::table('services', function (Blueprint $table) {
$table->boolean('is_container_label_escape_enabled')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('is_container_label_escape_enabled');
});
Schema::table('services', function (Blueprint $table) {
$table->dropColumn('is_container_label_escape_enabled');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->boolean('is_env_sorting_enabled')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('is_env_sorting_enabled');
});
}
};

View File

@@ -1,5 +1,3 @@
version: "3.8"
services:
coolify:
build:

View File

@@ -1,4 +1,3 @@
version: '3.8'
services:
coolify:
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-latest}"

View File

@@ -1,4 +1,3 @@
version: '3.8'
services:
coolify-testing-host:
init: true

View File

@@ -1,4 +1,3 @@
version: '3.8'
services:
coolify:
container_name: coolify
@@ -11,7 +10,6 @@ services:
depends_on:
- postgres
- redis
postgres:
image: postgres:15-alpine
container_name: coolify-db

View File

@@ -2,15 +2,15 @@ FROM alpine:3.17
ARG TARGETPLATFORM
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=24.0.9
ARG DOCKER_VERSION=26.1.2
# https://github.com/docker/compose/releases
ARG DOCKER_COMPOSE_VERSION=2.25.0
ARG DOCKER_COMPOSE_VERSION=2.27.0
# https://github.com/docker/buildx/releases
ARG DOCKER_BUILDX_VERSION=0.13.1
ARG DOCKER_BUILDX_VERSION=0.14.0
# https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=0.33.2
# https://github.com/railwayapp/nixpacks/releases
ARG NIXPACKS_VERSION=1.21.2
ARG NIXPACKS_VERSION=1.21.3
USER root
WORKDIR /artifacts

View File

@@ -2,7 +2,7 @@ FROM serversideup/php:8.2-fpm-nginx-v2.2.1
ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2024.2.1
ARG CLOUDFLARED_VERSION=2024.4.1
ARG POSTGRES_VERSION=15
RUN apt-get update

View File

@@ -15,7 +15,7 @@ FROM serversideup/php:8.2-fpm-nginx-v2.2.1
ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2024.2.1
ARG CLOUDFLARED_VERSION=2024.4.1
ARG POSTGRES_VERSION=15
WORKDIR /var/www/html

View File

@@ -2,11 +2,11 @@ FROM debian:12-slim
ARG TARGETPLATFORM
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=24.0.5
ARG DOCKER_VERSION=26.1.2
# https://github.com/docker/compose/releases
ARG DOCKER_COMPOSE_VERSION=2.21.0
ARG DOCKER_COMPOSE_VERSION=2.27.0
# https://github.com/docker/buildx/releases
ARG DOCKER_BUILDX_VERSION=0.11.2
ARG DOCKER_BUILDX_VERSION=0.14.0
USER root
WORKDIR /root

30
lang/zh-cn.json Normal file
View File

@@ -0,0 +1,30 @@
{
"auth.login": "登录",
"auth.login.azure": "使用 Microsoft 登录",
"auth.login.bitbucket": "使用 Bitbucket 登录",
"auth.login.github": "使用 GitHub 登录",
"auth.login.gitlab": "使用 Gitlab 登录",
"auth.login.google": "使用 Google 登录",
"auth.already_registered": "已经注册?",
"auth.confirm_password": "确认密码",
"auth.forgot_password": "忘记密码",
"auth.forgot_password_send_email": "发送密码重置邮件",
"auth.register_now": "注册",
"auth.logout": "退出登录",
"auth.register": "注册",
"auth.registration_disabled": "注册已禁用,请联系管理员",
"auth.reset_password": "重置密码",
"auth.failed": "这些凭据与我们的记录不符",
"auth.failed.callback": "处理第三方登录的回调时出错",
"auth.failed.password": "密码错误",
"auth.failed.email": "该账户未注册",
"auth.throttle": "登录次数过多,请在 :seconds 秒后重试",
"input.name": "用户名",
"input.email": "邮箱",
"input.password": "密码",
"input.password.again": "确认密码",
"input.code": "验证码",
"input.recovery_code": "恢复码",
"button.save": "保存",
"repository.url": "<span class='text-helper'>示例</span><br>对于公共代码仓库,请使用 <span class='text-helper'>https://...</span>。<br>对于私有代码仓库,请使用 <span class='text-helper'>git@...</span>。<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> 分支将被选择<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> 分支将被选择。<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> 分支将被选择。<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> 分支将被选择"
}

View File

@@ -0,0 +1,11 @@
$handle = fopen("/tmp/export.csv", "w");
App\Models\Team::chunk(100, function ($teams) use ($handle) {
foreach ($teams as $team) {
if ($team->subscription->stripe_invoice_paid == true) {
foreach ($team->members as $member) {
fputcsv($handle, [$member->email, $member->name], ",");
}
}
}
});
fclose($handle);

2
public/svgs/listmonk.svg Normal file
View File

@@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" width="163.03" height="30.38" viewBox="0 0 43.135 8.038" xmlns:v="https://vecta.io/nano"><circle cx="4.019" cy="4.019" r="3.149" fill="#fff" stroke="#0055d4" stroke-width="1.74"/><path d="M11.457 7.303q-.566 0-.879-.322-.313-.331-.313-.932V.712L11.5.572v5.442q0 .305.253.305.139 0 .244-.052l.253.879q-.357.157-.792.157zm2.619-4.754v4.615H12.84V2.549zM13.449.172q.331 0 .54.209.218.2.218.514 0 .313-.218.522-.209.2-.54.2-.331 0-.54-.2-.209-.209-.209-.522 0-.313.209-.514.209-.209.54-.209zm3.319 2.238q.975 0 1.672.557l-.47.705q-.583-.366-1.149-.366-.305 0-.47.113-.165.113-.165.305 0 .139.07.235.078.096.279.183.209.087.618.209.731.2 1.088.54.357.331.357.914 0 .462-.27.801-.261.34-.714.522-.453.174-1.01.174-.583 0-1.062-.174-.479-.183-.819-.496l.61-.679q.583.453 1.237.453.348 0 .549-.131.209-.139.209-.374 0-.183-.078-.287-.078-.104-.287-.192-.209-.096-.653-.218-.697-.192-1.036-.54-.331-.357-.331-.879 0-.392.226-.705.226-.313.636-.488.418-.183.967-.183zm5.342 4.536q-.253.174-.575.261-.313.096-.627.096-.714-.009-1.08-.409-.366-.401-.366-1.176V3.42h-.688v-.871h.688v-1.01l1.237-.148v1.158h1.062l-.122.871h-.94v2.273q0 .331.113.479.113.148.348.148.235 0 .522-.157zm5.493-4.536q.549 0 .879.374.34.374.34 1.019v3.361h-1.237V4.012q0-.679-.453-.679-.244 0-.427.157-.183.157-.374.488v3.187h-1.237V4.012q0-.679-.453-.679-.244 0-.427.165-.183.157-.366.479v3.187h-1.237V2.549h1.071l.096.575q.261-.348.583-.531.331-.183.758-.183.392 0 .679.2.287.192.418.549.287-.374.618-.557.34-.192.766-.192zm4.148 0q1.036 0 1.62.653.583.644.583 1.794 0 .731-.27 1.289-.261.549-.766.853-.496.305-1.176.305-1.036 0-1.628-.644-.583-.653-.583-1.803 0-.731.261-1.28.27-.557.766-.862.505-.305 1.193-.305zm0 .923q-.47 0-.705.374-.226.366-.226 1.149 0 .784.226 1.158.235.366.697.366.462 0 .688-.366.235-.374.235-1.158 0-.784-.226-1.149-.226-.374-.688-.374zm5.271-.923q.61 0 .949.374.34.366.34 1.019v3.361h-1.237V4.012q0-.374-.131-.522-.122-.157-.374-.157-.261 0-.479.165-.209.157-.409.479v3.187h-1.237V2.549h1.071l.096.583q.287-.357.627-.54.348-.183.784-.183zM40.2.572v6.592h-1.237V.712zm2.804 1.977l-1.472 2.029 1.602 2.586h-1.402l-1.489-2.525 1.48-2.09z"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

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

@@ -0,0 +1 @@
<svg fill="none" height="136" viewBox="0 0 136 136" width="136" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><rect height="136" rx="16" width="136"/></clipPath><g clip-path="url(#a)"><path d="m136 .00002289h-136l.00014448 135.99997711h135.99985552zm-108.73 50.64007711c0-7.43 6.03-13.46 13.46-13.46h25.91c.38 0 .73.23.89.58s.09.76-.17 1.05l-5.68 6.17c-.99 1.07-2.38 1.69-3.84 1.69h-17.04c-2.23 0-4.04 1.81-4.04 4.04v10.18c0 1.31-1.06 2.37-2.37 2.37h-4.74c-1.31 0-2.37-1.06-2.37-2.37v-10.25zm80.61 34.72c0 7.43-6.03 13.4599-13.46 13.4599h-11.01c-7.43 0-13.46-6.0299-13.46-13.4599v-19.27c0-1.31.49-2.57 1.38-3.54l6.42-6.97c.27-.29.69-.39 1.07-.25.37.15.62.4999.62.8999v29.0701c0 2.23 1.81 4.04 4.04 4.04h10.88c2.23 0 4.04-1.81 4.04-4.04v-34.59c0-2.23-1.81-4.04-4.04-4.04h-12.65c-1.45 0-2.83.61-3.82 1.67l-37.73 41h22.67c1.31 0 2.37 1.06 2.37 2.37v4.74c0 1.31-1.06 2.3699-2.37 2.3699h-30.55c-2.77 0-5.02-2.2499-5.02-5.0199v-2.5101c0-1.26.47-2.4699 1.33-3.3999l42.3-45.95c2.8-3.04 6.73-4.76 10.86-4.76h12.66c7.43 0 13.46 6.03 13.46 13.46v34.72z" fill="#000"/><g fill="#fff"><path d="m27.27 50.6401c0-7.43 6.03-13.46 13.46-13.46h25.91c.38 0 .73.23.89.58s.09.76-.17 1.05l-5.68 6.17c-.99 1.07-2.38 1.69-3.84 1.69h-17.04c-2.23 0-4.04 1.81-4.04 4.04v10.18c0 1.31-1.06 2.37-2.37 2.37h-4.74c-1.31 0-2.37-1.06-2.37-2.37v-10.25z"/><path d="m107.88 85.3601c0 7.43-6.03 13.4599-13.46 13.4599h-11.01c-7.43 0-13.46-6.0299-13.46-13.4599v-19.27c0-1.31.49-2.57 1.38-3.54l6.42-6.97c.27-.29.69-.39 1.07-.25.37.15.62.4999.62.8999v29.0701c0 2.23 1.81 4.04 4.04 4.04h10.88c2.23 0 4.04-1.81 4.04-4.04v-34.59c0-2.23-1.81-4.04-4.04-4.04h-12.65c-1.45 0-2.83.61-3.82 1.67l-37.73 41h22.67c1.31 0 2.37 1.06 2.37 2.37v4.74c0 1.31-1.06 2.3699-2.37 2.3699h-30.55c-2.77 0-5.02-2.2499-5.02-5.0199v-2.5101c0-1.26.47-2.4699 1.33-3.3999l42.3-45.95c2.8-3.04 6.73-4.76 10.86-4.76h12.66c7.43 0 13.46 6.03 13.46 13.46v34.72z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -32,7 +32,7 @@ body {
@apply block w-full py-1.5 rounded border-0 text-sm ring-1 ring-inset;
}
.input[type='password'] {
.input[type="password"] {
@apply pr-10;
}
@@ -52,7 +52,6 @@ button[isHighlighted]:not(:disabled) {
@apply text-white bg-coollabs hover:bg-coollabs-100;
}
h1 {
@apply text-2xl font-bold dark:text-white;
}
@@ -78,7 +77,7 @@ label {
}
table {
@apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300 ;
@apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300;
}
thead {
@@ -117,7 +116,7 @@ tr td:first-child {
@apply flex items-center gap-2 text-error;
}
.tag {
@apply px-2 py-1 cursor-pointer box-description dark:bg-coolgray-100 dark:hover:bg-coolgray-300 bg-neutral-100 hover:bg-neutral-200
@apply px-2 py-1 cursor-pointer box-description dark:bg-coolgray-100 dark:hover:bg-coolgray-300 bg-neutral-100 hover:bg-neutral-200;
}
.add-tag {
@apply flex items-center px-2 text-xs cursor-pointer dark:text-neutral-500/20 text-neutral-500 group-hover:text-neutral-700 group-hover:dark:text-white dark:hover:bg-coolgray-300 hover:bg-neutral-200;
@@ -135,7 +134,6 @@ tr td:first-child {
.badge-absolute {
@apply absolute top-0 right-0 w-2 h-2 border-none rounded-t-none rounded-r-none;
}
.badge-success {
@@ -159,7 +157,7 @@ tr td:first-child {
}
.menu-item {
@apply flex items-center w-full gap-3 py-1 pl-2 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300;
@apply flex items-center w-full gap-3 px-2 py-1 text-sm sm:pr-0 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 min-w-fit sm:min-w-64;
}
.menu-item-active {
@@ -174,7 +172,6 @@ tr td:first-child {
@apply w-6 h-6 dark:hover:text-white;
}
.scrollbar {
@apply scrollbar-thumb-coollabs-100 dark:scrollbar-track-coolgray-200 scrollbar-track-neutral-200 scrollbar-w-2;
}
@@ -188,7 +185,7 @@ tr td:first-child {
}
.navbar-main {
@apply flex items-center h-10 gap-6 pb-2 border-b-2 border-solid dark:border-coolgray-200;
@apply flex flex-col gap-4 pb-2 border-b-2 border-solid h-fit md:flex-row justify-items-start sm:justify-between dark:border-coolgray-200 md:items-center;
}
.loading {
@@ -203,20 +200,19 @@ tr td:first-child {
@apply relative flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border text-black dark:text-white hover:text-black border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline;
}
.box-boarding {
@apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black ;
@apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black;
}
.box-without-bg {
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black;
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black;
}
.box-without-bg-without-border {
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] ;
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem];
}
.on-box {
@apply rounded hover:bg-neutral-300 dark:hover:bg-coolgray-500/20;
}
.box-title {
@apply font-bold text-black dark:text-white group-hover:dark:text-white;
}

View File

@@ -4,8 +4,7 @@
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Coolify
</a>
<div
class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base">
<div class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Create an account

View File

@@ -84,7 +84,7 @@
<livewire:switch-team />
</div>
<ul role="list" class="flex flex-col flex-1 gap-y-7">
<li class="flex-1 ">
<li class="flex-1 overflow-x-hidden">
<ul role="list" class="flex flex-col h-full space-y-1.5">
@if (isSubscribed() || !isCloud())
<li>
@@ -161,10 +161,11 @@
class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('storage.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M4 6a8 3 0 1 0 16 0A8 3 0 1 0 4 6"/>
<path d="M4 6v6a8 3 0 0 0 16 0V6"/>
<path d="M4 12v6a8 3 0 0 0 16 0v-6"/>
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M4 6a8 3 0 1 0 16 0A8 3 0 1 0 4 6" />
<path d="M4 6v6a8 3 0 0 0 16 0V6" />
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
</g>
</svg>
S3 Storages
@@ -175,9 +176,11 @@
class="{{ request()->is('shared-variables*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('shared-variables.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M5 4C2.5 9 2.5 14 5 20M19 4c2.5 5 2.5 10 0 16M9 9h1c1 0 1 1 2.016 3.527C13 15 13 16 14 16h1"/>
<path d="M8 16c1.5 0 3-2 4-3.5S14.5 9 16 9"/>
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path
d="M5 4C2.5 9 2.5 14 5 20M19 4c2.5 5 2.5 10 0 16M9 9h1c1 0 1 1 2.016 3.527C13 15 13 16 14 16h1" />
<path d="M8 16c1.5 0 3-2 4-3.5S14.5 9 16 9" />
</g>
</svg>
Shared Variables
@@ -318,9 +321,7 @@
<div class="flex-1"></div>
@if (isInstanceAdmin() && !isCloud())
<li>
@persist('upgrade')
<livewire:upgrade />
@endpersist
<livewire:upgrade />
</li>
@endif
<li>

View File

@@ -1,18 +1,20 @@
<div class="pb-6">
<h1>Notifications</h1>
<div class="subtitle">Get notified about your infrastructure.</div>
<nav class="navbar-main">
<a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.email') }}">
<button>Email</button>
</a>
<a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.telegram') }}">
<button>Telegram</button>
</a>
<a class="{{ request()->routeIs('notifications.discord') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.discord') }}">
<button>Discord</button>
</a>
</nav>
<div class="navbar-main">
<nav class="flex items-center gap-6 min-h-10">
<a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.email') }}">
<button>Email</button>
</a>
<a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.telegram') }}">
<button>Telegram</button>
</a>
<a class="{{ request()->routeIs('notifications.discord') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.discord') }}">
<button>Discord</button>
</a>
</nav>
</div>
</div>

View File

@@ -8,7 +8,7 @@
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
class="fixed bottom-0 right-0 h-auto duration-300 ease-out px-5 pb-5 max-w-[46rem] z-[999]" x-cloak>
<div
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100/40 hover:dark:bg-coolgray-100/100 lg:p-8 lg:flex-row sm:rounded">
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 hover:dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded">
<div
class="flex flex-col items-start h-full pb-0 text-xs lg:items-center lg:flex-row lg:pr-6 lg:space-x-5 dark:text-neutral-300 ">
@if (isset($icon))

View File

@@ -1,35 +1,38 @@
@props([
'lastDeploymentInfo' => null,
'lastDeploymentLink' => null,
'resource' => null,
])
<nav class="flex pt-2 pb-10">
<ol class="flex items-center">
<ol class="flex flex-wrap items-center gap-y-1">
<li class="inline-flex items-center">
<a wire:navigate class="text-xs truncate lg:text-sm"
href="{{ route('project.show', ['project_uuid' => $this->parameters['project_uuid']]) }}">
{{ data_get($resource, 'environment.project.name', 'Undefined Name') }}</a>
</li>
<li>
<div class="flex items-center">
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
<a class="text-xs truncate lg:text-sm"
href="{{ route('project.resource.index', ['environment_name' => $this->parameters['environment_name'], 'project_uuid' => $this->parameters['project_uuid']]) }}">{{ $this->parameters['environment_name'] }}</a>
</div>
</li>
<li>
<div class="flex items-center">
<a wire:navigate class="text-xs truncate lg:text-sm"
href="{{ route('project.show', ['project_uuid' => $this->parameters['project_uuid']]) }}">
{{ data_get($resource, 'environment.project.name', 'Undefined Name') }}</a>
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
<span class="text-xs truncate lg:text-sm">{{ data_get($resource, 'name') }}</span>
</div>
</li>
<li>
<div class="flex items-center">
<a class="text-xs truncate lg:text-sm"
href="{{ route('project.resource.index', ['environment_name' => $this->parameters['environment_name'], 'project_uuid' => $this->parameters['project_uuid']]) }}">{{ $this->parameters['environment_name'] }}</a>
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
</div>
</li>
<li>
<div class="flex items-center">
<span class="text-xs truncate lg:text-sm">{{ data_get($resource, 'name') }}</span>
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
@@ -41,7 +44,7 @@
@if ($resource->getMorphClass() == 'App\Models\Service')
<x-status.services :service="$resource" />
@else
<x-status.index :resource="$resource" />
<x-status.index :resource="$resource" :lastDeploymentInfo="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
@endif
</ol>
</nav>

View File

@@ -1,12 +1,14 @@
<div class="pb-6">
<h1>Security</h1>
<div class="subtitle">Security related settings.</div>
<nav class="navbar-main">
<a href="{{ route('security.private-key.index') }}">
<button>Private Keys</button>
</a>
<a href="{{ route('security.api-tokens') }}">
<button>API tokens</button>
</a>
</nav>
<div class="navbar-main">
<nav class="flex items-center gap-6 scrollbar min-h-10">
<a href="{{ route('security.private-key.index') }}">
<button>Private Keys</button>
</a>
<a href="{{ route('security.api-tokens') }}">
<button>API tokens</button>
</a>
</nav>
</div>
</div>

View File

@@ -5,47 +5,49 @@
<livewire:server.proxy.status :server="$server" />
</div>
<div class="subtitle">{{ data_get($server, 'name') }}.</div>
<nav class="navbar-main">
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}"
href="{{ route('server.show', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>General</button>
</a>
<a class="{{ request()->routeIs('server.private-key') ? 'dark:text-white' : '' }}"
href="{{ route('server.private-key', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Private Key</button>
</a>
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}"
href="{{ route('server.resources', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Resources</button>
</a>
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
href="{{ route('server.proxy', [
<div class="navbar-main">
<nav class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}"
href="{{ route('server.show', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Proxy</button>
<button>General</button>
</a>
<a class="{{ request()->routeIs('server.destinations') ? 'dark:text-white' : '' }}"
href="{{ route('server.destinations', [
<a class="{{ request()->routeIs('server.private-key') ? 'dark:text-white' : '' }}"
href="{{ route('server.private-key', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Destinations</button>
<button>Private Key</button>
</a>
<a class="{{ request()->routeIs('server.log-drains') ? 'dark:text-white' : '' }}"
href="{{ route('server.log-drains', [
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}"
href="{{ route('server.resources', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Log Drains</button>
<button>Resources</button>
</a>
@endif
<div class="flex-1"></div>
<livewire:server.proxy.deploy :server="$server" />
</nav>
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
href="{{ route('server.proxy', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Proxy</button>
</a>
<a class="{{ request()->routeIs('server.destinations') ? 'dark:text-white' : '' }}"
href="{{ route('server.destinations', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Destinations</button>
</a>
<a class="{{ request()->routeIs('server.log-drains') ? 'dark:text-white' : '' }}"
href="{{ route('server.log-drains', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Log Drains</button>
</a>
@endif
</nav>
<div class="order-first sm:order-last">
<livewire:server.proxy.deploy :server="$server" />
</div>
</div>
</div>

View File

@@ -1,17 +1,19 @@
<div class="pb-5">
<h1>Settings</h1>
<div class="subtitle">Instance wide settings for Coolify.</div>
<nav class="navbar-main">
<a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}"
href="{{ route('settings.index') }}">
<button>Configuration</button>
</a>
@if (isCloud())
<a class="{{ request()->routeIs('settings.license') ? 'dark:text-white' : '' }}"
href="{{ route('settings.license') }}">
<button>Resale License</button>
<div class="navbar-main">
<nav class="flex items-center gap-6 min-h-10 whitespace-nowrap">
<a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}"
href="{{ route('settings.index') }}">
<button>Configuration</button>
</a>
@endif
<div class="flex-1"></div>
</nav>
@if (isCloud())
<a class="{{ request()->routeIs('settings.license') ? 'dark:text-white' : '' }}"
href="{{ route('settings.license') }}">
<button>Resale License</button>
</a>
@endif
<div class="flex-1"></div>
</nav>
</div>
</div>

View File

@@ -1,9 +1,14 @@
@props([
'lastDeploymentInfo' => null,
'lastDeploymentLink' => null,
'resource' => null,
])
@if (str($resource->status)->startsWith('running'))
<x-status.running :status="$resource->status" />
<x-status.running :status="$resource->status" :lastDeploymentInfo="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
@elseif(str($resource->status)->startsWith('restarting') ||
str($resource->status)->startsWith('starting') ||
str($resource->status)->startsWith('degraded'))
<x-status.restarting :status="$resource->status" />
<x-status.restarting :status="$resource->status" :lastDeploymentInfo="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
@else
<x-status.stopped :status="$resource->status" />
@endif

View File

@@ -1,12 +1,20 @@
@props([
'status' => 'Restarting',
'lastDeploymentInfo' => null,
'lastDeploymentLink' => null,
])
<div class="flex items-center">
<x-loading wire:loading.delay.longer />
<span wire:loading.remove.delay.longer class="flex items-center">
<div class="badge badge-warning "></div>
<div class="pl-2 pr-1 text-xs font-bold tracking-wider dark:text-warning">
{{ str($status)->before(':')->headline() }}
<div class="pl-2 pr-1 text-xs font-bold tracking-wider dark:text-warning" @if($lastDeploymentInfo) title="{{$lastDeploymentInfo}}" @endif>
@if ($lastDeploymentLink)
<a href="{{ $lastDeploymentLink }}" class="underline cursor-pointer">
{{ str($status)->before(':')->headline() }}
</a>
@else
{{ str($status)->before(':')->headline() }}
@endif
</div>
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
<div class="text-xs dark:text-warning">({{ str($status)->after(':') }})</div>

View File

@@ -1,12 +1,20 @@
@props([
'status' => 'Running',
'lastDeploymentInfo' => null,
'lastDeploymentLink' => null,
])
<div class="flex items-center">
<x-loading wire:loading.delay.longer />
<span wire:loading.remove.delay.longer class="flex items-center">
<div class="badge badge-success "></div>
<div class="pl-2 pr-1 text-xs font-bold tracking-wider text-success">
<div class="pl-2 pr-1 text-xs font-bold tracking-wider text-success" @if($lastDeploymentInfo) title="{{$lastDeploymentInfo}}" @endif>
@if ($lastDeploymentLink)
<a href="{{ $lastDeploymentLink }}" class="underline cursor-pointer">
{{ str($status)->before(':')->headline() }}
</a>
@else
{{ str($status)->before(':')->headline() }}
@endif
</div>
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
@if (str($status)->contains('unhealthy'))
@@ -17,8 +25,6 @@
</svg>
</x-slot:icon>
</x-helper>
{{-- @else
<div class="text-xs dark:text-success">({{ str($status)->after(':') }})</div> --}}
@endif
@endif
</span>

View File

@@ -6,14 +6,16 @@
</x-modal-input>
</div>
<div class="subtitle">Team wide configurations.</div>
<nav class="navbar-main">
<a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" href="{{ route('team.index') }}">
<button>General</button>
</a>
<a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}"
href="{{ route('team.member.index') }}">
<button>Members</button>
</a>
<div class="flex-1"></div>
</nav>
<div class="navbar-main">
<nav class="flex items-center gap-6 min-h-10">
<a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" href="{{ route('team.index') }}">
<button>General</button>
</a>
<a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}"
href="{{ route('team.member.index') }}">
<button>Members</button>
</a>
<div class="flex-1"></div>
</nav>
</div>
</div>

View File

@@ -11,22 +11,22 @@
<div class="grid gap-2 lg:grid-cols-1">
@forelse ($destinations as $destination)
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
<div class="box group">
<a class="flex flex-col mx-6"
<a class="box group"
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
<div class="box-title">{{ $destination->name }}</div>
<div class="box-description">server: {{ $destination->server->name }}</div>
<div class="flex flex-col mx-6">
<div class="box-title">{{ $destination->name }}</div>
<div class="box-description">server: {{ $destination->server->name }}</div>
</div>
</a>
</div>
@endif
@if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
<div class="box group">
<a class="flex flex-col mx-6"
<a class="box group"
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
<div class="box-title">{{ $destination->name }}</div>
<div class="box-description">server: {{ $destination->server->name }}</div>
<div class="flex flex-col mx-6">
<div class="box-title">{{ $destination->name }}</div>
<div class="box-description">server: {{ $destination->server->name }}</div>
</div>
</a>
</div>
@endif
@empty
<div>

View File

@@ -98,47 +98,6 @@
}
}
function revive() {
if (checkHealthInterval) return true;
console.log('Checking server\'s health...')
checkHealthInterval = setInterval(() => {
fetch('/api/health')
.then(response => {
if (response.ok) {
window.toast('Coolify is back online. Reloading...', {
type: 'success',
})
if (checkHealthInterval) clearInterval(checkHealthInterval);
setTimeout(() => {
window.location.reload();
}, 5000)
} else {
console.log('Waiting for server to come back from dead...');
}
})
}, 2000);
}
function upgrade() {
if (checkIfIamDeadInterval) return true;
console.log('Update initiated.')
checkIfIamDeadInterval = setInterval(() => {
fetch('/api/health')
.then(response => {
if (response.ok) {
console.log('It\'s alive. Waiting for server to be dead...');
} else {
window.toast('Update done, restarting Coolify!', {
type: 'success',
})
console.log('It\'s dead. Reviving... Standby... Bzz... Bzz...')
if (checkIfIamDeadInterval) clearInterval(checkIfIamDeadInterval);
revive();
}
})
}, 2000);
}
function copyToClipboard(text) {
navigator?.clipboard?.writeText(text) && window.Livewire.dispatch('success', 'Copied to clipboard.');
}

View File

@@ -57,8 +57,7 @@
Localhost is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
user
'root' or skip the boarding process and add a new private key manually to Coolify and to the
user or skip the boarding process and add a new private key manually to Coolify and to the
server.
<br />
Check this <a target="_blank" class="underline"
@@ -146,9 +145,12 @@
This server is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
user
'root' or skip the boarding process and add a new private key manually to Coolify and to the
user or skip the boarding process and add a new private key manually to Coolify and to the
server.
<br />
Check this <a target="_blank" class="underline"
href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further
help.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="w-64 box-boarding" wire:target="validateServer"
wire:click="validateServer">Check
@@ -229,10 +231,6 @@
<x-forms.button type="submit">Continue</x-forms.button>
</form>
</x-slot:actions>
<x-slot:explanation>
<p>Username should be <x-highlighted text="root" /> for now. We are working on to use
non-root users.</p>
</x-slot:explanation>
</x-boarding-step>
@elseif ($currentState === 'validate-server')
<x-boarding-step title="Validate & Configure Server">

View File

@@ -9,10 +9,10 @@
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Your subscription has been activated! Welcome onboard! <br>It could take a few seconds before your
subscription is activated.<br> Please be patient.
subscription is activated.<br> Please be patient.
</div>
@endif
<h3 class="pb-4">Projects</h3>
@@ -23,23 +23,24 @@
@if (data_get($project, 'environments')->count() === 1) onclick="gotoProject('{{ data_get($project, 'uuid') }}', '{{ data_get($project, 'environments.0.name', 'production') }}')"
@else
onclick="window.location.href = '{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}'" @endif>
<div class="flex flex-col justify-center flex-1 mx-6">
<div class="box-title">{{ $project->name }}</div>
<div class="box-description">
{{ $project->description }}</div>
</div>
<span
class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
<a class="hover:underline"
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="p-2 font-bold">+
Add Resource</span>
</a>
<a class="font-bold hover:underline"
<div class="flex flex-1 mx-6">
<div class="flex flex-col justify-center flex-1">
<div class="box-title">{{ $project->name }}</div>
<div class="box-description">
{{ $project->description }}</div>
</div>
<div class="flex items-center justify-center gap-2 text-xs font-bold ">
<a class="hover:underline"
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="p-2 font-bold">+
Add Resource</span>
</a>
<a class="hover:underline"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
Settings
</a>
</span>
</div>
</div>
</div>
@endforeach
</div>

View File

@@ -15,7 +15,7 @@
checkPusherInterval = setInterval(() => {
if (window.Echo && window.Echo.connector.pusher.connection.state !== 'connected') {
checkNumber++;
if (checkNumber > 4) {
if (checkNumber > 5) {
this.popups.realtime = true;
console.error(
'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)'
@@ -23,31 +23,36 @@
clearInterval(checkPusherInterval);
}
}
}, 1000);
}, 2000);
}
}
}">
@auth
<span x-show="popups.realtime === true">
<x-popup>
<x-slot:title>
<span class="font-bold text-left text-red-500">WARNING: </span>Realtime Error?!
</x-slot:title>
<x-slot:description>
<span>Coolify could not connect to its real-time service.<br>This will cause unusual problems on the UI
if
not fixed! <br><br>
Please ensure that you have opened the
<a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall' target='_blank'>required ports</a>,
check the
related <a class="underline" href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels'
target='_blank'>documentation</a> or get
help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>. </span>
</x-slot:description>
<x-slot:button-text @click="disableRealtime()">
Acknowledge & Disable This Popup
</x-slot:button-text>
</x-popup>
@if (!isCloud())
<x-popup>
<x-slot:title>
<span class="font-bold text-left text-red-500">WARNING: </span>Realtime Error?!
</x-slot:title>
<x-slot:description>
<span>Coolify could not connect to its real-time service.<br>This will cause unusual problems on the
UI
if
not fixed! <br><br>
Please ensure that you have opened the
<a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall'
target='_blank'>required ports</a>,
check the
related <a class="underline" href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels'
target='_blank'>documentation</a> or get
help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>.
</span>
</x-slot:description>
<x-slot:button-text @click="disableRealtime()">
Acknowledge & Disable This Popup
</x-slot:button-text>
</x-popup>
@endif
</span>
@endauth
<span x-show="popups.sponsorship">

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