Compare commits

...

259 Commits

Author SHA1 Message Date
Andras Bacsai
83a3871d43 Merge pull request #2129 from coollabsio/next
v4.0.0-beta.273
2024-05-03 11:42:46 +02:00
Andras Bacsai
8b6023c45a Refactor searchComponent function to use optional chaining and add id attribute in index.blade.php 2024-05-03 11:37:45 +02:00
Andras Bacsai
a401d4e760 Refactor searchComponent function to use optional chaining in index.blade.php 2024-05-03 11:34:45 +02:00
Andras Bacsai
a8520f6593 Refactor server form to dispatch 'proxyStatusUpdated' event when server is unreachable 2024-05-03 11:26:06 +02:00
Andras Bacsai
9a95d207cf fix: add port even if traefik is used 2024-05-03 11:26:00 +02:00
Andras Bacsai
c75d779f85 Fix server form to dispatch 'proxyStatusUpdated' event when server is unreachable 2024-05-03 11:20:42 +02:00
Andras Bacsai
b8f2066408 Merge pull request #2117 from RayBB/vikunja-template
add vikunja service template
2024-05-03 11:18:03 +02:00
Andras Bacsai
cc48b6cd96 Merge pull request #2101 from Geczy/patch-1
Update supabase service yaml to newest fixes
2024-05-03 11:15:59 +02:00
Andras Bacsai
b36c735445 Refactor service-templates.json to update formbricks image origin 2024-05-03 11:14:30 +02:00
Andras Bacsai
5f0f26a3ac Merge pull request #2103 from seu1a/next
fix: formbricks image origin
2024-05-03 11:14:01 +02:00
Andras Bacsai
6b81eaa65d Fix server form to dispatch 'proxyStatusUpdated' event when server is reachable 2024-05-03 10:54:46 +02:00
Andras Bacsai
644f15e80d Refactor server navbar and proxy components 2024-05-03 10:54:44 +02:00
Andras Bacsai
a8ddf4c2df Add unreachable notification when server is unreachable in Server.php 2024-05-03 10:49:12 +02:00
Andras Bacsai
3f9833117e Refactor Server.php to remove unnecessary coolify.yaml from cloud hosted servers 2024-05-03 10:32:04 +02:00
Andras Bacsai
6140d0c849 Refactor ServerStatusJob.php to remove coolify.yaml from cloud hosted servers 2024-05-03 10:31:25 +02:00
Andras Bacsai
f4cb7cea21 fix: better server vlaidation
fix: remove unnecessary coolify.yaml from cloud hosted servers
2024-05-03 10:22:28 +02:00
Andras Bacsai
140ec1f90f Update private key link styling in index.blade.php 2024-05-03 09:43:32 +02:00
Andras Bacsai
31d7220785 Update pricing plan details and add pay-as-you-go option 2024-05-03 09:38:04 +02:00
Andras Bacsai
38e1d36684 Update pricing plan details in pricing-plans.blade.php 2024-05-03 09:36:30 +02:00
Andras Bacsai
6397655f7d Update pricing plan details and add pay-as-you-go option 2024-05-03 09:35:09 +02:00
Matt
821bfe68c1 Merge branch 'next' into patch-1 2024-05-02 10:53:24 -05:00
Matt
596eaa7590 fix realtime 2024-05-02 10:52:55 -05:00
RayBB
08fd0f0fa9 add vikunja service template 2024-05-02 17:10:44 +02:00
Andras Bacsai
77c925ce4b Merge branch 'main' into next 2024-05-02 15:09:17 +02:00
Andras Bacsai
3338352b41 Merge pull request #2115 from coollabsio/andrasbacsai-patch-1
Update service-templates.json
2024-05-02 15:08:53 +02:00
Andras Bacsai
e3aa4143c7 Update service-templates.json 2024-05-02 15:08:37 +02:00
Andras Bacsai
0a93808076 Update Gitea volume path in gitea.yaml 2024-05-02 15:06:40 +02:00
Andras Bacsai
0cc21f9b99 Update subscription page title to "Subscriptions" 2024-05-02 14:27:11 +02:00
Andras Bacsai
d3543ea291 Update pricing plan details in pricing-plans.blade.php 2024-05-02 14:26:31 +02:00
Andras Bacsai
e824f7a28c Update version numbers to 4.0.0-beta.273 2024-05-02 14:26:27 +02:00
Andras Bacsai
56be0744f0 Merge pull request #2100 from coollabsio/next
v4.0.0-beta.272
2024-05-02 13:20:14 +02:00
Andras Bacsai
d874c71e0b Update pricing plan details in pricing-plans.blade.php 2024-05-02 13:01:10 +02:00
이슬아
487177af87 Merge branch 'next' into next 2024-05-02 19:33:39 +09:00
Andras Bacsai
0452a4e1ac feat: the final pricing plan, pay-as-you-go 2024-05-02 12:27:04 +02:00
Andras Bacsai
959a03214a fix: mongo 4.0 db backup 2024-05-02 11:45:53 +02:00
Andras Bacsai
fe22dfc531 fix: get logs with non-root user 2024-05-02 11:06:12 +02:00
Andras Bacsai
139e258664 ui: update resource operations view 2024-05-02 09:49:10 +02:00
Andras Bacsai
d1ab14966b fix: able to update source path for predefined volumes 2024-05-02 09:28:29 +02:00
이슬아
40ff3c731e Merge branch 'next' into next 2024-05-02 16:25:26 +09:00
Andras Bacsai
75d77a3648 fix: make s3 name and endpoint required 2024-05-02 09:21:54 +02:00
이슬아
921da7242d fix: formbricks image origin 2024-05-01 10:44:58 +09:00
Matt
67c9937e67 Merge branch 'next' into patch-1 2024-04-30 13:43:08 -05:00
Matt
bdcb3af6fa Update supabase.yaml 2024-04-30 13:41:08 -05:00
Andras Bacsai
17e81ab6bd Refactor setNotificationChannels method in DeploymentSuccess.php 2024-04-30 15:06:53 +02:00
Andras Bacsai
70e1ec2cd2 Add backoff property to SendMessageToDiscordJob.php 2024-04-30 15:06:49 +02:00
Andras Bacsai
c6a7fd405b Update version numbers to 4.0.0-beta.272 2024-04-30 15:00:57 +02:00
Andras Bacsai
4f0d1704c4 Merge pull request #2097 from coollabsio/next
v4.0.0-beta.271
2024-04-30 11:28:06 +02:00
Andras Bacsai
a966a5097e Merge pull request #2071 from iamEvanYT/mongodb-restore
feat: restore mongodb backup data
2024-04-30 11:21:25 +02:00
Andras Bacsai
080db4d462 Remove dispatch('configurationChanged') from check_status method in Heading.php 2024-04-30 11:20:57 +02:00
Andras Bacsai
4af766162e Fix repository URL handling in PublicGitRepository.php 2024-04-30 11:11:06 +02:00
Andras Bacsai
57d67bc4a8 Fix repository URL handling in PublicGitRepository.php and public-git-repository.blade.php 2024-04-29 13:43:45 +02:00
Andras Bacsai
16278f36ec fix: parse HEALTHCHECK from dockerfile 2024-04-29 13:33:28 +02:00
Andras Bacsai
05c6d67cab fix: respect start period and chekc interval for hc 2024-04-29 12:55:38 +02:00
Andras Bacsai
0c516843d8 Update health check validation rules in HealthChecks.php 2024-04-29 12:55:10 +02:00
Andras Bacsai
d400ac57d5 disable hc by default for some build packs 2024-04-29 12:54:58 +02:00
Andras Bacsai
2644efd9f7 Update input.blade.php and health-checks.blade.php 2024-04-29 12:54:36 +02:00
Andras Bacsai
11baa97ef6 Refactor services.php file to improve variable replacement logic 2024-04-29 12:04:32 +02:00
Andras Bacsai
c36636bd2d Add dispatch('refreshEnvs') to loadComposeFile method 2024-04-29 11:59:19 +02:00
Andras Bacsai
6bb05a6780 refactor backup download 2024-04-29 11:31:50 +02:00
Andras Bacsai
360f5db2cf add private key description 2024-04-29 11:06:06 +02:00
Andras Bacsai
bbbeacee4d Update popup-small.blade.php and configuration-checker.blade.php 2024-04-29 11:05:53 +02:00
Andras Bacsai
94ad56fc96 Add generate_unique_uuid parameter to fqdnLabelsForTraefik function 2024-04-29 10:49:50 +02:00
Andras Bacsai
3df80f2653 fix: autoupdate 2024-04-29 09:57:46 +02:00
Andras Bacsai
bb6c9cf49e fix: backups 2024-04-29 09:38:45 +02:00
Andras Bacsai
ae12222687 Update version to 4.0.0-beta.271 2024-04-29 09:30:18 +02:00
Andras Bacsai
094dfde048 Merge pull request #2090 from coollabsio/next
v4.0.0-beta.270
2024-04-28 11:21:00 +02:00
Andras Bacsai
714b887274 Fix database_name() method to return null instead of "???" 2024-04-28 11:18:37 +02:00
Andras Bacsai
8cfa88eff8 Merge pull request #2084 from iamEvanYT/backup-fix
fix: MongoDB backups failing
2024-04-28 11:18:15 +02:00
Andras Bacsai
3976b57100 fix: mongo db backup 2024-04-28 11:17:20 +02:00
iamEvan
bbcf484f7f Fix MongoDB Backups failing! 2024-04-27 11:22:58 +01:00
Andras Bacsai
249f35f948 Merge pull request #2078 from coollabsio/next
v4.0.0-beta.269
2024-04-26 21:12:57 +02:00
Andras Bacsai
b71f1a79c8 revert: variable parsing 2024-04-26 21:11:40 +02:00
Evan
e4aa3d310f Merge branch 'next' into mongodb-restore 2024-04-26 16:42:40 +02:00
Andras Bacsai
99f57269fb Update version to 4.0.0-beta.269 2024-04-26 15:38:04 +02:00
Andras Bacsai
93def3a557 Merge pull request #2076 from coollabsio/next
v4.0.0-beta.268
2024-04-26 15:19:38 +02:00
Andras Bacsai
a04674d93d Merge pull request #2006 from grahamhealy20/add-mono-font-to-textarea
Text areas use monospaced fonts with spellcheck disabled by default
2024-04-26 15:16:59 +02:00
Andras Bacsai
539cc187a8 Merge pull request #2033 from jere-co/update-service-docs-url
fix: Update service contribution docs URL
2024-04-26 15:15:23 +02:00
Andras Bacsai
fcdd975751 Merge pull request #2028 from duarteocarmo/fix-domains-message
Fix domains example
2024-04-26 15:13:41 +02:00
Andras Bacsai
8a4c2bf208 shared variables are more visible now on the ui 2024-04-26 14:59:03 +02:00
Andras Bacsai
50c5d533b0 Fix condition for displaying environment variable in show.blade.php 2024-04-26 14:13:32 +02:00
Andras Bacsai
f952553c76 fix: move s3 storages to separate view 2024-04-26 14:09:54 +02:00
Andras Bacsai
9a9be466f7 Refactor replaceVariables function in services.php to use a more concise syntax 2024-04-26 13:49:38 +02:00
Andras Bacsai
bbad029aa1 Update version to 4.0.0-beta.268 2024-04-26 12:59:54 +02:00
Andras Bacsai
eb748554c5 Fix environment variable generation in ApplicationDeploymentJob.php and Application.php 2024-04-26 12:59:51 +02:00
Andras Bacsai
c8b494e909 Add Odoo service and template files 2024-04-26 12:05:56 +02:00
Andras Bacsai
bd7fd725f6 Merge pull request #2068 from coollabsio/next
v4.0.0-beta.267
2024-04-26 10:37:47 +02:00
Andras Bacsai
3e0440ba53 feat: initial datalist 2024-04-26 10:33:55 +02:00
Andras Bacsai
83a29f8d85 Refactor parseDockerComposeFile function in shared.php to escape service labels with escapeDollarSign 2024-04-26 09:32:47 +02:00
iamEvan
8551e6e74a opps... wrong commit 2024-04-25 22:47:47 +01:00
iamEvan
3582cb3f46 fix mongodb imports 2024-04-25 22:44:55 +01:00
iamEvan
23de13b82c mac dev docker-compose 2024-04-25 22:44:46 +01:00
Evan
93acd4f18f Merge branch 'coollabsio:main' into mongodb-restore 2024-04-25 23:02:37 +02:00
Andras Bacsai
96883dabe3 fix tags + ui 2024-04-25 15:04:49 +02:00
Andras Bacsai
da521020f6 Fix styling issues and add new badge class in app.css 2024-04-25 14:51:25 +02:00
Andras Bacsai
ac78738061 Fix styling issues in index.blade.php 2024-04-25 14:32:37 +02:00
Andras Bacsai
b78ca71c0f Fix disabling of environment variables starting with SERVICE_FQDN or SERVICE_URL in Show.php 2024-04-25 14:29:23 +02:00
Andras Bacsai
cd7272d00a Fix environment variable generation in ApplicationDeploymentJob.php 2024-04-25 14:15:44 +02:00
Andras Bacsai
cd7489c569 Update version.php to 4.0.0-beta.267 2024-04-25 14:12:43 +02:00
Andras Bacsai
1f3f369df0 Merge branch 'main' into next 2024-04-25 13:55:57 +02:00
Andras Bacsai
911e29f2ea fix: refresh public ips on start 2024-04-25 13:52:52 +02:00
Andras Bacsai
d7b359e432 Fix cleanup of stuck resources in CleanupStuckedResources.php 2024-04-25 13:44:36 +02:00
Andras Bacsai
0b2c92cbe5 able to change storage name and path 2024-04-25 13:37:45 +02:00
Andras Bacsai
f626d964c5 Fix environment variable generation in ApplicationDeploymentJob.php 2024-04-25 13:25:35 +02:00
Andras Bacsai
b0e0b38549 Merge pull request #2037 from stefankommm/fix-branch-name
Fix: Encapsulated branch name when cloning branch
2024-04-25 13:16:06 +02:00
Andras Bacsai
94af4b2c7b Add pull request template with instructions to use 'next' branch as destination 2024-04-25 13:15:29 +02:00
Andras Bacsai
0b235e146f Fix readonly attribute in source.blade.php 2024-04-25 13:10:54 +02:00
Andras Bacsai
40f3301324 Fix remove dropzone script async attribute 2024-04-25 13:10:50 +02:00
Andras Bacsai
4fe90e59a8 Fix formatting issue in select.blade.php 2024-04-25 13:01:40 +02:00
Andras Bacsai
b28611ed08 Fix typo in create-scheduled-backup.blade.php 2024-04-25 12:59:25 +02:00
Andras Bacsai
672ad22e4f fix: n8n template 2024-04-25 12:32:35 +02:00
Andras Bacsai
c17bd5ec3a fix: able to select server when creating new destination 2024-04-25 12:14:02 +02:00
Andras Bacsai
2770755f9d feat: add db name to backup notifications 2024-04-25 12:09:46 +02:00
Andras Bacsai
ee69cdbf7b fix: 500 error on edge case 2024-04-25 12:09:36 +02:00
Andras Bacsai
a72a25640f fix: do no able to delete gh app without deleting resources 2024-04-25 11:53:06 +02:00
Andras Bacsai
e86f8ddd09 fix: caddy network issue
feat: connect compose based apps to predefined network
2024-04-25 11:49:34 +02:00
Andras Bacsai
66bdb39f1a Update version numbers to 4.0.0-beta.267 2024-04-24 18:03:20 +02:00
Graham Healy
aa368c3a63 Merge branch 'next' into add-mono-font-to-textarea 2024-04-24 16:20:23 +01:00
Andras Bacsai
b499a2ab87 Merge pull request #2027 from coollabsio/next
v4.0.0-beta.266
2024-04-24 14:35:04 +02:00
Andras Bacsai
49e4482947 Fix issue with logging environment variables in ApplicationDeploymentJob.php 2024-04-24 14:34:03 +02:00
Andras Bacsai
6e7e4250e6 Fix issue with logging environment variables in ApplicationDeploymentJob.php 2024-04-24 14:33:20 +02:00
Andras Bacsai
5a8ea15c4f Fix issue with saving environment variables in ApplicationDeploymentJob.php 2024-04-24 14:32:34 +02:00
Marek Štefanko
f9aa029e8e Added " " to encapsulate branch name when cloning 2024-04-20 00:10:50 +02:00
Jere Salonen
6653e379b3 feat: Update service contribution docs URL
Updated the URL in the service contribution docs to point to the correct
knowledge base page for adding a new service.
2024-04-19 09:57:46 +02:00
Duarte OC
930a611374 Fix domains example 2024-04-18 18:20:51 +02:00
Andras Bacsai
51e918be6d Fix issue with pulling helper image in Kernel.php and ApplicationDeploymentJob.php 2024-04-18 14:01:35 +02:00
Andras Bacsai
b0170eea8f Update version numbers to 4.0.0-beta.266 2024-04-18 14:01:29 +02:00
Andras Bacsai
144b34ca2e Merge pull request #2025 from coollabsio/next
v4.0.0-beta.265
2024-04-18 13:54:23 +02:00
Andras Bacsai
676c022e41 fix: .env saved to deployment server, not to build server 2024-04-18 13:53:10 +02:00
Andras Bacsai
7c0b98bb70 Fix sorting issue in save_environment_variables() function 2024-04-18 12:37:06 +02:00
Andras Bacsai
7779713392 Fix issue with user validation in Server.php 2024-04-18 11:48:10 +02:00
Andras Bacsai
5e1396025c Fix issue with saving environment variables in ApplicationDeploymentJob.php 2024-04-18 11:41:58 +02:00
Andras Bacsai
d09c88f71c Fix formatting issue in deployment show.blade.php 2024-04-18 11:14:26 +02:00
Andras Bacsai
7980f21b1b Update version numbers to 4.0.0-beta.265 2024-04-18 11:14:23 +02:00
Andras Bacsai
3843994a05 Merge pull request #2022 from coollabsio/next
v4.0.0-beta.264
2024-04-18 06:58:01 +02:00
Andras Bacsai
a1b08ca037 revert .env changes 2024-04-18 06:55:47 +02:00
Andras Bacsai
144cdd11ec Update version numbers to 4.0.0-beta.264 2024-04-18 06:55:40 +02:00
Andras Bacsai
b6531cdb10 Merge pull request #2016 from coollabsio/next
v4.0.0-beta.262
2024-04-17 18:50:54 +02:00
Andras Bacsai
0eef4a5fa1 Fix DNS validation issue and check domain usage 2024-04-17 18:49:31 +02:00
Andras Bacsai
42baaf8f2d Update version numbers to 4.0.0-beta.263 2024-04-17 18:49:25 +02:00
Andras Bacsai
08d9dff8eb Merge pull request #2014 from coollabsio/next
v4.0.0-beta.262
2024-04-17 16:47:14 +02:00
Andras Bacsai
01b3aab9bc fix: $ in labels escaped 2024-04-17 16:46:12 +02:00
Andras Bacsai
fde34ef178 version++ 2024-04-17 16:46:00 +02:00
Andras Bacsai
5195abec94 Update slogans in appsmith.yaml, appwrite.yaml, babybuddy.yaml, and budge.yaml 2024-04-17 15:48:14 +02:00
Andras Bacsai
aa79acd09e Merge pull request #2012 from coollabsio/next
v4.0.0-beta.261
2024-04-17 15:34:44 +02:00
Andras Bacsai
67011ccd72 fix: base64 encode .env 2024-04-17 15:34:31 +02:00
Andras Bacsai
e2cd7fe17e fix: rollback feature 2024-04-17 15:30:08 +02:00
Andras Bacsai
4c0624f489 fix: remove lazy loading until bug figured out 2024-04-17 15:16:08 +02:00
Andras Bacsai
8c033250b1 Update version numbers to 4.0.0-beta.261 2024-04-17 14:27:53 +02:00
Andras Bacsai
05b5a6fddf Update healthcheck URLs to use 127.0.0.1 instead of localhost 2024-04-17 14:27:51 +02:00
Andras Bacsai
df5bde7b8e Merge pull request #2008 from coollabsio/next
v4.0.0-beta.260
2024-04-17 12:52:09 +02:00
Andras Bacsai
ca5f52c48c Add Tolgee service to compose file and service templates 2024-04-17 12:48:11 +02:00
Andras Bacsai
3656adf059 Add Tolgee authentication fields to Service.php 2024-04-17 12:48:04 +02:00
Andras Bacsai
4381792b05 Refactor variable name display in stack-form.blade.php 2024-04-17 12:48:01 +02:00
Andras Bacsai
0a3c20b08a Fix issue with sorting environment variables in StackForm.php 2024-04-17 12:47:57 +02:00
Andras Bacsai
4ab3f41665 Remove debug statement in remoteProcess.php 2024-04-17 12:47:45 +02:00
=
c75ce9cbba Removing extra whitespace 2024-04-17 11:14:29 +01:00
Andras Bacsai
10356a4376 Fix issue with sorting environment variables in ApplicationDeploymentJob.php 2024-04-17 12:13:44 +02:00
=
4fb4e19e99 Using corrrect ternary pattern to generate textarea spellcheck attribute 2024-04-17 11:12:15 +01:00
Andras Bacsai
98c2056f53 Fix issue with saving environment variables and update version numbers 2024-04-17 12:08:35 +02:00
Andras Bacsai
74e8ae4d79 Merge pull request #1982 from coollabsio/next
v4.0.0-beta.259
2024-04-17 11:43:10 +02:00
Andras Bacsai
6b890cf3b3 Update user input label and add experimental note in by-ip.blade.php 2024-04-17 11:41:46 +02:00
Andras Bacsai
d62b471afe Refactor dispatch event names to use snake_case in General.php, FileStorage.php, Storage.php, All.php, and Show.php 2024-04-17 11:32:35 +02:00
Andras Bacsai
b97240963e Fix issue with saving environment variables in ApplicationDeploymentJob.php 2024-04-17 11:23:54 +02:00
Andras Bacsai
55ed0f2374 fix: remove temporary cloudflared config 2024-04-17 11:22:43 +02:00
Andras Bacsai
2c1b3d5790 Update Supabase Studio image version in supabase.yaml 2024-04-17 11:13:23 +02:00
Andras Bacsai
9c3b757578 fix: respect .env file (for default values) 2024-04-17 11:13:20 +02:00
Andras Bacsai
bc04ad96b5 Refactor Redis connection URLs in StandaloneDragonfly.php 2024-04-17 10:54:55 +02:00
Andras Bacsai
35b07a9c18 Refactor code to add sudo prefix for certain commands in multiple files 2024-04-17 10:49:34 +02:00
Andras Bacsai
17955fc419 Refactor error message styling in deployment show.blade.php 2024-04-17 10:49:30 +02:00
Andras Bacsai
ac98f5862a Refactor environment variable helper text in blade files 2024-04-17 10:49:24 +02:00
Andras Bacsai
63758c6679 Remove debug statement in ExecuteRemoteCommand trait 2024-04-16 21:30:28 +02:00
Andras Bacsai
7c765c47e6 Refactor user input in by-ip.blade.php 2024-04-16 21:30:10 +02:00
Andras Bacsai
58523b0000 Refactor code to add sudo prefix for certain commands 2024-04-16 21:22:57 +02:00
Andras Bacsai
41f4b36593 wip: non-root 2024-04-16 20:57:54 +02:00
=
379212b8fe Making text areas use monospaced font and disabling spellcheck by default 2024-04-16 17:29:09 +01:00
Andras Bacsai
a04c7831b1 wip: non-root user for remote servers 2024-04-16 15:42:38 +02:00
Andras Bacsai
1aec16a240 Refactor healthcheck configurations in Docker Compose files 2024-04-16 14:08:11 +02:00
Andras Bacsai
498626748d Update Supabase images to latest versions 2024-04-16 13:18:55 +02:00
Andras Bacsai
f24ec97607 Refactor ActivityMonitor component to fix max height option 2024-04-16 13:18:50 +02:00
Andras Bacsai
f0ad260eab Refactor ActivityMonitor component to add max height option 2024-04-16 13:14:09 +02:00
Andras Bacsai
92ee2be021 Merge pull request #1832 from estubmo/main
Upgrade Appwrite service template to 1.5
2024-04-16 12:56:54 +02:00
Andras Bacsai
02e4528f3e add and fix a lot of services 2024-04-16 12:42:12 +02:00
Andras Bacsai
f871353acc Refactor modal-input.blade.php and navbar.blade.php to improve code readability and maintainability 2024-04-16 12:41:44 +02:00
Andras Bacsai
faa1120e14 Refactor Show.php and docker.php to improve code readability and maintainability 2024-04-16 12:41:33 +02:00
Andras Bacsai
8a8000c80a Add Unleash service configuration to Service model 2024-04-16 12:41:28 +02:00
Andras Bacsai
8164610105 Refactor EditDomain.php and shared.php to improve DockerCompose parsing logic 2024-04-16 12:41:21 +02:00
Andras Bacsai
3935a3c885 Refactor ActivityMonitor component to add fullHeight option 2024-04-16 12:41:13 +02:00
Andras Bacsai
15ec39bc56 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 12:24:57 +02:00
Andras Bacsai
5dd2b4c439 Merge pull request #1990 from Dinip/add-unleash-as-a-service
Add Unleash as a service
2024-04-16 12:24:52 +02:00
Andras Bacsai
1ee05dda4b Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 11:59:01 +02:00
Andras Bacsai
5a86588cb5 Merge pull request #1950 from AmruthPillai/main
Add Reactive Resume as a Service
2024-04-16 11:58:54 +02:00
Andras Bacsai
af3269b362 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 11:47:22 +02:00
Andras Bacsai
a7a4baa06e Merge pull request #1925 from amerkestijn/main
Added ActivePieces as a service
2024-04-16 11:47:14 +02:00
Andras Bacsai
9a497b9bba Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 11:03:00 +02:00
Andras Bacsai
d2a1c449da Merge pull request #1907 from hades200082/services/logto
Added logto.io compose
2024-04-16 11:02:48 +02:00
Andras Bacsai
504524ea56 Refactor stop button in heading.blade.php to use modal confirmation component 2024-04-16 10:54:28 +02:00
Andras Bacsai
51e22b406c Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 10:51:49 +02:00
Andras Bacsai
1ec81482cc Merge pull request #1900 from iamEvanYT/cloudflared
Added Cloudflared as a Service
2024-04-16 10:51:40 +02:00
Andras Bacsai
fd610414fd Update UI styles in resource-view.blade.php, docker-image.blade.php, select.blade.php, and public-git-repository.blade.php 2024-04-16 10:50:28 +02:00
Andras Bacsai
1802d5d5da Add service_type field to Create.php and EditCompose.php 2024-04-16 10:44:23 +02:00
Andras Bacsai
e7e6c76b4e Refactor stop button in heading.blade.php to use modal confirmation component 2024-04-16 10:16:28 +02:00
Andras Bacsai
84c7bd63f5 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 10:12:29 +02:00
Andras Bacsai
f7187bb798 Merge pull request #1899 from kunumigab/main
Added Classicpress as a service
2024-04-16 10:12:20 +02:00
Andras Bacsai
00db5b69ab Update UI styles in resource-view.blade.php, docker-image.blade.php, select.blade.php, and public-git-repository.blade.php 2024-04-16 10:10:08 +02:00
Andras Bacsai
b16109ca56 Fix repository URL handling in PublicGitRepository.php 2024-04-16 10:09:59 +02:00
Andras Bacsai
a00b5aa136 Update .button styles in app.css 2024-04-16 10:09:56 +02:00
Andras Bacsai
0e43e5d295 Refactor General.php to update application fqdn handling 2024-04-16 09:43:42 +02:00
Andras Bacsai
9f1e2d6192 Update stack-form.blade.php to fix conditional check for fields count 2024-04-16 09:23:31 +02:00
Andras Bacsai
52c794a259 Sort containers when loading and mounting in Logs.php 2024-04-16 09:19:30 +02:00
Andras Bacsai
fcf4c5f328 Merge pull request #1828 from hades200082/services/authentik
Added Authentik identity server as a service
2024-04-16 09:07:13 +02:00
Andras Bacsai
85b33a60b3 feat: can edit file/dir volumes from ui in compose based apps 2024-04-15 19:47:17 +02:00
Andras Bacsai
c99bb4cfd7 Refactor StackForm and parseDockerComposeFile 2024-04-15 16:54:03 +02:00
Andras Bacsai
5b0a942b42 fix: make sure that confs when checking if it is changed sorted 2024-04-15 15:45:50 +02:00
Andras Bacsai
d94c9e522c Update SUPABASE_PUBLIC_URL environment variable in supabase.yaml 2024-04-15 15:25:56 +02:00
Andras Bacsai
8fab15abd3 Fix conditional check for existing tags in tags.blade.php 2024-04-15 15:25:54 +02:00
Andras Bacsai
7717304549 Merge pull request #1987 from Nicell/font-weights
Add missing font weights
2024-04-15 14:47:00 +02:00
Andras Bacsai
b3f1cc3edd Fix unnecessary debug statement in LocalFileVolume.php 2024-04-15 14:46:38 +02:00
Andras Bacsai
16f9c727f2 feat: lazy load stuffs + tell user if compose based deployments have missing envs 2024-04-15 14:23:25 +02:00
Andras Bacsai
007ba5fce9 Update advanced options ui 2024-04-15 13:44:31 +02:00
Andras Bacsai
5b36f07493 feat: literal env variables 2024-04-15 12:46:22 +02:00
Andras Bacsai
cbd2580736 Fix SSH command generation in remoteProcess.php 2024-04-15 11:46:23 +02:00
Andras Bacsai
fe872a59ca fix: helper image only pulled if required, not every 10 mins 2024-04-15 11:11:58 +02:00
iamEvan
c4dfd99a8c init commit 2024-04-14 21:31:55 +01:00
Dinip
5a34fe4704 Add unleash service without database 2024-04-14 16:30:29 +01:00
Dinip
2fc3dcce7e Add unleash as a service 2024-04-14 16:21:30 +01:00
Nick Winans
1a932fb749 Add missing font weights 2024-04-14 01:03:58 +02:00
Andras Bacsai
f8a2ee9a8d Update GitHub app styling in project creation form 2024-04-13 21:14:50 +02:00
Andras Bacsai
a972d456ba Update maintenance driver and store in app.php 2024-04-13 21:09:20 +02:00
Andras Bacsai
e9f4c9e687 Update text color in 503 error page 2024-04-13 21:04:38 +02:00
Andras Bacsai
58f7cdc15e Merge pull request #1983 from Emahhh/main
Added a suggestion on an error popup
2024-04-13 16:24:38 +02:00
Andras Bacsai
3f9b0643ee fix: check each required binaries one-by-one 2024-04-13 16:23:44 +02:00
Andras Bacsai
24bd4da94b Update horizon.php and docker-compose.prod.yml 2024-04-13 15:16:57 +02:00
Andras Bacsai
a18b4ffd0f Add allow_promotion_codes option to subscribeStripe method 2024-04-13 14:57:11 +02:00
Eirik Mo
c9e032e350 Update appwrite.yaml 2024-04-13 08:04:49 +02:00
Emanuele B
e1679ad433 Added a suggestion on an error popup 2024-04-13 00:03:36 +02:00
Andras Bacsai
933e395945 feat: dynamic mux time 2024-04-12 23:50:23 +02:00
Andras Bacsai
1f64a00bbf Update version numbers to 4.0.0-beta.259 2024-04-12 23:50:16 +02:00
Andras Bacsai
7eebc0c3a5 Fix bug in login functionality 2024-04-12 23:35:03 +02:00
Andras Bacsai
a2c9dabd77 Ignore service-templates.json in production build workflow 2024-04-12 23:34:44 +02:00
Andras Bacsai
6187c826b4 Merge pull request #1863 from hamanuha/main
Supabase Fixes: Default database name
2024-04-12 23:33:30 +02:00
Andras Bacsai
b8987963b3 Merge pull request #1973 from w3cj/fix-supabase-template
fix #1966: update supabase template
2024-04-12 23:32:24 +02:00
w3cj
0168ef55f0 fix #1966: update supabase template 2024-04-12 07:07:20 -06:00
Amruth Pillai
c3da3499d8 Add "Reactive Resume" to Self-Hostable Services 2024-04-08 16:45:26 +02:00
Eirik Mo
4d8553f959 Merge branch 'next' into main 2024-04-02 22:10:52 +02:00
Eirik Mo
eee1534da7 feat: upgrade Appwrite service template to 1.5 2024-04-02 22:09:39 +02:00
Eirik Mo
38c20f5737 Merge branch 'main' of https://github.com/estubmo/coolify 2024-04-02 21:07:50 +02:00
amerkestijn
ba85c5159d Added the ActivePieces service 2024-03-31 18:44:20 +02:00
amerkestijn
0388c5d5bc Added the ActivePieces service 2024-03-31 18:39:44 +02:00
Evan
3714c80adb Update cloudflared.yaml 2024-03-31 05:53:09 +08:00
Lee Conlin
735c1a23ea Added logto.io compose 2024-03-27 17:17:45 +00:00
iamEvan
88cfc96bd3 Cloudflared Service 2024-03-26 20:34:21 +00:00
Gabriel Peralta
11f389289d Added Classicpress as a service
Wordpress alternative without block editor (Gutenberg), Useful for small sites & lightweight blogs.

PR includes 3 version's with or without database, also logo.svg
2024-03-26 13:58:52 -04:00
Eirik Mo
553c1eacfc Merge branch 'coollabsio:main' into main 2024-03-21 09:39:22 +01:00
Manuel
0032d3cf6c Fix db_name quotes 2024-03-19 11:58:46 +01:00
Manuel
bd81cc1cc4 Use db name parameter for jwt sql 2024-03-19 10:35:50 +01:00
Manuel
bc36fcb722 Supabase Fixes: Default database name 2024-03-18 20:23:50 +01:00
Eirik Mo
11cd553949 Merge branch 'next' into main 2024-03-14 19:21:20 +01:00
Eirik Mo
45a3f82c8d feat: upgrade Appwrite service template to 1.5 2024-03-11 17:57:45 +01:00
Lee Conlin
e9c9a51b8d Added Authentik identity server as a service 2024-03-09 21:50:56 +00:00
254 changed files with 4029 additions and 1668 deletions

1
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1 @@
> Always use `next` branch as destination branch for PRs, not `main`

View File

@@ -3,6 +3,8 @@ name: Production Build (v4)
on:
push:
branches: ["main"]
paths-ignore:
- templates/service-templates.json
env:
REGISTRY: ghcr.io

View File

@@ -30,5 +30,5 @@ Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if y
Mails are caught by Mailpit: `localhost:8025`
## New Service Contribution
Check out the docs [here](https://coolify.io/docs/resources/services/add-service).
Check out the docs [here](https://coolify.io/docs/knowledge-base/add-a-service).

View File

@@ -99,7 +99,7 @@ class StartClickhouse
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -149,9 +149,9 @@ class StartDatabaseProxy
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
instant_remote_process([
"mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
"echo '{$dockerfile_base64}' | base64 -d | tee $configuration_dir/Dockerfile > /dev/null",
"echo '{$nginxconf_base64}' | base64 -d | tee $configuration_dir/nginx.conf > /dev/null",
"echo '{$dockercompose_base64}' | base64 -d | tee $configuration_dir/docker-compose.yaml > /dev/null",
"docker compose --project-directory {$configuration_dir} pull",
"docker compose --project-directory {$configuration_dir} up --build -d",
], $server);

View File

@@ -100,7 +100,7 @@ class StartDragonfly
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -107,7 +107,7 @@ class StartKeydb
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -100,7 +100,7 @@ class StartMariadb
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -171,6 +171,6 @@ class StartMariadb
$filename = 'custom-config.cnf';
$content = $this->database->mariadb_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
}

View File

@@ -116,7 +116,7 @@ class StartMongodb
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -184,13 +184,13 @@ class StartMongodb
$filename = 'mongod.conf';
$content = $this->database->mongo_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
private function add_default_database()
{
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
$content_base64 = base64_encode($content);
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null";
}
}

View File

@@ -100,7 +100,7 @@ class StartMysql
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -171,6 +171,6 @@ class StartMysql
$filename = 'custom-config.cnf';
$content = $this->database->mysql_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
}

View File

@@ -122,7 +122,7 @@ class StartPostgresql
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -197,7 +197,7 @@ class StartPostgresql
$filename = data_get($init_script, 'filename');
$content = data_get($init_script, 'content');
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/{$filename} > /dev/null";
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
}
}
@@ -209,6 +209,6 @@ class StartPostgresql
$filename = 'custom-postgres.conf';
$content = $this->database->postgres_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
}

View File

@@ -111,7 +111,7 @@ class StartRedis
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -16,11 +16,11 @@ class CheckConfiguration
return 'OK';
}
$proxy_path = $server->proxyPath();
$proxy_configuration = instant_remote_process([
$payload = [
"mkdir -p $proxy_path",
"cat $proxy_path/docker-compose.yml",
], $server, false);
];
$proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;

View File

@@ -13,8 +13,9 @@ class CheckProxy
if ($server->proxyType() === 'NONE') {
return false;
}
if (!$server->validateConnection()) {
throw new \Exception("Server Connection Error");
['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
if (!$uptime) {
throw new \Exception($error);
}
if (!$server->isProxyShouldRun()) {
if ($fromUI) {

View File

@@ -23,7 +23,7 @@ class SaveConfiguration
return instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
"echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null",
], $server);
}
}

View File

@@ -30,16 +30,18 @@ class StartProxy
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
"mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'",
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
"docker stack deploy -c docker-compose.yml coolify-proxy",
"echo 'Proxy started successfully.'"
]);
} else {
$caddfile = "import /dynamic/*.caddy";
$commands = $commands->merge([
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
"mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
"echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",

View File

@@ -29,8 +29,9 @@ class ConfigureCloudflared
$config = Yaml::dump($config, 12, 2);
$docker_compose_yml_base64 = base64_encode($config);
$commands = collect([
"mkdir -p /tmp/cloudflared && cd /tmp/cloudflared",
"echo '$docker_compose_yml_base64' | base64 -d > docker-compose.yml",
"mkdir -p /tmp/cloudflared",
"cd /tmp/cloudflared",
"echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
"docker compose pull",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
"docker compose up -d --remove-orphans",
@@ -39,6 +40,11 @@ class ConfigureCloudflared
} catch (\Throwable $e) {
ray($e);
throw $e;
} finally {
$commands = collect([
"rm -fr /tmp/cloudflared",
]);
instant_remote_process($commands, $server);
}
}
}

View File

@@ -48,20 +48,28 @@ class InstallDocker
if ($supported_os_type->contains('debian')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || apt-get update -y",
"command -v jq >/dev/null || apt install -y curl wget git jq",
"apt-get update -y",
"command -v curl >/dev/null || apt install -y curl",
"command -v wget >/dev/null || apt install -y wget",
"command -v git >/dev/null || apt install -y git",
"command -v jq >/dev/null || apt install -y jq",
]);
} else if ($supported_os_type->contains('rhel')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || dnf install -y curl wget git jq",
"command -v curl >/dev/null || dnf install -y curl",
"command -v wget >/dev/null || dnf install -y wget",
"command -v git >/dev/null || dnf install -y git",
"command -v jq >/dev/null || dnf install -y jq",
]);
} else if ($supported_os_type->contains('sles')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || zypper update -y",
"command -v jq >/dev/null || zypper install -y curl wget git jq",
"zypper update -y",
"command -v curl >/dev/null || zypper install -y curl",
"command -v wget >/dev/null || zypper install -y wget",
"command -v git >/dev/null || zypper install -y git",
"command -v jq >/dev/null || zypper install -y jq",
]);
} else {
throw new \Exception('Unsupported OS');
@@ -70,10 +78,13 @@ class InstallDocker
"echo 'Installing Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-$(date +\"%Y%m%d-%H%M%S\")\"",
"test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null",
"echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null",
"jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null",
"mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify",
"jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null",
"mv /etc/docker/daemon.json.appended /etc/docker/daemon.json",
"echo 'Restarting Docker Engine...'",
"systemctl enable docker >/dev/null 2>&1 || true",
"systemctl restart docker",

View File

@@ -168,10 +168,10 @@ Files:
$command = [
"echo 'Saving configuration'",
"mkdir -p $config_path",
"echo '{$parsers}' | base64 -d > $parsers_config",
"echo '{$config}' | base64 -d > $fluent_bit_config",
"echo '{$compose}' | base64 -d > $compose_path",
"echo '{$readme}' | base64 -d > $readme_path",
"echo '{$parsers}' | base64 -d | tee $parsers_config > /dev/null",
"echo '{$config}' | base64 -d | tee $fluent_bit_config > /dev/null",
"echo '{$compose}' | base64 -d | tee $compose_path > /dev/null",
"echo '{$readme}' | base64 -d | tee $readme_path > /dev/null",
"test -f $config_path/.env && rm $config_path/.env",
];

View File

@@ -62,7 +62,7 @@ class CleanupStuckedResources extends Command
$keydbs = StandaloneKeydb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($keydbs as $keydb) {
echo "Deleting stuck keydb: {$keydb->name}\n";
$redis->forceDelete();
$keydb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck keydb: {$e->getMessage()}\n";
@@ -71,7 +71,7 @@ class CleanupStuckedResources extends Command
$dragonflies = StandaloneDragonfly::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($dragonflies as $dragonfly) {
echo "Deleting stuck dragonfly: {$dragonfly->name}\n";
$redis->forceDelete();
$dragonfly->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck dragonfly: {$e->getMessage()}\n";
@@ -80,7 +80,7 @@ class CleanupStuckedResources extends Command
$clickhouses = StandaloneClickhouse::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($clickhouses as $clickhouse) {
echo "Deleting stuck clickhouse: {$clickhouse->name}\n";
$redis->forceDelete();
$clickhouse->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck clickhouse: {$e->getMessage()}\n";

View File

@@ -20,6 +20,7 @@ class Init extends Command
public function handle()
{
$this->alive();
get_public_ips();
$full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments');
if ($cleanup_deployments) {
@@ -56,6 +57,7 @@ class Init extends Command
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:stucked-resources');
}
private function restore_coolify_db_backup()
{
try {

View File

@@ -31,7 +31,7 @@ class Kernel extends ConsoleKernel
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
$this->pull_helper_image($schedule);
// $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
} else {
@@ -72,18 +72,18 @@ class Kernel extends ConsoleKernel
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
}
foreach ($containerServers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyTwoMinutes()->onOneServer();
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
if ($server->isLogDrainEnabled()) {
$schedule->job(new CheckLogDrainContainerJob($server))->everyTwoMinutes()->onOneServer();
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
}
}
foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyTwoMinutes()->onOneServer();
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
}
}
private function instance_auto_update($schedule)
{
if (isDev()) {
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();

View File

@@ -56,7 +56,7 @@ class Handler extends ExceptionHandler
{
$this->reportable(function (Throwable $e) {
if (isDev()) {
// return;
return;
}
if ($e instanceof RuntimeException) {
return;

View File

@@ -49,6 +49,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private string $deployment_uuid;
private int $pull_request_id;
private string $commit;
private bool $rollback;
private bool $force_rebuild;
private bool $restart_only;
@@ -94,7 +95,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private ?string $buildTarget = null;
private Collection $saved_outputs;
private ?string $full_healthcheck_url = null;
private bool $custom_healthcheck_found = false;
private string $serverUser = 'root';
private string $serverUserHomeDir = '/root';
@@ -117,6 +117,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
$this->commit = $this->application_deployment_queue->commit;
$this->rollback = $this->application_deployment_queue->rollback;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->restart_only = $this->application_deployment_queue->restart_only;
$this->only_this_server = $this->application_deployment_queue->only_this_server;
@@ -322,7 +323,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->prepare_builder_image();
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > {$this->workdir}{$this->dockerfile_location}")
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null")
],
);
$this->generate_image_names();
@@ -391,7 +392,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true
]);
$this->save_environment_variables();
// Build new container to limit downtime.
@@ -565,7 +566,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"mkdir -p $this->configuration_dir"
],
[
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
"echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
@@ -708,19 +709,82 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function save_environment_variables()
{
$envs = collect([]);
$ports = $this->application->main_port();
if ($this->pull_request_id !== 0) {
$this->env_filename = ".env-pr-$this->pull_request_id";
foreach ($this->application->environment_variables_preview as $env) {
$envs->push($env->key . '=' . $env->real_value);
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
$real_value = '\'' . $real_value . '\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
}
}
$envs->push($env->key . '=' . $real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
$envs->push("HOST=0.0.0.0");
}
// Add SOURCE_COMMIT if not exists
if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
$envs = $envs->sort(function ($a, $b) {
return strpos($a, '$') === false ? -1 : 1;
});
} else {
$this->env_filename = ".env";
foreach ($this->application->environment_variables as $env) {
$envs->push($env->key . '=' . $env->real_value);
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
$real_value = '\'' . $real_value . '\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
}
}
$envs->push($env->key . '=' . $real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
$envs->push("HOST=0.0.0.0");
}
// Add SOURCE_COMMIT if not exists
if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
$envs = $envs->sort(function ($a, $b) {
return strpos($a, '$') === false ? -1 : 1;
});
}
if ($envs->isEmpty()) {
$this->env_filename = null;
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
@@ -728,17 +792,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true
]
);
return;
if ($this->use_build_server) {
$this->server = $this->build_server;
}
} else {
$envs_base64 = base64_encode($envs->implode("\n"));
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null")
],
);
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$this->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
]
);
if ($this->use_build_server) {
$this->server = $this->build_server;
}
}
$envs_base64 = base64_encode($envs->implode("\n"));
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
],
[
"echo '$envs_base64' | base64 -d > $this->configuration_dir/{$this->env_filename}"
]
);
// $this->execute_remote_command([
// executeInDocker($this->deployment_uuid, "cat $this->workdir/.env 2>/dev/null || true"),
// "hidden" => true,
// "save" => "dotenv"
// ]);
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
// $base64_dotenv = base64_encode($this->saved_outputs->get('dotenv')->value());
// $this->execute_remote_command(
// [
// "echo '{$base64_dotenv}' | base64 -d | tee $this->configuration_dir/.env > /dev/null"
// ]
// );
// } else {
// $this->execute_remote_command(
// [
// "command" => "rm -f $this->configuration_dir/.env",
// "hidden" => true,
// "ignore_errors" => true
// ]
// );
// }
}
@@ -804,10 +902,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->server->isSwarm()) {
// Implement healthcheck for swarm
} else {
if ($this->application->isHealthcheckDisabled() && $this->custom_healthcheck_found === false) {
if ($this->application->isHealthcheckDisabled() && $this->application->custom_healthcheck_found === false) {
$this->newVersionIsHealthy = true;
return;
}
if ($this->application->custom_healthcheck_found) {
$this->application_deployment_queue->addLogEntry("Custom healthcheck found, skipping default healthcheck.");
}
// ray('New container name: ', $this->container_name);
if ($this->container_name) {
$counter = 1;
@@ -815,6 +916,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->full_healthcheck_url) {
$this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}");
}
$this->application_deployment_queue->addLogEntry("Waiting for the start period ({$this->application->health_check_start_period} seconds) before starting healthcheck.");
$sleeptime = 0;
while ($sleeptime < $this->application->health_check_start_period) {
Sleep::for(1)->seconds();
$sleeptime++;
}
while ($counter <= $this->application->health_check_retries) {
$this->execute_remote_command(
[
@@ -837,7 +944,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
break;
}
$counter++;
Sleep::for($this->application->health_check_interval)->seconds();
$sleeptime = 0;
while ($sleeptime < $this->application->health_check_interval) {
Sleep::for(1)->seconds();
$sleeptime++;
}
}
}
}
@@ -973,7 +1084,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh")
],
[
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa")
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null")
],
[
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa")
@@ -993,7 +1104,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
}
if ($this->saved_outputs->get('git_commit_sha')) {
if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) {
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
}
}
@@ -1117,15 +1228,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function generate_compose_file()
{
$this->create_workdir();
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
$ports = $this->application->main_port();
$onlyPort = null;
if (count($ports) > 0) {
$onlyPort = $ports[0];
}
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables($ports);
// $environment_variables = $this->generate_environment_variables($ports);
$this->save_environment_variables();
if (data_get($this->application, 'custom_labels')) {
$this->application->parseContainerLabels();
$labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels)));
@@ -1159,18 +1270,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->pull_request_id !== 0) {
$labels = collect(generateLabelsApplication($this->application, $this->preview));
}
$labels = $labels->map(function ($value, $key) {
return escapeDollarSign($value);
});
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
// Check for custom HEALTHCHECK
$this->custom_healthcheck_found = false;
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile_from_repo', "ignore_errors" => true
]);
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
if (str($dockerfile)->contains('HEALTHCHECK')) {
$this->custom_healthcheck_found = true;
}
$this->application->parseHealthcheckFromDockerfile($dockerfile);
}
$docker_compose = [
'version' => '3.8',
@@ -1179,7 +1290,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'image' => $this->production_image_name,
'container_name' => $this->container_name,
'restart' => RESTART_MODE,
'environment' => $environment_variables,
'expose' => $ports,
'networks' => [
$this->destination->network,
@@ -1200,23 +1310,34 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
]
];
if ($this->env_filename) {
$docker_compose['services'][$this->container_name]['env_file'] = [
$this->env_filename
];
}
if (!$this->custom_healthcheck_found) {
$docker_compose['services'][$this->container_name]['healthcheck'] = [
'test' => [
'CMD-SHELL',
$this->generate_healthcheck_commands()
],
'interval' => $this->application->health_check_interval . 's',
'timeout' => $this->application->health_check_timeout . 's',
'retries' => $this->application->health_check_retries,
'start_period' => $this->application->health_check_start_period . 's'
];
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
// } else {
// $docker_compose['services'][$this->container_name]['env_file'] = ['.env'];
// }
// }
// if ($this->env_filename) {
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
// $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename;
// } else {
// $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
// }
// }
if (!is_null($this->env_filename)) {
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
}
$docker_compose['services'][$this->container_name]['healthcheck'] = [
'test' => [
'CMD-SHELL',
$this->generate_healthcheck_commands()
],
'interval' => $this->application->health_check_interval . 's',
'timeout' => $this->application->health_check_timeout . 's',
'retries' => $this->application->health_check_retries,
'start_period' => $this->application->health_check_start_period . 's'
];
if (!is_null($this->application->limits_cpuset)) {
data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset);
}
@@ -1357,8 +1478,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
$this->save_environment_variables();
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), "hidden" => true]);
}
private function generate_local_persistent_volumes()
@@ -1399,7 +1519,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
return $local_persistent_volumes_names;
}
private function generate_environment_variables($ports)
/*private function generate_environment_variables($ports)
{
$environment_variables = collect();
if ($this->pull_request_id === 0) {
@@ -1415,7 +1535,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
foreach ($this->application->nixpacks_environment_variables as $env) {
if ($env->version === '4.0.0-beta.239') {
@@ -1423,7 +1548,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
} else {
foreach ($this->application->runtime_environment_variables_preview as $env) {
@@ -1432,7 +1562,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
if ($env->version === '4.0.0-beta.239') {
@@ -1440,7 +1575,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
}
// Add PORT if not exists, use the first port as default
@@ -1458,15 +1598,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$environment_variables->push("SOURCE_COMMIT=unknown");
}
}
ray($environment_variables->all());
return $environment_variables->all();
}
}*/
private function generate_healthcheck_commands()
{
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
return 'exit 0';
}
// if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
// // TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
// return 'exit 0';
// }
if (!$this->application->health_check_port) {
$health_check_port = $this->application->ports_exposes_array[0];
} else {
@@ -1539,7 +1680,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else {
if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true
@@ -1560,7 +1701,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1595,13 +1736,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile")
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null")
],
[
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null")
],
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1614,7 +1755,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1623,7 +1764,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else {
if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true
@@ -1644,7 +1785,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1762,7 +1903,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"),
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"),
"hidden" => true
]);
}

View File

@@ -47,7 +47,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
$applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($applications as $application) {
@@ -78,6 +77,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if (is_null($containers)) {
return;
}
$containers = format_docker_command_output_to_json($containers);
if ($containerReplicates) {
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
@@ -201,7 +201,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
// Notify user that this container should not be there.
}
}
}
if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0;

View File

@@ -289,7 +289,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
if ($this->backup->save_s3) {
$this->upload_to_s3();
}
$this->team?->notify(new BackupSuccess($this->backup, $this->database));
$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database));
$this->backup_log->update([
'status' => 'success',
'message' => $this->backup_output,
@@ -305,8 +305,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
]);
}
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
throw $e;
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database));
}
}
} catch (\Throwable $e) {
@@ -319,10 +318,15 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
private function backup_standalone_mongodb(string $databaseWithCollections): void
{
try {
ray($this->database->toArray());
$url = $this->database->get_db_url(useInternal: true);
if ($databaseWithCollections === 'all') {
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
if (str($this->database->image)->startsWith('mongo:4.0')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
} else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
}
} else {
if (str($databaseWithCollections)->contains(':')) {
$databaseName = str($databaseWithCollections)->before(':');
@@ -333,9 +337,17 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
}
$commands[] = "mkdir -p " . $this->backup_dir;
if ($collectionsToExclude->count() === 0) {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
if (str($this->database->image)->startsWith('mongo:4.0')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
} else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
}
} else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location";
if (str($this->database->image)->startsWith('mongo:4.0')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location";
} else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location";
}
}
}
$this->backup_output = instant_remote_process($commands, $this->server);

View File

@@ -15,7 +15,8 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncr
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 120;
public $timeout = 600;
public $tries = 1;
public function __construct(private bool $force = false)
{

View File

@@ -20,11 +20,12 @@ class SendMessageToDiscordJob implements ShouldQueue, ShouldBeEncrypted
* @var int
*/
public $tries = 5;
public $backoff = 10;
/**
* The maximum number of unhandled exceptions to allow before failing.
*/
public int $maxExceptions = 3;
public int $maxExceptions = 5;
public function __construct(
public string $text,

View File

@@ -57,7 +57,7 @@ class SendMessageToTelegramJob implements ShouldQueue, ShouldBeEncrypted
}
}
$payload = [
'parse_mode' => 'markdown',
// 'parse_mode' => 'markdown',
'reply_markup' => json_encode([
'inline_keyboard' => [
[...$inlineButtons],

View File

@@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Models\Application;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Bus\Queueable;
@@ -16,11 +17,11 @@ class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public ServiceApplication|ServiceDatabase $service)
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
{
}
public function handle()
{
$this->service->getFilesFromServer(isInit: true);
$this->resource->getFilesFromServer(isInit: true);
}
}

View File

@@ -38,11 +38,12 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function handle()
{
if (!$this->server->isServerReady($this->tries)) {
throw new \RuntimeException('Server is not ready.');
return "Server is not ready yet.";
};
try {
if ($this->server->isFunctional()) {
$this->cleanup(notify: false);
$this->removeCoolifyYaml();
}
} catch (\Throwable $e) {
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
@@ -50,6 +51,16 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
return handleError($e);
}
}
private function removeCoolifyYaml()
{
// This will remote the coolify.yaml file from the server as it is not needed on cloud servers
if (isCloud() && $this->server->id !== 0) {
$file = $this->server->proxyPath() . "/dynamic/coolify.yaml";
return instant_remote_process([
"rm -f $file",
], $this->server, false);
}
}
public function cleanup(bool $notify = false): void
{
$this->disk_usage = $this->server->getDiskUsage();

View File

@@ -13,6 +13,7 @@ class ActivityMonitor extends Component
public $activityId;
public $eventToDispatch = 'activityFinished';
public $isPollingActive = false;
public bool $fullHeight = false;
public bool $showWaiting = false;
protected $activity;

View File

@@ -11,15 +11,17 @@ class NewActivityMonitor extends Component
public ?string $header = null;
public $activityId;
public $eventToDispatch = 'activityFinished';
public $eventData = null;
public $isPollingActive = false;
protected $activity;
protected $listeners = ['newActivityMonitor' => 'newMonitorActivity'];
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished', $eventData = null)
{
$this->activityId = $activityId;
$this->eventToDispatch = $eventToDispatch;
$this->eventData = $eventData;
$this->hydrateActivity();
@@ -55,8 +57,12 @@ class NewActivityMonitor extends Component
}
return;
}
$this->dispatch($this->eventToDispatch);
ray('Dispatched event: ' . $this->eventToDispatch);
if (!is_null($this->eventData)) {
$this->dispatch($this->eventToDispatch, $this->eventData);
} else {
$this->dispatch($this->eventToDispatch);
}
ray('Dispatched event: ' . $this->eventToDispatch . ' with data: ' . $this->eventData);
}
}
}

View File

@@ -27,6 +27,8 @@ class Advanced extends Component
'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required',
'application.settings.gpu_options' => 'string|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.connect_to_docker_network' => 'boolean|required',
];
public function mount() {
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
@@ -54,8 +56,14 @@ class Advanced extends Component
$this->application->settings->is_stripprefix_enabled = $this->is_stripprefix_enabled;
$this->dispatch('resetDefaultLabels', false);
}
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
} else {
$this->application->parseCompose();
}
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->dispatch('configurationChanged');
}
public function submit() {
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
use App\Models\LocalFileVolume;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Component;
@@ -33,7 +34,8 @@ class General extends Component
public $parsedServiceDomains = [];
protected $listeners = [
'resetDefaultLabels'
'resetDefaultLabels',
'configurationChanged' => '$refresh'
];
protected $rules = [
'application.name' => 'required',
@@ -71,7 +73,6 @@ class General extends Component
'application.post_deployment_command' => 'nullable',
'application.post_deployment_command_container' => 'nullable',
'application.settings.is_static' => 'boolean|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.watch_paths' => 'nullable',
];
@@ -107,7 +108,6 @@ 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_static' => 'Is static',
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
'application.settings.is_build_server_enabled' => 'Is build server enabled',
'application.watch_paths' => 'Watch paths',
];
@@ -124,7 +124,7 @@ class General extends Component
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes;
$this->customLabels = $this->application->parseContainerLabels();
$this->customLabels = $this->application->parseContainerLabels();
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
@@ -156,8 +156,37 @@ class General extends Component
return;
}
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
$compose = $this->application->parseCompose();
$services = data_get($compose, 'services');
if ($services) {
$volumes = collect($services)->map(function ($service) {
return data_get($service, 'volumes');
})->flatten()->filter(function ($volume) {
return str($volume)->startsWith('/data/coolify');
})->unique()->values();
foreach ($volumes as $volume) {
$source = Str::of($volume)->before(':');
$target = Str::of($volume)->after(':')->beforeLast(':');
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application)
],
[
'fs_path' => $source,
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application)
]
);
}
}
$this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded');
$this->dispatch('refresh_storages');
$this->dispatch('refreshEnvs');
} catch (\Throwable $e) {
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
@@ -165,7 +194,6 @@ class General extends Component
return handleError($e, $this);
} finally {
$this->initLoadingCompose = false;
}
}
public function generateDomain(string $serviceName)
@@ -184,7 +212,14 @@ class General extends Component
$this->loadComposeFile();
}
}
public function updatedApplicationFqdn() {
public function updatedApplicationFqdn()
{
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return str($domain)->trim()->lower();
});
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$this->resetDefaultLabels();
}
public function updatedApplicationBuildPack()
@@ -302,11 +337,6 @@ class General extends Component
check_domain_usage(resource: $this->application);
}
}
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
} else {
$this->parsedServices = $this->application->parseCompose();
}
}
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();

View File

@@ -39,7 +39,8 @@ class Heading extends Component
}
if ($showNotification) $this->dispatch('success', "Success", "Application status updated.");
$this->dispatch('configurationChanged');
// Removed because it caused flickering
// $this->dispatch('configurationChanged');
}
public function force_deploy_without_cache()

View File

@@ -18,7 +18,6 @@ class Rollback extends Component
{
$this->parameters = get_route_parameters();
}
public function rollbackImage($commit)
{
$deployment_uuid = new Cuid2(7);
@@ -27,6 +26,7 @@ class Rollback extends Component
application: $this->application,
deployment_uuid: $deployment_uuid,
commit: $commit,
rollback: true,
force_rebuild: false,
);
return redirect()->route('project.application.deployment.show', [

View File

@@ -35,11 +35,6 @@ class Execution extends Component
$this->executions = $executions;
$this->s3s = currentTeam()->s3s;
}
public function cleanupFailed()
{
$this->backup->executions()->where('status', 'failed')->delete();
$this->dispatch('refreshBackupExecutions');
}
public function render()
{
return view('livewire.project.database.backup.execution');

View File

@@ -2,9 +2,7 @@
namespace App\Livewire\Project\Database;
use Illuminate\Support\Facades\Storage;
use Livewire\Component;
use Symfony\Component\HttpFoundation\StreamedResponse;
class BackupExecutions extends Component
{
@@ -16,11 +14,15 @@ class BackupExecutions extends Component
$userId = auth()->user()->id;
return [
"echo-private:team.{$userId},BackupCreated" => 'refreshBackupExecutions',
"refreshBackupExecutions",
"deleteBackup"
];
}
public function cleanupFailed()
{
$this->backup->executions()->where('status', 'failed')->delete();
$this->refreshBackupExecutions();
}
public function deleteBackup($exeuctionId)
{
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();

View File

@@ -27,6 +27,7 @@ class Import extends Component
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 string $mongodbRestoreCommand = 'mongorestore --authenticationDatabase=admin --username $MONGO_INITDB_ROOT_USERNAME --password $MONGO_INITDB_ROOT_PASSWORD --uri mongodb://localhost:27017 --gzip --archive=';
public function getListeners()
{
@@ -62,8 +63,7 @@ class Import extends Component
$this->resource->getMorphClass() == 'App\Models\StandaloneRedis' ||
$this->resource->getMorphClass() == 'App\Models\StandaloneKeydb' ||
$this->resource->getMorphClass() == 'App\Models\StandaloneDragonfly' ||
$this->resource->getMorphClass() == 'App\Models\StandaloneClickhouse' ||
$this->resource->getMorphClass() == 'App\Models\StandaloneMongodb'
$this->resource->getMorphClass() == 'App\Models\StandaloneClickhouse'
) {
$this->unsupported = true;
}
@@ -101,6 +101,10 @@ class Import extends Component
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->postgresqlRestoreCommand} {$tmpPath}'";
$this->importCommands[] = "rm {$tmpPath}";
break;
case 'App\Models\StandaloneMongodb':
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mongodbRestoreCommand}{$tmpPath}'";
$this->importCommands[] = "rm {$tmpPath}";
break;
}
$this->importCommands[] = "docker exec {$this->container} sh -c 'rm {$tmpPath}'";

View File

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

View File

@@ -16,28 +16,6 @@ class EnvironmentEdit extends Component
'environment.name' => 'required|min:3|max:255',
'environment.description' => 'nullable|min:3|max:255',
];
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$found = $this->environment->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->environment->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);
$this->environment->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->parameters = get_route_parameters();

View File

@@ -89,9 +89,22 @@ class PublicGitRepository extends Component
public function load_branch()
{
try {
$this->validate([
'repository_url' => 'required|url'
]);
if (str($this->repository_url)->startsWith('git@')) {
$github_instance = str($this->repository_url)->after('git@')->before(':');
$repository = str($this->repository_url)->after(':')->before('.git');
$this->repository_url = 'https://' . str($github_instance) . '/' . $repository;
}
if (
(str($this->repository_url)->startsWith('https://') ||
str($this->repository_url)->startsWith('http://')) &&
!str($this->repository_url)->endsWith('.git') &&
!str($this->repository_url)->contains('github.com')
) {
$this->repository_url = $this->repository_url . '.git';
}
if (str($this->repository_url)->contains('github.com')) {
$this->repository_url = str($this->repository_url)->before('.git')->value();
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -168,7 +181,6 @@ class PublicGitRepository extends Component
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
@@ -181,7 +193,6 @@ class PublicGitRepository extends Component
'name' => generate_application_name($this->git_repository, $this->git_branch),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
@@ -193,7 +204,6 @@ class PublicGitRepository extends Component
];
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;

View File

@@ -70,6 +70,8 @@ CMD ["nginx", "-g", "daemon off;"]
'fqdn' => $fqdn
]);
$application->parseHealthcheckFromDockerfile(dockerfile: collect(str($this->dockerfile)->trim()->explode("\n")), isInit: true);
return redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid,
'environment_name' => $environment->name,

View File

@@ -40,7 +40,7 @@ class Create extends Component
$database = create_standalone_keydb($environment->id, $destination_uuid);
} else if ($type->value() === 'dragonfly') {
$database = create_standalone_dragonfly($environment->id, $destination_uuid);
}else if ($type->value() === 'clickhouse') {
} else if ($type->value() === 'clickhouse') {
$database = create_standalone_clickhouse($environment->id, $destination_uuid);
}
return redirect()->route('project.database.configuration', [
@@ -60,14 +60,19 @@ class Create extends Component
}
if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
$service = Service::create([
$service_payload = [
'name' => "$oneClickServiceName-" . str()->random(10),
'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id,
'service_type' => $oneClickServiceName,
'server_id' => (int) $server_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
];
if ($oneClickServiceName === 'cloudflared') {
data_set($service_payload, 'connect_to_docker_network', true);
}
$service = Service::create($service_payload);
$service->name = "$oneClickServiceName-" . $service->uuid;
$service->save();
if ($oneClickDotEnvs?->count() > 0) {

View File

@@ -13,11 +13,13 @@ class EditCompose extends Component
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
];
public function mount() {
public function mount()
{
$this->service = Service::find($this->serviceId);
}
public function saveEditedCompose() {
public function saveEditedCompose()
{
$this->dispatch('info', "Saving new docker compose...");
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
}

View File

@@ -41,7 +41,7 @@ class EditDomain extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('generateDockerCompose');
$this->application->service->parse();
$this->dispatch('refresh');
$this->dispatch('configurationChanged');
}

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Service;
use App\Models\Application;
use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -12,7 +13,7 @@ use Illuminate\Support\Str;
class FileStorage extends Component
{
public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase|StandaloneClickhouse $resource;
public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource;
public string $fs_path;
public ?string $workdir = null;
@@ -33,6 +34,43 @@ class FileStorage extends Component
$this->fs_path = $this->fileStorage->fs_path;
}
}
public function convertToDirectory() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = true;
$this->fileStorage->content = null;
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
}
}
public function convertToFile() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = false;
$this->fileStorage->content = null;
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
}
}
public function delete() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->delete();
$this->dispatch('success', 'File deleted.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
}
}
public function submit()
{
$original = $this->fileStorage->getOriginal();

View File

@@ -3,12 +3,13 @@
namespace App\Livewire\Project\Service;
use App\Models\Service;
use Illuminate\Support\Collection;
use Livewire\Component;
class StackForm extends Component
{
public Service $service;
public $fields = [];
public Collection $fields;
protected $listeners = ["saveCompose"];
public $rules = [
'service.docker_compose_raw' => 'required',
@@ -20,6 +21,7 @@ class StackForm extends Component
public $validationAttributes = [];
public function mount()
{
$this->fields = collect([]);
$extraFields = $this->service->extraFields();
foreach ($extraFields as $serviceName => $fields) {
foreach ($fields as $fieldKey => $field) {
@@ -27,18 +29,20 @@ class StackForm extends Component
$value = data_get($field, 'value');
$rules = data_get($field, 'rules', 'nullable');
$isPassword = data_get($field, 'isPassword');
$this->fields[$key] = [
$this->fields->put($key, [
"serviceName" => $serviceName,
"key" => $key,
"name" => $fieldKey,
"value" => $value,
"isPassword" => $isPassword,
"rules" => $rules
];
]);
$this->rules["fields.$key.value"] = $rules;
$this->validationAttributes["fields.$key.value"] = $fieldKey;
}
}
$this->fields = $this->fields->sortDesc();
}
public function saveCompose($raw)
{

View File

@@ -7,12 +7,13 @@ use Livewire\Component;
class Storage extends Component
{
protected $listeners = ['addNewVolume'];
public $resource;
public function render()
public function getListeners()
{
return view('livewire.project.service.storage');
return [
'addNewVolume',
'refresh_storages' => '$refresh',
];
}
public function addNewVolume($data)
{
@@ -27,9 +28,13 @@ class Storage extends Component
$this->resource->refresh();
$this->dispatch('success', 'Storage added successfully');
$this->dispatch('clearAddStorage');
$this->dispatch('refreshStorages');
$this->dispatch('refresh_storages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.service.storage');
}
}

View File

@@ -7,11 +7,13 @@ use Livewire\Component;
class Add extends Component
{
public $parameters;
public bool $shared = false;
public bool $is_preview = false;
public string $key;
public ?string $value = null;
public bool $is_build_time = false;
public bool $is_multiline = false;
public bool $is_literal = false;
protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [
@@ -19,12 +21,14 @@ class Add extends Component
'value' => 'nullable',
'is_build_time' => 'required|boolean',
'is_multiline' => 'required|boolean',
'is_literal' => 'required|boolean',
];
protected $validationAttributes = [
'key' => 'key',
'value' => 'value',
'is_build_time' => 'build',
'is_multiline' => 'multiline',
'is_literal' => 'literal',
];
public function mount()
@@ -47,6 +51,7 @@ class Add extends Component
'value' => $this->value,
'is_build_time' => $this->is_build_time,
'is_multiline' => $this->is_multiline,
'is_literal' => $this->is_literal,
'is_preview' => $this->is_preview,
]);
$this->clear();
@@ -58,5 +63,6 @@ class Add extends Component
$this->value = '';
$this->is_build_time = false;
$this->is_multiline = false;
$this->is_literal = false;
}
}

View File

@@ -15,8 +15,10 @@ class All extends Component
public ?string $variables = null;
public ?string $variablesPreview = null;
public string $view = 'normal';
protected $listeners = ['refreshEnvs', 'saveKey' => 'submit'];
protected $listeners = [
'refreshEnvs',
'saveKey' => 'submit',
];
public function mount()
{
$resourceClass = get_class($this->resource);
@@ -161,6 +163,7 @@ class All extends Component
$environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time'];
$environment->is_multiline = $data['is_multiline'];
$environment->is_literal = $data['is_literal'];
$environment->is_preview = $data['is_preview'];
switch ($this->resource->type()) {

View File

@@ -16,12 +16,16 @@ class Show extends Component
public bool $isLocked = false;
public bool $isSharedVariable = false;
public string $type;
protected $listeners = [
"compose_loaded" => '$refresh',
];
protected $rules = [
'env.key' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
'env.is_multiline' => 'required|boolean',
'env.is_literal' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable',
];
@@ -30,6 +34,7 @@ class Show extends Component
'env.value' => 'Value',
'env.is_build_time' => 'Build Time',
'env.is_multiline' => 'Multiline',
'env.is_literal' => 'Literal',
'env.is_shown_once' => 'Shown Once',
];

View File

@@ -91,15 +91,35 @@ class GetLogs extends Component
if ($this->container) {
if ($this->showTimeStamps) {
if ($this->server->isSwarm()) {
$sshCommand = generateSshCommand($this->server, "docker service logs -n {$this->numberOfLines} -t {$this->container}");
$command = "docker service logs -n {$this->numberOfLines} -t {$this->container}";
if ($this->server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
$sshCommand = generateSshCommand($this->server, $command);
} else {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
$command = "docker logs -n {$this->numberOfLines} -t {$this->container}";
if ($this->server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
$sshCommand = generateSshCommand($this->server, $command);
}
} else {
if ($this->server->isSwarm()) {
$sshCommand = generateSshCommand($this->server, "docker service logs -n {$this->numberOfLines} {$this->container}");
$command = "docker service logs -n {$this->numberOfLines} {$this->container}";
if ($this->server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
$sshCommand = generateSshCommand($this->server, $command);
} else {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
$command = "docker logs -n {$this->numberOfLines} {$this->container}";
if ($this->server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
$sshCommand = generateSshCommand($this->server, $command);
}
}
if ($refresh) {

View File

@@ -17,18 +17,17 @@ class HealthChecks extends Component
'resource.health_check_return_code' => 'integer',
'resource.health_check_scheme' => 'string',
'resource.health_check_response_text' => 'nullable|string',
'resource.health_check_interval' => 'integer',
'resource.health_check_timeout' => 'integer',
'resource.health_check_retries' => 'integer',
'resource.health_check_interval' => 'integer|min:1',
'resource.health_check_timeout' => 'integer|min:1',
'resource.health_check_retries' => 'integer|min:1',
'resource.health_check_start_period' => 'integer',
'resource.custom_healthcheck_found' => 'boolean',
];
public function instantSave()
{
$this->resource->save();
$this->dispatch('success', 'Health check updated.');
}
public function submit()
{

View File

@@ -44,7 +44,7 @@ class Logs extends Component
} else {
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
}
$server->containers = $containers;
$server->containers = $containers->sort();
} catch (\Exception $e) {
return handleError($e, $this);
}
@@ -94,6 +94,7 @@ class Logs extends Component
$this->servers = $this->servers->push($this->resource->server);
}
}
$this->containers = $this->containers->sort();
} catch (\Exception $e) {
return handleError($e, $this);
}

View File

@@ -7,10 +7,5 @@ use Livewire\Component;
class All extends Component
{
public $resource;
protected $listeners = ['refreshStorages'];
public function refreshStorages()
{
$this->resource->refresh();
}
protected $listeners = ['refresh_storages' => '$refresh'];
}

View File

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

View File

@@ -76,14 +76,15 @@ class Form extends Component
public function checkLocalhostConnection()
{
$this->submit();
$uptime = $this->server->validateConnection();
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_reachable = true;
$this->server->settings->is_usable = true;
$this->server->settings->save();
$this->dispatch('proxyStatusUpdated');
} else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.');
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error);
return;
}
}

View File

@@ -23,7 +23,8 @@ class Deploy extends Component
'proxyStatusUpdated',
'traefikDashboardAvailable',
'serverRefresh' => 'proxyStatusUpdated',
"checkProxy", "startProxy"
"checkProxy",
"startProxy"
];
}

View File

@@ -68,7 +68,7 @@ class NewDynamicConfiguration extends Component
}
$base64_value = base64_encode($this->value);
instant_remote_process([
"echo '{$base64_value}' | base64 -d > {$file}",
"echo '{$base64_value}' | base64 -d | tee {$file} > /dev/null",
], $this->server);
if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy();

View File

@@ -35,10 +35,11 @@ class ShowPrivateKey extends Component
public function checkConnection()
{
try {
$uptime = $this->server->validateConnection();
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
} else {
ray($error);
$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/knowledge-base/server/openssh">documentation</a> for further help.');
return;
}

View File

@@ -11,7 +11,7 @@ class ValidateAndInstall extends Component
{
public Server $server;
public int $number_of_tries = 0;
public int $max_tries = 1;
public int $max_tries = 3;
public bool $install = true;
public $uptime = null;
public $supported_os_type = null;
@@ -32,9 +32,8 @@ class ValidateAndInstall extends Component
'refresh' => '$refresh',
];
public function init(bool $install = true)
public function init(int $data = 0)
{
$this->install = $install;
$this->uptime = null;
$this->supported_os_type = null;
$this->docker_installed = null;
@@ -42,7 +41,7 @@ class ValidateAndInstall extends Component
$this->docker_compose_installed = null;
$this->proxy_started = null;
$this->error = null;
$this->number_of_tries = 0;
$this->number_of_tries = $data;
if (!$this->ask) {
$this->dispatch('validateConnection');
}
@@ -66,16 +65,15 @@ class ValidateAndInstall extends Component
} else {
$this->proxy_started = true;
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function validateConnection()
{
$this->uptime = $this->server->validateConnection();
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (!$this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.';
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error;
return;
}
$this->dispatch('validateOS');
@@ -99,10 +97,10 @@ class ValidateAndInstall extends Component
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
return;
} else {
if ($this->number_of_tries == 0) {
if ($this->number_of_tries <= $this->max_tries) {
$activity = $this->server->installDocker();
$this->number_of_tries++;
$this->dispatch('newActivityMonitor', $activity->id, 'init');
$this->dispatch('newActivityMonitor', $activity->id, 'init', $this->number_of_tries);
}
return;
}

View File

@@ -69,13 +69,14 @@ class Configuration extends Component
}
$this->validate();
if ($this->settings->is_dns_validation_enabled) {
if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
ray('asdf');
if (!validate_dns_entry($this->settings->fqdn, $this->server)) {
$this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.<br><br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$error_show = true;
}
}
check_domain_usage(domain: $this->settings->fqdn);
if ($this->settings->fqdn) check_domain_usage(domain: $this->settings->fqdn);
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
return str($dns)->trim()->lower();

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Livewire\SharedVariables\Environment;
use App\Models\Project;
use Illuminate\Support\Collection;
use Livewire\Component;
class Index extends Component
{
public Collection $projects;
public function mount() {
$this->projects = Project::ownedByCurrentTeam()->get();
}
public function render()
{
return view('livewire.shared-variables.environment.index');
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Livewire\SharedVariables\Environment;
use App\Models\Application;
use App\Models\Project;
use Livewire\Component;
class Show extends Component
{
public Project $project;
public Application $application;
public $environment;
public array $parameters;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$found = $this->environment->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->environment->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'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();
}
public function render()
{
return view('livewire.shared-variables.environment.show');
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Livewire\SharedVariables;
use Livewire\Component;
class Index extends Component
{
public function render()
{
return view('livewire.shared-variables.index');
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Livewire\SharedVariables\Project;
use App\Models\Project;
use Illuminate\Support\Collection;
use Livewire\Component;
class Index extends Component
{
public Collection $projects;
public function mount() {
$this->projects = Project::ownedByCurrentTeam()->get();
}
public function render()
{
return view('livewire.shared-variables.project.index');
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Livewire\SharedVariables\Project;
use App\Models\Project;
use Livewire\Component;
class Show extends Component
{
public Project $project;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$found = $this->project->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->project->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'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();
if (!$project) {
return redirect()->route('dashboard');
}
$this->project = $project;
}
public function render()
{
return view('livewire.shared-variables.project.show');
}
}

View File

@@ -1,11 +1,11 @@
<?php
namespace App\Livewire;
namespace App\Livewire\SharedVariables\Team;
use App\Models\Team;
use Livewire\Component;
class TeamSharedVariablesIndex extends Component
class Index extends Component
{
public Team $team;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
@@ -21,6 +21,7 @@ class TeamSharedVariablesIndex extends Component
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'type' => 'team',
'team_id' => currentTeam()->id,
]);
@@ -36,6 +37,6 @@ class TeamSharedVariablesIndex extends Component
}
public function render()
{
return view('livewire.team-shared-variables-index');
return view('livewire.shared-variables.team.index');
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Livewire\Team\Storage;
namespace App\Livewire\Storage;
use App\Models\S3Storage;
use Livewire\Component;
@@ -16,13 +16,13 @@ class Create extends Component
public string $endpoint;
public S3Storage $storage;
protected $rules = [
'name' => 'nullable|min:3|max:255',
'name' => 'required|min:3|max:255',
'description' => 'nullable|min:3|max:255',
'region' => 'required|max:255',
'key' => 'required|max:255',
'secret' => 'required|max:255',
'bucket' => 'required|max:255',
'endpoint' => 'nullable|url|max:255',
'endpoint' => 'required|url|max:255',
];
protected $validationAttributes = [
'name' => 'Name',
@@ -65,7 +65,7 @@ class Create extends Component
$this->storage->team_id = currentTeam()->id;
$this->storage->testConnection();
$this->storage->save();
return redirect()->route('team.storage.show', $this->storage->uuid);
return redirect()->route('storage.show', $this->storage->uuid);
} catch (\Throwable $e) {
$this->dispatch('error', 'Failed to create storage.', $e->getMessage());
// return handleError($e, $this);

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Livewire\Team\Storage;
namespace App\Livewire\Storage;
use App\Models\S3Storage;
use Livewire\Component;
@@ -43,7 +43,7 @@ class Form extends Component
{
try {
$this->storage->delete();
return redirect()->route('team.storage.index');
return redirect()->route('storage.index');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Livewire\Team\Storage;
namespace App\Livewire\Storage;
use App\Models\S3Storage;
use Livewire\Component;
@@ -13,6 +13,6 @@ class Index extends Component
}
public function render()
{
return view('livewire.team.storage.index');
return view('livewire.storage.index');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Livewire\Storage;
use App\Models\S3Storage;
use Livewire\Component;
class Show extends Component
{
public $storage = null;
public function mount()
{
$this->storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->first();
if (!$this->storage) {
abort(404);
}
}
public function render()
{
return view('livewire.storage.show');
}
}

View File

@@ -54,6 +54,7 @@ class PricingPlans extends Component
return;
}
$payload = [
'allow_promotion_codes' => true,
'billing_address_collection' => 'required',
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
'line_items' => [[

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Livewire\Tags;
use App\Models\ApplicationDeploymentQueue;
use Livewire\Component;
class Deployments extends Component
{
public $deployments_per_tag_per_server = [];
public $resource_ids = [];
public function render()
{
return view('livewire.tags.deployments');
}
public function get_deployments()
{
try {
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $this->resource_ids)->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
} catch (\Exception $e) {
return handleError($e, $this);
}
}
}

View File

@@ -20,32 +20,22 @@ class Index extends Component
public $webhook = null;
public $deployments_per_tag_per_server = [];
public function updatedTag()
public function tag_updated()
{
if ($this->tag == "") {
return;
}
$tag = $this->tags->where('name', $this->tag)->first();
if (!$tag) {
$this->dispatch('error', "Tag ({$this->tag}) not found.");
$this->tag = "";
return;
}
$this->webhook = generatTagDeployWebhook($tag->name);
$this->applications = $tag->applications()->get();
$this->services = $tag->services()->get();
$this->get_deployments();
}
public function get_deployments()
{
try {
$resource_ids = $this->applications->pluck('id');
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function redeploy_all()
{
try {
@@ -67,7 +57,7 @@ class Index extends Component
{
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
if ($this->tag) {
$this->updatedTag();
$this->tag_updated();
}
}
public function render()

View File

@@ -17,6 +17,6 @@ class Show extends Component
}
public function render()
{
return view('livewire.team.storage.show');
return view('livewire.storage.show');
}
}

View File

@@ -346,6 +346,10 @@ class Application extends BaseModel
}
return null;
}
public function main_port()
{
return $this->settings->is_static ? [80] : $this->ports_exposes_array;
}
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc');
@@ -511,9 +515,9 @@ class Application extends BaseModel
{
$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->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
} else {
$newConfigHash .= json_encode($this->environment_variables_preview->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables_preview->get('value')->sort());
}
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
@@ -579,9 +583,9 @@ class Application extends BaseModel
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
$baseDir = $custom_base_dir ?? $this->generateBaseDir($deployment_uuid);
$commands = collect([]);
$git_clone_command = "git clone -b {$this->git_branch}";
$git_clone_command = "git clone -b \"{$this->git_branch}\"";
if ($only_checkout) {
$git_clone_command = "git clone --no-checkout -b {$this->git_branch}";
$git_clone_command = "git clone --no-checkout -b \"{$this->git_branch}\"";
}
if ($pull_request_id !== 0) {
$pr_branch_name = "pr-{$pull_request_id}-coolify";
@@ -653,13 +657,13 @@ class Application extends BaseModel
if ($exec_in_docker) {
$commands = collect([
executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"),
executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
executeInDocker($deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
]);
} else {
$commands = collect([
"mkdir -p /root/.ssh",
"echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa",
"echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null",
"chmod 600 /root/.ssh/id_rsa",
]);
}
@@ -954,4 +958,56 @@ class Application extends BaseModel
});
return $matches->count() > 0;
}
public function getFilesFromServer(bool $isInit = false)
{
getFilesystemVolumesFromServer($this, $isInit);
}
public function parseHealthcheckFromDockerfile($dockerfile, bool $isInit = false) {
if (str($dockerfile)->contains('HEALTHCHECK') && ($this->isHealthcheckDisabled() || $isInit)) {
$healthcheckCommand = null;
$lines = $dockerfile->toArray();
foreach ($lines as $line) {
$trimmedLine = trim($line);
if (str_starts_with($trimmedLine, 'HEALTHCHECK')) {
$healthcheckCommand .= trim($trimmedLine, '\\ ');
continue;
}
if (isset($healthcheckCommand) && str_contains($trimmedLine, '\\')) {
$healthcheckCommand .= ' ' . trim($trimmedLine, '\\ ');
}
if (isset($healthcheckCommand) && !str_contains($trimmedLine, '\\') && !empty($healthcheckCommand)) {
$healthcheckCommand .= ' ' . $trimmedLine;
break;
}
}
if (str($healthcheckCommand)->isNotEmpty()) {
$interval = str($healthcheckCommand)->match('/--interval=(\d+)/');
$timeout = str($healthcheckCommand)->match('/--timeout=(\d+)/');
$start_period = str($healthcheckCommand)->match('/--start-period=(\d+)/');
$start_interval = str($healthcheckCommand)->match('/--start-interval=(\d+)/');
$retries = str($healthcheckCommand)->match('/--retries=(\d+)/');
if ($interval->isNotEmpty()) {
$this->health_check_interval = $interval->toInteger();
}
if ($timeout->isNotEmpty()) {
$this->health_check_timeout = $timeout->toInteger();
}
if ($start_period->isNotEmpty()) {
$this->health_check_start_period = $start_period->toInteger();
}
// if ($start_interval) {
// $this->health_check_start_interval = $start_interval->value();
// }
if ($retries->isNotEmpty()) {
$this->health_check_retries = $retries->toInteger();
}
if ($interval || $timeout || $start_period || $start_interval || $retries) {
$this->custom_healthcheck_found = true;
$this->save();
}
}
}
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class EnvironmentVariable extends Model
{
@@ -81,6 +82,44 @@ class EnvironmentVariable extends Model
}
);
}
protected function isFoundInCompose(): Attribute
{
return Attribute::make(
get: function () {
if (!$this->application_id) {
return true;
}
$found_in_compose = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (!$compose) {
return true;
}
$yaml = Yaml::parse($compose);
$services = collect(data_get($yaml, 'services'));
if ($services->isEmpty()) {
return false;
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
if ($environments->isEmpty()) {
$found_in_compose = false;
break;
}
$found_in_compose = $environments->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_compose) {
break;
}
}
return $found_in_compose;
}
);
}
protected function isShared(): Attribute
{
return Attribute::make(

View File

@@ -20,6 +20,26 @@ class LocalFileVolume extends BaseModel
{
return $this->morphTo('resource');
}
public function deleteStorageOnServer()
{
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
} else {
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
$commands = collect([
"cd $workdir"
]);
$fs_path = data_get($this, 'fs_path');
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
$commands->push("rm -rf $fs_path");
}
ray($commands);
return instant_remote_process($commands, $server);
}
public function saveStorageOnServer()
{
$isService = data_get($this->resource, 'service');
@@ -63,8 +83,7 @@ class LocalFileVolume extends BaseModel
$content = base64_encode($content);
$chmod = $fileVolume->chmod;
$chown = $fileVolume->chown;
ray($content, $path, $chmod, $chown);
$commands->push("echo '$content' | base64 -d > $path");
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
$commands->push("chmod +x $path");
if ($chown) {
$commands->push("chown $chown $path");
@@ -72,7 +91,6 @@ class LocalFileVolume extends BaseModel
if ($chmod) {
$commands->push("chmod $chmod $path");
}
}
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");

View File

@@ -48,7 +48,7 @@ class S3Storage extends BaseModel
if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
$mail = new MailMessage();
$mail->subject('Coolify: S3 Storage Connection Error');
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storage.show', ['storage_uuid' => $this->uuid])]);
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('storage.show', ['storage_uuid' => $this->uuid])]);
$users = collect([]);
$members = $this->team->members()->get();
foreach ($members as $user) {

View File

@@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Sleep;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
use Illuminate\Support\Str;
@@ -141,7 +142,7 @@ respond 404
$base64 = base64_encode($conf);
instant_remote_process([
"mkdir -p $dynamic_conf_path",
"echo '$base64' | base64 -d > $default_redirect_file",
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this);
$this->reloadCaddy();
return;
@@ -223,7 +224,7 @@ respond 404
instant_remote_process([
"mkdir -p $dynamic_conf_path",
"echo '$base64' | base64 -d > $default_redirect_file",
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this);
if (config('app.env') == 'local') {
@@ -239,7 +240,7 @@ respond 404
$dynamic_config_path = $this->proxyPath() . "/dynamic";
if ($this->proxyType() === 'TRAEFIK_V2') {
$file = "$dynamic_config_path/coolify.yaml";
if (empty($settings->fqdn)) {
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0)) {
instant_remote_process([
"rm -f $file",
], $this);
@@ -349,7 +350,7 @@ respond 404
$base64 = base64_encode($yaml);
instant_remote_process([
"mkdir -p $dynamic_config_path",
"echo '$base64' | base64 -d > $file",
"echo '$base64' | base64 -d | tee $file > /dev/null",
], $this);
if (config('app.env') == 'local') {
@@ -358,7 +359,7 @@ respond 404
}
} else if ($this->proxyType() === 'CADDY') {
$file = "$dynamic_config_path/coolify.caddy";
if (empty($settings->fqdn)) {
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0)) {
instant_remote_process([
"rm -f $file",
], $this);
@@ -376,7 +377,7 @@ $schema://$host {
}";
$base64 = base64_encode($caddy_file);
instant_remote_process([
"echo '$base64' | base64 -d > $file",
"echo '$base64' | base64 -d | tee $file > /dev/null",
], $this);
$this->reloadCaddy();
}
@@ -467,63 +468,53 @@ $schema://$host {
if ($this->skipServer()) {
return false;
}
$serverUptimeCheckNumber = $this->unreachable_count;
if ($this->unreachable_count < $tries) {
$serverUptimeCheckNumber = $this->unreachable_count + 1;
}
if ($this->unreachable_count > $tries) {
$serverUptimeCheckNumber = $tries;
}
$serverUptimeCheckNumberMax = $tries;
// ray('server: ' . $this->name);
// ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
// ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
$result = $this->validateConnection();
if ($result) {
if ($this->unreachable_notification_sent === true) {
$this->update(['unreachable_notification_sent' => false]);
}
return true;
} else {
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
// Reached max number of retries
if ($this->unreachable_notification_sent === false) {
ray('Server unreachable, sending notification...');
$this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]);
$checkIteration = 1;
$isServerReady = false;
while ($checkIteration < $tries) {
['uptime' => $uptime] = $this->validateConnection();
if ($uptime) {
if ($this->unreachable_notification_sent === true) {
$this->update(['unreachable_notification_sent' => false]);
}
if ($this->settings->is_reachable === true) {
$this->settings()->update([
'is_reachable' => false,
]);
}
foreach ($this->applications() as $application) {
$application->update(['status' => 'exited']);
}
foreach ($this->databases() as $database) {
$database->update(['status' => 'exited']);
}
foreach ($this->services()->get() as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
$app->update(['status' => 'exited']);
}
foreach ($dbs as $db) {
$db->update(['status' => 'exited']);
}
}
} else {
$this->update([
'unreachable_count' => $this->unreachable_count + 1,
$this->settings()->update([
'is_reachable' => true,
]);
$isServerReady = true;
break;
} else {
ray('Server is not ready yet.');
$checkIteration++;
Sleep::for(10)->seconds();
}
return false;
}
if ($isServerReady) {
return $isServerReady;
}
if ($this->unreachable_notification_sent === false) {
ray('Server unreachable, sending notification...');
$this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]);
}
$this->settings()->update([
'is_reachable' => false,
]);
foreach ($this->applications() as $application) {
$application->update(['status' => 'exited']);
}
foreach ($this->databases() as $database) {
$database->update(['status' => 'exited']);
}
foreach ($this->services()->get() as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
$app->update(['status' => 'exited']);
}
foreach ($dbs as $db) {
$db->update(['status' => 'exited']);
}
}
return false;
}
public function getDiskUsage()
{
@@ -551,7 +542,7 @@ $schema://$host {
public function loadUnmanagedContainers()
{
if ($this->isFunctional()) {
$containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
$containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
$containers = format_docker_command_output_to_json($containers);
$containers = $containers->map(function ($container) {
$labels = data_get($container, 'Labels');
@@ -748,34 +739,36 @@ $schema://$host {
$server = Server::find($this->id);
if (!$server) {
return false;
return ['uptime' => false, 'error' => 'Server not found.'];
}
if ($server->skipServer()) {
return false;
return ['uptime' => false, 'error' => 'Server skipped.'];
}
// EC2 does not have `uptime` command, lol
$uptime = instant_remote_process(['ls /'], $server, false);
if (!$uptime) {
$server->settings()->update([
'is_reachable' => false,
]);
return false;
} else {
try {
// EC2 does not have `uptime` command, lol
instant_remote_process(['ls /'], $server);
$server->settings()->update([
'is_reachable' => true,
]);
$server->update([
'unreachable_count' => 0,
]);
if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]);
}
return ['uptime' => true, 'error' => null];
} catch (\Throwable $e) {
$server->settings()->update([
'is_reachable' => false,
]);
if (data_get($server, 'unreachable_notification_sent') === false) {
ray('Server unreachable, sending notification...');
$this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]);
}
return ['uptime' => false, 'error' => $e->getMessage()];
}
if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]);
}
return true;
}
public function installDocker()
{
@@ -784,7 +777,7 @@ $schema://$host {
}
public function validateDockerEngine($throwError = false)
{
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
$dockerBinary = instant_remote_process(["command -v docker"], $this, false, no_sudo: true);
if (is_null($dockerBinary)) {
$this->settings->is_usable = false;
$this->settings->save();
@@ -861,4 +854,11 @@ $schema://$host {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
}
}
public function isNonRoot()
{
if ($this->user instanceof Stringable) {
return $this->user->value() !== 'root';
}
return $this->user !== 'root';
}
}

View File

@@ -14,20 +14,20 @@ class Service extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$domains = $this->applications()->get()->pluck('fqdn')->toArray();
$domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
$domains = implode(',', $domains);
$applicationImages = $this->applications()->get()->pluck('image');
$databaseImages = $this->databases()->get()->pluck('image');
$applicationImages = $this->applications()->get()->pluck('image')->sort();
$databaseImages = $this->databases()->get()->pluck('image')->sort();
$images = $applicationImages->merge($databaseImages);
$images = implode(',', $images->toArray());
$applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten();
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten();
$applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
$storages = $applicationStorages->merge($databaseStorages)->implode('updated_at');
$newConfigHash = $images . $domains . $images . $storages;
$newConfigHash .= json_encode($this->environment_variables()->get('value'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -150,6 +150,76 @@ class Service extends BaseModel
foreach ($applications as $application) {
$image = str($application->image)->before(':')->value();
switch ($image) {
case str($image)?->contains('tolgee'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
$data = $data->merge([
'Admin User' => [
'key' => 'TOLGEE_AUTHENTICATION_INITIAL_USERNAME',
'value' => 'admin',
'readonly' => true,
'rules' => 'required',
],
]);
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
'key' => 'SERVICE_PASSWORD_TOLGEE',
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Tolgee', $data);
break;
case str($image)?->contains('logto'):
$data = collect([]);
$logto_endpoint = $this->environment_variables()->where('key', 'LOGTO_ENDPOINT')->first();
$logto_admin_endpoint = $this->environment_variables()->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
if ($logto_endpoint) {
$data = $data->merge([
'Endpoint' => [
'key' => data_get($logto_endpoint, 'key'),
'value' => data_get($logto_endpoint, 'value'),
'rules' => 'required|url',
],
]);
}
if ($logto_admin_endpoint) {
$data = $data->merge([
'Admin Endpoint' => [
'key' => data_get($logto_admin_endpoint, 'key'),
'value' => data_get($logto_admin_endpoint, 'value'),
'rules' => 'required|url',
],
]);
}
$fields->put('Logto', $data);
break;
case str($image)?->contains('unleash-server'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UNLEASH')->first();
$data = $data->merge([
'Admin User' => [
'key' => 'SERVICE_USER_UNLEASH',
'value' => 'admin',
'readonly' => true,
'rules' => 'required',
],
]);
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
'key' => 'SERVICE_PASSWORD_UNLEASH',
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Unleash', $data);
break;
case str($image)?->contains('grafana'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
@@ -618,7 +688,7 @@ class Service extends BaseModel
$commands[] = "cd $workdir";
$docker_compose_base64 = base64_encode($this->docker_compose);
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
$commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null";
$envs = $this->environment_variables()->get();
$commands[] = "rm -f .env || true";
foreach ($envs as $env) {

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class SharedEnvironmentVariable extends Model

View File

@@ -45,7 +45,7 @@ class StandaloneClickhouse extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {

View File

@@ -44,7 +44,7 @@ class StandaloneDragonfly extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -167,9 +167,9 @@ class StandaloneDragonfly extends BaseModel
public function get_db_url(bool $useInternal = false): string
{
if ($this->is_public && !$useInternal) {
return "redis://{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
} else {
return "redis://{$this->dragonfly_password}@{$this->uuid}:6379/0";
return "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0";
}
}

View File

@@ -44,7 +44,7 @@ class StandaloneKeydb extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->keydb_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -208,5 +208,4 @@ class StandaloneKeydb extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
}

View File

@@ -46,7 +46,7 @@ class StandaloneMariadb extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->mariadb_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {

View File

@@ -49,7 +49,7 @@ class StandaloneMongodb extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->mongo_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {

View File

@@ -46,7 +46,7 @@ class StandaloneMysql extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->mysql_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {

View File

@@ -58,7 +58,7 @@ class StandalonePostgresql extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->postgres_initdb_args . $this->postgres_host_auth_method;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {

View File

@@ -41,7 +41,7 @@ class StandaloneRedis extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->redis_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {

View File

@@ -69,10 +69,10 @@ class DeploymentFailed extends Notification implements ShouldQueue
public function toDiscord(): string
{
if ($this->preview) {
$message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
$message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . ' (' . $this->preview->fqdn . ') deployment failed: ';
$message .= '[View Deployment Logs](' . $this->deployment_url . ')';
} else {
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
$message = 'Coolify: Deployment failed of ' . $this->application_name . ' (' . $this->fqdn . '): ';
$message .= '[View Deployment Logs](' . $this->deployment_url . ')';
}
return $message;
@@ -80,9 +80,9 @@ class DeploymentFailed extends Notification implements ShouldQueue
public function toTelegram(): array
{
if ($this->preview) {
$message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
$message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . ' (' . $this->preview->fqdn . ') deployment failed: ';
} else {
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
$message = 'Coolify: Deployment failed of ' . $this->application_name . ' (' . $this->fqdn . '): ';
}
$buttons[] = [
"text" => "Deployment logs",

View File

@@ -43,15 +43,8 @@ class DeploymentSuccess extends Notification implements ShouldQueue
public function via(object $notifiable): array
{
$channels = setNotificationChannels($notifiable, 'deployments');
if (isCloud()) {
$channels = array_filter($channels, function ($channel) {
return $channel !== 'App\Notifications\Channels\EmailChannel';
});
}
return $channels;
return setNotificationChannels($notifiable, 'deployments');
}
public function toMail(): MailMessage
{
$mail = new MailMessage();

View File

@@ -15,11 +15,12 @@ class BackupFailed extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 1;
public $backoff = 10;
public $tries = 2;
public string $name;
public string $frequency;
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output)
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output, public $database_name)
{
$this->name = $database->name;
$this->frequency = $backup->frequency;
@@ -27,7 +28,7 @@ class BackupFailed extends Notification implements ShouldQueue
public function via(object $notifiable): array
{
return [DiscordChannel::class, TelegramChannel::class, MailChannel::class];
return setNotificationChannels($notifiable, 'database_backups');
}
public function toMail(): MailMessage
@@ -36,6 +37,7 @@ class BackupFailed extends Notification implements ShouldQueue
$mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
$mail->view('emails.backup-failed', [
'name' => $this->name,
'database_name' => $this->database_name,
'frequency' => $this->frequency,
'output' => $this->output,
]);
@@ -44,11 +46,11 @@ class BackupFailed extends Notification implements ShouldQueue
public function toDiscord(): string
{
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}";
}
public function toTelegram(): array
{
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
$message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}";
return [
"message" => $message,
];

View File

@@ -12,11 +12,12 @@ class BackupSuccess extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 1;
public $backoff = 10;
public $tries = 3;
public string $name;
public string $frequency;
public function __construct(ScheduledDatabaseBackup $backup, public $database)
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $database_name)
{
$this->name = $database->name;
$this->frequency = $backup->frequency;
@@ -33,6 +34,7 @@ class BackupSuccess extends Notification implements ShouldQueue
$mail->subject("Coolify: Backup successfully done for {$this->database->name}");
$mail->view('emails.backup-success', [
'name' => $this->name,
'database_name' => $this->database_name,
'frequency' => $this->frequency,
]);
return $mail;
@@ -40,11 +42,12 @@ class BackupSuccess extends Notification implements ShouldQueue
public function toDiscord(): string
{
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
}
public function toTelegram(): array
{
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
$message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
ray($message);
return [
"message" => $message,
];

View File

@@ -20,9 +20,9 @@ class EventServiceProvider extends ServiceProvider
MaintenanceModeDisabledNotification::class,
],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle',
\SocialiteProviders\Azure\AzureExtendSocialite::class . '@handle',
],
ProxyStarted::class => [
ProxyStarted::class => [
ProxyStartedNotification::class,
],
];

View File

@@ -34,7 +34,13 @@ trait ExecuteRemoteCommand
$ignore_errors = data_get($single_command, 'ignore_errors', false);
$append = data_get($single_command, 'append', true);
$this->save = data_get($single_command, 'save');
if ($this->server->isNonRoot()) {
if (str($command)->startsWith('docker exec')) {
$command = str($command)->replace('docker exec', 'sudo docker exec');
} else {
$command = parseLineForSudo($command, $this->server);
}
}
$remote_command = generateSshCommand($this->server, $command);
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) {
$output = Str::of($output)->trim();

View File

@@ -0,0 +1,38 @@
<?php
namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Str;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
class Datalist extends Component
{
/**
* Create a new component instance.
*/
public function __construct(
public ?string $id = null,
public ?string $name = null,
public ?string $label = null,
public ?string $helper = null,
public bool $required = false,
public string $defaultClass = "input"
) {
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
if (is_null($this->id)) $this->id = new Cuid2(7);
if (is_null($this->name)) $this->name = $this->id;
$this->label = Str::title($this->label);
return view('components.forms.datalist');
}
}

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