Compare commits

...

192 Commits

Author SHA1 Message Date
Andras Bacsai
09bcd693f5 Merge pull request #1683 from coollabsio/next
v4.0.0-beta.201
2024-01-29 14:04:30 +01:00
Andras Bacsai
0c15e45419 Update WordPress database configuration 2024-01-29 14:03:59 +01:00
Andras Bacsai
31cbf552a2 Merge pull request #1677 from mraf/wordpress-template
fix: add env variables for wordpress template without database
2024-01-29 13:58:31 +01:00
Andras Bacsai
f7853ee174 Refactor deployments_per_server variable and update dashboard view
This commit refactors the `deployments_per_server` variable in the `Dashboard` class to remove the type hint and updates the corresponding view file to handle the changes. The `deployments_per_server` variable is now grouped by `server_name` and converted to an array. This improves the organization and readability of the code.
2024-01-29 13:26:50 +01:00
Andras Bacsai
de3a7b6eca Add previous page functionality to deployment index
This commit adds the functionality to navigate to the previous page in the deployment index. It includes changes to the `Index.php` and `index.blade.php` files.
2024-01-29 13:06:26 +01:00
Andras Bacsai
b56c7c34cb fix: unhealthy deployments should be failed 2024-01-29 12:51:20 +01:00
Andras Bacsai
49845f3da7 fix: webhooks for multiple apps 2024-01-29 11:23:04 +01:00
Andras Bacsai
987409bae4 fix: bitbucket manual deployments 2024-01-29 10:43:18 +01:00
Andras Bacsai
07d8461f96 Fix URL encoding in deployment and status notifications 2024-01-29 08:49:05 +01:00
Andras Bacsai
f255a71434 Merge pull request #1673 from Niki2k1/feat/bitbucket-manual-webhook
feat: added manual webhook support for bitbucket
2024-01-29 08:38:05 +01:00
Andras Bacsai
2a2818ac0d Merge pull request #1680 from iamEvanYT/discord-deployment-url-fix
fix: encode project name in discord webhook notifications
2024-01-29 08:32:57 +01:00
Andras Bacsai
fd3cdc2c7d Update deployment status border color 2024-01-29 08:32:04 +01:00
Andras Bacsai
84c3f832ae Add missing closing div tag in dashboard.blade.php 2024-01-29 08:30:00 +01:00
Andras Bacsai
70c28fceeb fix: change env variable length 2024-01-29 08:24:21 +01:00
iamEvan
f2c4f83f5a Fix 2024-01-27 20:33:22 +00:00
iamEvan
c8dd6f07ac Encode Project Name 2024-01-27 20:22:27 +00:00
Andras Bacsai
561e424a7d feat: dashboard live deployment view 2024-01-27 18:44:40 +01:00
Andras Bacsai
c46d38907e fix: queue 2024-01-27 17:18:13 +01:00
Andras Bacsai
5c334bbac6 feat: add PR comments 2024-01-26 18:46:50 +01:00
Andras Bacsai
9628072b0c Update dependencies in package.json and mix-manifest.json 2024-01-26 11:36:44 +01:00
Andras Bacsai
39ecff9f90 Update version numbers 2024-01-26 11:34:18 +01:00
Andras Bacsai
d1daec060a Merge pull request #1675 from coollabsio/next
v4.0.0-beta.200
2024-01-26 11:25:32 +01:00
Andras Bacsai
fb5bea7f91 Update Docker actions versions 2024-01-26 11:23:49 +01:00
Andras Bacsai
efc3ea6e40 Update Docker actions versions 2024-01-26 11:20:27 +01:00
Andras Bacsai
ecefb9e1f5 Update vite version to 4.5.2 2024-01-26 11:15:35 +01:00
Andras Bacsai
a4dea2009a Remove unused imports and routes 2024-01-26 11:13:02 +01:00
Andras Bacsai
829e41f93f Delete TeamSharedVariablesIndexTest.php 2024-01-26 11:12:07 +01:00
Andras Bacsai
e7e3adc7fb Fix condition for checking localhost key in ProductionSeeder.php 2024-01-26 11:11:41 +01:00
Andras Bacsai
a993fef235 Update Docker build command 2024-01-26 10:56:22 +01:00
Andras Bacsai
81df71416c Update Docker images and add pull_policy 2024-01-26 10:52:58 +01:00
Andras Bacsai
40af7aa025 Update Docker build command to include latest tag 2024-01-26 10:46:46 +01:00
Andras Bacsai
050155328b Update toast.blade.php to use x-html instead of x-text 2024-01-26 10:37:53 +01:00
Andras Bacsai
376c081bed Add .env file as read-only volume 2024-01-26 10:37:33 +01:00
Andras Bacsai
9f5e1fa9e3 Update docker-compose.windows.yml file 2024-01-26 10:34:48 +01:00
Andras Bacsai
7d139fd33b Add environment file for Windows Docker Desktop 2024-01-26 10:31:37 +01:00
Andras Bacsai
b75a2857a0 Update environment variables and Docker image for Windows Docker Desktop 2024-01-26 10:24:42 +01:00
Andras Bacsai
ab6c1ddc20 Update docker-compose.windows.yml with bind mount for .env file 2024-01-26 10:16:44 +01:00
Andras Bacsai
dc0b0980a9 Update coolify image and environment variables 2024-01-26 09:57:59 +01:00
Andras Bacsai
788d1711db Refactor SSH command generation in remoteProcess.php 2024-01-26 09:36:08 +01:00
Andras Bacsai
d92dc4c5e6 Update testing-host image and remove unnecessary build configuration 2024-01-26 09:03:59 +01:00
Andras Bacsai
4bf34aea62 Add Coolify Testing Host workflow 2024-01-26 08:59:32 +01:00
Andras Bacsai
7e9a54ce67 Fix SSH command generation and disable mux in validateConnection() 2024-01-26 08:54:56 +01:00
Andras Bacsai
f8c19e1fb3 Update contact links in error and subscription views 2024-01-26 08:39:54 +01:00
Andras Bacsai
27c1bda09b Update button labels in create forms 2024-01-25 16:02:31 +01:00
Andras Bacsai
1af7ffcdc4 Refactor input field for port number 2024-01-25 15:58:58 +01:00
Andras Bacsai
39647367a5 Add build pack selection and update related fields 2024-01-25 15:57:04 +01:00
Andras Bacsai
ae4b263810 Update GitHub App registration button 2024-01-25 15:26:23 +01:00
Andras Bacsai
8901bb5df8 Refactor deployment cancellation and queue management 2024-01-25 13:45:17 +01:00
mraf
053aa25d2c fix: add env variables for wordpress template without database 2024-01-25 12:27:41 +01:00
Andras Bacsai
7a7157c155 fix: deployment queue
fix: cancel deployment
ui: changed to simpler toaster
2024-01-25 11:57:47 +01:00
Andras Bacsai
0c5e8600bd Update build pack settings and port values 2024-01-25 08:59:11 +01:00
Andras Bacsai
048e153025 Remove unnecessary condition in setDestination method 2024-01-25 08:57:16 +01:00
Andras Bacsai
e7cafe6850 fix: restrict concurrent deployments per server 2024-01-25 08:36:47 +01:00
Andras Bacsai
1385a86084 Refactor team() method and update references to team() in get_real_environment_variables() method 2024-01-24 15:56:43 +01:00
Andras Bacsai
348923ae02 Refactor realValue() method to include resource lookup 2024-01-24 15:54:55 +01:00
Andras Bacsai
7d754558b0 Fix branch selection and handle missing service 2024-01-24 12:26:14 +01:00
Andras Bacsai
744609e7e9 fix sentry errors 2024-01-24 12:10:03 +01:00
Andras Bacsai
9bd05b65a3 Update build pack and make GithubApp nullable 2024-01-24 12:07:58 +01:00
Andras Bacsai
d42934f258 fix: sentry error 2024-01-24 11:57:51 +01:00
Andras Bacsai
ff752e2411 feat: able to deploy multiple resources with webhook 2024-01-24 11:49:40 +01:00
Andras Bacsai
4120fba9a8 Update default concurrent_builds value to 2 2024-01-24 11:48:00 +01:00
Andras Bacsai
6ecb9c21ce cloud: send notification email if payment 2024-01-24 11:28:01 +01:00
Andras Bacsai
01f7b07fa3 feat: concurrent builds / server 2024-01-24 11:12:23 +01:00
Niklas Lausch
54d8cb9027 feat: added manual webhook support for bitbucket 2024-01-24 10:56:24 +01:00
Andras Bacsai
238337fecb Add new shared variable and update variable usage 2024-01-23 20:26:45 +01:00
Andras Bacsai
7a51acbf8d add slide-over component 2024-01-23 19:01:17 +01:00
Andras Bacsai
fb478c79b3 feat: shared environments 2024-01-23 17:13:23 +01:00
Andras Bacsai
abcc004953 feat: clone any resource 2024-01-22 16:08:18 +01:00
Andras Bacsai
2edf71a0dd feat: move resources between projects / environments 2024-01-22 15:12:38 +01:00
Andras Bacsai
cbec39099a Refactor Git section in advanced.blade.php 2024-01-22 14:13:40 +01:00
Andras Bacsai
dba5499182 Update version numbers + update glitchtip 2024-01-22 12:00:44 +01:00
Andras Bacsai
2fdf52929c Merge pull request #1670 from coollabsio/next
v4.0.0-beta.199
2024-01-22 11:02:30 +01:00
Andras Bacsai
8128dfc061 Update resource limits helper links 2024-01-22 10:47:47 +01:00
Andras Bacsai
2b394d6fea fix: show container on logs/executecontainer command views
fix: exclude containers with restart: no from hc
feat: add compose to predefined docker network
service: add glitchtip
2024-01-21 14:30:03 +01:00
Andras Bacsai
964ded1d0b fix: redis custom conf 2024-01-21 12:06:51 +01:00
Andras Bacsai
838c3830d6 Merge pull request #1664 from coollabsio/next
v4.0.0-beta.198
2024-01-18 15:06:12 +01:00
Andras Bacsai
2db93bd9b9 Add echo statement for queue cleanup and update cleanup message 2024-01-18 14:56:12 +01:00
Andras Bacsai
2f82dedd4f Add call to 'cleanup:queue' command in Init.php and remove 'cleanup:queue' command from Kernel.php 2024-01-18 14:49:47 +01:00
Andras Bacsai
8106602d15 Add CleanupQueue command to clean up Redis queue keys 2024-01-18 14:47:17 +01:00
Andras Bacsai
3d0bf6b472 Update resource name in notification messages 2024-01-18 14:03:51 +01:00
Andras Bacsai
ba7a7e9695 Update server and version configurations 2024-01-18 13:33:57 +01:00
Andras Bacsai
dd0ad04384 Merge pull request #1662 from coollabsio/next
Do not report server is not ready
2024-01-18 12:45:05 +01:00
Andras Bacsai
910a1f43a9 Do not report server is not ready 2024-01-18 12:44:20 +01:00
Andras Bacsai
e2f959ce4c Merge pull request #1659 from coollabsio/next
v4.0.0-beta.197
2024-01-18 12:14:50 +01:00
Andras Bacsai
68fe886fb0 Add restart functionality to proxy deployment 2024-01-18 12:14:11 +01:00
Andras Bacsai
4631c73809 Update general.blade.php with build server option and network section 2024-01-18 12:05:48 +01:00
Andras Bacsai
77558b37da refactor build server 2024-01-18 11:40:13 +01:00
Andras Bacsai
e060409a76 fix: links 2024-01-18 11:24:07 +01:00
Andras Bacsai
af01bc3e77 fix: service deletion bug! 2024-01-17 15:48:01 +01:00
Andras Bacsai
1e158badfc Update button text in by-ip.blade.php 2024-01-17 15:41:38 +01:00
Andras Bacsai
c620bb58ed Add build pack selection and show/hide static site options 2024-01-17 15:41:32 +01:00
Andras Bacsai
8a91395472 Update server model to use 'coolify' instead of 'coolify-overlay' for the name field 2024-01-17 14:11:46 +01:00
Andras Bacsai
c5f3398b73 Update build server and swarm support messages 2024-01-17 14:06:41 +01:00
Andras Bacsai
3878527de8 Update server unreachable notifications to include automatic revival 2024-01-17 14:02:54 +01:00
Andras Bacsai
4abcb2d5b9 Update notification labels in Discord and Telegram settings 2024-01-17 12:23:58 +01:00
Andras Bacsai
a635e51486 fix: server status job 2024-01-17 11:52:56 +01:00
Andras Bacsai
b6ce2e9122 typo 2024-01-17 11:39:45 +01:00
Andras Bacsai
8c60dd5523 typo 2024-01-17 11:21:47 +01:00
Andras Bacsai
94e2d951c4 Revert commented out code and execute remote command to remove Docker container 2024-01-16 15:45:54 +01:00
Andras Bacsai
381e24bea5 fix: git pull command for deploy key based previews 2024-01-16 15:45:19 +01:00
Andras Bacsai
2b1e35980f empty nixpacks type result in error 2024-01-16 15:26:44 +01:00
Andras Bacsai
a42c8da344 fix: proxy ui view
feat: build server 🌮
2024-01-16 15:19:14 +01:00
Andras Bacsai
7a0e415ecf fix: checkbox click 2024-01-16 12:28:59 +01:00
Andras Bacsai
d721f4809a fix ui 2024-01-16 12:20:40 +01:00
Andras Bacsai
22431eee9a fix: change proxy view 2024-01-16 11:32:56 +01:00
Andras Bacsai
c058c0a766 Update release version to 4.0.0-beta.197 2024-01-16 08:38:39 +01:00
Andras Bacsai
1724c0d3ff Merge pull request #1658 from coollabsio/next
v4.0.0-beta.196
2024-01-15 20:22:57 +01:00
Andras Bacsai
0b8f48230f Remove unnecessary echo statements in Server.php 2024-01-15 20:22:13 +01:00
Andras Bacsai
e8d84b7067 Update server and version configurations 2024-01-15 19:57:29 +01:00
Andras Bacsai
5236bbc757 Merge pull request #1655 from coollabsio/next
v4.0.0-beta.195
2024-01-15 14:30:43 +01:00
Andras Bacsai
094e1d1bba Fix condition for merge_request actions in webhooks.php 2024-01-15 14:29:54 +01:00
Andras Bacsai
68b25523d6 Fix action condition in merge_request webhook handler 2024-01-15 14:27:45 +01:00
Andras Bacsai
bdc478d5f5 Update daisyui version in package.json and package-lock.json 2024-01-15 13:46:38 +01:00
Andras Bacsai
002472d7c6 Update version numbers and dependencies 2024-01-15 13:40:32 +01:00
Andras Bacsai
0d65bf62b9 Merge pull request #1654 from coollabsio/next
v4.0.0-beta.194
2024-01-15 13:24:01 +01:00
Andras Bacsai
01c7e76071 only add gzip + https redirect once 2024-01-15 13:23:28 +01:00
Andras Bacsai
884ae0efb0 Merge pull request #1653 from coollabsio/next
v4.0.0-beta.193
2024-01-15 13:03:40 +01:00
Andras Bacsai
8e7040bf7c Update Docker Compose commands to include SOURCE_COMMIT environment variable 2024-01-15 12:59:21 +01:00
Andras Bacsai
059e6a88eb Replace comma with pipe in customLabels 2024-01-15 12:30:49 +01:00
Andras Bacsai
9947158f7e fix gzip compression 2024-01-15 12:12:34 +01:00
Andras Bacsai
61aa9e8766 Merge pull request #1651 from coollabsio/next
v4.0.0-beta.192
2024-01-15 11:53:34 +01:00
Andras Bacsai
75813a289c check domains againts cloudflare ip range 2024-01-15 11:37:26 +01:00
Andras Bacsai
af11d8cf3d Merge pull request #1650 from coollabsio/next
v4.0.0-beta.191
2024-01-15 11:06:15 +01:00
Andras Bacsai
48990db699 Add link to documentation for further help 2024-01-15 11:00:09 +01:00
Andras Bacsai
da71353bfa Update application and proxy configuration 2024-01-15 10:49:39 +01:00
Andras Bacsai
0f5559bc61 fix: service stack view 2024-01-15 10:19:37 +01:00
Andras Bacsai
1afb509c33 add domain validation + custom dns servers
add new guides / docs
2024-01-15 10:03:15 +01:00
Andras Bacsai
bccca6e874 add docs to server validation 2024-01-15 09:03:46 +01:00
Andras Bacsai
083dc15053 Update version and add OpenSSH server detection and PermitRootLogin check 2024-01-15 08:40:46 +01:00
Andras Bacsai
1b6d376472 refactor: compose file and install script 2024-01-12 21:26:51 +01:00
Andras Bacsai
891deee05a Update version numbers to 4.0.0-beta.191 2024-01-12 15:42:05 +01:00
Andras Bacsai
5ffbba908b Refactor healthcheck test in StartPostgresql.php 2024-01-12 15:41:49 +01:00
Andras Bacsai
f762959c9f Refactor select.blade.php file 2024-01-12 15:41:46 +01:00
Andras Bacsai
90a5a23fd9 Fix initial username placeholder in PostgreSQL database view 2024-01-12 15:41:43 +01:00
Andras Bacsai
94e87141ff Merge pull request #1639 from coollabsio/next
Fix resource limits for CPU set
2024-01-12 14:37:34 +01:00
Andras Bacsai
fceaf3e94b Fix resource limits for CPU set 2024-01-12 14:30:25 +01:00
Andras Bacsai
3be554cb55 Merge pull request #1638 from coollabsio/next
v4.0.0-beta.190
2024-01-12 14:20:13 +01:00
Andras Bacsai
27b18fbedf Refactor database and application start scripts 2024-01-12 14:15:15 +01:00
Andras Bacsai
5e7c6906b3 fix: cpuset limits was determined in a way that apps only used 1 CPU max, ehh, sorry. 2024-01-12 13:47:01 +01:00
Andras Bacsai
d05ffe32a3 Merge pull request #1630 from coollabsio/next
v4.0.0-beta.189
2024-01-12 12:51:00 +01:00
Andras Bacsai
f1298d1db4 do not send notification of scheduled task failed 2024-01-12 12:44:08 +01:00
Andras Bacsai
408738e08d Add service status to Index.php and update resource display in index.blade.php 2024-01-12 11:31:51 +01:00
Andras Bacsai
8d04fbdb74 feat: search between resources 2024-01-12 11:25:20 +01:00
Andras Bacsai
dccb31d17e fix: service deletion command 2024-01-12 09:11:36 +01:00
Andras Bacsai
f61210287e Update release version to 4.0.0-beta.189 2024-01-12 08:56:59 +01:00
Andras Bacsai
18ad7220f0 10 mins server check -> 5 mins 2024-01-12 08:49:03 +01:00
Andras Bacsai
79e0df1d43 fix: cleanup docker stuffs before upgrading 2024-01-12 08:45:24 +01:00
Andras Bacsai
a2f53085e5 fix: preview deployments with nixpacks 2024-01-12 08:38:08 +01:00
Andras Bacsai
c5782252ea Merge branch 'fix' into next 2024-01-11 19:00:13 +01:00
Andras Bacsai
bf3d88facd Merge pull request #1632 from coollabsio/fix
v4.0.0-beta.188
2024-01-11 18:59:23 +01:00
Andras Bacsai
e45b0bf715 Update version numbers to 4.0.0-beta.188 2024-01-11 18:55:20 +01:00
Andras Bacsai
9db6c12eea fix missing a end tag 2024-01-11 18:54:55 +01:00
Andras Bacsai
3a391b69e8 fix: restart should not update config hash 2024-01-11 14:34:48 +01:00
Andras Bacsai
cc1fb83c79 cleanup 2024-01-11 14:25:55 +01:00
Andras Bacsai
efa5dd28f1 fix: load profile and set envs on remote cmd 2024-01-11 14:25:42 +01:00
Andras Bacsai
f1eddae379 cleanup 2024-01-11 14:24:54 +01:00
Andras Bacsai
34febe670d fix: load profile on remote commands 2024-01-11 14:13:43 +01:00
Andras Bacsai
3137131a1a Merge pull request #1628 from coollabsio/next
v4.0.0-beta.187
2024-01-11 12:57:01 +01:00
Andras Bacsai
1b6546d26c fix: nixpacks envs
fix: append logs
2024-01-11 12:56:02 +01:00
Andras Bacsai
b9f820cef4 fix: save cmd output propely (merge) 2024-01-11 12:33:28 +01:00
Andras Bacsai
d8639f58d7 Merge pull request #1625 from coollabsio/next
v4.0.0-beta.186
2024-01-11 11:56:00 +01:00
Andras Bacsai
cf9be9355f fix: upload limit on ui 2024-01-11 11:55:17 +01:00
Andras Bacsai
e36bb11ba8 fix: undead endpoint 2024-01-11 11:34:05 +01:00
Andras Bacsai
190beb3d3f fix: escape build envs properly for nixpacks + docker build 2024-01-11 11:32:32 +01:00
Andras Bacsai
890a6925d1 fix: email verification / forgot password 2024-01-11 08:52:30 +01:00
Andras Bacsai
d03b8420f8 Merge pull request #1627 from coollabsio/fix
fix: Email verification and forgot-password
2024-01-11 08:47:05 +01:00
Andras Bacsai
cbd3c880c3 Add email verification and password reset functionality 2024-01-11 07:16:28 +01:00
Andras Bacsai
6b24001876 feat: import backups 2024-01-10 15:42:54 +01:00
Andras Bacsai
6bb45430c9 fix: submit error on postgresql 2024-01-10 14:41:05 +01:00
Andras Bacsai
bc6b4ed850 Merge branch 'stooit-feat/db-import' into next 2024-01-10 14:37:30 +01:00
Andras Bacsai
8a63ef5da9 Merge branch 'feat/db-import' of github.com:stooit/coolify into stooit-feat/db-import 2024-01-10 14:36:59 +01:00
Andras Bacsai
e324866a27 Refactor database configuration layout 2024-01-10 14:34:04 +01:00
Andras Bacsai
0e5f733657 Add import tab to database configuration view 2024-01-10 14:33:21 +01:00
Andras Bacsai
c5932ed337 smaller ui fixes 2024-01-10 12:41:29 +01:00
Andras Bacsai
ef428f844f fix: do not include thegameplan.json into build image 2024-01-10 12:26:27 +01:00
Andras Bacsai
eb8b752a6e Merge pull request #1607 from stooit/feat/scheduled-tasks-cron
feat: Scheduled tasks (cron)
2024-01-10 12:08:47 +01:00
Andras Bacsai
ce0b38035c Merge branch 'next' into feat/scheduled-tasks-cron 2024-01-10 12:03:48 +01:00
Andras Bacsai
562a8f1fac Merge pull request #1614 from RayBB/fix-typos
fix typos
2024-01-10 11:56:29 +01:00
Andras Bacsai
68f1621757 Merge branch 'main' into fix-typos 2024-01-10 11:55:33 +01:00
Andras Bacsai
e3087573bb Merge pull request #1618 from Illyism/patch-1
Update README.md
2024-01-10 11:52:52 +01:00
Andras Bacsai
7869f223a3 Update version numbers to 4.0.0-beta.186 2024-01-10 11:52:39 +01:00
Ilias Ism
14b32e30cd Update README.md 2024-01-08 23:47:11 +01:00
Raymond Berger
5aaad66fe5 fix data naming conflict 2024-01-08 21:59:26 +01:00
Ray Berger
5ee29c6072 fix typos 2024-01-07 22:32:54 +00:00
Stuart Rowlands
557e1407d0 Added database import feature. 2024-01-06 15:24:57 +10:00
Stuart Rowlands
512197021b Minor cleanup. 2024-01-05 15:26:51 +10:00
Stuart Rowlands
e233ec05b5 Merge branch 'main' into feat/scheduled-tasks-cron 2024-01-05 15:08:20 +10:00
Stuart Rowlands
d0e3a20a65 Merge branch 'main' of github.com:stooit/coolify 2024-01-05 15:08:06 +10:00
Stuart Rowlands
e2e6813632 Functional scheduled executions.
Display last executions.
Support for Services.
2024-01-05 15:06:36 +10:00
Stuart Rowlands
9bbe9567c7 Start scheduled task job execution. 2024-01-01 18:23:58 -08:00
Stuart Rowlands
7913a639b5 Complete add/edit/delete for scheduled tasks.
Refactor views.
2024-01-01 18:23:29 -08:00
Stuart Rowlands
adecf328fc WIP start of scheduled tasks. 2024-01-01 10:33:16 -08:00
225 changed files with 6620 additions and 2410 deletions

View File

@@ -0,0 +1,12 @@
IS_WINDOWS_DOCKER_DESKTOP=true
APP_ID=coolify-windows-docker-desktop
APP_NAME=Coolify
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
DB_PASSWORD=coolify
REDIS_PASSWORD=coolify
PUSHER_APP_ID=coolify
PUSHER_APP_KEY=coolify
PUSHER_APP_SECRET=coolify

View File

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

View File

@@ -15,15 +15,15 @@ jobs:
amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
@@ -36,15 +36,15 @@ jobs:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
@@ -59,13 +59,13 @@ jobs:
needs: [amd64, aarch64]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}

View File

@@ -12,9 +12,9 @@ jobs:
amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -24,7 +24,7 @@ jobs:
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
@@ -34,9 +34,9 @@ jobs:
aarch64:
runs-on: [self-hosted, arm64]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -46,7 +46,7 @@ jobs:
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
@@ -61,13 +61,13 @@ jobs:
needs: [amd64, aarch64]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -78,7 +78,7 @@ jobs:
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:

View File

@@ -31,7 +31,7 @@ Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and
<a href="https://github.com/whitesidest"><img src="https://avatars.githubusercontent.com/u/12365916?s=52&v=4" width="60px" alt="Tyler Whitesides" /></a>
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
<a href="https://github.com/Illyism"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
@@ -93,6 +93,10 @@ Contact us [here](https://coolify.io/docs/contact).
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
# Repo Activity
![Alt](https://repobeats.axiom.co/api/embed/eab1c8066f9c59d0ad37b76c23ebb5ccac4278ae.svg "Repobeats analytics image")
# Star History
[![Star History Chart](https://api.star-history.com/svg?repos=coollabsio/coolify&type=Date)](https://star-history.com/#coollabsio/coolify&Date)

View File

@@ -57,7 +57,6 @@ class StartMariadb
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -69,6 +68,9 @@ class StartMariadb
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
@@ -138,7 +140,7 @@ class StartMariadb
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {

View File

@@ -64,7 +64,6 @@ class StartMongodb
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -76,6 +75,9 @@ class StartMongodb
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
@@ -121,7 +123,7 @@ class StartMongodb
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()
@@ -154,7 +156,7 @@ class StartMongodb
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {

View File

@@ -57,7 +57,6 @@ class StartMysql
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -69,6 +68,9 @@ class StartMysql
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
@@ -138,7 +140,7 @@ class StartMysql
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {

View File

@@ -50,12 +50,8 @@ class StartPostgresql
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'pg_isready',
'-d',
$this->database->postgres_db,
'-U',
$this->database->postgres_user,
"CMD-SHELL",
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1"
],
'interval' => '5s',
'timeout' => '5s',
@@ -67,7 +63,6 @@ class StartPostgresql
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -79,6 +74,9 @@ class StartPostgresql
]
]
];
if (!is_null($this->database->limits_cpuset)) {
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'] = [
@@ -166,7 +164,7 @@ class StartPostgresql
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->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {

View File

@@ -3,6 +3,7 @@
namespace App\Actions\Database;
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -66,7 +67,6 @@ class StartRedis
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -78,6 +78,9 @@ class StartRedis
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
@@ -104,7 +107,7 @@ class StartRedis
'target' => '/usr/local/etc/redis/redis.conf',
'read_only' => true,
];
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
@@ -148,7 +151,7 @@ class StartRedis
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
@@ -163,8 +166,9 @@ class StartRedis
return;
}
$filename = 'redis.conf';
$content = $this->database->redis_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
Storage::disk('local')->put("tmp/redis.conf_{$this->database->uuid}", $this->database->redis_conf);
$path = Storage::path("tmp/redis.conf_{$this->database->uuid}");
instant_scp($path, "{$this->configuration_dir}/{$filename}", $this->database->destination->server);
Storage::disk('local')->delete("tmp/redis.conf_{$this->database->uuid}");
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Proxy;
use App\Events\ProxyStatusChanged;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -13,7 +14,6 @@ class StartProxy
public function handle(Server $server, bool $async = true): string|Activity
{
try {
$proxyType = $server->proxyType();
$commands = collect([]);
$proxy_path = get_proxy_path();

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
class CleanupDocker
{
use AsAction;
public function handle(Server $server, bool $force = true)
{
if ($force) {
instant_remote_process(['docker image prune -af'], $server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
instant_remote_process(['docker builder prune -af'], $server, false);
} else {
instant_remote_process(['docker image prune -f'], $server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
instant_remote_process(['docker builder prune -f'], $server, false);
}
}
}

View File

@@ -22,6 +22,7 @@ class UpdateCoolify
if (!$this->server) {
return;
}
CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);

View File

@@ -10,8 +10,10 @@ class DeleteService
use AsAction;
public function handle(Service $service)
{
StopService::run($service);
$server = data_get($service, 'server');
if ($server->isFunctional()) {
StopService::run($service);
}
$storagesToDelete = collect([]);
$service->environment_variables()->delete();

View File

@@ -12,7 +12,6 @@ class StartService
public function handle(Service $service)
{
ray('Starting service: ' . $service->name);
$network = $service->destination->network;
$service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir();
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
@@ -24,10 +23,13 @@ class StartService
$commands[] = "echo 'Starting containers.'";
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
$compose = data_get($service, 'docker_compose', []);
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
foreach ($serviceNames as $serviceName => $serviceConfig) {
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
if (data_get($service, 'connect_to_docker_network')) {
$compose = data_get($service, 'docker_compose', []);
$network = $service->destination->network;
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
foreach ($serviceNames as $serviceName => $serviceConfig) {
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
}
}
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
return $activity;

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
class CleanupQueue extends Command
{
protected $signature = 'cleanup:queue';
protected $description = 'Cleanup Queue';
public function handle()
{
echo "Running queue cleanup...\n";
$prefix = config('database.redis.options.prefix');
$keys = Redis::connection()->keys('*:laravel*');
foreach ($keys as $key) {
$keyWithoutPrefix = str_replace($prefix, '', $key);
Redis::connection()->del($keyWithoutPrefix);
}
}
}

View File

@@ -9,8 +9,6 @@ use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\Team;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Models\Waitlist;
use App\Notifications\Application\DeploymentFailed;
use App\Notifications\Application\DeploymentSuccess;
@@ -18,13 +16,11 @@ use App\Notifications\Application\StatusChanged;
use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess;
use App\Notifications\Test;
use App\Notifications\TransactionalEmails\InvitationLink;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Mail;
use Illuminate\Support\Str;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select;

View File

@@ -30,7 +30,7 @@ class Init extends Command
$this->alive();
$cleanup = $this->option('cleanup');
if ($cleanup) {
echo "Running cleanup\n";
echo "Running cleanups...\n";
$this->cleanup_stucked_resources();
// Required for falsely deleted coolify db
$this->restore_coolify_db_backup();
@@ -54,6 +54,7 @@ class Init extends Command
$settings->update(['is_auto_update_enabled' => false]);
}
}
$this->call('cleanup:queue');
}
private function restore_coolify_db_backup()
{
@@ -99,7 +100,7 @@ class Init extends Command
return;
}
try {
Http::get("https://undead.coollabs.io/coolify/v4/alive?appId=$id&version=$version");
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
echo "I am alive!\n";
} catch (\Throwable $e) {
echo "Error in alive: {$e->getMessage()}\n";
@@ -142,83 +143,83 @@ class Init extends Command
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {
echo "Deleting stucked application: {$application->name}\n";
echo "Deleting stuck application: {$application->name}\n";
$application->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked application: {$e->getMessage()}\n";
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
}
try {
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($postgresqls as $postgresql) {
echo "Deleting stucked postgresql: {$postgresql->name}\n";
echo "Deleting stuck postgresql: {$postgresql->name}\n";
$postgresql->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked postgresql: {$e->getMessage()}\n";
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
}
try {
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($redis as $redis) {
echo "Deleting stucked redis: {$redis->name}\n";
echo "Deleting stuck redis: {$redis->name}\n";
$redis->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked redis: {$e->getMessage()}\n";
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
}
try {
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mongodbs as $mongodb) {
echo "Deleting stucked mongodb: {$mongodb->name}\n";
echo "Deleting stuck mongodb: {$mongodb->name}\n";
$mongodb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked mongodb: {$e->getMessage()}\n";
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
}
try {
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mysqls as $mysql) {
echo "Deleting stucked mysql: {$mysql->name}\n";
echo "Deleting stuck mysql: {$mysql->name}\n";
$mysql->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked mysql: {$e->getMessage()}\n";
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
}
try {
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mariadbs as $mariadb) {
echo "Deleting stucked mariadb: {$mariadb->name}\n";
echo "Deleting stuck mariadb: {$mariadb->name}\n";
$mariadb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked mariadb: {$e->getMessage()}\n";
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
}
try {
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($services as $service) {
echo "Deleting stucked service: {$service->name}\n";
echo "Deleting stuck service: {$service->name}\n";
$service->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked service: {$e->getMessage()}\n";
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
}
try {
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($serviceApps as $serviceApp) {
echo "Deleting stucked serviceapp: {$serviceApp->name}\n";
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
$serviceApp->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked serviceapp: {$e->getMessage()}\n";
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
}
try {
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($serviceDbs as $serviceDb) {
echo "Deleting stucked serviceapp: {$serviceDb->name}\n";
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
$serviceDb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked serviceapp: {$e->getMessage()}\n";
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
}
// Cleanup any resources that are not attached to any environment or destination or server

View File

@@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Jobs\DeleteResourceJob;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
@@ -91,7 +92,7 @@ class ServicesDelete extends Command
if (!$confirmed) {
break;
}
$toDelete->delete();
DeleteResourceJob::dispatch($toDelete);
}
}
}
@@ -115,7 +116,7 @@ class ServicesDelete extends Command
if (!$confirmed) {
return;
}
$toDelete->delete();
DeleteResourceJob::dispatch($toDelete);
}
}
}
@@ -139,7 +140,7 @@ class ServicesDelete extends Command
if (!$confirmed) {
return;
}
$toDelete->delete();
DeleteResourceJob::dispatch($toDelete);
}
}
}

View File

@@ -5,12 +5,14 @@ namespace App\Console;
use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
use App\Models\Team;
use Illuminate\Console\Scheduling\Schedule;
@@ -30,6 +32,7 @@ class Kernel extends ConsoleKernel
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
$this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
@@ -41,6 +44,7 @@ class Kernel extends ConsoleKernel
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
}
}
private function pull_helper_image($schedule)
@@ -56,10 +60,10 @@ class Kernel extends ConsoleKernel
$servers = Server::all()->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);
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} else {
$servers = Server::all()->where('ip', '!=', '1.2.3.4');
$containerServers = $servers->where('settings.is_swarm_worker', false);
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
}
foreach ($containerServers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
@@ -68,7 +72,7 @@ class Kernel extends ConsoleKernel
}
}
foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
}
}
private function instance_auto_update($schedule)
@@ -107,6 +111,32 @@ class Kernel extends ConsoleKernel
}
}
private function check_scheduled_tasks($schedule)
{
$scheduled_tasks = ScheduledTask::all();
if ($scheduled_tasks->isEmpty()) {
ray('no scheduled tasks');
return;
}
foreach ($scheduled_tasks as $scheduled_task) {
$service = $scheduled_task->service()->get();
$application = $scheduled_task->application()->get();
if (!$application && !$service) {
ray('application/service attached to scheduled task does not exist');
$scheduled_task->delete();
continue;
}
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
}
$schedule->job(new ScheduledTaskJob(
task: $scheduled_task
))->cron($scheduled_task->frequency)->onOneServer();
}
}
protected function commands(): void
{
$this->load(__DIR__ . '/Commands');

View File

@@ -10,4 +10,5 @@ enum ProcessStatus: string
case ERROR = 'error';
case KILLED = 'killed';
case CANCELLED = 'cancelled';
case CLOSED = 'closed';
}

View File

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

View File

@@ -3,14 +3,13 @@
namespace App\Http\Controllers;
use App\Events\TestEvent;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
@@ -35,25 +34,25 @@ class Controller extends BaseController
public function verify() {
return view('auth.verify-email');
}
public function email_verify() {
request()->fulfill();
public function email_verify(EmailVerificationRequest $request) {
$request->fulfill();
$name = request()->user()?->name;
send_internal_notification("User {$name} verified their email address.");
return redirect(RouteServiceProvider::HOME);
}
public function forgot_password() {
public function forgot_password(Request $request) {
if (is_transactional_emails_active()) {
$arrayOfRequest = request()->only(Fortify::email());
request()->merge([
$arrayOfRequest = $request->only(Fortify::email());
$request->merge([
'email' => Str::lower($arrayOfRequest['email']),
]);
$type = set_transanctional_email_settings();
if (!$type) {
return response()->json(['message' => 'Transactional emails are not active'], 400);
}
request()->validate([Fortify::email() => 'required|email']);
$request->validate([Fortify::email() => 'required|email']);
$status = Password::broker(config('fortify.passwords'))->sendResetLink(
request()->only(Fortify::email())
$request->only(Fortify::email())
);
if ($status == Password::RESET_LINK_SENT) {
return app(SuccessfulPasswordResetLinkRequestResponse::class, ['status' => $status]);

File diff suppressed because it is too large Load Diff

View File

@@ -17,38 +17,34 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public string $build_logs_url;
public Application $application;
public ApplicationPreview $preview;
public string $body;
public function __construct(
public string $application_id,
public int $pull_request_id,
public string $deployment_uuid,
public string $status
public Application $application,
public ApplicationPreview $preview,
public ProcessStatus $status,
public ?string $deployment_uuid = null
) {
}
public function handle()
{
try {
$this->application = Application::findOrFail($this->application_id);
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
if ($this->status === ProcessStatus::IN_PROGRESS->value) {
if ($this->status === ProcessStatus::CLOSED) {
$this->delete_comment();
return;
} else if ($this->status === ProcessStatus::IN_PROGRESS) {
$this->body = "The preview deployment is in progress. 🟡\n\n";
}
if ($this->status === ProcessStatus::FINISHED->value) {
} else if ($this->status === ProcessStatus::FINISHED) {
$this->body = "The preview deployment is ready. 🟢\n\n";
if ($this->preview->fqdn) {
$this->body .= "[Open Preview]({$this->preview->fqdn}) | ";
}
}
if ($this->status === ProcessStatus::ERROR->value) {
} else if ($this->status === ProcessStatus::ERROR) {
$this->body = "The preview deployment failed. 🔴\n\n";
}
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n";
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET";
@@ -77,10 +73,14 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
private function create_comment()
{
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->preview->pull_request_id}/comments", method: 'post', data: [
'body' => $this->body,
]);
$this->preview->pull_request_issue_comment_id = $data['id'];
$this->preview->save();
}
private function delete_comment()
{
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');
}
}

View File

@@ -27,6 +27,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
@@ -37,15 +40,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return $this->server->uuid;
}
public function __construct(public Server $server)
{
$this->handle();
}
public function handle()
{
if (!$this->server->isServerReady($this->tries)) {
return 'Server is not reachable.';
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
try {
if ($this->server->isSwarm()) {

View File

@@ -31,11 +31,16 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
{
try {
$server = $this->resource->destination->server;
$this->resource->delete();
if (!$server->isFunctional()) {
$this->resource->forceDelete();
if ($this->resource->type() === 'service') {
ray('dispatching delete service');
DeleteService::dispatch($this->resource);
} else {
$this->resource->forceDelete();
}
return 'Server is not functional';
}
$this->resource->delete();
switch ($this->resource->type()) {
case 'application':
StopApplication::run($this->resource);

View File

@@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Actions\Server\CleanupDocker;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -43,9 +44,7 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
ray('Usage before: ' . $this->usageBefore);
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
ray('Cleaning up ' . $this->server->name);
instant_remote_process(['docker image prune -af'], $this->server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server, false);
instant_remote_process(['docker builder prune -af'], $this->server, false);
CleanupDocker::run($this->server);
$usageAfter = $this->server->getDiskUsage();
if ($usageAfter < $this->usageBefore) {
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);

View File

@@ -19,7 +19,7 @@ class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): string

View File

@@ -0,0 +1,117 @@
<?php
namespace App\Jobs;
use App\Models\ScheduledTask;
use App\Models\ScheduledTaskExecution;
use App\Models\Server;
use App\Models\Application;
use App\Models\Service;
use App\Models\Team;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Throwable;
class ScheduledTaskJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public ?Team $team = null;
public Server $server;
public ScheduledTask $task;
public Application|Service $resource;
public ?ScheduledTaskExecution $task_log = null;
public string $task_status = 'failed';
public ?string $task_output = null;
public array $containers = [];
public function __construct($task)
{
$this->task = $task;
if ($service = $task->service()->first()) {
$this->resource = $service;
} else if ($application = $task->application()->first()) {
$this->resource = $application;
} else {
throw new \Exception('ScheduledTaskJob failed: No resource found.');
}
$this->team = Team::find($task->team_id);
}
public function middleware(): array
{
return [new WithoutOverlapping($this->task->id)];
}
public function uniqueId(): int
{
return $this->task->id;
}
public function handle(): void
{
try {
$this->task_log = ScheduledTaskExecution::create([
'scheduled_task_id' => $this->task->id,
]);
$this->server = $this->resource->destination->server;
if ($this->resource->type() == 'application') {
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
if ($containers->count() > 0) {
$containers->each(function ($container) {
$this->containers[] = str_replace('/', '', $container['Names']);
});
}
}
elseif ($this->resource->type() == 'service') {
$this->resource->applications()->get()->each(function ($application) {
if (str(data_get($application, 'status'))->contains('running')) {
$this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
}
});
}
if (count($this->containers) == 0) {
throw new \Exception('ScheduledTaskJob failed: No containers running.');
}
if (count($this->containers) > 1 && empty($this->task->container)) {
throw new \Exception('ScheduledTaskJob failed: More than one container exists but no container name was provided.');
}
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) . '"';
$exec = "docker exec {$containerName} {$cmd}";
$this->task_output = instant_remote_process([$exec], $this->server, true);
$this->task_log->update([
'status' => 'success',
'message' => $this->task_output,
]);
return;
}
}
// No valid container was found.
throw new \Exception('ScheduledTaskJob failed: No valid container was found. Is the container name correct?');
} catch (\Throwable $e) {
if ($this->task_log) {
$this->task_log->update([
'status' => 'failed',
'message' => $this->task_output ?? $e->getMessage(),
]);
}
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
throw $e;
}
}
}

View File

@@ -37,6 +37,9 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function handle()
{
if (!$this->server->isServerReady($this->tries)) {
throw new \RuntimeException('Server is not ready.');
};
try {
if ($this->server->isFunctional()) {
$this->cleanup(notify: false);

View File

@@ -3,6 +3,7 @@
namespace App\Livewire;
use App\Enums\ProcessStatus;
use App\Models\User;
use Livewire\Component;
use Spatie\Activitylog\Models\Activity;
@@ -10,14 +11,16 @@ class ActivityMonitor extends Component
{
public ?string $header = null;
public $activityId;
public $eventToDispatch = 'activityFinished';
public $isPollingActive = false;
protected $activity;
protected $listeners = ['newMonitorActivity'];
public function newMonitorActivity($activityId)
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
{
$this->activityId = $activityId;
$this->eventToDispatch = $eventToDispatch;
$this->hydrateActivity();
@@ -35,13 +38,28 @@ class ActivityMonitor extends Component
// $this->setStatus(ProcessStatus::IN_PROGRESS);
$exit_code = data_get($this->activity, 'properties.exitCode');
if ($exit_code !== null) {
if ($exit_code === 0) {
// $this->setStatus(ProcessStatus::FINISHED);
} else {
// $this->setStatus(ProcessStatus::ERROR);
}
// if ($exit_code === 0) {
// // $this->setStatus(ProcessStatus::FINISHED);
// } else {
// // $this->setStatus(ProcessStatus::ERROR);
// }
$this->isPollingActive = false;
$this->dispatch('activityFinished');
if ($exit_code === 0) {
if ($this->eventToDispatch !== null) {
if (str($this->eventToDispatch)->startsWith('App\\Events\\')) {
$causer_id = data_get($this->activity, 'causer_id');
$user = User::find($causer_id);
if ($user) {
foreach($user->teams as $team) {
$teamId = $team->id;
$this->eventToDispatch::dispatch($teamId);
}
}
return;
}
$this->dispatch($this->eventToDispatch);
}
}
}
}

View File

@@ -2,18 +2,35 @@
namespace App\Livewire;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Support\Collection;
use Livewire\Component;
class Dashboard extends Component
{
public $projects = [];
public $servers = [];
public Collection $servers;
public $deployments_per_server;
public function mount()
{
$this->servers = Server::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
$this->get_deployments();
}
public function get_deployments()
{
$this->deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $this->servers->pluck("id"))->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
}
// public function getIptables()
// {

View File

@@ -16,6 +16,7 @@ class Advanced extends Component
'application.settings.is_force_https_enabled' => 'boolean|required',
'application.settings.is_log_drain_enabled' => 'boolean|required',
'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required',
'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required',

View File

@@ -15,6 +15,7 @@ class Index extends Component
public int $skip = 0;
public int $default_take = 40;
public bool $show_next = false;
public bool $show_prev = false;
public ?string $pull_request_id = null;
protected $queryString = ['pull_request_id'];
public function mount()
@@ -60,15 +61,30 @@ class Index extends Component
{
$this->load_deployments();
}
public function previous_page(?int $take = null)
{
public function load_deployments(int|null $take = null)
if ($take) {
$this->skip = $this->skip - $take;
}
$this->skip = $this->skip - $this->default_take;
if ($this->skip < 0) {
$this->show_prev = false;
$this->skip = 0;
}
$this->load_deployments();
}
public function next_page(?int $take = null)
{
if ($take) {
$this->skip = $this->skip + $take;
}
$take = $this->default_take;
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
$this->show_prev = true;
$this->load_deployments();
}
public function load_deployments()
{
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $this->default_take);
$this->deployments = $deployments;
$this->deployments_count = $count;
$this->show_pull_request_only();

View File

@@ -41,13 +41,10 @@ class DeploymentNavbar extends Component
public function cancel()
{
try {
$kill_command = "kill -9 {$this->application_deployment_queue->current_process_id}";
if ($this->application_deployment_queue->current_process_id) {
$process = Process::run("ps -p {$this->application_deployment_queue->current_process_id} -o command --no-headers");
if (Str::of($process->output())->contains([$this->server->ip, 'EOF-COOLIFY-SSH'])) {
Process::run($kill_command);
}
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
if ($this->application_deployment_queue->logs) {
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
$new_log_entry = [
'command' => $kill_command,
'output' => "Deployment cancelled by user.",
@@ -60,15 +57,17 @@ class DeploymentNavbar extends Component
$this->application_deployment_queue->update([
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
]);
instant_remote_process([$kill_command], $this->server);
}
} catch (\Throwable $e) {
ray($e);
return handleError($e, $this);
} finally {
$this->application_deployment_queue->update([
'current_process_id' => null,
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
]);
queue_next_deployment($this->application);
// queue_next_deployment($this->application);
}
}
}

View File

@@ -67,6 +67,7 @@ class General extends Component
'application.docker_compose_custom_start_command' => 'nullable',
'application.docker_compose_custom_build_command' => 'nullable',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
];
protected $validationAttributes = [
'application.name' => 'name',
@@ -100,6 +101,7 @@ class General extends Component
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
'application.settings.is_build_server_enabled' => 'Is build server enabled',
];
public function mount()
{
@@ -117,7 +119,7 @@ class General extends Component
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
$this->customLabels = $this->application->parseContainerLabels();
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
}
@@ -182,7 +184,7 @@ class General extends Component
}
public function checkLabelUpdates()
{
if (md5($this->application->custom_labels) !== md5(implode(",", generateLabelsApplication($this->application)))) {
if (md5($this->application->custom_labels) !== md5(implode("|", generateLabelsApplication($this->application)))) {
$this->labelsChanged = true;
} else {
$this->labelsChanged = false;
@@ -201,7 +203,7 @@ class General extends Component
}
public function resetDefaultLabels($showToaster = true)
{
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->ports_exposes = $this->application->ports_exposes;
$this->submit($showToaster);
}
@@ -209,13 +211,13 @@ class General extends Component
public function updatedApplicationFqdn()
{
$this->resetDefaultLabels(false);
$this->dispatch('success', 'Labels reseted to default!');
// $this->dispatch('success', 'Labels reset to default!');
}
public function submit($showToaster = true)
{
try {
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
}
@@ -227,7 +229,6 @@ class General extends Component
if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false);
}
if (data_get($this->application, 'build_pack') === 'dockerimage') {
$this->validate([
'application.docker_registry_image_name' => 'required',
@@ -235,9 +236,16 @@ class General extends Component
]);
}
if (data_get($this->application, 'fqdn')) {
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return Str::of($domain)->trim()->lower();
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$domains = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return str($domain)->trim()->lower();
});
$domains = $domains->unique();
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS settings for: $domain failed.<br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
}
}
$this->application->fqdn = $domains->implode(',');
}

View File

@@ -3,7 +3,6 @@
namespace App\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Events\ApplicationStatusChanged;
use App\Jobs\ContainerStatusJob;
use App\Jobs\ServerStatusJob;
use App\Models\Application;
@@ -39,7 +38,7 @@ class Heading extends Component
} else {
dispatch(new ServerStatusJob($this->application->destination->server));
}
if ($showNotification) $this->dispatch('success', "Application ({$this->application->name}) status updated.");
if ($showNotification) $this->dispatch('success', "Application status updated.");
}
public function force_deploy_without_cache()
@@ -55,7 +54,7 @@ class Heading extends Component
}
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
application: $this->application,
deployment_uuid: $this->deploymentUuid,
force_rebuild: false,
is_new_deployment: true,
@@ -74,12 +73,16 @@ class Heading extends Component
return;
}
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
$this->dispatch('error', 'Please set a Docker image name first.');
$this->dispatch('error', 'To deploy to a Swarm cluster you must set a Docker image name first.');
return;
}
if (data_get($this->application, 'settings.is_build_server_enabled') && is_null($this->application->docker_registry_image_name)) {
$this->dispatch('error', 'To use a build server you must set a Docker image name first.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
return;
}
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
application: $this->application,
deployment_uuid: $this->deploymentUuid,
force_rebuild: $force_rebuild,
);
@@ -108,7 +111,7 @@ class Heading extends Component
{
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
application: $this->application,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
is_new_deployment: true,
@@ -124,7 +127,7 @@ class Heading extends Component
{
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
application: $this->application,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
);

View File

@@ -47,10 +47,11 @@ class Previews extends Component
]);
}
queue_application_deployment(
application_id: $this->application->id,
application: $this->application,
deployment_uuid: $this->deployment_uuid,
force_rebuild: true,
force_rebuild: false,
pull_request_id: $pull_request_id,
git_type: $found->git_type ?? null,
);
return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],

View File

@@ -24,7 +24,7 @@ class Rollback extends Component
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application_id: $this->application->id,
application: $this->application,
deployment_uuid: $deployment_uuid,
commit: $commit,
force_rebuild: false,

View File

@@ -0,0 +1,139 @@
<?php
namespace App\Livewire\Project\Database;
use Exception;
use Livewire\Component;
use Livewire\WithFileUploads;
use App\Models\Server;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Storage;
class Import extends Component
{
use WithFileUploads;
public $file;
public $resource;
public $parameters;
public $containers;
public bool $validated = true;
public bool $scpInProgress = false;
public bool $importRunning = false;
public string $validationMsg = '';
public Server $server;
public string $container;
public array $importCommands = [];
public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB';
public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p $MYSQL_PASSWORD $MYSQL_DATABASE';
public string $mariadbRestoreCommand = 'mariadb -u $MARIADB_USER -p $MARIADB_PASSWORD $MARIADB_DATABASE';
public function getListeners()
{
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
];
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->getContainers();
}
public function getContainers()
{
$this->containers = collect();
if (!data_get($this->parameters, 'database_uuid')) {
abort(404);
}
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
abort(404);
}
}
}
}
}
$this->resource = $resource;
$this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid;
if (str(data_get($this, 'resource.status'))->startsWith('running')) {
$this->containers->push($this->container);
}
if ($this->containers->count() > 1) {
$this->validated = false;
$this->validationMsg = 'The database service has more than one container running. Cannot import.';
}
if (
$this->resource->getMorphClass() == 'App\Models\StandaloneRedis'
|| $this->resource->getMorphClass() == 'App\Models\StandaloneMongodb'
) {
$this->validated = false;
$this->validationMsg = 'This database type is not currently supported.';
}
}
public function runImport()
{
$this->validate([
'file' => 'required|file|max:102400'
]);
$this->importRunning = true;
$this->scpInProgress = true;
try {
$uploadedFilename = $this->file->store('backup-import');
$path = Storage::path($uploadedFilename);
$tmpPath = '/tmp/' . basename($uploadedFilename);
// SCP the backup file to the server.
instant_scp($path, $tmpPath, $this->server);
$this->scpInProgress = false;
$this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}";
switch ($this->resource->getMorphClass()) {
case 'App\Models\StandaloneMariadb':
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mariadbRestoreCommand} < {$tmpPath}'";
$this->importCommands[] = "rm {$tmpPath}";
break;
case 'App\Models\StandaloneMysql':
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mysqlRestoreCommand} < {$tmpPath}'";
$this->importCommands[] = "rm {$tmpPath}";
break;
case 'App\Models\StandalonePostgresql':
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->postgresqlRestoreCommand} {$tmpPath}'";
$this->importCommands[] = "rm {$tmpPath}";
break;
}
$this->importCommands[] = "docker exec {$this->container} sh -c 'rm {$tmpPath}'";
$this->importCommands[] = "docker exec {$this->container} sh -c 'echo \"Import finished with exit code $?\"'";
if (!empty($this->importCommands)) {
$activity = remote_process($this->importCommands, $this->server, ignore_errors: true);
$this->dispatch('newMonitorActivity', $activity->id);
}
} catch (\Throwable $e) {
$this->validated = false;
$this->validationMsg = $e->getMessage();
}
}
}

View File

@@ -12,7 +12,24 @@ class Edit extends Component
'project.name' => 'required|min:3|max:255',
'project.description' => 'nullable|string|max:255',
];
public function mount() {
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$this->project->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'type' => 'project',
'team_id' => currentTeam()->id,
]);
$this->project->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();

View File

@@ -12,14 +12,30 @@ class EnvironmentEdit extends Component
public Application $application;
public $environment;
public array $parameters;
protected $rules = [
'environment.name' => 'required|min:3|max:255',
'environment.description' => 'nullable|min:3|max:255',
];
public function mount() {
$this->parameters = get_route_parameters();
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$this->environment->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);
$this->environment->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first();
$this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first();
}

View File

@@ -39,6 +39,8 @@ class GithubPrivateRepository extends Component
public bool $is_static = false;
public string|null $publish_directory = null;
protected int $page = 1;
public $build_pack = 'nixpacks';
public bool $show_is_static = true;
public function mount()
@@ -49,6 +51,20 @@ class GithubPrivateRepository extends Component
$this->repositories = $this->branches = collect();
$this->github_apps = GithubApp::private();
}
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
$this->show_is_static = true;
$this->port = 3000;
} else if ($this->build_pack === 'static') {
$this->show_is_static = false;
$this->is_static = false;
$this->port = 80;
} else {
$this->show_is_static = false;
$this->is_static = false;
}
}
public function loadRepositories($github_app_id)
{
$this->repositories = collect();
@@ -95,7 +111,7 @@ class GithubPrivateRepository extends Component
$this->loadBranchByPage();
}
}
$this->selected_branch_name = data_get($this->branches,'0.name');
$this->selected_branch_name = data_get($this->branches, '0.name', 'main');
}
protected function loadBranchByPage()

View File

@@ -29,12 +29,17 @@ class GithubPrivateRepositoryDeployKey extends Component
public string $repository_url;
public string $branch;
public $build_pack = 'nixpacks';
public bool $show_is_static = true;
protected $rules = [
'repository_url' => 'required',
'branch' => 'required|string',
'port' => 'required|numeric',
'is_static' => 'required|boolean',
'publish_directory' => 'nullable|string',
'build_pack' => 'required|string',
];
protected $validationAttributes = [
'repository_url' => 'Repository',
@@ -42,6 +47,7 @@ class GithubPrivateRepositoryDeployKey extends Component
'port' => 'Port',
'is_static' => 'Is static',
'publish_directory' => 'Publish directory',
'build_pack' => 'Build pack',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp|string $git_source = 'other';
@@ -62,6 +68,20 @@ class GithubPrivateRepositoryDeployKey extends Component
}
}
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
$this->show_is_static = true;
$this->port = 3000;
} else if ($this->build_pack === 'static') {
$this->show_is_static = false;
$this->is_static = false;
$this->port = 80;
} else {
$this->show_is_static = false;
$this->is_static = false;
}
}
public function instantSave()
{
if ($this->is_static) {

View File

@@ -30,18 +30,22 @@ class PublicGitRepository extends Component
public GithubApp|GitlabApp|string $git_source = 'other';
public string $git_host;
public string $git_repository;
public $build_pack = 'nixpacks';
public bool $show_is_static = true;
protected $rules = [
'repository_url' => 'required|url',
'port' => 'required|numeric',
'is_static' => 'required|boolean',
'publish_directory' => 'nullable|string',
'build_pack' => 'required|string',
];
protected $validationAttributes = [
'repository_url' => 'repository',
'port' => 'port',
'is_static' => 'static',
'publish_directory' => 'publish directory',
'build_pack' => 'build pack',
];
public function mount()
@@ -53,7 +57,20 @@ class PublicGitRepository extends Component
$this->parameters = get_route_parameters();
$this->query = request()->query();
}
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
$this->show_is_static = true;
$this->port = 3000;
} else if ($this->build_pack === 'static') {
$this->show_is_static = false;
$this->is_static = false;
$this->port = 80;
} else {
$this->show_is_static = false;
$this->is_static = false;
}
}
public function instantSave()
{
if ($this->is_static) {
@@ -157,6 +174,7 @@ class PublicGitRepository extends Component
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'build_pack' => $this->build_pack,
];
} else {
$application_init = [
@@ -170,7 +188,8 @@ class PublicGitRepository extends Component
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
'source_type' => $this->git_source->getMorphClass(),
'build_pack' => $this->build_pack,
];
}

View File

@@ -104,7 +104,7 @@ class Select extends Component
if ($this->includeSwarm) {
$this->servers = $this->allServers;
} else {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
}
}
public function setType(string $type)
@@ -120,13 +120,13 @@ class Select extends Component
case 'mongodb':
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
break;
}
if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) {
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
}
if ($type === "existing-postgresql") {
$this->current_step = $type;

View File

@@ -10,7 +10,15 @@ class Index extends Component
{
public Project $project;
public Environment $environment;
public function mount () {
public $applications = [];
public $postgresqls = [];
public $redis = [];
public $mongodbs = [];
public $mysqls = [];
public $mariadbs = [];
public $services = [];
public function mount()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
@@ -21,6 +29,84 @@ class Index extends Component
}
$this->project = $project;
$this->environment = $environment;
$this->applications = $environment->applications->sortBy('name');
$this->applications = $this->applications->map(function ($application) {
if (data_get($application, 'environment.project.uuid')) {
$application->hrefLink = route('project.application.configuration', [
'project_uuid' => data_get($application, 'environment.project.uuid'),
'environment_name' => data_get($application, 'environment.name'),
'application_uuid' => data_get($application, 'uuid')
]);
}
return $application;
});
$this->postgresqls = $environment->postgresqls->sortBy('name');
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
if (data_get($postgresql, 'environment.project.uuid')) {
$postgresql->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($postgresql, 'environment.project.uuid'),
'environment_name' => data_get($postgresql, 'environment.name'),
'database_uuid' => data_get($postgresql, 'uuid')
]);
}
return $postgresql;
});
$this->redis = $environment->redis->sortBy('name');
$this->redis = $this->redis->map(function ($redis) {
if (data_get($redis, 'environment.project.uuid')) {
$redis->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($redis, 'environment.project.uuid'),
'environment_name' => data_get($redis, 'environment.name'),
'database_uuid' => data_get($redis, 'uuid')
]);
}
return $redis;
});
$this->mongodbs = $environment->mongodbs->sortBy('name');
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
if (data_get($mongodb, 'environment.project.uuid')) {
$mongodb->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($mongodb, 'environment.project.uuid'),
'environment_name' => data_get($mongodb, 'environment.name'),
'database_uuid' => data_get($mongodb, 'uuid')
]);
}
return $mongodb;
});
$this->mysqls = $environment->mysqls->sortBy('name');
$this->mysqls = $this->mysqls->map(function ($mysql) {
if (data_get($mysql, 'environment.project.uuid')) {
$mysql->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($mysql, 'environment.project.uuid'),
'environment_name' => data_get($mysql, 'environment.name'),
'database_uuid' => data_get($mysql, 'uuid')
]);
}
return $mysql;
});
$this->mariadbs = $environment->mariadbs->sortBy('name');
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
if (data_get($mariadb, 'environment.project.uuid')) {
$mariadb->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($mariadb, 'environment.project.uuid'),
'environment_name' => data_get($mariadb, 'environment.name'),
'database_uuid' => data_get($mariadb, 'uuid')
]);
}
return $mariadb;
});
$this->services = $environment->services->sortBy('name');
$this->services = $this->services->map(function ($service) {
if (data_get($service, 'environment.project.uuid')) {
$service->hrefLink = route('project.service.configuration', [
'project_uuid' => data_get($service, 'environment.project.uuid'),
'environment_name' => data_get($service, 'environment.name'),
'service_uuid' => data_get($service, 'uuid')
]);
$service->status = serviceStatus($service);
}
return $service;
});
}
public function render()
{

View File

@@ -30,7 +30,10 @@ class Configuration extends Component
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->first();
if (!$this->service) {
return redirect()->route('dashboard');
}
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}

View File

@@ -14,6 +14,7 @@ class StackForm extends Component
'service.docker_compose' => 'required',
'service.name' => 'required',
'service.description' => 'nullable',
'service.connect_to_docker_network' => 'nullable',
];
public $validationAttributes = [];
public function mount()
@@ -44,6 +45,9 @@ class StackForm extends Component
$this->service->docker_compose_raw = $raw;
$this->submit();
}
public function instantSave() {
$this->service->save();
}
public function submit()
{

View File

@@ -8,5 +8,5 @@ class Destination extends Component
{
public $resource;
public $servers = [];
public $additionalServers = [];
public $additional_servers = [];
}

View File

@@ -32,7 +32,6 @@ class Add extends Component
public function submit()
{
$this->validate();
ray($this->key, $this->value, $this->is_build_time);
$this->dispatch('saveKey', [
'key' => $this->key,
'value' => $this->value,

View File

@@ -3,16 +3,18 @@
namespace App\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use App\Models\SharedEnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Show extends Component
{
public $parameters;
public ModelsEnvironmentVariable $env;
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
public ?string $modalId = null;
public bool $isDisabled = false;
public bool $isLocked = false;
public bool $isSharedVariable = false;
public string $type;
protected $rules = [
@@ -20,16 +22,20 @@ class Show extends Component
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable',
];
protected $validationAttributes = [
'key' => 'Key',
'value' => 'Value',
'is_build_time' => 'Build Time',
'is_shown_once' => 'Shown Once',
'env.key' => 'Key',
'env.value' => 'Value',
'env.is_build_time' => 'Build Time',
'env.is_shown_once' => 'Shown Once',
];
public function mount()
{
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
$this->isSharedVariable = true;
}
$this->modalId = new Cuid2(7);
$this->parameters = get_route_parameters();
$this->checkEnvs();
@@ -44,9 +50,16 @@ class Show extends Component
$this->isLocked = true;
}
}
public function serialize() {
data_forget($this->env, 'real_value');
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
data_forget($this->env, 'is_build_time');
}
}
public function lock()
{
$this->env->is_shown_once = true;
$this->serialize();
$this->env->save();
$this->checkEnvs();
$this->dispatch('refreshEnvs');
@@ -57,10 +70,23 @@ class Show extends Component
}
public function submit()
{
$this->validate();
$this->env->save();
$this->dispatch('success', 'Environment variable updated successfully.');
$this->dispatch('refreshEnvs');
try {
if ($this->isSharedVariable) {
$this->validate([
'env.key' => 'required|string',
'env.value' => 'nullable',
'env.is_shown_once' => 'required|boolean',
]);
} else {
$this->validate();
}
$this->serialize();
$this->env->save();
$this->dispatch('success', 'Environment variable updated successfully.');
$this->dispatch('refreshEnvs');
} catch(\Exception $e) {
return handleError($e);
}
}
public function delete()

View File

@@ -79,21 +79,21 @@ class ExecuteContainerCommand extends Component
$this->resource = $resource;
$this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid;
if (str(data_get($this,'resource.status'))->startsWith('running')) {
// if (!str(data_get($this,'resource.status'))->startsWith('exited')) {
$this->containers->push($this->container);
}
// }
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource->applications()->get()->each(function ($application) {
if (str(data_get($application, 'status'))->contains('running')) {
// if (str(data_get($application, 'status'))->contains('running')) {
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
}
// }
});
$this->resource->databases()->get()->each(function ($database) {
if (str(data_get($database, 'status'))->contains('running')) {
// if (str(data_get($database, 'status'))->contains('running')) {
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
}
// }
});
$this->server = $this->resource->server;
@@ -108,8 +108,7 @@ class ExecuteContainerCommand extends Component
$this->validate();
try {
// Wrap command to prevent escaped execution in the host.
$cmd = 'sh -c "' . 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} {$this->container} {$cmd}";
} else {

View File

@@ -70,21 +70,21 @@ class Logs extends Component
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid;
if (str(data_get($this, 'resource.status'))->startsWith('running')) {
// if (str(data_get($this, 'resource.status'))->startsWith('running')) {
$this->containers->push($this->container);
}
// }
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource->applications()->get()->each(function ($application) {
if (str(data_get($application, 'status'))->contains('running')) {
// if (str(data_get($application, 'status'))->contains('running')) {
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
}
// }
});
$this->resource->databases()->get()->each(function ($database) {
if (str(data_get($database, 'status'))->contains('running')) {
// if (str(data_get($database, 'status'))->contains('running')) {
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
}
// }
});
$this->server = $this->resource->server;

View File

@@ -44,8 +44,8 @@ class ResourceLimits extends Component
if (!$this->resource->limits_cpus) {
$this->resource->limits_cpus = "0";
}
if (!$this->resource->limits_cpuset) {
$this->resource->limits_cpuset = "0";
if ($this->resource->limits_cpuset === "") {
$this->resource->limits_cpuset = null;
}
if (!$this->resource->limits_cpu_shares) {
$this->resource->limits_cpu_shares = 1024;

View File

@@ -0,0 +1,173 @@
<?php
namespace App\Livewire\Project\Shared;
use App\Models\Environment;
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class ResourceOperations extends Component
{
public $resource;
public $projectUuid;
public $environmentName;
public $projects;
public $servers;
public function mount()
{
$parameters = get_route_parameters();
$this->projectUuid = $parameters['project_uuid'];
$this->environmentName = $parameters['environment_name'];
$this->projects = Project::ownedByCurrentTeam()->get();
$this->servers = currentTeam()->servers;
}
public function cloneTo($destination_id)
{
$new_destination = StandaloneDocker::find($destination_id);
if (!$new_destination) {
$new_destination = SwarmDocker::find($destination_id);
}
if (!$new_destination) {
return $this->addError('destination_id', 'Destination not found.');
}
$uuid = (string)new Cuid2(7);
$server = $new_destination->server;
if ($this->resource->getMorphClass() === 'App\Models\Application') {
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
'name' => $this->resource->name . '-clone-' . $uuid,
'fqdn' => generateFqdn($server, $uuid),
'status' => 'exited',
'destination_id' => $new_destination->id,
]);
$new_resource->save();
$environmentVaribles = $this->resource->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) {
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
'application_id' => $new_resource->id,
]);
$newEnvironmentVariable->save();
}
$persistentVolumes = $this->resource->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
$newPersistentVolume = $volume->replicate()->fill([
'name' => $new_resource->uuid . '-' . str($volume->name)->afterLast('-'),
'resource_id' => $new_resource->id,
]);
$newPersistentVolume->save();
}
$route = route('project.application.configuration', [
'project_uuid' => $this->projectUuid,
'environment_name' => $this->environmentName,
'application_uuid' => $new_resource->uuid,
]) . "#resource-operations";
return redirect()->to($route);
} else if (
$this->resource->getMorphClass() === 'App\Models\StandalonePostgresql' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneMongodb' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneMysql' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneMariadb' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneRedis'
) {
$uuid = (string)new Cuid2(7);
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
'name' => $this->resource->name . '-clone-' . $uuid,
'status' => 'exited',
'started_at' => null,
'destination_id' => $new_destination->id,
]);
$new_resource->save();
$environmentVaribles = $this->resource->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) {
$payload = [];
if ($this->resource->type() === 'standalone-postgresql') {
$payload['standalone_postgresql_id'] = $new_resource->id;
} else if ($this->resource->type() === 'standalone-redis') {
$payload['standalone_redis_id'] = $new_resource->id;
} else if ($this->resource->type() === 'standalone-mongodb') {
$payload['standalone_mongodb_id'] = $new_resource->id;
} else if ($this->resource->type() === 'standalone-mysql') {
$payload['standalone_mysql_id'] = $new_resource->id;
} else if ($this->resource->type() === 'standalone-mariadb') {
$payload['standalone_mariadb_id'] = $new_resource->id;
}
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
$newEnvironmentVariable->save();
}
$route = route('project.database.configuration', [
'project_uuid' => $this->projectUuid,
'environment_name' => $this->environmentName,
'database_uuid' => $new_resource->uuid,
]) . "#resource-operations";
return redirect()->to($route);
} else if ($this->resource->type() === 'service') {
$uuid = (string)new Cuid2(7);
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
'name' => $this->resource->name . '-clone-' . $uuid,
'destination_id' => $new_destination->id,
]);
$new_resource->save();
foreach ($new_resource->applications() as $application) {
$application->update([
'status' => 'exited',
]);
}
foreach ($new_resource->databases() as $database) {
$database->update([
'status' => 'exited',
]);
}
$new_resource->parse();
$route = route('project.service.configuration', [
'project_uuid' => $this->projectUuid,
'environment_name' => $this->environmentName,
'service_uuid' => $new_resource->uuid,
]) . "#resource-operations";
return redirect()->to($route);
}
return;
}
public function moveTo($environment_id)
{
try {
$new_environment = Environment::findOrFail($environment_id);
$this->resource->update([
'environment_id' => $environment_id
]);
if ($this->resource->type() === 'application') {
$route = route('project.application.configuration', [
'project_uuid' => $new_environment->project->uuid,
'environment_name' => $new_environment->name,
'application_uuid' => $this->resource->uuid,
]) . "#resource-operations";
return redirect()->to($route);
} else if (str($this->resource->type())->startsWith('standalone-')) {
$route = route('project.database.configuration', [
'project_uuid' => $new_environment->project->uuid,
'environment_name' => $new_environment->name,
'database_uuid' => $this->resource->uuid,
]) . "#resource-operations";
return redirect()->to($route);
} else if ($this->resource->type() === 'service') {
$route = route('project.service.configuration', [
'project_uuid' => $new_environment->project->uuid,
'environment_name' => $new_environment->name,
'service_uuid' => $this->resource->uuid,
]) . "#resource-operations";
return redirect()->to($route);
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.shared.resource-operations');
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Livewire\Project\Shared\ScheduledTask;
use Livewire\Component;
class Add extends Component
{
public $parameters;
public string $name;
public string $command;
public string $frequency;
public ?string $container = '';
protected $listeners = ['clearScheduledTask' => 'clear'];
protected $rules = [
'name' => 'required|string',
'command' => 'required|string',
'frequency' => 'required|string',
'container' => 'nullable|string',
];
protected $validationAttributes = [
'name' => 'name',
'command' => 'command',
'frequency' => 'frequency',
'container' => 'container',
];
public function mount()
{
$this->parameters = get_route_parameters();
}
public function submit()
{
$this->validate();
$isValid = validate_cron_expression($this->frequency);
if (!$isValid) {
$this->dispatch('error', 'Invalid Cron / Human expression.');
return;
}
$this->dispatch('saveScheduledTask', [
'name' => $this->name,
'command' => $this->command,
'frequency' => $this->frequency,
'container' => $this->container,
]);
$this->clear();
}
public function clear()
{
$this->name = '';
$this->command = '';
$this->frequency = '';
$this->container = '';
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Livewire\Project\Shared\ScheduledTask;
use App\Models\ScheduledTask;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class All extends Component
{
public $resource;
public string|null $modalId = null;
public ?string $variables = null;
public array $parameters;
protected $listeners = ['refreshTasks', 'saveScheduledTask' => 'submit'];
public function mount()
{
$this->parameters = get_route_parameters();
$this->modalId = new Cuid2(7);
}
public function refreshTasks()
{
$this->resource->refresh();
}
public function submit($data)
{
try {
$task = new ScheduledTask();
$task->name = $data['name'];
$task->command = $data['command'];
$task->frequency = $data['frequency'];
$task->container = $data['container'];
$task->team_id = currentTeam()->id;
switch ($this->resource->type()) {
case 'application':
$task->application_id = $this->resource->id;
break;
case 'standalone-postgresql':
$task->standalone_postgresql_id = $this->resource->id;
break;
case 'service':
$task->service_id = $this->resource->id;
break;
}
$task->save();
$this->refreshTasks();
$this->dispatch('success', 'Scheduled task added successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Livewire\Project\Shared\ScheduledTask;
use Illuminate\Support\Facades\Storage;
use Livewire\Component;
class Executions extends Component
{
public $executions = [];
public $selectedKey;
public function getListeners()
{
return [
"selectTask",
];
}
public function selectTask($key): void
{
if ($key == $this->selectedKey) {
$this->selectedKey = null;
return;
}
$this->selectedKey = $key;
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Livewire\Project\Shared\ScheduledTask;
use App\Models\ScheduledTask as ModelsScheduledTask;
use Livewire\Component;
use App\Models\Application;
use App\Models\Service;
use Visus\Cuid2\Cuid2;
class Show extends Component
{
public $parameters;
public Application|Service $resource;
public ModelsScheduledTask $task;
public ?string $modalId = null;
public string $type;
protected $rules = [
'task.name' => 'required|string',
'task.command' => 'required|string',
'task.frequency' => 'required|string',
'task.container' => 'nullable|string',
];
protected $validationAttributes = [
'name' => 'name',
'command' => 'command',
'frequency' => 'frequency',
'container' => 'container',
];
public function mount()
{
$this->parameters = get_route_parameters();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
}
$this->modalId = new Cuid2(7);
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
}
public function submit()
{
$this->validate();
$this->task->save();
$this->dispatch('success', 'Scheduled task updated successfully.');
$this->dispatch('refreshTasks');
}
public function delete()
{
try {
$this->task->delete();
if ($this->type == 'application') {
return redirect()->route('project.application.configuration', $this->parameters);
}
else {
return redirect()->route('project.service.configuration', $this->parameters);
}
} catch (\Exception $e) {
return handleError($e);
}
}
}

View File

@@ -10,9 +10,11 @@ class Webhooks extends Component
public ?string $deploywebhook = null;
public ?string $githubManualWebhook = null;
public ?string $gitlabManualWebhook = null;
public ?string $bitbucketManualWebhook = null;
protected $rules = [
'resource.manual_webhook_secret_github' => 'nullable|string',
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
'resource.manual_webhook_secret_bitbucket' => 'nullable|string',
];
public function saveSecret()
{
@@ -29,6 +31,7 @@ class Webhooks extends Component
$this->deploywebhook = generateDeployWebhook($this->resource);
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
$this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket');
}
public function render()
{

View File

@@ -26,6 +26,8 @@ class Form extends Component
'server.settings.is_reachable' => 'required',
'server.settings.is_swarm_manager' => 'required|boolean',
'server.settings.is_swarm_worker' => 'required|boolean',
'server.settings.is_build_server' => 'required|boolean',
'server.settings.concurrent_builds' => 'required|integer|min:1',
'wildcard_domain' => 'nullable|url',
];
protected $validationAttributes = [
@@ -38,6 +40,8 @@ class Form extends Component
'server.settings.is_reachable' => 'Is reachable',
'server.settings.is_swarm_manager' => 'Swarm Manager',
'server.settings.is_swarm_worker' => 'Swarm Worker',
'server.settings.is_build_server' => 'Build Server',
'server.settings.concurrent_builds' => 'Concurrent Builds',
];
public function mount()
@@ -76,7 +80,7 @@ class Form extends Component
$this->server->settings->is_usable = true;
$this->server->settings->save();
} else {
$this->dispatch('error', 'Server is not reachable. Please check your connection and configuration.');
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
return;
}
}
@@ -85,12 +89,12 @@ class Form extends Component
try {
$uptime = $this->server->validateConnection();
if (!$uptime) {
$install && $this->dispatch('error', 'Server is not reachable. Please check your connection and configuration.');
$install && $this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
return;
}
$supported_os_type = $this->server->validateOS();
if (!$supported_os_type) {
$install && $this->dispatch('error', 'Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/servers#install-docker-engine-manually">documentation</a>.');
$install && $this->dispatch('error', 'Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.');
return;
}
$dockerInstalled = $this->server->validateDockerEngine();

View File

@@ -25,6 +25,8 @@ class ByIp extends Component
public bool $is_swarm_worker = false;
public $selected_swarm_cluster = null;
public bool $is_build_server = false;
public $swarm_managers = [];
protected $rules = [
'name' => 'required|string',
@@ -34,6 +36,7 @@ class ByIp extends Component
'port' => 'required|integer',
'is_swarm_manager' => 'required|boolean',
'is_swarm_worker' => 'required|boolean',
'is_build_server' => 'required|boolean',
];
protected $validationAttributes = [
'name' => 'Name',
@@ -43,6 +46,7 @@ class ByIp extends Component
'port' => 'Port',
'is_swarm_manager' => 'Swarm Manager',
'is_swarm_worker' => 'Swarm Worker',
'is_build_server' => 'Build Server',
];
public function mount()
@@ -89,8 +93,14 @@ class ByIp extends Component
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
}
$server = Server::create($payload);
$server->settings->is_swarm_manager = $this->is_swarm_manager;
$server->settings->is_swarm_worker = $this->is_swarm_worker;
if ($this->is_build_server) {
$this->is_swarm_manager = false;
$this->is_swarm_worker = false;
} else {
$server->settings->is_swarm_manager = $this->is_swarm_manager;
$server->settings->is_swarm_worker = $this->is_swarm_worker;
}
$server->settings->is_build_server = $this->is_build_server;
$server->settings->save();
$server->addInitialNetwork();
return redirect()->route('server.show', $server->uuid);

View File

@@ -33,7 +33,6 @@ class Proxy extends Component
{
$this->server->proxy = null;
$this->server->save();
$this->dispatch('proxyStatusUpdated');
}
public function select_proxy($proxy_type)

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Server\Proxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Events\ProxyStatusChanged;
use App\Models\Server;
use Livewire\Component;
@@ -14,7 +15,17 @@ class Deploy extends Component
public ?string $currentRoute = null;
public ?string $serverIp = null;
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated', "checkProxy", "startProxy"];
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ProxyStatusChanged" => 'proxyStarted',
'proxyStatusUpdated',
'traefikDashboardAvailable',
'serverRefresh' => 'proxyStatusUpdated',
"checkProxy", "startProxy"
];
}
public function mount()
{
@@ -29,12 +40,22 @@ class Deploy extends Component
{
$this->traefikDashboardAvailable = $data;
}
public function proxyStarted()
{
CheckProxy::run($this->server, true);
$this->dispatch('success', 'Proxy started.');
}
public function proxyStatusUpdated()
{
$this->server->refresh();
}
public function ip()
{
public function restart() {
try {
$this->stop();
$this->dispatch('checkProxy');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function checkProxy()
{
@@ -50,7 +71,7 @@ class Deploy extends Component
{
try {
$activity = StartProxy::run($this->server);
$this->dispatch('newMonitorActivity', $activity->id);
$this->dispatch('newMonitorActivity', $activity->id, ProxyStatusChanged::class);
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -77,6 +98,5 @@ class Deploy extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -39,7 +39,7 @@ class ShowPrivateKey extends Component
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
} else {
$this->dispatch('error', 'Server is not reachable. Please check your connection and private key configuration.');
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/configuration#openssh-server">documentation</a> for further help.');
return;
}
} catch (\Throwable $e) {

View File

@@ -15,6 +15,7 @@ class Configuration extends Component
public bool $do_not_track;
public bool $is_auto_update_enabled;
public bool $is_registration_enabled;
public bool $is_dns_validation_enabled;
public bool $next_channel;
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
protected Server $server;
@@ -24,12 +25,14 @@ class Configuration extends Component
'settings.resale_license' => 'nullable',
'settings.public_port_min' => 'required',
'settings.public_port_max' => 'required',
'settings.custom_dns_servers' => 'nullable',
];
protected $validationAttributes = [
'settings.fqdn' => 'FQDN',
'settings.resale_license' => 'Resale License',
'settings.public_port_min' => 'Public port min',
'settings.public_port_max' => 'Public port max',
'settings.custom_dns_servers' => 'Custom DNS servers',
];
public function mount()
@@ -38,6 +41,7 @@ class Configuration extends Component
$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->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
}
public function instantSave()
@@ -45,6 +49,7 @@ class Configuration extends Component
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
if ($this->next_channel) {
$this->settings->next_channel = false;
$this->next_channel = false;
@@ -63,6 +68,14 @@ class Configuration extends Component
return;
}
$this->validate();
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
return str($dns)->trim()->lower();
});
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
$this->settings->save();
$this->server = Server::findOrFail(0);
$this->setup_instance_fqdn();

View File

@@ -17,7 +17,7 @@ class Change extends Component
public ?bool $preview_deployment_permissions = true;
public $parameters;
public GithubApp $github_app;
public ?GithubApp $github_app;
public string $name;
public bool $is_system_wide;

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Livewire;
use App\Models\Team;
use Livewire\Component;
class TeamSharedVariablesIndex extends Component
{
public Team $team;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$this->team->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'type' => 'team',
'team_id' => currentTeam()->id,
]);
$this->team->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->team = currentTeam();
}
public function render()
{
return view('livewire.team-shared-variables-index');
}
}

View File

@@ -111,6 +111,13 @@ class Application extends BaseModel
}
// End of build packs / deployment types
public function is_github_based(): bool
{
if (data_get($this, 'source')) {
return true;
}
return false;
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {
@@ -263,6 +270,10 @@ class Application extends BaseModel
: explode(',', $this->ports_exposes)
);
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function serviceType()
{
$found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) {
@@ -315,6 +326,11 @@ class Application extends BaseModel
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'like', 'NIXPACKS_%');
}
public function scheduled_tasks(): HasMany
{
return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc');
}
public function private_key()
{
return $this->belongsTo(PrivateKey::class);
@@ -424,9 +440,9 @@ class Application extends BaseModel
}
public function isConfigurationChanged($save = false)
{
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->health_check_path . $this->health_check_port . $this->health_check_host . $this->health_check_method . $this->health_check_return_code . $this->health_check_scheme . $this->health_check_response_text . $this->health_check_interval . $this->health_check_timeout . $this->health_check_retries . $this->health_check_start_period . $this->health_check_enabled . $this->limits_memory . $this->limits_swap . $this->limits_swappiness . $this->limits_reservation . $this->limits_cpus . $this->limits_cpuset . $this->limits_cpu_shares . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
$newConfigHash .= json_encode($this->environment_variables->all());
$newConfigHash .= json_encode($this->environment_variables());
} else {
$newConfigHash .= json_encode($this->environment_variables_preview->all());
}
@@ -637,7 +653,6 @@ class Application extends BaseModel
'mem_swappiness' => $this->limits_memory_swappiness,
'mem_reservation' => $this->limits_memory_reservation,
'cpus' => (float) $this->limits_cpus,
'cpuset' => $this->limits_cpuset,
'cpu_shares' => $this->limits_cpu_shares,
]
],
@@ -649,6 +664,9 @@ class Application extends BaseModel
]
]
];
if (!is_null($this->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->limits_cpuset);
}
if ($server->isSwarm()) {
data_forget($docker_compose, 'services.' . $container_name . '.container_name');
data_forget($docker_compose, 'services.' . $container_name . '.expose');
@@ -796,7 +814,7 @@ class Application extends BaseModel
}
return $git_clone_command;
}
function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null)
function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null)
{
$branch = $this->git_branch;
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
@@ -809,7 +827,6 @@ class Application extends BaseModel
if ($pull_request_id !== 0) {
$pr_branch_name = "pr-{$pull_request_id}-coolify";
}
if ($this->deploymentType() === 'source') {
$source_html_url = data_get($this, 'source.html_url');
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL));
@@ -915,6 +932,34 @@ class Application extends BaseModel
$fullRepoUrl = $customRepository;
$git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}";
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command);
if ($pull_request_id !== 0) {
if ($git_type === 'gitlab') {
$branch = "merge-requests/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
} else if ($git_type === 'github') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
} else if ($git_type === 'bitbucket') {
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
}
}
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
} else {
@@ -995,7 +1040,7 @@ class Application extends BaseModel
if (!$composeFileContent) {
$this->docker_compose_location = $initialDockerComposeLocation;
$this->save();
throw new \Exception("Could not load base compose file from $workdir$composeFile");
throw new \RuntimeException("Could not load base compose file from $workdir$composeFile");
} else {
$this->docker_compose_raw = $composeFileContent;
$this->save();
@@ -1059,7 +1104,7 @@ class Application extends BaseModel
$customLabels = base64_decode($this->custom_labels);
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
ray('custom_labels contains non-ascii characters');
$customLabels = str(implode(",", generateLabelsApplication($this, $preview)))->replace(',', "\n");
$customLabels = str(implode("|", generateLabelsApplication($this, $preview)))->replace("|", "\n");
}
$this->custom_labels = base64_encode($customLabels);
$this->save();

View File

@@ -17,6 +17,9 @@ class Environment extends Model
$this->services()->count() == 0;
}
public function environment_variables() {
return $this->hasMany(SharedEnvironmentVariable::class);
}
public function applications()
{
return $this->hasMany(Application::class);

View File

@@ -15,6 +15,7 @@ class EnvironmentVariable extends Model
'value' => 'encrypted',
'is_build_time' => 'boolean',
];
protected $appends = ['real_value', 'is_shared'];
protected static function booted()
{
@@ -48,24 +49,78 @@ class EnvironmentVariable extends Model
set: fn (?string $value = null) => $this->set_environment_variables($value),
);
}
private function get_environment_variables(?string $environment_variable = null): string|null
public function realValue(): Attribute
{
$resource = null;
if ($this->application_id) {
$resource = Application::find($this->application_id);
} else if ($this->service_id) {
$resource = Service::find($this->service_id);
} else if ($this->database_id) {
$resource = StandalonePostgresql::find($this->database_id);
if (!$resource) {
$resource = StandaloneMysql::find($this->database_id);
if (!$resource) {
$resource = StandaloneRedis::find($this->database_id);
if (!$resource) {
$resource = StandaloneMongodb::find($this->database_id);
if (!$resource) {
$resource = StandaloneMariadb::find($this->database_id);
}
}
}
}
}
return Attribute::make(
get: function () use ($resource) {
return $this->get_real_environment_variables($this->value, $resource);
}
);
}
protected function isShared(): Attribute
{
return Attribute::make(
get: function () {
$type = str($this->value)->after("{{")->before(".")->value;
if (str($this->value)->startsWith('{{' . $type) && str($this->value)->endsWith('}}')) {
return true;
}
return false;
}
);
}
private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null
{
// $team_id = currentTeam()->id;
if (!$environment_variable) {
return null;
}
$environment_variable = trim(decrypt($environment_variable));
if (Str::startsWith($environment_variable, '{{') && Str::endsWith($environment_variable, '}}') && Str::contains($environment_variable, 'global.')) {
$variable = Str::after($environment_variable, 'global.');
$environment_variable = trim($environment_variable);
$type = str($environment_variable)->after("{{")->before(".")->value;
if (str($environment_variable)->startsWith("{{" . $type) && str($environment_variable)->endsWith('}}')) {
$variable = Str::after($environment_variable, "{$type}.");
$variable = Str::before($variable, '}}');
$variable = Str::of($variable)->trim()->value;
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
ray('global env variable');
return $environment_variable;
if ($type === 'environment') {
$id = $resource->environment->id;
} else if ($type === 'project') {
$id = $resource->environment->project->id;
} else {
$id = $resource->team()->id;
}
$environment_variable_found = SharedEnvironmentVariable::where("type", $type)->where('key', $variable)->where('team_id', $resource->team()->id)->where("{$type}_id", $id)->first();
if ($environment_variable_found) {
return $environment_variable_found->value;
}
}
return $environment_variable;
}
private function get_environment_variables(?string $environment_variable = null): string|null
{
if (!$environment_variable) {
return null;
}
return trim(decrypt($environment_variable));
}
private function set_environment_variables(?string $environment_variable = null): string|null
{
@@ -73,6 +128,10 @@ class EnvironmentVariable extends Model
return null;
}
$environment_variable = trim($environment_variable);
$type = str($environment_variable)->after("{{")->before(".")->value;
if (str($environment_variable)->startsWith("{{" . $type) && str($environment_variable)->endsWith('}}')) {
return encrypt((string) str($environment_variable)->replace(' ', ''));
}
return encrypt($environment_variable);
}

View File

@@ -27,7 +27,9 @@ class Project extends BaseModel
$project->settings()->delete();
});
}
public function environment_variables() {
return $this->hasMany(SharedEnvironmentVariable::class);
}
public function environments()
{
return $this->hasMany(Environment::class);

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
class ScheduledTask extends BaseModel
{
protected $guarded = [];
public function service()
{
return $this->belongsTo(Service::class);
}
public function application()
{
return $this->belongsTo(Application::class);
}
public function latest_log(): HasOne
{
return $this->hasOne(ScheduledTaskExecution::class)->latest();
}
public function executions(): HasMany
{
return $this->hasMany(ScheduledTaskExecution::class);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ScheduledTaskExecution extends BaseModel
{
protected $guarded = [];
public function scheduledTask(): BelongsTo
{
return $this->belongsTo(ScheduledTask::class);
}
}

View File

@@ -71,7 +71,7 @@ class Server extends BaseModel
static public function isUsable()
{
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false);
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false);
}
static public function destinationsByServer(string $server_id)
@@ -112,7 +112,7 @@ class Server extends BaseModel
]);
} else {
StandaloneDocker::create([
'name' => 'coolify-overlay',
'name' => 'coolify',
'network' => 'coolify',
'server_id' => $this->id,
]);
@@ -141,6 +141,10 @@ class Server extends BaseModel
{
return $this->ip === 'host.docker.internal' || $this->id === 0;
}
static public function buildServers($teamId)
{
return Server::whereTeamId($teamId)->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_build_server', true);
}
public function skipServer()
{
if ($this->ip === '1.2.3.4') {
@@ -194,7 +198,7 @@ class Server extends BaseModel
foreach ($this->databases() as $database) {
$database->update(['status' => 'exited']);
}
foreach ($this->services() as $service) {
foreach ($this->services()->get() as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
@@ -328,7 +332,7 @@ class Server extends BaseModel
}
public function isProxyShouldRun()
{
if ($this->proxyType() === ProxyTypes::NONE->value) {
if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
return false;
}
// foreach ($this->applications() as $application) {
@@ -358,10 +362,10 @@ class Server extends BaseModel
public function validateOS(): bool | Stringable
{
$os_release = instant_remote_process(['cat /etc/os-release'], $this);
$datas = collect(explode("\n", $os_release));
$releaseLines = collect(explode("\n", $os_release));
$collectedData = collect([]);
foreach ($datas as $data) {
$item = Str::of($data)->trim();
foreach ($releaseLines as $line) {
$item = Str::of($line)->trim();
$collectedData->put($item->before('=')->value(), $item->after('=')->lower()->replace('"', '')->value());
}
$ID = data_get($collectedData, 'ID');
@@ -394,6 +398,8 @@ class Server extends BaseModel
}
public function validateConnection()
{
config()->set('coolify.mux_enabled', false);
$server = Server::find($this->id);
if (!$server) {
return false;
@@ -436,7 +442,7 @@ class Server extends BaseModel
}
$this->settings->is_usable = true;
$this->settings->save();
$this->validateCoolifyNetwork(isSwarm: false);
$this->validateCoolifyNetwork(isSwarm: false, isBuildServer: $this->settings->is_build_server);
return true;
}
public function validateDockerSwarm()
@@ -466,8 +472,11 @@ class Server extends BaseModel
$this->settings->save();
return true;
}
public function validateCoolifyNetwork($isSwarm = false)
public function validateCoolifyNetwork($isSwarm = false, $isBuildServer = false)
{
if ($isBuildServer) {
return;
}
if ($isSwarm) {
return instant_remote_process(["docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true"], $this, false);
} else {

View File

@@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class Service extends BaseModel
{
@@ -17,6 +18,10 @@ class Service extends BaseModel
{
return 'service';
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function extraFields()
{
$fields = collect([]);
@@ -396,6 +401,10 @@ class Service extends BaseModel
}
return null;
}
public function scheduled_tasks(): HasMany
{
return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc');
}
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
@@ -419,7 +428,7 @@ class Service extends BaseModel
$envs = $this->environment_variables()->get();
$commands[] = "rm -f .env || true";
foreach ($envs as $env) {
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
}
if ($envs->count() === 0) {
$commands[] = "touch .env";

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SharedEnvironmentVariable extends Model
{
protected $guarded = [];
protected $casts = [
'key' => 'string',
'value' => 'encrypted',
];
}

View File

@@ -42,6 +42,10 @@ class StandaloneMariadb extends BaseModel
$database->environment_variables()->delete();
});
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {

View File

@@ -45,6 +45,10 @@ class StandaloneMongodb extends BaseModel
$database->environment_variables()->delete();
});
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function isLogDrainEnabled()
{
return data_get($this, 'is_log_drain_enabled', false);

View File

@@ -42,6 +42,10 @@ class StandaloneMysql extends BaseModel
$database->environment_variables()->delete();
});
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {

View File

@@ -74,7 +74,10 @@ class StandalonePostgresql extends BaseModel
);
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function type(): string
{
return 'standalone-postgresql';

View File

@@ -37,6 +37,10 @@ class StandaloneRedis extends BaseModel
$database->environment_variables()->delete();
});
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {

View File

@@ -70,7 +70,9 @@ class Team extends Model implements SendsDiscord, SendsEmail
);
}
public function environment_variables() {
return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id');
}
public function members()
{
return $this->belongsToMany(User::class, 'team_user', 'team_id', 'user_id')->withPivot('role');

View File

@@ -38,7 +38,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
}
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
}
public function via(object $notifiable): array

View File

@@ -38,7 +38,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
}
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
}
public function via(object $notifiable): array

View File

@@ -31,7 +31,7 @@ class StatusChanged extends Notification implements ShouldQueue
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
}
$this->resource_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->resource->uuid}";
$this->resource_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->resource->uuid}";
}
public function via(object $notifiable): array

View File

@@ -14,8 +14,8 @@ class EmailChannel
{
try {
$this->bootConfigs($notifiable);
$recepients = $notifiable->getRecepients($notification);
if (count($recepients) === 0) {
$recipients = $notifiable->getRecepients($notification);
if (count($recipients) === 0) {
throw new Exception('No email recipients found');
}
@@ -24,7 +24,7 @@ class EmailChannel
[],
[],
fn (Message $message) => $message
->to($recepients)
->to($recipients)
->subject($mailMessage->subject)
->html((string)$mailMessage->render())
);
@@ -35,8 +35,8 @@ class EmailChannel
}
ray($e->getMessage());
$message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:";
if (isset($recepients)) {
$message .= implode(', ', $recepients);
if (isset($recipients)) {
$message .= implode(', ', $recipients);
}
if (isset($mailMessage)) {
$message .= " with subject: {$mailMessage->subject}";

View File

@@ -37,12 +37,12 @@ class ContainerStopped extends Notification implements ShouldQueue
public function toDiscord(): string
{
$message = "Coolify: A resource has been stopped unexpectedly on {$this->server->name}";
$message = "Coolify: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}";
return $message;
}
public function toTelegram(): array
{
$message = "Coolify: A resource has been stopped unexpectedly on {$this->server->name}";
$message = "Coolify: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}";
$payload = [
"message" => $message,
];

View File

@@ -52,13 +52,13 @@ class Unreachable extends Notification implements ShouldQueue
public function toDiscord(): string
{
$message = "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.";
$message = "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.";
return $message;
}
public function toTelegram(): array
{
return [
"message" => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."
"message" => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations."
];
}
}

View File

@@ -32,10 +32,11 @@ trait ExecuteRemoteCommand
$hidden = data_get($single_command, 'hidden', false);
$customType = data_get($single_command, 'type');
$ignore_errors = data_get($single_command, 'ignore_errors', false);
$append = data_get($single_command, 'append', true);
$this->save = data_get($single_command, 'save');
$remote_command = generateSshCommand($this->server, $command);
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType) {
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) {
$output = Str::of($output)->trim();
if ($output->startsWith('╔')) {
$output = "\n" . $output;
@@ -59,7 +60,15 @@ trait ExecuteRemoteCommand
$this->application_deployment_queue->save();
if ($this->save) {
$this->saved_outputs[$this->save] = Str::of($output)->trim();
if (data_get($this->saved_outputs, $this->save, null) === null) {
data_set($this->saved_outputs, $this->save, str());
}
if ($append) {
$this->saved_outputs[$this->save] .= str($output)->trim();
$this->saved_outputs[$this->save] = str($this->saved_outputs[$this->save]);
} else {
$this->saved_outputs[$this->save] = str($output)->trim();
}
}
});
$this->application_deployment_queue->update([

View File

@@ -19,7 +19,7 @@ class Checkbox extends Component
public string|null $helper = null,
public string|bool $instantSave = false,
public bool $disabled = false,
public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700"
public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700",
) {
//
}

View File

@@ -19,7 +19,7 @@ class Select extends Component
public string|null $label = null,
public string|null $helper = null,
public bool $required = false,
public string $defaultClass = "select select-sm w-full rounded text-white text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none"
public string $defaultClass = "select select-sm w-full rounded text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none"
) {
//
}

View File

@@ -1,22 +1,28 @@
<?php
use App\Jobs\ApplicationDeployDockerImageJob;
use App\Jobs\ApplicationDeploymentJob;
use App\Jobs\ApplicationDeploymentNewJob;
use App\Jobs\ApplicationDeploySimpleDockerfileJob;
use App\Jobs\ApplicationRestartJob;
use App\Jobs\MultipleApplicationDeploymentJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\Server;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
function queue_application_deployment(int $application_id, 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 $is_new_deployment = false)
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 $is_new_deployment = false)
{
$application_id = $application->id;
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
$deployment_url = $deployment_link->getPath();
$server_id = $application->destination->server->id;
$server_name = $application->destination->server->name;
$deployment = ApplicationDeploymentQueue::create([
'application_id' => $application_id,
'application_name' => $application->name,
'server_id' => $server_id,
'server_name' => $server_name,
'deployment_uuid' => $deployment_uuid,
'deployment_url' => $deployment_url,
'pull_request_id' => $pull_request_id,
'force_rebuild' => $force_rebuild,
'is_webhook' => $is_webhook,
@@ -24,17 +30,21 @@ function queue_application_deployment(int $application_id, string $deployment_uu
'commit' => $commit,
'git_type' => $git_type
]);
$queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at');
$running_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'in_progress')->get()->sortByDesc('created_at');
ray('Q:' . $queued_deployments->count() . 'R:' . $running_deployments->count() . '| Queuing deployment: ' . $deployment_uuid . ' of applicationID: ' . $application_id . ' pull request: ' . $pull_request_id . ' with commit: ' . $commit . ' and is it forced: ' . $force_rebuild);
if ($queued_deployments->count() > 1) {
$queued_deployments = $queued_deployments->skip(1);
$queued_deployments->each(function ($queued_deployment, $key) {
$queued_deployment->status = 'cancelled by system';
$queued_deployment->save();
});
$deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get()->sortByDesc('created_at');
$same_application_deployments = $deployments->where('application_id', $application_id);
$in_progress = $same_application_deployments->filter(function ($value, $key) {
return $value->status === 'in_progress';
});
if ($in_progress->count() > 0) {
return;
}
if ($running_deployments->count() > 0) {
$server = Server::find($server_id);
$concurrent_builds = $server->settings->concurrent_builds;
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}");
if ($deployments->count() > $concurrent_builds) {
return;
}
if ($is_new_deployment) {
@@ -51,7 +61,9 @@ function queue_application_deployment(int $application_id, string $deployment_uu
function queue_next_deployment(Application $application, bool $isNew = false)
{
$next_found = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->first();
$server_id = $application->destination->server_id;
$next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();;
// ray($next_found, $server_id);
if ($next_found) {
if ($isNew) {
dispatch(new ApplicationDeploymentNewJob(
@@ -167,7 +179,6 @@ function generateComposeFile(string $deploymentUuid, Server $server, string $net
'mem_swappiness' => $application->limits_memory_swappiness,
'mem_reservation' => $application->limits_memory_reservation,
'cpus' => (int) $application->limits_cpus,
'cpuset' => $application->limits_cpuset,
'cpu_shares' => $application->limits_cpu_shares,
]
],
@@ -179,6 +190,9 @@ function generateComposeFile(string $deploymentUuid, Server $server, string $net
]
]
];
if (!is_null($application->limits_cpuset)) {
data_set($docker_compose, "services.{$containerName}.cpuset", $application->limits_cpuset);
}
if ($server->isLogDrainEnabled() && $application->isLogDrainEnabled()) {
$docker_compose['services'][$containerName]['logging'] = [
'driver' => 'fluentd',

View File

@@ -215,6 +215,8 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
{
$labels = collect([]);
$labels->push('traefik.enable=true');
$labels->push("traefik.http.middlewares.gzip.compress=true");
$labels->push("traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https");
foreach ($domains as $loop => $domain) {
try {
$uuid = new Cuid2(7);
@@ -233,14 +235,15 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
// Set labels for https
$labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
$labels->push("traefik.http.routers.{$https_label}.entryPoints=https");
$labels->push("traefik.http.routers.{$https_label}.middlewares=gzip");
if ($port) {
$labels->push("traefik.http.routers.{$https_label}.service={$https_label}");
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
}
if ($path !== '/') {
$labels->push("traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix");
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
$labels->push("traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix,gzip");
} else {
$labels->push("traefik.http.routers.{$https_label}.middlewares=gzip");
}
$labels->push("traefik.http.routers.{$https_label}.tls=true");
@@ -260,14 +263,15 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
// Set labels for http
$labels->push("traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
$labels->push("traefik.http.routers.{$http_label}.entryPoints=http");
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
if ($port) {
$labels->push("traefik.http.services.{$http_label}.loadbalancer.server.port=$port");
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
}
if ($path !== '/') {
$labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix");
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
$labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix,gzip");
} else {
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
}
}
} catch (\Throwable $e) {

View File

@@ -69,6 +69,7 @@ function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $m
}
$json = $response->json();
if ($response->failed() && $throwError) {
ray($json);
throw new \Exception("Failed to get data from {$source->name} with error:<br><br>" . $json['message'] . "<br><br>Rate Limit resets at: " . Carbon::parse((int)$response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s') . 'UTC');
}
return [

View File

@@ -103,9 +103,6 @@ function generate_default_proxy_configuration(Server $server)
"traefik.http.routers.traefik.entrypoints=http",
"traefik.http.routers.traefik.service=api@internal",
"traefik.http.services.traefik.loadbalancer.server.port=8080",
// Global Middlewares
"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https",
"traefik.http.middlewares.gzip.compress=true",
];
$config = [
"version" => "3.8",
@@ -198,10 +195,23 @@ function setup_dynamic_configuration()
$traefik_dynamic_conf = [
'http' =>
[
'middlewares' => [
'redirect-to-https' => [
'redirectscheme' => [
'scheme' => 'https',
],
],
'gzip' => [
'compress' => true,
],
],
'routers' =>
[
'coolify-http' =>
[
'middlewares' => [
0 => 'gzip',
],
'entryPoints' => [
0 => 'http',
],
@@ -251,7 +261,7 @@ function setup_dynamic_configuration()
if ($schema === 'https') {
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
0 => 'redirect-to-https@docker',
0 => 'redirect-to-https',
];
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
@@ -288,7 +298,7 @@ function setup_dynamic_configuration()
], $server);
if (config('app.env') == 'local') {
ray($yaml);
// ray($yaml);
}
}
}

View File

@@ -67,7 +67,48 @@ function savePrivateKeyToFs(Server $server)
return $location;
}
function generateSshCommand(Server $server, string $command, bool $isMux = true)
function generateScpCommand(Server $server, string $source, string $dest)
{
$user = $server->user;
$port = $server->port;
$privateKeyLocation = savePrivateKeyToFs($server);
$timeout = config('constants.ssh.command_timeout');
$connectionTimeout = config('constants.ssh.connection_timeout');
$serverInterval = config('constants.ssh.server_interval');
$scp_command = "timeout $timeout scp ";
$scp_command .= "-i {$privateKeyLocation} "
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
. '-o PasswordAuthentication=no '
. "-o ConnectTimeout=$connectionTimeout "
. "-o ServerAliveInterval=$serverInterval "
. '-o RequestTTY=no '
. '-o LogLevel=ERROR '
. "-P {$port} "
. "{$source} "
. "{$user}@{$server->ip}:{$dest}";
return $scp_command;
}
function instant_scp(string $source, string $dest, Server $server, $throwError = true)
{
$timeout = config('constants.ssh.command_timeout');
$scp_command = generateScpCommand($server, $source, $dest);
$process = Process::timeout($timeout)->run($scp_command);
$output = trim($process->output());
$exitCode = $process->exitCode();
if ($exitCode !== 0) {
if (!$throwError) {
return null;
}
return excludeCertainErrors($process->errorOutput(), $exitCode);
}
if ($output === 'null') {
$output = null;
}
return $output;
}
function generateSshCommand(Server $server, string $command)
{
$user = $server->user;
$port = $server->port;
@@ -79,7 +120,7 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true)
$delimiter = 'EOF-COOLIFY-SSH';
$ssh_command = "timeout $timeout ssh ";
if ($isMux && config('coolify.mux_enabled')) {
if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) {
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
}
if (data_get($server, 'settings.is_cloudflare_tunnel')) {

View File

@@ -22,14 +22,11 @@ use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use App\Notifications\Internal\GeneralNotification;
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Illuminate\Database\QueryException;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Route;
@@ -40,6 +37,7 @@ use Visus\Cuid2\Cuid2;
use phpseclib3\Crypt\RSA;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
use PurplePixie\PhpDns\DNSQuery;
function base_configuration_dir(): string
{
@@ -1038,6 +1036,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if (!data_get($service, 'restart')) {
data_set($service, 'restart', RESTART_MODE);
}
if (data_get($service, 'restart') === 'no') {
$savedService->update(['exclude_from_status' => true]);
}
data_set($service, 'container_name', $containerName);
data_forget($service, 'volumes.*.content');
data_forget($service, 'volumes.*.isDirectory');
@@ -1592,3 +1593,70 @@ function getRealtime()
return $envDefined;
}
}
function validate_dns_entry(string $fqdn, Server $server)
{
# https://www.cloudflare.com/ips-v4/#
$cloudflare_ips = collect(['173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13', '172.64.0.0/13', '131.0.72.0/22']);
$url = Url::fromString($fqdn);
$host = $url->getHost();
if (str($host)->contains('sslip.io')) {
return true;
}
$settings = InstanceSettings::get();
$is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled');
if (!$is_dns_validation_enabled) {
return true;
}
$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;
} else {
$ip = $server->ip;
}
$found_matching_ip = false;
$type = \PurplePixie\PhpDns\DNSTypes::NAME_A;
foreach ($dns_servers as $dns_server) {
try {
ray("Checking $host on $dns_server");
$query = new DNSQuery($dns_server);
$results = $query->query($host, $type);
if ($results === false || $query->hasError()) {
ray("Error: " . $query->getLasterror());
} else {
foreach ($results as $result) {
if ($result->getType() == $type) {
if (ip_match($result->getData(), $cloudflare_ips->toArray(), $match)) {
ray("Found match in Cloudflare IPs: $match");
$found_matching_ip = true;
break;
}
if ($result->getData() === $ip) {
ray($host . " has IP address " . $result->getData());
ray($result->getString());
$found_matching_ip = true;
break;
}
}
}
}
} catch (\Exception $e) {
}
}
ray("Found match: $found_matching_ip");
return $found_matching_ip;
}
function ip_match($ip, $cidrs, &$match = null)
{
foreach ((array) $cidrs as $cidr) {
list($subnet, $mask) = explode('/', $cidr);
if (((ip2long($ip) & ($mask = ~((1 << (32 - $mask)) - 1))) == (ip2long($subnet) & $mask))) {
$match = $cidr;
return true;
}
}
return false;
}

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