Compare commits

...

242 Commits

Author SHA1 Message Date
Andras Bacsai
cc8f09f05e Merge pull request #2422 from coollabsio/next
v4.0.0-beta.296
2024-06-10 12:09:53 +02:00
Andras Bacsai
2beda08717 Refactor CheckLogDrainContainerJob handle method 2024-06-10 11:56:42 +02:00
Andras Bacsai
b455d153ae Update version to 4.0.0-beta.296 2024-06-10 11:56:39 +02:00
Andras Bacsai
5fddf01820 Merge pull request #2355 from coollabsio/next
v4.0.0-beta.295
2024-06-10 11:04:39 +02:00
Andras Bacsai
c80434141d fix: gitlab merge request should close PR 2024-06-10 10:42:52 +02:00
Andras Bacsai
d1128c7a1e fix: multiline variable should be literal + should be multiline in bash with \ 2024-06-09 22:37:23 +02:00
Andras Bacsai
aae81313a6 Refactor TelegramChannel to handle additional notification types 2024-06-09 21:44:13 +02:00
Andras Bacsai
4667f96b40 feat: db proxy logs 2024-06-09 21:33:17 +02:00
Andras Bacsai
28c320ae97 chore: Update install.sh script to version 1.3.2 and handle Linux Mint as Ubuntu 2024-06-09 15:52:23 +02:00
Andras Bacsai
45017efe00 rename migration 2024-06-07 18:16:42 +02:00
Andras Bacsai
a20290cac8 wip: new services based git apps 2024-06-07 17:21:46 +02:00
Andras Bacsai
31e02a154c refactor: Improve handling of Docker volumes in parseDockerComposeFile function 2024-06-07 17:06:27 +02:00
Andras Bacsai
023ee5db99 fix: Set default name for Docker volumes if it is null 2024-06-07 16:55:08 +02:00
Andras Bacsai
05d2e15ab5 update service-templates 2024-06-07 12:28:42 +02:00
Andras Bacsai
7d6590c60a Merge pull request #2347 from tikotzky/patch-1
Add GLITCHTIP_DOMAIN to glitchtip worker service
2024-06-07 12:28:21 +02:00
Andras Bacsai
3152ce183b Merge pull request #2374 from coollabsio/revert-2365-fix-navbar-scroll
Revert "Enhancement: Preserve scroll position in navbar to improve UX"
2024-06-07 12:25:37 +02:00
Andras Bacsai
d9f1a7c4d0 Revert "Enhancement: Preserve scroll position in navbar to improve UX" 2024-06-07 12:25:05 +02:00
Andras Bacsai
952aed3c49 Merge pull request #2365 from avila-gabriel/fix-navbar-scroll
Enhancement: Preserve scroll position in navbar to improve UX
2024-06-07 11:05:55 +02:00
Andras Bacsai
ab3c433450 Merge pull request #2352 from Geczy/patch-2
fix: supabase service, newest versions
2024-06-07 11:04:50 +02:00
Andras Bacsai
2b5e4a34d4 Merge pull request #2364 from TheLazyLemur/main
Add support  to install.sh for PopOS
2024-06-07 11:04:02 +02:00
Andras Bacsai
35cea852ca feat: add titles 2024-06-07 11:01:10 +02:00
Andras Bacsai
88581c8983 Update BUG_REPORT.yml 2024-06-07 10:28:43 +02:00
Andras Bacsai
a7a9aab189 feat: Add bounty program link to bug report template 2024-06-07 10:28:01 +02:00
Andras Bacsai
370c9b63cf fix: post deployment command could fail, but won't make the deployment fail anymore
feat: better error for post deployment command
2024-06-06 15:13:21 +02:00
Andras Bacsai
7cb08849de refactor: Improve pre and post deployment command inputs 2024-06-06 15:11:17 +02:00
Andras Bacsai
7f052163e3 fix: comment id should be string
fix: do not wait for GH response, stop preview before
2024-06-06 12:50:38 +02:00
Andras Bacsai
277d939033 services: rocketchat 2024-06-06 11:39:23 +02:00
Andras Bacsai
26fbdcfab0 Merge pull request #2344 from LEstradioto/feat--add-rocketchat-template
feat: add rocketchat template
2024-06-06 11:37:50 +02:00
Andras Bacsai
6d63ba9d4d chore: Update supported OS list with almalinux 2024-06-06 11:36:51 +02:00
Andras Bacsai
8963f4fd62 refactor: Initialize null properties in Github Change component 2024-06-06 11:10:16 +02:00
Andras Bacsai
463021a9f3 refactor: Remove unused variables and improve code readability 2024-06-06 11:09:27 +02:00
Andras Bacsai
f71a8e9fef fix: sort backup executions 2024-06-06 10:46:19 +02:00
Dan Rousseau
2dd5be1b4e chore: Update install.sh to support PopOS 2024-06-06 09:48:02 +02:00
Gabriel Avila
608838045f Preserve scroll position in navbar after livewire update 2024-06-06 04:14:53 -03:00
Andras Bacsai
899d506faa disable internal notifications on the cloud 2024-06-05 15:34:25 +02:00
Andras Bacsai
21b3e3ea05 refactor: Update deployment previews heading to "Deployments" 2024-06-05 15:32:56 +02:00
Andras Bacsai
a68951541c fix: handle previously defined compose previews 2024-06-05 15:29:00 +02:00
Andras Bacsai
7fd0deedb1 feat: able to add several domains to compose based previews 2024-06-05 15:14:44 +02:00
Andras Bacsai
e9e12ad843 feat: able to change database passwords on the UI. It won't sync to the database. 2024-06-05 11:44:25 +02:00
Andras Bacsai
4fd3185d12 fix: backup executions view 2024-06-05 11:44:10 +02:00
Andras Bacsai
f5ccebfd41 early return 2024-06-05 11:21:02 +02:00
Andras Bacsai
294721eef9 fix: autoupdate process 2024-06-04 21:57:00 +02:00
Andras Bacsai
8af509992d fix: custom docker compose commands, add project dir if needed 2024-06-04 21:26:49 +02:00
Matt
11fccb8e89 fix: supabase service, newest versions 2024-06-04 07:58:40 -05:00
Andras Bacsai
1e126dd2c3 refactor: Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview 2024-06-04 12:59:45 +02:00
Andras Bacsai
cfe2f889a4 refactor: Append utm_source parameter to documentation URL 2024-06-04 12:59:35 +02:00
Andras Bacsai
1bd76b0e07 chore: Update version numbers to 4.0.0-beta.295 2024-06-04 12:23:43 +02:00
Andras Bacsai
6d8c935cc7 Merge pull request #2330 from coollabsio/next
v4.0.0-beta.294
2024-06-04 11:30:11 +02:00
Andras Bacsai
7144cee0f6 chore: Update Dockerfile with latest versions of Docker, Docker Compose, Docker Buildx, Pack, and Nixpacks 2024-06-04 11:27:50 +02:00
Andras Bacsai
f75a8d56f2 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-06-04 11:25:55 +02:00
Andras Bacsai
2f321bcfd9 refactor: Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview 2024-06-04 11:25:53 +02:00
Andras Bacsai
a157f4f17b refactor: Remove commented out code for clearing Ray logs 2024-06-04 11:25:46 +02:00
Andras Bacsai
7723c623d5 fix: check env in args for compose based apps 2024-06-04 11:25:40 +02:00
Luan Estradioto
bc3bb78916 add rocketchat template 2024-06-03 18:12:47 -03:00
Mordy Tikotzky
0ebf5e49fb Add GLITCHTIP_DOMAIN to glitchtip worker service
Without it set on the worker emails send via the worker point to localhost instead of the correct domain.
2024-06-03 15:01:52 -04:00
Andras Bacsai
c340921fbb refactor 2024-06-03 19:12:49 +02:00
Andras Bacsai
30e26b101c Merge pull request #2343 from hamedyosefian/main
Create fa.json
2024-06-03 18:47:17 +02:00
hamedyosefian
1b17cab663 Create fa.json 2024-06-02 20:55:06 +03:30
Andras Bacsai
ab039adf97 remove new deploymentjob 2024-05-31 14:07:09 +02:00
Andras Bacsai
62fe10df31 fix: only ignore volumes with driver_opts 2024-05-31 14:04:17 +02:00
Andras Bacsai
2004a751dd Merge pull request #2199 from ivangsm/sonarqube-template
update sonarqube template
2024-05-31 13:02:32 +02:00
Andras Bacsai
ace127acf4 refactor: Improve display of deployment time in index.blade.php 2024-05-31 13:01:44 +02:00
Andras Bacsai
82c5497a06 Merge pull request #2278 from EstebX/wrong-time-during-a-failed-deployment
fix: wrong time during a failed deployment
2024-05-31 12:57:37 +02:00
Andras Bacsai
dbb7989027 Merge branch 'next' into wrong-time-during-a-failed-deployment 2024-05-31 12:56:20 +02:00
Andras Bacsai
103f677a93 Update StackForm to sort fields by name 2024-05-31 12:55:14 +02:00
Andras Bacsai
cb6bf78595 feat: Add port configuration for Vaultwarden service 2024-05-31 12:54:16 +02:00
Andras Bacsai
62334ddef7 Merge pull request #2293 from iamEvanYT/vaultwarden-fix
Fix Vaultwarden + Add Configs
2024-05-31 12:44:16 +02:00
Andras Bacsai
778f67f2e4 refactor: Update slogan in shlink.yaml 2024-05-31 12:28:56 +02:00
Andras Bacsai
48737f8e60 refactor: Update Docker Compose parsing for services 2024-05-31 12:28:49 +02:00
Andras Bacsai
94de62e503 refactor: Improve Docker Compose parsing for services 2024-05-31 12:09:11 +02:00
Andras Bacsai
0445052898 refactor: Add log entry when starting new application deployment 2024-05-31 12:07:42 +02:00
Andras Bacsai
ccde90ea91 refactor: Update form layout in invite-link.blade.php 2024-05-31 11:20:36 +02:00
Andras Bacsai
ed94355019 default dark mode 2024-05-31 11:20:31 +02:00
Andras Bacsai
02fcd1b5fc refactor: Remove unnecessary form class in profile index.blade.php 2024-05-31 11:14:07 +02:00
Andras Bacsai
ca934e7cdf fix: allow invitations via email 2024-05-31 11:03:43 +02:00
Andras Bacsai
0ddd5f0a79 refactor: Remove unnecessary port appending in updateCompose function 2024-05-31 10:55:08 +02:00
Andras Bacsai
099d6801b7 fix: logto service 2024-05-31 10:55:02 +02:00
Andras Bacsai
62293926ec Merge pull request #2327 from iamEvanYT/service-fqdn-fix
fix: SERVICE_FQDN appending source port
2024-05-31 10:46:37 +02:00
Andras Bacsai
c96daad12c refactor: Improve Docker Compose parsing for services 2024-05-31 10:34:07 +02:00
Andras Bacsai
9cc3be152f fix: compose issues 2024-05-31 10:21:38 +02:00
Andras Bacsai
f841c0d4ba refactor: Update storage form inputs in show.blade.php 2024-05-31 09:59:39 +02:00
Andras Bacsai
3cd1d8135e chore: Update Dockerfile to install vim 2024-05-31 09:41:40 +02:00
Andras Bacsai
86474d9f90 fix: parse docker version better 2024-05-31 09:41:34 +02:00
Andras Bacsai
9bf87a3033 chore: Add Lightspeed.run as a sponsor 2024-05-31 08:45:17 +02:00
iamEvan
71e32520cf fix: SERVICE_FQDN has source port in it 2024-05-30 20:05:44 +01:00
Andras Bacsai
e3e938c8eb refactor: Remove unnecessary logging statements from UpdateCoolify 2024-05-30 20:32:07 +02:00
Andras Bacsai
03aa440424 chore: Update version numbers to 4.0.0-beta.294 2024-05-30 20:31:47 +02:00
Andras Bacsai
85ca38be90 Merge pull request #2325 from coollabsio/next
v4.0.0-beta.293
2024-05-30 20:08:37 +02:00
Andras Bacsai
7c9790dff0 chore: Improve upgrade.blade.php with clearer instructions and formatting 2024-05-30 20:07:42 +02:00
Andras Bacsai
40a71a11cb chore: Add upgrade guide link to upgrade.blade.php 2024-05-30 20:07:06 +02:00
Andras Bacsai
46a500f5e5 async update process 2024-05-30 20:02:11 +02:00
Andras Bacsai
2d1d03bf8e chore: Update version numbers to 4.0.0-beta.293 2024-05-30 20:02:03 +02:00
Andras Bacsai
1092d00c7a Merge pull request #2324 from coollabsio/next
v4.0.0-beta.292
2024-05-30 19:46:11 +02:00
Andras Bacsai
cd58e0d01e fixes 2024-05-30 19:45:36 +02:00
Andras Bacsai
30a9783348 feat: Add manual update option to UpdateCoolify handle method 2024-05-30 19:38:33 +02:00
Andras Bacsai
c839cf50af fix: spamming :D 2024-05-30 19:35:44 +02:00
Andras Bacsai
f8d607b06f chore: Update version numbers to 4.0.0-beta.292 2024-05-30 19:35:38 +02:00
Andras Bacsai
cfadeb07b1 Merge pull request #2317 from coollabsio/next
v4.0.0-beta.291
2024-05-30 13:06:30 +02:00
Andras Bacsai
072850be0b Refactor ApplicationDeploymentJob.php to remove unnecessary code and improve code structure 2024-05-30 13:02:01 +02:00
Andras Bacsai
71d120bc4e fix: fine-tune cdn pulls 2024-05-30 12:56:29 +02:00
Andras Bacsai
68d3cea528 fix: multiple server deployments
feat: custom preview deployment fqdn
ui: improvements here and there
2024-05-30 12:28:29 +02:00
Andras Bacsai
07e801f44d refactor: Remove unnecessary debug statements and improve code structure in RunRemoteProcess.php and ApplicationDeploymentJob.php 2024-05-30 10:14:48 +02:00
Andras Bacsai
ee5c694aa2 fix: compose previews does have env variables 2024-05-30 10:14:43 +02:00
Andras Bacsai
efa5eb1770 chore: Update version numbers to 4.0.0-beta.291 2024-05-30 10:14:29 +02:00
Andras Bacsai
42e37246f3 Merge pull request #2311 from coollabsio/next
v4.0.0-beta.290
2024-05-29 19:18:39 +02:00
Andras Bacsai
dabb08ff4a refactor: Remove unnecessary debug statement in ApplicationDeploymentJob.php 2024-05-29 18:51:38 +02:00
Andras Bacsai
66b0e04cc6 fix: able to redeploy dockerfile based apps without cache 2024-05-29 18:22:19 +02:00
Andras Bacsai
74824b7737 fix: compose load with non-root user 2024-05-29 18:01:10 +02:00
Andras Bacsai
df2bcdb854 refactor new deployment job 2024-05-29 15:28:03 +02:00
Andras Bacsai
a8e9ee2e95 Refactor ApplicationDeploymentJob.php to remove logo and improve code structure 2024-05-29 15:18:02 +02:00
Andras Bacsai
5093697b27 refactor: Improve code structure in ApplicationDeploymentJob.php 2024-05-29 15:17:39 +02:00
Andras Bacsai
5bacd63805 chore: Update version numbers to 4.0.0-beta.290 2024-05-29 15:16:24 +02:00
Andras Bacsai
1d5932e63f revert 2024-05-29 15:15:03 +02:00
Andras Bacsai
022762c0c9 refactor: applicationdeploymentjob 2024-05-29 15:11:17 +02:00
Esteban Ecallard
e16bd194a3 fix: removal of the failed deployment condition, addition of since started instead of finished time 2024-05-29 12:35:32 +00:00
Esteban Ecallard
a3765c19e3 feat: if the time seems too long it remains at 0s 2024-05-29 12:32:47 +00:00
Andras Bacsai
a845d92d88 Merge pull request #2305 from coollabsio/next
v4.0.0-beta.289
2024-05-29 12:11:10 +02:00
Andras Bacsai
668c9e5a64 refactor: Update destination.blade.php to add group class for better styling 2024-05-29 11:17:55 +02:00
Andras Bacsai
aaa06f4120 fix: build server dirs not created on main server 2024-05-29 11:17:16 +02:00
Andras Bacsai
e26f4ce707 chore: Update deployment index.blade.php script for better performance 2024-05-29 11:13:22 +02:00
Andras Bacsai
a8c3a2d991 Refactor git commands in ApplicationDeploymentJob.php 2024-05-29 10:43:57 +02:00
Andras Bacsai
6d52cef73a chore: Update modal styles for better user experience 2024-05-29 10:43:49 +02:00
Andras Bacsai
edacfcdec7 Update status component links to open in a new tab 2024-05-29 10:02:01 +02:00
Andras Bacsai
683872ef4e test zoom 2024-05-29 10:00:15 +02:00
Andras Bacsai
7a299ba1f9 chore: Update laravel/socialite to version v5.14.0 and livewire/livewire to version 3.4.9 2024-05-29 09:48:49 +02:00
Andras Bacsai
f5eaedfc72 just to create a bug report for livewire 2024-05-29 09:09:22 +02:00
Andras Bacsai
11b6afbe09 chore: rename docker dirs 2024-05-29 08:44:57 +02:00
Andras Bacsai
cd7340915b feat: Add PHP memory limit environment variable to docker-compose.prod.yml 2024-05-28 20:22:18 +02:00
Andras Bacsai
b38bb3df5d chore: Remove unnecessary wire:navigate attribute in breadcrumbs.blade.php 2024-05-28 19:58:51 +02:00
Andras Bacsai
1f7725ada3 chore: Fix formatting issue in deployment index.blade.php file 2024-05-28 19:00:59 +02:00
Andras Bacsai
622095ebc4 fix: throw exception 2024-05-28 15:11:25 +02:00
Andras Bacsai
397b7fefe3 fix: test new upgrade process? 2024-05-28 15:05:18 +02:00
Andras Bacsai
10d38b709b fix: add missing team model 2024-05-28 14:49:03 +02:00
Andras Bacsai
98985690f0 fix: publish horizon 2024-05-28 14:44:09 +02:00
Andras Bacsai
f50c483c64 fix: sync upgrade process 2024-05-28 14:44:03 +02:00
Andras Bacsai
a15eca137d chore: update for version 289 2024-05-28 14:38:44 +02:00
Evan
ba4be02e75 Merge branch 'next' into vaultwarden-fix 2024-05-28 12:39:54 +01:00
iamEvan
d4f6a86a57 Remove SMTP Env Variables 2024-05-28 12:39:40 +01:00
Andras Bacsai
6a058372bb Merge pull request #2303 from coollabsio/next
v4.0.0-beta.288
2024-05-28 13:07:55 +02:00
Andras Bacsai
e6cce350bd fix: volume adding 2024-05-28 13:07:07 +02:00
iamEvan
7c0c1e6cf8 Add Real IP support for Reverse Proxies 2024-05-28 01:12:59 +01:00
iamEvan
39f787b7db fix: sort by defined order + fixed typo 2024-05-27 23:41:42 +01:00
iamEvan
424437446d Added Push Notification Variables 2024-05-27 23:40:35 +01:00
Andras Bacsai
60a1859d89 Update README.md 2024-05-27 15:33:16 +02:00
Andras Bacsai
ad5c1639e8 fix: do not allow service storage mount point modifications 2024-05-27 15:11:00 +02:00
Andras Bacsai
92828b22fa chore: Update Sentry release version to 4.0.0-beta.288 2024-05-27 15:10:41 +02:00
Andras Bacsai
e470096e4e Merge pull request #2297 from coollabsio/next
v4.0.0-beta.287
2024-05-27 14:26:45 +02:00
Andras Bacsai
3c41608ee9 turn off docker engien restart for now 2024-05-27 14:16:10 +02:00
Andras Bacsai
035e145cd1 feat: add more persistent storage types 2024-05-27 14:14:44 +02:00
Andras Bacsai
2f621279c2 chore: Add null checks for team in Stripe webhook 2024-05-27 14:14:31 +02:00
Andras Bacsai
c30185c6ae feat: Handle incomplete expired subscriptions in Stripe webhook 2024-05-27 12:03:48 +02:00
Evan
908c74eb27 Merge branch 'next' into vaultwarden-fix 2024-05-27 17:45:00 +08:00
Andras Bacsai
10c17fc9a9 chore: Add Thompson Edolo as a sponsor 2024-05-27 10:29:51 +02:00
Andras Bacsai
a6a0cb928a fix: force load services from cdn on reload list 2024-05-27 10:27:18 +02:00
Andras Bacsai
8bca988520 chore: Update Sentry release version to 4.0.0-beta.287 2024-05-27 10:27:04 +02:00
Andras Bacsai
0af0af8d8a Merge pull request #2266 from coollabsio/next
v4.0.0-beta.286
2024-05-27 10:17:11 +02:00
Evan
97da13c3c4 Merge branch 'coollabsio:main' into vaultwarden-fix 2024-05-27 16:04:02 +08:00
iamEvan
7134b46cdc Fix Vaultwarden + Add Configs 2024-05-27 08:51:32 +01:00
Andras Bacsai
aecdf7a3d3 revert composer lock 2024-05-27 09:29:17 +02:00
Andras Bacsai
37e37d1998 fix: sentry 2024-05-24 17:37:51 +02:00
Andras Bacsai
6103a8590d fix: sentry error 2024-05-24 17:29:38 +02:00
Andras Bacsai
ba62dadc00 fix: sentry 2024-05-24 17:28:05 +02:00
Andras Bacsai
26073b82fd fix: sentry 2024-05-24 17:26:05 +02:00
Andras Bacsai
9b73bca79d fix: sentry error + livewire downgrade 2024-05-24 17:20:20 +02:00
Andras Bacsai
0a0bb0ca13 fix: sentry 2024-05-24 17:06:26 +02:00
Andras Bacsai
652df47c5c fix: sentry issue 2024-05-24 17:05:18 +02:00
Andras Bacsai
9248be2177 refactor: Update docker network creation in ApplicationDeploymentJob 2024-05-24 14:44:02 +02:00
Andras Bacsai
b3800fc42e init policies 2024-05-24 14:15:16 +02:00
Andras Bacsai
15f304736f fix livewire bug 2024-05-24 13:55:04 +02:00
Andras Bacsai
21cdf59065 fix chatwoot 2024-05-24 13:54:57 +02:00
Andras Bacsai
e3693afb75 Merge pull request #2283 from odraude7/main
Add chatwoot template
2024-05-24 13:11:25 +02:00
Andras Bacsai
009a753585 package updates 2024-05-24 12:31:28 +02:00
Andras Bacsai
5c72541044 fix: improve build server functionalities 2024-05-24 12:01:04 +02:00
Andras Bacsai
a01e604443 feat: add container logs in case the container does not start healthy 2024-05-24 11:50:31 +02:00
Andras Bacsai
52b339d0b8 refactor: Add isBuildServer method to Server model 2024-05-24 11:50:16 +02:00
Andras Bacsai
579ed5b9c0 fix: build server should not have a proxy 2024-05-24 11:17:23 +02:00
Andras Bacsai
63e64b8bcc refactor: Remove redundant heading in backup settings page 2024-05-24 09:35:36 +02:00
Andras Bacsai
64f8583975 fix: root team able to download backups 2024-05-24 09:33:09 +02:00
Eduardo Neves
75e8064044 Add chatwoot template 2024-05-23 17:25:55 -03:00
Andras Bacsai
6f3e38e392 refactor: Add Huly services to compose file 2024-05-23 15:21:29 +02:00
Andras Bacsai
cb4d244f19 refactor: Update edit-domain form in project service view 2024-05-23 15:21:24 +02:00
Esteban Ecallard
de3b8a10a0 Update index.blade.php 2024-05-23 15:20:28 +02:00
Esteban Ecallard
8feece702c fix: wrong time during a failed deployment 2024-05-23 15:15:18 +02:00
Andras Bacsai
900308afec fix: better way to add curl/wget to nixpacks 2024-05-23 14:28:11 +02:00
Andras Bacsai
b47925a319 fix: bitbucket commits link 2024-05-23 14:28:03 +02:00
Andras Bacsai
853325d9fd fix: pre and post deployment commands 2024-05-23 13:30:37 +02:00
Andras Bacsai
494be37715 remove comment 2024-05-23 13:08:57 +02:00
Andras Bacsai
d35cb5d072 fix: add wget to nixpacks builds 2024-05-23 13:08:46 +02:00
Andras Bacsai
df9ec711c5 fix: JSON_UNESCAPED_UNICODE 2024-05-23 11:58:54 +02:00
Andras Bacsai
d9d0837024 fix: disable unreachable/revived notifications for now 2024-05-23 11:32:45 +02:00
Andras Bacsai
086138fbd9 fix: disable containerStopped job for now 2024-05-23 11:31:52 +02:00
Andras Bacsai
bdbd4b57b7 refactor 2024-05-23 11:30:39 +02:00
Andras Bacsai
5475448af5 feat: gitea manual webhooks 2024-05-23 11:30:18 +02:00
Andras Bacsai
c3da3f11d9 fix: Update error message for invalid token to mention invalid signature 2024-05-23 11:30:08 +02:00
Andras Bacsai
244c81587c fix: templates 2024-05-23 11:13:06 +02:00
Andras Bacsai
a3877a2cb1 feat: exclude_from_hc magic 2024-05-23 11:12:53 +02:00
Andras Bacsai
206df82d63 fix: Do not pull templates in dev 2024-05-23 11:12:37 +02:00
Andras Bacsai
54e1e7684d chore: Remove unnecessary content from Docker Compose file 2024-05-23 08:50:15 +02:00
Andras Bacsai
27eef36677 Merge pull request #2216 from theh2so4/main
Templates
2024-05-23 08:32:27 +02:00
Andras Bacsai
d91953e70b add service-templates 2024-05-23 08:31:55 +02:00
Andras Bacsai
bbc5a49054 Delete templates/service-templates.json 2024-05-23 08:28:48 +02:00
Andras Bacsai
f89fe9fbab Update templates/compose/glance.yaml
Co-authored-by: Jonas <mrlordalfred@gmail.com>
2024-05-23 08:28:28 +02:00
Andras Bacsai
0c042bfe50 Merge pull request #2272 from samuelteixeiras/patch-1
Add `--ignore-existing` to minio-createbucket
2024-05-23 08:27:16 +02:00
Samuel Teixeira
1a7894b15e Update supabase.yaml
Add `--ignore-existing` into bucket creation to avoid the service be unhealthy.
2024-05-23 03:55:13 +01:00
Andras Bacsai
543f983e41 fix: ghost subdir 2024-05-22 21:10:37 +02:00
Andras Bacsai
97e7e473b8 Update proxy headings in server view 2024-05-22 21:10:26 +02:00
Andras Bacsai
79c30f7a94 Fix issue with starting proxy 2024-05-22 21:09:53 +02:00
Andras Bacsai
cccf86d388 feat: if proxy stopped manually, it won't start back again 2024-05-22 20:42:08 +02:00
Andras Bacsai
c102c23831 Refactor database restart button in service configuration view 2024-05-22 19:09:58 +02:00
Andras Bacsai
80ada9c90a fix: add subpath for services 2024-05-22 15:45:30 +02:00
Andras Bacsai
6c3b4070ba chore: Refactor container name logic in GetContainersStatus.php and ForcePasswordReset.php 2024-05-22 14:44:11 +02:00
Andras Bacsai
4b287b758d feat: Improve Docker Engine start logic in ServerStatusJob 2024-05-22 14:25:27 +02:00
Andras Bacsai
b6d129a5c1 fix: show first 20 users only in admin view 2024-05-22 14:23:55 +02:00
Andras Bacsai
4d08147647 chore: Change pre and post deployment command length in applications table 2024-05-22 12:41:22 +02:00
Andras Bacsai
ace7c17f2b fix: Check proxy functionality before removing unnecessary coolify.yaml file and checking Docker Engine 2024-05-22 12:20:36 +02:00
Andras Bacsai
10f3d8aa0f fix: use local versions + service templates and query them every 10 minutes 2024-05-22 09:23:17 +02:00
Andras Bacsai
3237ca0d97 Update version numbers to 4.0.0-beta.286 2024-05-22 09:22:56 +02:00
Andras Bacsai
5682ab9570 Merge pull request #2261 from coollabsio/next
v4.0.0-beta.285
2024-05-21 17:47:04 +02:00
Andras Bacsai
a3d73634e7 feat: scheduled task failed notification 2024-05-21 15:36:26 +02:00
Andras Bacsai
98b6aec203 feat: admin view for deleting users 2024-05-21 14:29:06 +02:00
Andras Bacsai
7feb788ed3 fix: show it docker compose has syntax errors 2024-05-21 12:02:04 +02:00
Andras Bacsai
bea490081b ui: responsive here and there 2024-05-21 11:23:53 +02:00
Andras Bacsai
7adc3ca003 feat: Add SerpAPI as a Github Sponsor 2024-05-21 11:20:12 +02:00
Andras Bacsai
f8cbc63ab0 fix: optimize new resource creation 2024-05-21 10:17:32 +02:00
Andras Bacsai
418590fb35 Update version numbers to 4.0.0-beta.285 2024-05-21 10:16:31 +02:00
Andras Bacsai
56144482f1 Merge pull request #2244 from coollabsio/next
v4.0.0-beta.284
2024-05-19 20:56:17 +02:00
Andras Bacsai
59f681e6af revert: hc return code check 2024-05-19 20:54:16 +02:00
Andras Bacsai
d3296f5180 feat: add hc logs to healthchecks 2024-05-18 18:48:33 +02:00
Andras Bacsai
c6fff0aa13 Update version numbers to 4.0.0-beta.284 2024-05-17 18:54:21 +02:00
Andras Bacsai
41e0c42282 Remove warning message about only supporting root user login via SSH in the future 2024-05-17 18:54:11 +02:00
Andras Bacsai
ede2274816 Merge pull request #2234 from coollabsio/next
v4.0.0-beta.283
2024-05-17 15:34:00 +02:00
Andras Bacsai
ead672afb2 fix: PR deployments have good predefined envs 2024-05-17 15:30:27 +02:00
Andras Bacsai
73bc7b045e feat: Add pull_request_id filter to get_last_successful_deployment method in Application model 2024-05-17 15:28:54 +02:00
Andras Bacsai
3281502c25 feat: Update healthcheck test in StartMongodb action 2024-05-17 14:35:37 +02:00
Andras Bacsai
ca35e536db chore: Update version to 4.0.0-beta.283 2024-05-17 14:35:31 +02:00
TheH2SO4
0d89d4d0d3 Glance
[+] Releasing Glance template.
2024-05-17 13:27:23 +02:00
TheH2SO4
285623a02b MediaWiki
[!] Fix image
2024-05-16 11:48:51 +02:00
TheH2SO4
d71a0ddd66 MediaWiki
[+] MediaWiki (SQLite)
2024-05-16 11:11:12 +02:00
TheH2SO4
c2d2b3f3b2 Docuseal
[+] Fix
2024-05-15 22:54:46 +02:00
TheH2SO4
4aa153fcf1 Docuseal
[!] Docuseal (SQLite) fix compose encode
2024-05-15 22:27:56 +02:00
TheH2SO4
824f63a3ad Docuseal
[+] Docuseal (SQLite)
[+] Docuseal (Postgres)
2024-05-15 22:25:58 +02:00
Iván Salazar
f3fe4433ae update sonarqube template 2024-05-13 21:33:36 -06:00
275 changed files with 4709 additions and 2839 deletions

View File

@@ -1,17 +1,28 @@
name: Bug report
description: Create a new bug report
description: 'Create a new bug report.'
title: '[Bug]: '
body:
- type: markdown
attributes:
value: >-
# 💎 Bounty program (with
[algora.io](https://console.algora.io/org/coollabsio/bounties/new))
If you would like to prioritize the issue resolution, you can add bounty
to this issue.
Click [here](https://console.algora.io/org/coollabsio/bounties/new) to
get started.
- type: textarea
attributes:
label: Description
description: A clear and concise description of the problem
validations:
required: true
- type: textarea
attributes:
label: Minimal Reproduction (if possible, example repository)
description: Please provide a step by step guide to reproduce the issue
description: Please provide a step by step guide to reproduce the issue.
validations:
required: true
- type: textarea

View File

@@ -26,7 +26,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
file: docker/prod/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
@@ -47,7 +47,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
file: docker/prod/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64

View File

@@ -29,7 +29,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
file: docker/prod/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
@@ -51,7 +51,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
file: docker/prod/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64

View File

@@ -1,3 +1,6 @@
[![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new)
[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open)
[![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed)
# About the Project
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
@@ -34,13 +37,16 @@ Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
## Github Sponsors ($40+)
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
<a href="https://serpapi.com/?utm_source=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
<a href="https://www.quantcdn.io/?utm_source=coolify.io"><img src="https://github.com/quantcdn.png" width="60px" alt="QuantCDN"/></a>
<a href="https://www.runpod.io/?utm_source=coolify.io">
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
<a href="https://lightspeed.run/?utm_source=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://x.com/mrsmith9ja?utm_source=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>

View File

@@ -60,7 +60,7 @@ class RunRemoteProcess
$decoded = json_decode(
data_get($activity, 'description'),
associative: true,
flags: JSON_THROW_ON_ERROR
flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE
);
} catch (\JsonException $exception) {
return '';
@@ -164,8 +164,7 @@ class RunRemoteProcess
public function encodeOutput($type, $output)
{
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
$outputStack[] = [
'type' => $type,
'output' => $output,
@@ -174,12 +173,12 @@ class RunRemoteProcess
'order' => $this->getLatestCounter(),
];
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR);
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
}
protected function getLatestCounter(): int
{
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
if ($description === null || count($description) === 0) {
return 1;
}

View File

@@ -29,6 +29,7 @@ class StartClickhouse
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
@@ -93,6 +94,11 @@ class StartClickhouse
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@@ -32,6 +32,7 @@ class StartDragonfly
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
@@ -94,6 +95,11 @@ class StartDragonfly
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@@ -32,6 +32,7 @@ class StartKeydb
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_keydb();
@@ -92,6 +93,11 @@ class StartKeydb
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@@ -28,6 +28,7 @@ class StartMariadb
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql();
@@ -86,6 +87,11 @@ class StartMariadb
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@@ -30,6 +30,7 @@ class StartMongodb
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mongo_conf();
@@ -50,8 +51,9 @@ class StartMongodb
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'mongosh --eval "printjson(db.runCommand(\"ping\"))"'
"CMD",
"echo",
"ok"
],
'interval' => '5s',
'timeout' => '5s',
@@ -93,6 +95,11 @@ class StartMongodb
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@@ -28,6 +28,7 @@ class StartMysql
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql();
@@ -86,6 +87,11 @@ class StartMysql
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@@ -29,6 +29,7 @@ class StartPostgresql
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->generate_init_scripts();
@@ -92,6 +93,11 @@ class StartPostgresql
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@@ -32,6 +32,7 @@ class StartRedis
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_redis();
@@ -96,6 +97,11 @@ class StartRedis
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@@ -6,14 +6,12 @@ use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction;
class GetContainersStatus
@@ -24,9 +22,9 @@ class GetContainersStatus
public function handle(Server $server)
{
if (isDev()) {
$server = Server::find(0);
}
// if (isDev()) {
// $server = Server::find(0);
// }
$this->server = $server;
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
@@ -154,7 +152,7 @@ class GetContainersStatus
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
// TODO: fix this with sentinel
// TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'name') === "$uuid-proxy";
@@ -223,7 +221,19 @@ class GetContainersStatus
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
if ($name) {
if ($fqdn) {
$containerName = "$name, available at $fqdn";
} else {
$containerName = $name;
}
} else {
if ($fqdn) {
$containerName = $fqdn;
} else {
$containerName = null;
}
}
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');
@@ -233,7 +243,7 @@ class GetContainersStatus
} else {
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
$exitedService->update(['status' => 'exited']);
}
@@ -260,7 +270,7 @@ class GetContainersStatus
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) {
@@ -285,7 +295,7 @@ class GetContainersStatus
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) {
@@ -309,14 +319,14 @@ class GetContainersStatus
} else {
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// Check if proxy is running
$this->server->proxyType();
$foundProxyContainer = $containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
// TODO: fix this with sentinel
// TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {
return data_get($value, 'name') === 'coolify-proxy';
@@ -442,19 +452,21 @@ class GetContainersStatus
if ($database_id) {
$service_db = ServiceDatabase::where('id', $database_id)->first();
if ($service_db) {
$uuid = $service_db->service->uuid;
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
$uuid = data_get($service_db, 'service.uuid');
if ($uuid) {
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($service_db);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($service_db);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
}
}
@@ -539,7 +551,19 @@ class GetContainersStatus
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
if ($name) {
if ($fqdn) {
$containerName = "$name, available at $fqdn";
} else {
$containerName = $name;
}
} else {
if ($fqdn) {
$containerName = $fqdn;
} else {
$containerName = null;
}
}
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');
@@ -549,7 +573,7 @@ class GetContainersStatus
} else {
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
$exitedService->update(['status' => 'exited']);
}
@@ -576,7 +600,7 @@ class GetContainersStatus
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) {
@@ -601,7 +625,7 @@ class GetContainersStatus
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) {
@@ -625,7 +649,7 @@ class GetContainersStatus
} else {
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// Check if proxy is running

View File

@@ -10,7 +10,18 @@ class CheckProxy
use AsAction;
public function handle(Server $server, $fromUI = false)
{
if ($server->proxyType() === 'NONE') {
if (!$server->isFunctional()) {
return false;
}
if ($server->isBuildServer()) {
if ($server->proxy) {
$server->proxy = null;
$server->save();
}
return false;
}
$proxyType = $server->proxyType();
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
return false;
}
['uptime' => $uptime, 'error' => $error] = $server->validateConnection();

View File

@@ -15,7 +15,7 @@ class StartProxy
{
try {
$proxyType = $server->proxyType();
if (is_null($proxyType) || $proxyType === 'NONE') {
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) {
return 'OK';
}
$commands = collect([]);

View File

@@ -12,12 +12,10 @@ class UpdateCoolify
public ?Server $server = null;
public ?string $latestVersion = null;
public ?string $currentVersion = null;
public bool $async = false;
public function handle(bool $force = false, bool $async = false)
public function handle($manual_update = false)
{
try {
$this->async = $async;
$settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob');
$this->server = Server::find(0);
@@ -27,28 +25,19 @@ class UpdateCoolify
CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
// if ($settings->next_channel) {
// ray('next channel enabled');
// $this->latestVersion = 'next';
// }
if ($force) {
$this->update();
} else {
if (!$manual_update) {
if (!$settings->is_auto_update_enabled) {
return 'Auto update is disabled';
return;
}
if ($this->latestVersion === $this->currentVersion) {
return 'Already on latest version';
return;
}
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
return 'Latest version is lower than current version?!';
return;
}
$this->update();
}
$this->update();
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
throw $e;
}
}
@@ -56,34 +45,15 @@ class UpdateCoolify
private function update()
{
if (isDev()) {
ray("Running update on local docker container. Updating to $this->latestVersion");
if ($this->async) {
ray('Running async update');
remote_process([
"sleep 10"
], $this->server);
} else {
instant_remote_process([
"sleep 10"
], $this->server);
}
ray('Update done');
return;
} else {
ray('Running update on production server');
if ($this->async) {
remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server);
} else {
instant_remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server);
}
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
remote_process([
"sleep 10"
], $this->server);
return;
}
remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server);
return;
}
}

View File

@@ -26,7 +26,6 @@ class ServicesGenerate extends Command
*/
public function handle()
{
// ray()->clearAll();
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
$files = array_filter($files, function ($file) {
return strpos($file, '.yaml') !== false;
@@ -40,7 +39,7 @@ class ServicesGenerate extends Command
$serviceTemplatesJson[$name] = $parsed;
}
}
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT);
$serviceTemplatesJson = json_encode($serviceTemplatesJson);
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
}
@@ -63,6 +62,7 @@ class ServicesGenerate extends Command
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
if ($documentation->count() > 0) {
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
$documentation = str($documentation)->append('?utm_source=coolify.io');
} else {
$documentation = 'https://coolify.io/docs';
}

View File

@@ -6,12 +6,12 @@ use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\PullCoolifyImageJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
@@ -29,34 +29,33 @@ class Kernel extends ConsoleKernel
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
// Server Jobs
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
// $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily();
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->pull_helper_image($schedule);
$this->pull_images($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('cleanup:database --yes')->daily();
$schedule->command('uploads:clear')->everyTwoMinutes();
}
}
private function pull_helper_image($schedule)
private function pull_images($schedule)
{
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
@@ -87,16 +86,6 @@ class Kernel extends ConsoleKernel
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
}
}
private function instance_auto_update($schedule)
{
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();
if ($settings->is_auto_update_enabled) {
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes()->onOneServer();
}
}
private function check_scheduled_backups($schedule)
{
$scheduled_backups = ScheduledDatabaseBackup::all();

View File

@@ -82,7 +82,7 @@ class Bitbucket extends Controller
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
'message' => 'Invalid signature.',
]);
ray('Invalid signature');
continue;

View File

@@ -0,0 +1,222 @@
<?php
namespace App\Http\Controllers\Webhook;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class Gitea extends Controller
{
public function manual(Request $request)
{
try {
$return_payloads = collect([]);
$x_gitea_delivery = request()->header('X-Gitea-Delivery');
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$files = Storage::disk('webhooks-during-maintenance')->files();
$gitea_delivery_found = collect($files)->filter(function ($file) use ($x_gitea_delivery) {
return Str::contains($file, $x_gitea_delivery);
})->first();
if ($gitea_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
'attributes' => $request->attributes->all(),
'request' => $request->request->all(),
'query' => $request->query->all(),
'server' => $request->server->all(),
'files' => $request->files->all(),
'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(),
'content' => $request->getContent(),
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitea::manual_{$x_gitea_delivery}", $json);
return;
}
$x_gitea_event = Str::lower($request->header('X-Gitea-Event'));
$x_hub_signature_256 = Str::after($request->header('X-Hub-Signature-256'), 'sha256=');
$content_type = $request->header('Content-Type');
$payload = $request->collect();
if ($x_gitea_event === 'ping') {
// Just pong
return response('pong');
}
if ($content_type !== 'application/json') {
$payload = json_decode(data_get($payload, 'payload'), true);
}
if ($x_gitea_event === 'push') {
$branch = data_get($payload, 'ref');
$full_name = data_get($payload, 'repository.full_name');
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
$branch = Str::after($branch, 'refs/heads/');
}
$added_files = data_get($payload, 'commits.*.added');
$removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray($changed_files);
ray('Manual Webhook Gitea Push Event with branch: ' . $branch);
}
if ($x_gitea_event === 'pull_request') {
$action = data_get($payload, 'action');
$full_name = data_get($payload, 'repository.full_name');
$pull_request_id = data_get($payload, 'number');
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
$branch = data_get($payload, 'pull_request.head.ref');
$base_branch = data_get($payload, 'pull_request.base.ref');
ray('Webhook Gitea Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
}
if (!$branch) {
return response('Nothing to do. No branch found in the request.');
}
$applications = Application::where('git_repository', 'like', "%$full_name%");
if ($x_gitea_event === 'push') {
$applications = $applications->where('git_branch', $branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.");
}
}
if ($x_gitea_event === 'pull_request') {
$applications = $applications->where('git_branch', $base_branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$base_branch'.");
}
}
foreach ($applications as $application) {
$webhook_secret = data_get($application, 'manual_webhook_secret_gitea');
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) {
ray('Invalid signature');
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid signature.',
]);
continue;
}
$isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Server is not functional.',
]);
continue;
}
if ($x_gitea_event === 'push') {
if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying ' . $application->name . ' with branch ' . $branch);
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: data_get($payload, 'after', 'HEAD'),
is_webhook: true,
);
$return_payloads->push([
'status' => 'success',
'message' => 'Deployment queued.',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
]);
} else {
$paths = str($application->watch_paths)->explode("\n");
$return_payloads->push([
'status' => 'failed',
'message' => 'Changed files do not match watch paths. Ignoring deployment.',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'details' => [
'changed_files' => $changed_files,
'watch_paths' => $paths,
],
]);
}
} else {
$return_payloads->push([
'status' => 'failed',
'message' => 'Deployments disabled.',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
]);
}
}
if ($x_gitea_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) {
ApplicationPreview::create([
'git_type' => 'gitea',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
]);
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: data_get($payload, 'head.sha', 'HEAD'),
is_webhook: true,
git_type: 'gitea'
);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment queued.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Preview deployments disabled.',
]);
}
}
if ($action === 'closed') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();
$container_name = generateApplicationContainerName($application, $pull_request_id);
// ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment closed.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'No preview deployment found.',
]);
}
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}
}

View File

@@ -106,7 +106,7 @@ class Github extends Controller
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
'message' => 'Invalid signature.',
]);
continue;
}
@@ -410,11 +410,12 @@ class Github extends Controller
if ($action === 'closed' || $action === 'close') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$container_name = generateApplicationContainerName($application, $pull_request_id);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
$found->delete();
$container_name = generateApplicationContainerName($application, $pull_request_id);
// ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
@@ -430,7 +431,6 @@ class Github extends Controller
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());

View File

@@ -109,7 +109,7 @@ class Gitlab extends Controller
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
'message' => 'Invalid signature.',
]);
ray('Invalid signature');
continue;
@@ -202,7 +202,7 @@ class Gitlab extends Controller
]);
ray('Preview deployments disabled for ' . $application->name);
}
} else if ($action === 'closed' || $action === 'close') {
} else if ($action === 'closed' || $action === 'close' || $action === 'merge') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();

View File

@@ -150,6 +150,10 @@ class Stripe extends Controller
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
}
if (!$subscription) {
if ($status === 'incomplete_expired') {
send_internal_notification('Subscription incomplete expired for customer: ' . $customerId);
return response("Subscription incomplete expired", 200);
}
send_internal_notification('No subscription found for: ' . $customerId);
return response("No subscription found", 400);
}
@@ -166,9 +170,11 @@ class Stripe extends Controller
$quantity = data_get($data, 'items.data.0.quantity', 10);
}
$team = data_get($subscription, 'team');
$team->update([
'custom_server_limit' => $quantity,
]);
if ($team) {
$team->update([
'custom_server_limit' => $quantity,
]);
}
ServerLimitCheckJob::dispatch($team);
}
$subscription->update([
@@ -210,7 +216,9 @@ class Stripe extends Controller
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
$team->trialEnded();
if ($team) {
$team->trialEnded();
}
$subscription->update([
'stripe_subscription_id' => null,
'stripe_plan_id' => null,

View File

@@ -67,6 +67,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Save original server between phases
private Server $original_server;
private Server $mainServer;
private bool $is_this_additional_server = false;
private ?ApplicationPreview $preview = null;
private ?string $git_type = null;
private bool $only_this_server = false;
@@ -123,6 +124,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$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->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
$this->only_this_server = $this->application_deployment_queue->only_this_server;
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
@@ -136,6 +138,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->destination = $this->server->destinations()->where('id', $this->application_deployment_queue->destination_id)->first();
$this->server = $this->mainServer = $this->destination->server;
$this->serverUser = $this->server->user;
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
@@ -149,28 +153,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Set preview fqdn
if ($this->pull_request_id !== 0) {
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
if ($this->application->fqdn) {
if (str($this->application->fqdn)->contains(',')) {
$url = Url::fromString(str($this->application->fqdn)->explode(',')[0]);
$preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]);
} else {
$url = Url::fromString($this->application->fqdn);
if (data_get($this->preview, 'fqdn')) {
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
}
}
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$this->preview->fqdn = $preview_fqdn;
$this->preview->save();
}
$this->preview = $this->application->generate_preview_fqdn($this->pull_request_id);
if ($this->application->is_github_based()) {
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS);
}
@@ -184,6 +167,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
public function handle(): void
{
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
if (!$this->server->isFunctional()) {
$this->application_deployment_queue->addLogEntry("Server is not functional.");
$this->fail("Server is not functional.");
@@ -281,7 +267,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function decide_what_to_do()
{
if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') {
if ($this->restart_only) {
$this->just_restart();
return;
} else if ($this->pull_request_id !== 0) {
@@ -303,7 +289,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function post_deployment()
{
if ($this->server->isProxyShouldRun()) {
GetContainersStatus::dispatch($this->server);
// dispatch(new ContainerStatusJob($this->server));
@@ -331,18 +316,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
$this->generate_image_names();
// Always rebuild dockerfile based container.
// if (!$this->force_rebuild) {
// $this->check_image_locally_or_remotely();
// if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
// $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
// $this->generate_compose_file();
// $this->push_to_docker_registry();
// $this->rolling_update();
// return;
// }
// }
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
@@ -372,9 +345,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
if (data_get($this->application, 'docker_compose_custom_start_command')) {
$this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command;
if (!str($this->docker_compose_custom_start_command)->contains('--project-directory')) {
$this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value();
}
}
if (data_get($this->application, 'docker_compose_custom_build_command')) {
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
if (!str($this->docker_compose_custom_build_command)->contains('--project-directory')) {
$this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value();
}
}
if ($this->pull_request_id === 0) {
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}.");
@@ -390,15 +369,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
$yaml = $composeFile = $this->application->docker_compose_raw;
$this->save_environment_variables();
} else {
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id'));
$this->save_environment_variables();
if (!is_null($this->env_filename)) {
$services = collect($composeFile['services']);
$services = $services->map(function ($service, $name) {
$service['env_file'] = [$this->env_filename];
return $service;
});
$composeFile['services'] = $services->toArray();
}
if (is_null($composeFile)) {
$this->application_deployment_queue->addLogEntry("Failed to parse docker-compose file.");
$this->fail("Failed to parse docker-compose file.");
return;
}
$yaml = Yaml::dump($composeFile->toArray(), 10);
}
$this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([
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.
$this->application_deployment_queue->addLogEntry("Pulling & building required images.");
@@ -407,13 +400,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_build_command}"), "hidden" => true],
);
} else {
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, $command), "hidden" => true],
);
}
$this->stop_running_container(force: true);
$this->application_deployment_queue->addLogEntry("Starting new application.");
$networkId = $this->application->uuid;
if ($this->pull_request_id !== 0) {
$networkId = "{$this->application->uuid}-{$this->pull_request_id}";
@@ -422,7 +420,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// TODO
} else {
$this->execute_remote_command([
"docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true
"docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true
], [
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
]);
@@ -438,9 +436,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
ray("{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
["{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
["command" => $command, "hidden" => true],
);
}
} else {
@@ -450,8 +454,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
$this->write_deployment_configurations();
} else {
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
[executeInDocker($this->deployment_uuid, $command), "hidden" => true],
);
$this->write_deployment_configurations();
}
@@ -470,16 +479,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->clone_repository();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->should_skip_build()) {
return;
}
}
@@ -499,21 +503,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
ray('pushing to docker registry');
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->should_skip_build()) {
return;
}
if ($this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
}
}
$this->clone_repository();
$this->cleanup_git();
@@ -532,15 +527,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->should_skip_build()) {
return;
}
}
@@ -608,7 +598,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
ray('additional_servers');
$forceFail = true;
}
if ($this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0) {
if ($this->is_this_additional_server) {
ray('this is an additional_servers, no pushy pushy');
return;
}
@@ -623,8 +613,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
if ($this->application->docker_registry_image_tag) {
// Tag image with latest
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
// Tag image with docker_registry_image_tag
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag.");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
@@ -634,7 +624,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
}
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
} catch (Exception $e) {
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
if ($forceFail) {
@@ -665,9 +654,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
} else {
$this->dockerImageTag = str($this->commit)->substr(0, 128);
if ($this->application->docker_registry_image_tag) {
$this->dockerImageTag = $this->application->docker_registry_image_tag;
}
// if ($this->application->docker_registry_image_tag) {
// $this->dockerImageTag = $this->application->docker_registry_image_tag;
// }
if ($this->application->docker_registry_image_name) {
$this->build_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build";
$this->production_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}";
@@ -682,19 +671,42 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->addLogEntry("Restarting {$this->customRepository}:{$this->application->git_branch} on {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->check_image_locally_or_remotely();
if ($this->should_skip_build()) {
return;
}
}
private function should_skip_build()
{
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
$this->generate_compose_file();
$this->rolling_update();
$this->post_deployment();
if ($this->is_this_additional_server) {
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->restart_only) {
$this->post_deployment();
}
return true;
}
if (!$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
return true;
} else {
$this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
}
} else {
$this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Redeploying the application.");
$this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Building new image.");
}
if ($this->restart_only) {
$this->restart_only = false;
$this->decide_what_to_do();
}
return false;
}
private function check_image_locally_or_remotely()
{
@@ -713,6 +725,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function save_environment_variables()
{
$envs = collect([]);
$local_branch = $this->branch;
if ($this->pull_request_id !== 0) {
$local_branch = "pull/{$this->pull_request_id}/head";
}
$sort = $this->application->settings->is_env_sorting_enabled;
if ($sort) {
$sorted_environment_variables = $this->application->environment_variables->sortBy('key');
@@ -740,14 +756,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$envs->push("COOLIFY_URL={$url}");
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$this->application->git_branch}");
$envs->push("COOLIFY_BRANCH={$local_branch}");
}
foreach ($sorted_environment_variables_preview as $env) {
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
if ($env->is_literal || $env->is_multiline) {
$real_value = '\'' . $real_value . '\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
@@ -780,18 +796,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
$envs->push("COOLIFY_URL={$url}");
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$this->application->git_branch}");
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}");
}
foreach ($sorted_environment_variables as $env) {
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
if ($env->is_literal || $env->is_multiline) {
$real_value = '\'' . $real_value . '\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
ray($real_value);
}
}
$envs->push($env->key . '=' . $real_value);
@@ -810,16 +827,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$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}",
"hidden" => true,
"ignore_errors" => true
]
);
if ($this->use_build_server) {
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
"hidden" => true,
"ignore_errors" => true
]
);
$this->server = $this->build_server;
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
"hidden" => true,
"ignore_errors" => true
]
);
} else {
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
"hidden" => true,
"ignore_errors" => true
]
);
}
} else {
$envs_base64 = base64_encode($envs->implode("\n"));
@@ -831,38 +861,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
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->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
]
);
$this->server = $this->build_server;
} else {
$this->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
]
);
}
}
// $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
// ]
// );
// }
}
@@ -959,9 +971,23 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"save" => "health_check",
"append" => false
],
[
"docker inspect --format='{{json .State.Health.Log}}' {$this->container_name}",
"hidden" => true,
"save" => "health_check_logs",
"append" => false
],
);
$this->application_deployment_queue->addLogEntry("Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}");
$health_check_logs = data_get(collect(json_decode($this->saved_outputs->get('health_check_logs')))->last(), 'Output', '(no logs)');
if (empty($health_check_logs)) {
$health_check_logs = '(no logs)';
}
$health_check_return_code = data_get(collect(json_decode($this->saved_outputs->get('health_check_logs')))->last(), 'ExitCode', '(no return code)');
if ($health_check_logs !== '(no logs)' || $health_check_return_code !== '(no return code)') {
$this->application_deployment_queue->addLogEntry("Healthcheck logs: {$health_check_logs} | Return code: {$health_check_return_code}");
}
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') {
$this->newVersionIsHealthy = true;
$this->application->update(['status' => 'running']);
@@ -970,6 +996,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') {
$this->newVersionIsHealthy = false;
$this->query_logs();
break;
}
$counter++;
@@ -979,9 +1006,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$sleeptime++;
}
}
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'starting') {
$this->query_logs();
}
}
}
}
private function query_logs()
{
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Container logs:");
$this->execute_remote_command(
[
"command" => "docker logs -n 100 {$this->container_name}",
"type" => "stderr",
"ignore_errors" => true,
],
);
$this->application_deployment_queue->addLogEntry("----------------------------------------");
}
private function deploy_pull_request()
{
if ($this->application->build_pack === 'dockercompose') {
@@ -995,8 +1038,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_image_names();
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->clone_repository();
$this->set_base_dir();
$this->cleanup_git();
if ($this->application->build_pack === 'nixpacks') {
$this->generate_nixpacks_confs();
@@ -1013,14 +1056,32 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function create_workdir()
{
$this->execute_remote_command(
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
],
[
"command" => "mkdir -p {$this->configuration_dir}"
],
);
if ($this->use_build_server) {
$this->server = $this->original_server;
$this->execute_remote_command(
[
"command" => "mkdir -p {$this->configuration_dir}"
],
);
$this->server = $this->build_server;
$this->execute_remote_command(
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
],
[
"command" => "mkdir -p {$this->configuration_dir}"
],
);
} else {
$this->execute_remote_command(
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
],
[
"command" => "mkdir -p {$this->configuration_dir}"
],
);
}
}
private function prepare_builder_image()
{
@@ -1100,10 +1161,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]));
}
}
private function set_base_dir()
{
$this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}.");
}
private function set_coolify_variables()
{
$this->coolify_variables = "SOURCE_COMMIT={$this->commit} ";
@@ -1124,6 +1181,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function check_git_if_build_needed()
{
$this->generate_git_import_commands();
$local_branch = $this->branch;
if ($this->pull_request_id !== 0) {
$local_branch = "pull/{$this->pull_request_id}/head";
}
$private_key = data_get($this->application, 'private_key.private_key');
if ($private_key) {
$private_key = base64_encode($private_key);
@@ -1138,7 +1199,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa")
],
[
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->fullRepoUrl} {$this->branch}"),
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->fullRepoUrl} {$local_branch}"),
"hidden" => true,
"save" => "git_commit_sha"
],
@@ -1146,7 +1207,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->fullRepoUrl} {$this->branch}"),
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->fullRepoUrl} {$local_branch}"),
"hidden" => true,
"save" => "git_commit_sha"
],
@@ -1230,8 +1291,21 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Do any modifications here
$this->generate_env_variables();
$merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', [])));
$aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []);
if (count($aptPkgs) === 0) {
data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']);
} else {
if (!in_array('curl', $aptPkgs)) {
$aptPkgs[] = 'curl';
}
if (!in_array('wget', $aptPkgs)) {
$aptPkgs[] = 'wget';
}
data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs);
}
data_set($parsed, 'variables', $merged_envs->toArray());
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
}
}
}
@@ -1299,6 +1373,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$onlyPort = $ports[0];
}
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->application->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
// $environment_variables = $this->generate_environment_variables($ports);
$this->save_environment_variables();
@@ -1499,6 +1574,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if (count($persistent_storages) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
@@ -1605,12 +1685,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->health_check_path) {
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}";
$generated_healthchecks_commands = [
"curl -o /dev/null -w \"%{http_code}\" -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} | grep -q \"{$this->application->health_check_return_code}\" || wget --save-headers -O - {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} 2>/dev/null |grep HTTP/ |grep -q \"{$this->application->health_check_return_code}\" || exit 1"
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || exit 1"
];
} else {
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/";
$generated_healthchecks_commands = [
"curl -o /dev/null -w \"%{http_code}\" -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ | grep -q \"{$this->application->health_check_return_code}\" || wget --save-headers -O - {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ 2>/dev/null |grep HTTP/ |grep -q \"{$this->application->health_check_return_code}\" || exit 1"
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || exit 1"
];
}
return implode(' ', $generated_healthchecks_commands);
@@ -1737,7 +1817,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else {
// Pure Dockerfile based deployment
if ($this->application->dockerfile) {
$build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
if ($this->force_rebuild) {
$build_command = "docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
} else {
$build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
}
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
@@ -1865,11 +1949,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($this->pull_request_id === 0) {
foreach ($this->application->build_environment_variables as $env) {
$value = escapeshellarg($env->real_value);
if (str($value)->contains("\n") && data_get($env, 'is_multiline') === true) {
$value = str_replace("\n", "\\\n", $value);
}
$this->build_args->push("--build-arg {$env->key}={$value}");
}
} else {
foreach ($this->application->build_environment_variables_preview as $env) {
$value = escapeshellarg($env->real_value);
if (str($value)->contains("\n") && data_get($env, 'is_multiline') === true) {
$value = str_replace("\n", "\\\n", $value);
}
$this->build_args->push("--build-arg {$env->key}={$value}");
}
}
@@ -1885,10 +1975,20 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
if ($this->pull_request_id === 0) {
foreach ($this->application->build_environment_variables as $env) {
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
if (str($env->real_value)->contains("\n") && data_get($env, 'is_multiline') === true) {
$value = str_replace("\n", "\\\n", $env->real_value);
} else {
$value = $env->real_value;
}
$dockerfile->splice(1, 0, "ARG {$env->key}={$value}");
}
} else {
foreach ($this->application->build_environment_variables_preview as $env) {
if (str($env->real_value)->contains("\n") && data_get($env, 'is_multiline') === true) {
$value = str_replace("\n", "\\\n", $env->real_value);
} else {
$value = $env->real_value;
}
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
}
}
@@ -1908,16 +2008,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($containers->count() == 0) {
return;
}
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output): {$this->application->pre_deployment_command}");
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output/errors).");
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) {
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->pre_deployment_command) . '"';
$cmd = "sh -c '" . str_replace("'", "'\''", $this->application->pre_deployment_command) . "'";
$exec = "docker exec {$containerName} {$cmd}";
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
'command' => $exec, 'hidden' => true
],
);
return;
@@ -1931,19 +2031,29 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if (empty($this->application->post_deployment_command)) {
return;
}
$this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output): {$this->application->post_deployment_command}");
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output).");
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) {
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->post_deployment_command) . '"';
$cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'";
$exec = "docker exec {$containerName} {$cmd}";
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
],
);
try {
$this->execute_remote_command(
[
'command' => $exec, 'hidden' => true, 'save' => 'post-deployment-command-output'
],
);
} catch (Exception $e) {
$post_deployment_command_output = $this->saved_outputs->get('post-deployment-command-output');
if ($post_deployment_command_output) {
$this->application_deployment_queue->addLogEntry("Post-deployment command failed.");
$this->application_deployment_queue->addLogEntry($post_deployment_command_output, 'stderr');
}
}
return;
}
}

View File

@@ -31,6 +31,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
{
try {
if ($this->application->is_public_repository()) {
ray('Public repository. Skipping comment update.');
return;
}
if ($this->status === ProcessStatus::CLOSED) {
@@ -59,7 +60,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
}
} catch (\Throwable $e) {
ray($e);
throw $e;
return $e;
}
}

View File

@@ -40,7 +40,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
return false;
}
}
public function handle(): void
public function handle()
{
// ray("checking log drain statuses for {$this->server->id}");
try {
@@ -70,7 +70,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
}
if (!$this->server->log_drain_notification_sent) {
ray('Log drain container still unhealthy. Sending notification...');
$this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
// $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
$this->server->update(['log_drain_notification_sent' => true]);
}
} else {
@@ -80,9 +80,9 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
}
}
} catch (\Throwable $e) {
send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage());
if (!isCloud()) send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage());
ray($e->getMessage());
handleError($e);
return handleError($e);
}
}
}

View File

@@ -18,12 +18,12 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncr
public $timeout = 600;
public $tries = 1;
public function __construct(private bool $force = false)
public function __construct()
{
}
public function handle(): void
{
UpdateCoolify::run(force: $this->force, async: false);
UpdateCoolify::run();
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullCoolifyImageJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 1000;
public function __construct()
{
}
public function handle(): void
{
try {
if (isDev() || isCloud()) {
return;
}
$server = Server::findOrFail(0);
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
}
$latest_version = get_latest_version_of_coolify();
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
$settings = InstanceSettings::get();
$current_version = config('version');
if (!$settings->is_auto_update_enabled) {
return;
}
if ($latest_version === $current_version) {
return;
}
if (version_compare($latest_version, $current_version, '<')) {
return;
}
instant_remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $latest_version"
], $server);
} catch (\Throwable $e) {
throw $e;
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Jobs;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullTemplatesFromCDN implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 10;
public function __construct()
{
}
public function handle(): void
{
try {
if (!isDev()) {
ray('PullTemplatesAndVersions service-templates');
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
if ($response->successful()) {
$services = $response->json();
File::put(base_path('templates/service-templates.json'), json_encode($services));
} else {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $response->status() . ' ' . $response->body());
}
}
} catch (\Throwable $e) {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $e->getMessage());
ray($e->getMessage());
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullVersionsFromCDN implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 10;
public function __construct()
{
}
public function handle(): void
{
try {
if (!isDev() && !isCloud()) {
ray('PullTemplatesAndVersions versions.json');
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
} else {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $response->status() . ' ' . $response->body());
}
}
} catch (\Throwable $e) {
throw $e;
}
}
}

View File

@@ -8,14 +8,13 @@ use App\Models\Server;
use App\Models\Application;
use App\Models\Service;
use App\Models\Team;
use App\Notifications\ScheduledTask\TaskFailed;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Throwable;
class ScheduledTaskJob implements ShouldQueue
{
@@ -114,6 +113,7 @@ class ScheduledTaskJob implements ShouldQueue
'message' => $this->task_output ?? $e->getMessage(),
]);
}
$this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
throw $e;
}

View File

@@ -41,7 +41,6 @@ class SendMessageToDiscordJob implements ShouldQueue, ShouldBeEncrypted
$payload = [
'content' => $this->text,
];
ray($payload);
Http::post($this->webhookUrl, $payload);
}
}

View File

@@ -43,7 +43,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
try {
if ($this->server->isFunctional()) {
$this->cleanup(notify: false);
$this->removeCoolifyYaml();
$this->remove_unnecessary_coolify_yaml();
if (config('coolify.is_sentinel_enabled')) {
$this->server->checkSentinel();
}
@@ -53,8 +53,44 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
ray($e->getMessage());
return handleError($e);
}
try {
// $this->check_docker_engine();
} catch (\Throwable $e) {
// Do nothing
}
}
private function removeCoolifyYaml()
private function check_docker_engine()
{
$version = instant_remote_process([
"docker info",
], $this->server, false);
if (is_null($version)) {
$os = instant_remote_process([
"cat /etc/os-release | grep ^ID=",
], $this->server, false);
$os = str($os)->after('ID=')->trim();
if ($os === 'ubuntu') {
try {
instant_remote_process([
"systemctl start docker",
], $this->server);
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
} else {
try {
instant_remote_process([
"service docker start",
], $this->server);
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
}
}
}
private function remove_unnecessary_coolify_yaml()
{
// This will remote the coolify.yaml file from the server as it is not needed on cloud servers
if (isCloud() && $this->server->id !== 0) {

View File

@@ -17,5 +17,7 @@ class ProxyStartedNotification
$this->server = data_get($event, 'data');
$this->server->setupDefault404Redirect();
$this->server->setupDynamicProxyConfiguration();
$this->server->proxy->force_stop = false;
$this->server->save();
}
}

View File

@@ -52,6 +52,9 @@ class Index extends Component
public function mount()
{
if (auth()->user()?->isMember() && auth()->user()->currentTeam()->show_boarding === true) {
return redirect()->route('dashboard');
}
$this->privateKeyName = generate_random_name();
$this->remoteServerName = generate_random_name();
if (isDev()) {

View File

@@ -10,7 +10,7 @@ class Compose extends Component
public string $base64 = '';
public $services;
public function mount() {
$this->services = getServiceTemplates();
$this->services = get_service_templates();
}
public function setService(string $selected) {
$this->base64 = data_get($this->services, $selected . '.compose');

View File

@@ -24,7 +24,7 @@ class ForcePasswordReset extends Component
}
public function render()
{
return view('livewire.force-password-reset');
return view('livewire.force-password-reset')->layout('layouts.simple');
}
public function submit()
{

View File

@@ -16,6 +16,7 @@ class Discord extends Component
'team.discord_notifications_deployments' => 'nullable|boolean',
'team.discord_notifications_status_changes' => 'nullable|boolean',
'team.discord_notifications_database_backups' => 'nullable|boolean',
'team.discord_notifications_scheduled_tasks' => 'nullable|boolean',
];
protected $validationAttributes = [
'team.discord_webhook_url' => 'Discord Webhook',

View File

@@ -28,6 +28,7 @@ class Email extends Component
'team.smtp_notifications_deployments' => 'nullable|boolean',
'team.smtp_notifications_status_changes' => 'nullable|boolean',
'team.smtp_notifications_database_backups' => 'nullable|boolean',
'team.smtp_notifications_scheduled_tasks' => 'nullable|boolean',
'team.use_instance_email_settings' => 'boolean',
'team.resend_enabled' => 'nullable|boolean',
'team.resend_api_key' => 'nullable',

View File

@@ -18,10 +18,12 @@ class Telegram extends Component
'team.telegram_notifications_deployments' => 'nullable|boolean',
'team.telegram_notifications_status_changes' => 'nullable|boolean',
'team.telegram_notifications_database_backups' => 'nullable|boolean',
'team.telegram_notifications_scheduled_tasks' => 'nullable|boolean',
'team.telegram_notifications_test_message_thread_id' => 'nullable|string',
'team.telegram_notifications_deployments_message_thread_id' => 'nullable|string',
'team.telegram_notifications_status_changes_message_thread_id' => 'nullable|string',
'team.telegram_notifications_database_backups_message_thread_id' => 'nullable|string',
'team.telegram_notifications_scheduled_tasks_thread_id' => 'nullable|string',
];
protected $validationAttributes = [
'team.telegram_token' => 'Token',

View File

@@ -31,7 +31,7 @@ class General extends Component
public ?string $initialDockerComposeLocation = null;
public ?string $initialDockerComposePrLocation = null;
public $parsedServices = [];
public null|Collection $parsedServices;
public $parsedServiceDomains = [];
protected $listeners = [
@@ -118,6 +118,10 @@ class General extends Component
{
try {
$this->parsedServices = $this->application->parseCompose();
if (is_null($this->parsedServices) || empty($this->parsedServices)) {
$this->dispatch('error', "Failed to parse your docker-compose file. Please check the syntax and try again.");
return;
}
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
}
@@ -137,7 +141,7 @@ class General extends Component
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
if ($this->application->build_pack === 'dockercompose' && !$this->application->docker_compose_raw) {
$this->initLoadingCompose = true;
$this->dispatch('info', 'Loading docker compose file...');
$this->dispatch('info', 'Loading docker compose file.');
}
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
@@ -160,6 +164,10 @@ class General extends Component
return;
}
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
if (is_null($this->parsedServices)) {
$this->dispatch('error', "Failed to parse your docker-compose file. Please check the syntax and try again.");
return;
}
$compose = $this->application->parseCompose();
$services = data_get($compose, 'services');
if ($services) {
@@ -279,7 +287,7 @@ class General extends Component
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "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.");
$showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
}
}
}
@@ -344,7 +352,7 @@ class General extends Component
$domain = data_get($service, 'domain');
if ($domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "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.");
$showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
}
check_domain_usage(resource: $this->application);
}

View File

@@ -25,6 +25,7 @@ class Heading extends Component
return [
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'check_status',
"compose_loaded" => '$refresh',
"update_links" => '$refresh',
];
}
public function mount()

View File

@@ -2,10 +2,12 @@
namespace App\Livewire\Project\Application;
use App\Actions\Docker\GetContainersStatus;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Illuminate\Support\Collection;
use Livewire\Component;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
class Previews extends Component
@@ -16,6 +18,9 @@ class Previews extends Component
public Collection $pull_requests;
public int $rate_limit_remaining;
protected $rules = [
'application.previews.*.fqdn' => 'string|nullable',
];
public function mount()
{
$this->pull_requests = collect();
@@ -33,7 +38,88 @@ class Previews extends Component
return handleError($e, $this);
}
}
public function save_preview($preview_id)
{
try {
$success = true;
$preview = $this->application->previews->find($preview_id);
if (data_get_str($preview, 'fqdn')->isNotEmpty()) {
$preview->fqdn = str($preview->fqdn)->replaceEnd(',', '')->trim();
$preview->fqdn = str($preview->fqdn)->replaceStart(',', '')->trim();
$preview->fqdn = str($preview->fqdn)->trim()->lower();
if (!validate_dns_entry($preview->fqdn, $this->application->destination->server)) {
$this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$preview->fqdn->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$success = false;
}
check_domain_usage(resource: $this->application, domain: $preview->fqdn);
}
if (!$preview) {
throw new \Exception('Preview not found');
}
$success && $preview->save();
$success && $this->dispatch('success', 'Preview saved.<br><br>Do not forget to redeploy the preview to apply the changes.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function generate_preview($preview_id)
{
$preview = $this->application->previews->find($preview_id);
if (!$preview) {
$this->dispatch('error', 'Preview not found.');
return;
}
$fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
$url = Url::fromString($fqdn);
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $preview->pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
$this->dispatch('success', 'Domain generated.');
}
public function add(int $pull_request_id, string|null $pull_request_html_url = null)
{
try {
if ($this->application->build_pack === 'dockercompose') {
$this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found && !is_null($pull_request_html_url)) {
$found = ApplicationPreview::create([
'application_id' => $this->application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
'docker_compose_domains' => $this->application->docker_compose_domains,
]);
}
$found->generate_preview_fqdn_compose();
$this->application->refresh();
} else {
$this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found && !is_null($pull_request_html_url)) {
$found = ApplicationPreview::create([
'application_id' => $this->application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
]);
}
$this->application->generate_preview_fqdn($pull_request_id);
$this->application->refresh();
$this->dispatch('update_links');
$this->dispatch('success', 'Preview added.');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function deploy(int $pull_request_id, string|null $pull_request_html_url = null)
{
try {
@@ -82,17 +168,31 @@ class Previews extends Component
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
}
}
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
$this->application->refresh();
GetContainersStatus::dispatchSync($this->application->destination->server);
$this->dispatch('reloadWindow');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function previewRefresh()
public function delete(int $pull_request_id)
{
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
try {
if ($this->application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
} else {
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
foreach ($containers as $container) {
$name = str_replace('/', '', $container['Names']);
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
}
}
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
$this->application->refresh();
$this->dispatch('update_links');
$this->dispatch('success', 'Preview deleted.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Livewire\Project\Application;
use App\Models\ApplicationPreview;
use Livewire\Component;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
class PreviewsCompose extends Component
{
public $service;
public $serviceName;
public ApplicationPreview $preview;
public function render()
{
return view('livewire.project.application.previews-compose');
}
public function save()
{
$domain = data_get($this->service, 'domain');
$docker_compose_domains = data_get($this->preview, 'docker_compose_domains');
$docker_compose_domains = json_decode($docker_compose_domains, true);
$docker_compose_domains[$this->serviceName]['domain'] = $domain;
$this->preview->docker_compose_domains = json_encode($docker_compose_domains);
$this->preview->save();
$this->dispatch('update_links');
$this->dispatch('success', 'Domain saved.');
}
public function generate()
{
$domains = collect(json_decode($this->preview->application->docker_compose_domains)) ?? collect();
$domain = $domains->first(function ($_, $key) {
return $key === $this->serviceName;
});
if ($domain) {
$domain = data_get($domain, 'domain');
$url = Url::fromString($domain);
$template = $this->preview->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$docker_compose_domains = data_get($this->preview, 'docker_compose_domains');
$docker_compose_domains = json_decode($docker_compose_domains, true);
$docker_compose_domains[$this->serviceName]['domain'] = $this->service->domain = $preview_fqdn;
$this->preview->docker_compose_domains = json_encode($docker_compose_domains);
$this->preview->save();
}
$this->dispatch('update_links');
$this->dispatch('success', 'Domain generated.');
}
}

View File

@@ -2,12 +2,13 @@
namespace App\Livewire\Project\Database\Backup;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
class Execution extends Component
{
public $database;
public $backup;
public ?ScheduledDatabaseBackup $backup;
public $executions;
public $s3s;
public function mount()

View File

@@ -7,7 +7,6 @@ use Livewire\Component;
class Index extends Component
{
public $database;
public $s3s;
public function mount()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
@@ -36,7 +35,6 @@ class Index extends Component
]);
}
$this->database = $database;
$this->s3s = currentTeam()->s3s;
}
public function render()
{

View File

@@ -2,12 +2,13 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
use Spatie\Url\Url;
class BackupEdit extends Component
{
public $backup;
public ?ScheduledDatabaseBackup $backup;
public $s3s;
public ?string $status = null;
public array $parameters;
@@ -36,7 +37,7 @@ class BackupEdit extends Component
{
$this->parameters = get_route_parameters();
if (is_null(data_get($this->backup, 's3_storage_id'))) {
$this->backup->s3_storage_id = 'default';
data_set($this->backup, 's3_storage_id', 'default');
}
}

View File

@@ -2,11 +2,12 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
class BackupExecutions extends Component
{
public $backup;
public ?ScheduledDatabaseBackup $backup = null;
public $executions = [];
public $setDeletableBackup;
public function getListeners()
@@ -20,8 +21,11 @@ class BackupExecutions extends Component
public function cleanupFailed()
{
$this->backup?->executions()->where('status', 'failed')->delete();
$this->refreshBackupExecutions();
if ($this->backup) {
$this->backup->executions()->where('status', 'failed')->delete();
$this->refreshBackupExecutions();
$this->dispatch('success', 'Failed backups cleaned up.');
}
}
public function deleteBackup($exeuctionId)
{
@@ -45,6 +49,8 @@ class BackupExecutions extends Component
}
public function refreshBackupExecutions(): void
{
$this->executions = $this->backup->executions()->get()->sortByDesc('created_at');
if ($this->backup) {
$this->executions = $this->backup->executions()->get()->sortBy('created_at');
}
}
}

View File

@@ -4,12 +4,14 @@ namespace App\Livewire\Project\Database\Clickhouse;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneClickhouse;
use Exception;
use Livewire\Component;
class General extends Component
{
public Server $server;
public StandaloneClickhouse $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -43,10 +45,11 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Illuminate\Support\Collection;
use Livewire\Component;
class CreateScheduledBackup extends Component
@@ -12,7 +13,7 @@ class CreateScheduledBackup extends Component
public bool $enabled = true;
public bool $save_s3 = false;
public $s3_storage_id;
public $s3s;
public Collection $s3s;
protected $rules = [
'frequency' => 'required|string',
@@ -24,6 +25,7 @@ class CreateScheduledBackup extends Component
];
public function mount()
{
$this->s3s = currentTeam()->s3s;
if ($this->s3s->count() > 0) {
$this->s3_storage_id = $this->s3s->first()->id;
}

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Dragonfly;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneDragonfly;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneDragonfly $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -41,10 +43,11 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Keydb;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneKeydb;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneKeydb $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -43,10 +45,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Mariadb;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneMariadb;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneMariadb $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -50,10 +52,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Mongodb;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneMongodb;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneMongodb $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -48,11 +50,13 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced()
{
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Mysql;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneMysql;
use Exception;
use Livewire\Component;
@@ -13,6 +14,7 @@ class General extends Component
protected $listeners = ['refresh'];
public StandaloneMysql $database;
public Server $server;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -50,11 +52,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced()
{
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Postgresql;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Exception;
use Livewire\Component;
@@ -13,6 +14,7 @@ use function Aws\filter;
class General extends Component
{
public StandalonePostgresql $database;
public Server $server;
public string $new_filename;
public string $new_content;
public ?string $db_url = null;
@@ -57,11 +59,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced()
{
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Redis;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneRedis;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneRedis $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -43,10 +45,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
class ScheduledBackups extends Component
@@ -9,7 +10,7 @@ class ScheduledBackups extends Component
public $database;
public $parameters;
public $type;
public $selectedBackup;
public ?ScheduledDatabaseBackup $selectedBackup;
public $selectedBackupId;
public $s3s;
protected $listeners = ['refreshScheduledBackups'];

View File

@@ -6,6 +6,7 @@ use App\Models\Application;
use App\Models\GithubApp;
use App\Models\GitlabApp;
use App\Models\Project;
use App\Models\Service;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Carbon\Carbon;
@@ -33,6 +34,8 @@ class PublicGitRepository extends Component
public $build_pack = 'nixpacks';
public bool $show_is_static = true;
public bool $new_compose_services = false;
protected $rules = [
'repository_url' => 'required|url',
'port' => 'required|numeric',
@@ -177,6 +180,31 @@ class PublicGitRepository extends Component
$project = Project::where('uuid', $project_uuid)->first();
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services ) {
$server = $destination->server;
$new_service = [
'name' => 'service' . str()->random(10),
'docker_compose_raw' => 'coolify',
'environment_id' => $environment->id,
'server_id' => $server->id,
];
if ($this->git_source === 'other') {
$new_service['git_repository'] = $this->git_repository;
$new_service['git_branch'] = $this->git_branch;
} else {
$new_service['git_repository'] = $this->git_repository;
$new_service['git_branch'] = $this->git_branch;
$new_service['source_id'] = $this->git_source->id;
$new_service['source_type'] = $this->git_source->getMorphClass();
}
$service = Service::create($new_service);
return redirect()->route('project.service.configuration', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
return;
}
if ($this->git_source === 'other') {
$application_init = [
'name' => generate_random_name(),

View File

@@ -15,10 +15,10 @@ class Select extends Component
public string $type;
public string $server_id;
public string $destination_uuid;
public Countable|array|Server $allServers = [];
public Countable|array|Server $servers = [];
public Collection|array $standaloneDockers = [];
public Collection|array $swarmDockers = [];
public Collection|null|Server $allServers;
public Collection|null|Server $servers;
public ?Collection $standaloneDockers;
public ?Collection $swarmDockers;
public array $parameters;
public Collection|array $services = [];
public Collection|array $allServices = [];
@@ -91,7 +91,7 @@ class Select extends Component
});
} else {
$this->search = null;
$this->allServices = getServiceTemplates();
$this->allServices = get_service_templates($force);
$this->services = $this->allServices->filter(function ($service, $key) {
return str_contains(strtolower($key), strtolower($this->search));
});
@@ -107,7 +107,11 @@ class Select extends Component
if ($this->includeSwarm) {
$this->servers = $this->allServers;
} else {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
if ($this->allServers instanceof Collection) {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
} else {
$this->servers = $this->allServers;
}
}
}
public function setType(string $type)
@@ -126,13 +130,21 @@ class Select extends Component
case 'mongodb':
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
if ($this->allServers instanceof Collection) {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
} else {
$this->servers = $this->allServers;
}
break;
}
if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) {
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
if ($this->allServers instanceof Collection) {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
} else {
$this->servers = $this->allServers;
}
}
if ($type === "existing-postgresql") {
$this->current_step = $type;

View File

@@ -10,9 +10,9 @@ use Livewire\Component;
class Create extends Component
{
public $type;
public $project;
public function mount()
{
$services = getServiceTemplates();
$type = str(request()->query('type'));
$destination_uuid = request()->query('destination');
$server_id = request()->query('server_id');
@@ -21,87 +21,92 @@ class Create extends Component
if (!$project) {
return redirect()->route('dashboard');
}
$this->project = $project;
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
if (!$environment) {
return redirect()->route('dashboard');
}
if (in_array($type, DATABASE_TYPES)) {
if ($type->value() === "postgresql") {
$database = create_standalone_postgresql($environment->id, $destination_uuid);
} else if ($type->value() === 'redis') {
$database = create_standalone_redis($environment->id, $destination_uuid);
} else if ($type->value() === 'mongodb') {
$database = create_standalone_mongodb($environment->id, $destination_uuid);
} else if ($type->value() === 'mysql') {
$database = create_standalone_mysql($environment->id, $destination_uuid);
} else if ($type->value() === 'mariadb') {
$database = create_standalone_mariadb($environment->id, $destination_uuid);
} else if ($type->value() === 'keydb') {
$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') {
$database = create_standalone_clickhouse($environment->id, $destination_uuid);
}
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $database->uuid,
]);
}
if ($type->startsWith('one-click-service-') && !is_null((int)$server_id)) {
$oneClickServiceName = $type->after('one-click-service-')->value();
$oneClickService = data_get($services, "$oneClickServiceName.compose");
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
if ($oneClickDotEnvs) {
$oneClickDotEnvs = str(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
return !empty($value);
});
}
if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
$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);
if (isset($type) && isset($destination_uuid) && isset($server_id)) {
$services = get_service_templates();
if (in_array($type, DATABASE_TYPES)) {
if ($type->value() === "postgresql") {
$database = create_standalone_postgresql($environment->id, $destination_uuid);
} else if ($type->value() === 'redis') {
$database = create_standalone_redis($environment->id, $destination_uuid);
} else if ($type->value() === 'mongodb') {
$database = create_standalone_mongodb($environment->id, $destination_uuid);
} else if ($type->value() === 'mysql') {
$database = create_standalone_mysql($environment->id, $destination_uuid);
} else if ($type->value() === 'mariadb') {
$database = create_standalone_mariadb($environment->id, $destination_uuid);
} else if ($type->value() === 'keydb') {
$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') {
$database = create_standalone_clickhouse($environment->id, $destination_uuid);
}
$service = Service::create($service_payload);
$service->name = "$oneClickServiceName-" . $service->uuid;
$service->save();
if ($oneClickDotEnvs?->count() > 0) {
$oneClickDotEnvs->each(function ($value) use ($service) {
$key = str()->before($value, '=');
$value = str(str()->after($value, '='));
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
$generatedValue = generateEnvValue($command->value(), $service);
}
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'service_id' => $service->id,
'is_build_time' => false,
'is_preview' => false,
]);
});
}
$service->parse(isNew: true);
return redirect()->route('project.service.configuration', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $database->uuid,
]);
}
if ($type->startsWith('one-click-service-') && !is_null((int)$server_id)) {
$oneClickServiceName = $type->after('one-click-service-')->value();
$oneClickService = data_get($services, "$oneClickServiceName.compose");
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
if ($oneClickDotEnvs) {
$oneClickDotEnvs = str(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
return !empty($value);
});
}
if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
$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) {
$oneClickDotEnvs->each(function ($value) use ($service) {
$key = str()->before($value, '=');
$value = str(str()->after($value, '='));
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
$generatedValue = generateEnvValue($command->value(), $service);
}
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'service_id' => $service->id,
'is_build_time' => false,
'is_preview' => false,
]);
});
}
$service->parse(isNew: true);
return redirect()->route('project.service.configuration', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
}
}
$this->type = $type->value();
}
$this->type = $type->value();
}
public function render()
{

View File

@@ -67,7 +67,6 @@ class Configuration extends Component
GetContainersStatus::run($this->service->server);
// dispatch_sync(new ContainerStatusJob($this->service->server));
$this->dispatch('refresh')->self();
$this->dispatch('updateStatus');
} catch (\Exception $e) {
return handleError($e, $this);
}

View File

@@ -7,13 +7,20 @@ use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Livewire\Component;
use Illuminate\Support\Str;
class FileStorage extends Component
{
public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource;
public ServiceApplication|StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase|Application $resource;
public string $fs_path;
public ?string $workdir = null;
@@ -27,7 +34,7 @@ class FileStorage extends Component
{
$this->resource = $this->fileStorage->service;
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
$this->workdir = $this->resource->service->workdir();
$this->workdir = $this->resource->service?->workdir();
$this->fs_path = Str::of($this->fileStorage->fs_path)->after('.');
} else {
$this->workdir = null;

View File

@@ -30,7 +30,6 @@ class Navbar extends Component
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
"updateStatus"=> '$refresh',
];
}
public function serviceStarted()

View File

@@ -42,7 +42,7 @@ class StackForm extends Component
$this->validationAttributes["fields.$key.value"] = $fieldKey;
}
}
$this->fields = $this->fields->sortDesc();
$this->fields = $this->fields->sortBy('name');
}
public function saveCompose($raw)
{
@@ -52,7 +52,7 @@ class StackForm extends Component
public function instantSave()
{
$this->service->save();
$this->dispatch('success', 'Service settings saved.');
$this->dispatch('success', 'Service settings saved.');
}
public function submit()

View File

@@ -24,6 +24,7 @@ class Danger extends Component
public function delete()
{
try {
// $this->authorize('delete', $this->resource);
$this->resource->delete();
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations);
return redirect()->route('project.resource.index', [

View File

@@ -43,6 +43,11 @@ class GetLogs extends Component
$this->showTimeStamps = $this->resource->is_include_timestamps;
}
}
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
}
}
}
}
public function doSomethingWithThisChunkOfOutput($output)
@@ -77,13 +82,6 @@ class GetLogs extends Component
if (!$this->server->isFunctional()) {
return;
}
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
} else {
$this->pull_request = 'branch';
}
}
if (!$refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) return;
if (!$this->numberOfLines) {
$this->numberOfLines = 1000;

View File

@@ -103,6 +103,14 @@ class Logs extends Component
}
}
$this->containers = $this->containers->sort();
if (data_get($this->query,'pull_request_id')) {
$this->containers = $this->containers->filter(function ($container) {
return str_contains($container, $this->query['pull_request_id']);
});
ray($this->containers);
}
$this->loadMetrics();
} catch (\Exception $e) {
return handleError($e, $this);

View File

@@ -3,21 +3,31 @@
namespace App\Livewire\Project\Shared\Storages;
use App\Models\Application;
use App\Models\LocalFileVolume;
use Livewire\Component;
class Add extends Component
{
public $resource;
public $uuid;
public $parameters;
public $isSwarm = false;
public string $name;
public string $mount_path;
public ?string $host_path = null;
public string $file_storage_path;
public ?string $file_storage_content = null;
public string $file_storage_directory_source;
public string $file_storage_directory_destination;
public $rules = [
'name' => 'required|string',
'mount_path' => 'required|string',
'host_path' => 'string|nullable',
'file_storage_path' => 'string',
'file_storage_content' => 'nullable|string',
'file_storage_directory_source' => 'string',
'file_storage_directory_destination' => 'string',
];
protected $listeners = ['clearAddStorage' => 'clear'];
@@ -26,10 +36,16 @@ class Add extends Component
'name' => 'name',
'mount_path' => 'mount',
'host_path' => 'host',
'file_storage_path' => 'file storage path',
'file_storage_content' => 'file storage content',
'file_storage_directory_source' => 'file storage directory source',
'file_storage_directory_destination' => 'file storage directory destination',
];
public function mount()
{
$this->file_storage_directory_source = application_configuration_dir() . "/{$this->resource->uuid}";
$this->uuid = $this->resource->uuid;
$this->parameters = get_route_parameters();
if (data_get($this->parameters, 'application_uuid')) {
$applicationUuid = $this->parameters['application_uuid'];
@@ -43,18 +59,75 @@ class Add extends Component
}
}
}
public function submit()
public function submitFileStorage()
{
try {
$this->validate($this->rules);
$this->validate([
'file_storage_path' => 'string',
'file_storage_content' => 'nullable|string',
]);
$this->file_storage_path = trim($this->file_storage_path);
$this->file_storage_path = str($this->file_storage_path)->start('/')->value();
if ($this->resource->getMorphClass() === 'App\Models\Application') {
$fs_path = application_configuration_dir() . '/' . $this->resource->uuid . $this->file_storage_path;
}
LocalFileVolume::create(
[
'fs_path' => $fs_path,
'mount_path' => $this->file_storage_path,
'content' => $this->file_storage_content,
'is_directory' => false,
'resource_id' => $this->resource->id,
'resource_type' => get_class($this->resource)
],
);
$this->dispatch('refresh_storages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submitFileStorageDirectory()
{
try {
$this->validate([
'file_storage_directory_source' => 'string',
'file_storage_directory_destination' => 'string',
]);
$this->file_storage_directory_source = trim($this->file_storage_directory_source);
$this->file_storage_directory_source = str($this->file_storage_directory_source)->start('/')->value();
$this->file_storage_directory_destination = trim($this->file_storage_directory_destination);
$this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value();
LocalFileVolume::create(
[
'fs_path' => $this->file_storage_directory_source,
'mount_path' => $this->file_storage_directory_destination,
'is_directory' => true,
'resource_id' => $this->resource->id,
'resource_type' => get_class($this->resource)
],
);
$this->dispatch('refresh_storages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submitPersistentVolume()
{
try {
$this->validate([
'name' => 'required|string',
'mount_path' => 'required|string',
'host_path' => 'string|nullable',
]);
$name = $this->uuid . '-' . $this->name;
$this->dispatch('addNewVolume', [
'name' => $name,
'mount_path' => $this->mount_path,
'host_path' => $this->host_path,
]);
$this->dispatch('closeStorageModal');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -12,6 +12,8 @@ class Show extends Component
public bool $isReadOnly = false;
public ?string $modalId = null;
public bool $isFirst = true;
public bool $isService = false;
public ?string $startedAt = null;
protected $rules = [
'storage.name' => 'required|string',

View File

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

View File

@@ -58,6 +58,12 @@ class Form extends Component
$this->server->refresh();
$this->server->settings->refresh();
}
public function updatedServerSettingsIsBuildServer()
{
$this->dispatch('serverInstalled');
$this->dispatch('serverRefresh');
$this->dispatch('proxyStatusUpdated');
}
public function instantSave()
{
try {

View File

@@ -97,6 +97,9 @@ class ByIp extends Component
if ($this->is_swarm_worker) {
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
}
if ($this->is_build_server) {
data_forget($payload, 'proxy');
}
$server = Server::create($payload);
if ($this->is_build_server) {
$this->is_swarm_manager = false;

View File

@@ -72,6 +72,8 @@ class Deploy extends Component
public function startProxy()
{
try {
$this->server->proxy->force_stop = false;
$this->server->save();
$activity = StartProxy::run($this->server);
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
} catch (\Throwable $e) {
@@ -86,17 +88,15 @@ class Deploy extends Component
instant_remote_process([
"docker service rm coolify-proxy_traefik",
], $this->server);
$this->server->proxy->status = 'exited';
$this->server->save();
$this->dispatch('proxyStatusUpdated');
} else {
instant_remote_process([
"docker rm -f coolify-proxy",
], $this->server);
$this->server->proxy->status = 'exited';
$this->server->save();
$this->dispatch('proxyStatusUpdated');
}
$this->server->proxy->status = 'exited';
$this->server->proxy->force_stop = true;
$this->server->save();
$this->dispatch('proxyStatusUpdated');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -129,6 +129,9 @@ class ValidateAndInstall extends Component
}
}
if ($this->server->isBuildServer()) {
return;
}
$this->dispatch('startProxy');
}
public function render()

View File

@@ -36,7 +36,7 @@ class Backup extends Component
public function mount()
{
$this->backup = $this->database?->scheduledBackups->first() ?? [];
$this->backup = $this->database?->scheduledBackups->first() ?? null;
$this->executions = $this->backup?->executions ?? [];
}
public function add_coolify_database()

View File

@@ -70,9 +70,8 @@ class Configuration extends Component
$this->validate();
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.");
$this->dispatch('error', "Validating DNS failed.<br><br>Make sure you have added the DNS records correctly.<br><br>{$this->settings->fqdn}->{$this->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$error_show = true;
}
}

View File

@@ -11,16 +11,16 @@ use Livewire\Component;
class Change extends Component
{
public string $webhook_endpoint;
public ?string $ipv4;
public ?string $ipv6;
public ?string $fqdn;
public ?string $ipv4 = null;
public ?string $ipv6 = null;
public ?string $fqdn = null;
public ?bool $default_permissions = true;
public ?bool $preview_deployment_permissions = true;
public ?bool $administration = false;
public $parameters;
public ?GithubApp $github_app;
public ?GithubApp $github_app = null;
public string $name;
public bool $is_system_wide;

View File

@@ -0,0 +1,128 @@
<?php
namespace App\Livewire\Team;
use App\Models\Team;
use App\Models\User;
use Livewire\Component;
class AdminView extends Component
{
public $users;
public ?string $search = "";
public bool $lots_of_users = false;
private $number_of_users_to_show = 20;
public function mount()
{
if (!isInstanceAdmin()) {
return redirect()->route('dashboard');
}
$this->getUsers();
}
public function submitSearch()
{
if ($this->search !== "") {
$this->users = User::where(function ($query) {
$query->where('name', 'like', "%{$this->search}%")
->orWhere('email', 'like', "%{$this->search}%");
})->get()->filter(function ($user) {
return $user->id !== auth()->id();
});
} else {
$this->getUsers();
}
}
public function getUsers()
{
$users = User::where('id', '!=', auth()->id())->get();
if ($users->count() > $this->number_of_users_to_show) {
$this->lots_of_users = true;
$this->users = $users->take($this->number_of_users_to_show);
} else {
$this->lots_of_users = false;
$this->users = $users;
}
}
private function finalizeDeletion(User $user, Team $team)
{
$servers = $team->servers;
foreach ($servers as $server) {
$resources = $server->definedResources();
foreach ($resources as $resource) {
ray("Deleting resource: " . $resource->name);
$resource->forceDelete();
}
ray("Deleting server: " . $server->name);
$server->forceDelete();
}
$projects = $team->projects;
foreach ($projects as $project) {
ray("Deleting project: " . $project->name);
$project->forceDelete();
}
$team->members()->detach($user->id);
ray('Deleting team: ' . $team->name);
$team->delete();
}
public function delete($id)
{
if (!auth()->user()->isInstanceAdmin()) {
return $this->dispatch('error', 'You are not authorized to delete users');
}
$user = User::find($id);
$teams = $user->teams;
foreach ($teams as $team) {
ray($team->name);
$user_alone_in_team = $team->members->count() === 1;
if ($team->id === 0) {
if ($user_alone_in_team) {
ray('user is alone in the root team, do nothing');
return $this->dispatch('error', 'User is alone in the root team, cannot delete');
}
}
if ($user_alone_in_team) {
ray('user is alone in the team');
$this->finalizeDeletion($user, $team);
continue;
}
ray('user is not alone in the team');
if ($user->isOwner()) {
$found_other_owner_or_admin = $team->members->filter(function ($member) {
return $member->pivot->role === 'owner' || $member->pivot->role === 'admin';
})->where('id', '!=', $user->id)->first();
if ($found_other_owner_or_admin) {
ray('found other owner or admin');
$team->members()->detach($user->id);
continue;
} else {
$found_other_member_who_is_not_owner = $team->members->filter(function ($member) {
return $member->pivot->role === 'member';
})->first();
if ($found_other_member_who_is_not_owner) {
ray('found other member who is not owner');
$found_other_member_who_is_not_owner->pivot->role = 'owner';
$found_other_member_who_is_not_owner->pivot->save();
$team->members()->detach($user->id);
} else {
// This should never happen as if the user is the only member in the team, the team should be deleted already.
ray('found no other member who is not owner');
$this->finalizeDeletion($user, $team);
}
continue;
}
} else {
ray('user is not owner');
$team->members()->detach($user->id);
}
}
ray("Deleting user: " . $user->name);
$user->delete();
$this->getUsers();
}
public function render()
{
return view('livewire.team.admin-view');
}
}

View File

@@ -17,6 +17,10 @@ class InviteLink extends Component
public string $email;
public string $role = 'member';
protected $rules = [
'email' => 'required|email',
'role' => 'required|string',
];
public function mount()
{
$this->email = isDev() ? 'test3@example.com' : '';
@@ -34,6 +38,7 @@ class InviteLink extends Component
private function generate_invite_link(bool $sendEmail = false)
{
try {
$this->validate();
$member_emails = currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) {
return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");

View File

@@ -5,11 +5,9 @@ namespace App\Livewire;
use App\Actions\Server\UpdateCoolify;
use Livewire\Component;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
class Upgrade extends Component
{
use WithRateLimiting;
public bool $showProgress = false;
public bool $updateInProgress = false;
public bool $isUpgradeAvailable = false;
@@ -31,9 +29,8 @@ class Upgrade extends Component
if ($this->updateInProgress) {
return;
}
$this->rateLimit(1, 60);
$this->updateInProgress = true;
UpdateCoolify::run(force: true, async: true);
UpdateCoolify::run(manual_update: true);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -10,6 +10,7 @@ use Illuminate\Support\Collection;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str;
use RuntimeException;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
@@ -113,6 +114,18 @@ class Application extends BaseModel
}
return null;
}
public function failedTaskLink($task_uuid)
{
if (data_get($this, 'environment.project.uuid')) {
return route('project.application.scheduled-tasks', [
'project_uuid' => data_get($this, 'environment.project.uuid'),
'environment_name' => data_get($this, 'environment.name'),
'application_uuid' => data_get($this, 'uuid'),
'task_uuid' => $task_uuid
]);
}
return null;
}
public function settings()
{
return $this->hasOne(ApplicationSetting::class);
@@ -201,6 +214,13 @@ class Application extends BaseModel
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
return "https://{$git_repository}/commit/{$link}";
}
if (str($this->git_repository)->contains('bitbucket')) {
$git_repository = str_replace('.git', '', $this->git_repository);
$url = Url::fromString($git_repository);
$url = $url->withUserInfo('');
$url = $url->withPath($url->getPath() . '/commits/' . $link);
return $url->__toString();
}
return $this->git_repository;
}
public function dockerfileLocation(): Attribute
@@ -459,7 +479,7 @@ class Application extends BaseModel
}
public function get_last_successful_deployment()
{
return ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'finished')->orderBy('created_at', 'desc')->first();
return ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'finished')->where('pull_request_id', 0)->orderBy('created_at', 'desc')->first();
}
public function get_last_days_deployments()
{
@@ -649,7 +669,9 @@ class Application extends BaseModel
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}";
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
}
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
if (!$only_checkout) {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
}
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
} else {
@@ -839,14 +861,10 @@ class Application extends BaseModel
instant_remote_process($commands, $this->destination->server, false);
}
function parseCompose(int $pull_request_id = 0)
function parseCompose(int $pull_request_id = 0, ?int $preview_id = null)
{
if ($this->docker_compose_raw) {
$mainCompose = parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id);
if ($this->getMorphClass() === 'App\Models\Application' && $this->docker_compose_pr_raw) {
parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, is_pr: true);
}
return $mainCompose;
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
} else {
return collect([]);
}
@@ -868,7 +886,8 @@ class Application extends BaseModel
// }
$commands = collect([
"rm -rf /tmp/{$uuid}",
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
"mkdir -p /tmp/{$uuid}",
"cd /tmp/{$uuid}",
$cloneCommand,
"git sparse-checkout init --cone",
"git sparse-checkout set {$fileList->implode(' ')}",
@@ -879,29 +898,15 @@ class Application extends BaseModel
if (!$composeFileContent) {
$this->docker_compose_location = $initialDockerComposeLocation;
$this->save();
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile");
$commands = collect([
"rm -rf /tmp/{$uuid}",
]);
instant_remote_process($commands, $this->destination->server, false);
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
} else {
$this->docker_compose_raw = $composeFileContent;
$this->save();
}
// if ($composeFile === $prComposeFile) {
// $this->docker_compose_pr_raw = $composeFileContent;
// $this->save();
// } else {
// $commands = collect([
// "cd /tmp/{$uuid}",
// "cat .$workdir$prComposeFile",
// ]);
// $composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
// if (!$composePrFileContent) {
// $this->docker_compose_pr_location = $initialDockerComposePrLocation;
// $this->save();
// throw new \Exception("Could not load compose file from $workdir$prComposeFile");
// } else {
// $this->docker_compose_pr_raw = $composePrFileContent;
// $this->save();
// }
// }
$commands = collect([
"rm -rf /tmp/{$uuid}",
@@ -1043,4 +1048,30 @@ class Application extends BaseModel
}
}
}
function generate_preview_fqdn(int $pull_request_id)
{
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pull_request_id);
if (is_null(data_get($preview, 'fqdn')) && $this->fqdn) {
if (str($this->fqdn)->contains(',')) {
$url = Url::fromString(str($this->fqdn)->explode(',')[0]);
$preview_fqdn = getFqdnWithoutPort(str($this->fqdn)->explode(',')[0]);
} else {
$url = Url::fromString($this->fqdn);
if (data_get($preview, 'fqdn')) {
$preview_fqdn = getFqdnWithoutPort(data_get($preview, 'fqdn'));
}
}
$template = $this->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
}
return $preview;
}
}

View File

@@ -2,6 +2,9 @@
namespace App\Models;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
class ApplicationPreview extends BaseModel
{
protected $guarded = [];
@@ -34,4 +37,26 @@ class ApplicationPreview extends BaseModel
{
return $this->belongsTo(Application::class);
}
function generate_preview_fqdn_compose()
{
$domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect();
foreach ($domains as $service_name => $domain) {
$domain = data_get($domain, 'domain');
$url = Url::fromString($domain);
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$docker_compose_domains = data_get($this, 'docker_compose_domains');
$docker_compose_domains = json_decode($docker_compose_domains, true);
$docker_compose_domains[$service_name]['domain'] = $preview_fqdn;
$docker_compose_domains = json_encode($docker_compose_domains);
$this->docker_compose_domains = $docker_compose_domains;
$this->save();
}
}
}

View File

@@ -8,6 +8,18 @@ use Illuminate\Database\Eloquent\Model;
class Environment extends Model
{
protected $guarded = [];
protected static function booted()
{
static::deleting(function ($environment) {
$shared_variables = $environment->environment_variables();
foreach ($shared_variables as $shared_variable) {
ray('Deleting environment shared variable: ' . $shared_variable->name);
$shared_variable->delete();
}
});
}
public function isEmpty()
{
return $this->applications()->count() == 0 &&

View File

@@ -90,6 +90,7 @@ class EnvironmentVariable extends Model
return true;
}
$found_in_compose = false;
$found_in_args = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (!$compose) {
@@ -102,21 +103,35 @@ class EnvironmentVariable extends Model
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
if ($environments->isEmpty()) {
$args = collect(data_get($service, 'build.args'));
if ($environments->isEmpty() && $args->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;
}
$found_in_args = $args->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_args) {
break;
}
}
return $found_in_compose;
return $found_in_compose || $found_in_args;
}
);
}

View File

@@ -25,6 +25,11 @@ class Project extends BaseModel
static::deleting(function ($project) {
$project->environments()->delete();
$project->settings()->delete();
$shared_variables = $project->environment_variables();
foreach ($shared_variables as $shared_variable) {
ray('Deleting project shared variable: ' . $shared_variable->name);
$shared_variable->delete();
}
});
}
public function environment_variables()
@@ -55,6 +60,7 @@ class Project extends BaseModel
return $this->hasManyThrough(Application::class, Environment::class);
}
public function postgresqls()
{
return $this->hasManyThrough(StandalonePostgresql::class, Environment::class);
@@ -91,4 +97,7 @@ class Project extends BaseModel
{
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count() + $this->keydbs()->count() + $this->dragonflies()->count() + $this->services()->count() + $this->clickhouses()->count();
}
public function databases() {
return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get());
}
}

View File

@@ -525,7 +525,7 @@ $schema://$host {
// Reached max number of retries
if ($this->unreachable_notification_sent === false) {
ray('Server unreachable, sending notification...');
$this->team?->notify(new Unreachable($this));
// $this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]);
}
if ($this->settings->is_reachable === true) {
@@ -825,7 +825,7 @@ $schema://$host {
'unreachable_count' => 0,
]);
if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team?->notify(new Revived($server));
// $server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]);
}
return ['uptime' => true, 'error' => null];
@@ -897,7 +897,9 @@ $schema://$host {
}
public function validateDockerEngineVersion()
{
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this, false);
$dockerVersionRaw = instant_remote_process(["docker version --format json"], $this, false);
$dockerVersionJson = json_decode($dockerVersionRaw, true);
$dockerVersion = data_get($dockerVersionJson, 'Server.Version', '0.0.0');
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->settings->is_usable = false;
@@ -927,4 +929,7 @@ $schema://$host {
}
return $this->user !== 'root';
}
public function isBuildServer() {
return $this->settings->is_build_server;
}
}

View File

@@ -472,6 +472,72 @@ class Service extends BaseModel
}
$fields->put('Admin', $data->toArray());
break;
case str($image)?->contains('vaultwarden'):
$data = collect([]);
$DATABASE_URL = $this->environment_variables()->where('key', 'DATABASE_URL')->first();
$ADMIN_TOKEN = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_ADMIN')->first();
$SIGNUP_ALLOWED = $this->environment_variables()->where('key', 'SIGNUP_ALLOWED')->first();
$PUSH_ENABLED = $this->environment_variables()->where('key', 'PUSH_ENABLED')->first();
$PUSH_INSTALLATION_ID = $this->environment_variables()->where('key', 'PUSH_SERVICE_ID')->first();
$PUSH_INSTALLATION_KEY = $this->environment_variables()->where('key', 'PUSH_SERVICE_KEY')->first();
if ($DATABASE_URL) {
$data = $data->merge([
'Database URL' => [
'key' => data_get($DATABASE_URL, 'key'),
'value' => data_get($DATABASE_URL, 'value'),
],
]);
}
if ($ADMIN_TOKEN) {
$data = $data->merge([
'Admin Password' => [
'key' => data_get($ADMIN_TOKEN, 'key'),
'value' => data_get($ADMIN_TOKEN, 'value'),
'isPassword' => true,
],
]);
}
if ($SIGNUP_ALLOWED) {
$data = $data->merge([
'Signup Allowed' => [
'key' => data_get($SIGNUP_ALLOWED, 'key'),
'value' => data_get($SIGNUP_ALLOWED, 'value'),
'rules' => 'required|string|in:true,false',
],
]);
}
if ($PUSH_ENABLED) {
$data = $data->merge([
'Push Enabled' => [
'key' => data_get($PUSH_ENABLED, 'key'),
'value' => data_get($PUSH_ENABLED, 'value'),
'rules' => 'required|string|in:true,false',
],
]);
}
if ($PUSH_INSTALLATION_ID) {
$data = $data->merge([
'Push Installation ID' => [
'key' => data_get($PUSH_INSTALLATION_ID, 'key'),
'value' => data_get($PUSH_INSTALLATION_ID, 'value'),
],
]);
}
if ($PUSH_INSTALLATION_KEY) {
$data = $data->merge([
'Push Installation Key' => [
'key' => data_get($PUSH_INSTALLATION_KEY, 'key'),
'value' => data_get($PUSH_INSTALLATION_KEY, 'value'),
'isPassword' => true,
],
]);
}
$fields->put('Vaultwarden', $data);
break;
}
}
$databases = $this->databases()->get();
@@ -653,9 +719,21 @@ class Service extends BaseModel
}
return null;
}
public function failedTaskLink($task_uuid)
{
if (data_get($this, 'environment.project.uuid')) {
return route('project.service.scheduled-tasks', [
'project_uuid' => data_get($this, 'environment.project.uuid'),
'environment_name' => data_get($this, 'environment.name'),
'service_uuid' => data_get($this, 'uuid'),
'task_uuid' => $task_uuid
]);
}
return null;
}
public function documentation()
{
$services = getServiceTemplates();
$services = get_service_templates();
$service = data_get($services, str($this->name)->beforeLast('-')->value, []);
return data_get($service, 'documentation', config('constants.docs.base_url'));
}

View File

@@ -40,6 +40,13 @@ class ServiceApplication extends BaseModel
{
return 'service';
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function workdir() {
return service_configuration_dir() . "/{$this->service->uuid}";
}
public function serviceType()
{
$found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) {

View File

@@ -59,6 +59,13 @@ class ServiceDatabase extends BaseModel
}
return "{$realIp}:{$port}";
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function workdir() {
return service_configuration_dir() . "/{$this->service->uuid}";
}
public function service()
{
return $this->belongsTo(Service::class);

View File

@@ -26,6 +26,34 @@ class Team extends Model implements SendsDiscord, SendsEmail
throw new \Exception('You are not allowed to update this team.');
}
});
static::deleting(function ($team) {
$keys = $team->privateKeys;
foreach ($keys as $key) {
ray('Deleting key: ' . $key->name);
$key->delete();
}
$sources = $team->sources();
foreach ($sources as $source) {
ray('Deleting source: ' . $source->name);
$source->delete();
}
$tags = Tag::whereTeamId($team->id)->get();
foreach ($tags as $tag) {
ray('Deleting tag: ' . $tag->name);
$tag->delete();
}
$shared_variables = $team->environment_variables();
foreach ($shared_variables as $shared_variable) {
ray('Deleting team shared variable: ' . $shared_variable->name);
$shared_variable->delete();
}
$s3s = $team->s3s;
foreach ($s3s as $s3) {
ray('Deleting s3: ' . $s3->name);
$s3->delete();
}
});
}
public function routeNotificationForDiscord()

View File

@@ -22,6 +22,8 @@ class TelegramChannel
$topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id');
break;
case 'App\Notifications\Application\StatusChanged':
case 'App\Notifications\Container\ContainerRestarted':
case 'App\Notifications\Container\ContainerStopped':
$topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
break;
case 'App\Notifications\Application\DeploymentSuccess':
@@ -32,6 +34,9 @@ class TelegramChannel
case 'App\Notifications\Database\BackupFailed':
$topicId = data_get($notifiable, 'telegram_notifications_database_backups_message_thread_id');
break;
case 'App\Notifications\ScheduledTask\TaskFailed':
$topicId = data_get($notifiable, 'telegram_notifications_scheduled_tasks_thread_id');
break;
}
if (!$telegramToken || !$chatId || !$message) {
return;

View File

@@ -31,7 +31,7 @@ class ContainerRestarted extends Notification implements ShouldQueue
$mail->view('emails.container-restarted', [
'containerName' => $this->name,
'serverName' => $this->server->name,
'url' => $this->url ,
'url' => $this->url,
]);
return $mail;
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Notifications\ScheduledTask;
use App\Models\ScheduledTask;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class TaskFailed extends Notification implements ShouldQueue
{
use Queueable;
public $backoff = 10;
public $tries = 2;
public ?string $url = null;
public function __construct(public ScheduledTask $task, public string $output)
{
if ($task->application) {
$this->url = $task->application->failedTaskLink($task->uuid);
} else if ($task->service) {
$this->url = $task->service->failedTaskLink($task->uuid);
}
}
public function via(object $notifiable): array
{
return setNotificationChannels($notifiable, 'scheduled_tasks');
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject("Coolify: [ACTION REQUIRED] Scheduled task ({$this->task->name}) failed.");
$mail->view('emails.scheduled-task-failed', [
'task' => $this->task,
'url' => $this->url,
'output' => $this->output,
]);
return $mail;
}
public function toDiscord(): string
{
return "Coolify: Scheduled task ({$this->task->name}, [link]({$this->url})) failed with output: {$this->output}";
}
public function toTelegram(): array
{
$message = "Coolify: Scheduled task ({$this->task->name}) failed with output: {$this->output}";
if ($this->url) {
$buttons[] = [
"text" => "Open task in Coolify",
"url" => (string) $this->url
];
}
return [
"message" => $message,
];
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Policies;
use App\Models\Application;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class ApplicationPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Application $application): bool
{
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Application $application): bool
{
return true;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Application $application): bool
{
if ($user->isAdmin()) {
return true;
}
return false;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Application $application): bool
{
return true;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Application $application): bool
{
return true;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Policies;
use App\Models\Service;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class ServicePolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Service $service): bool
{
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Service $service): bool
{
return true;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Service $service): bool
{
if ($user->isAdmin()) {
return true;
}
return false;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Service $service): bool
{
return true;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Service $service): bool
{
if ($user->isAdmin()) {
return true;
}
return false;
}
public function stop(User $user, Service $service): bool
{
if ($user->isAdmin()) {
return true;
}
return false;
}
}

View File

@@ -43,16 +43,10 @@ function queue_application_deployment(Application $application, string $deployme
]);
if ($no_questions_asked) {
$deployment->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
} else if (next_queuable($server_id, $application_id)) {
$deployment->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
@@ -63,6 +57,7 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment)
$deployment->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
@@ -75,6 +70,7 @@ function queue_next_deployment(Application $application)
$next_found->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next_found->id,
));

View File

@@ -38,7 +38,7 @@ const SPECIFIC_SERVICES = [
// Based on /etc/os-release
const SUPPORTED_OS = [
'ubuntu debian raspbian',
'centos fedora rhel ol rocky amzn',
'centos fedora rhel ol rocky amzn almalinux',
'sles opensuse-leap opensuse-tumbleweed'
];

View File

@@ -173,14 +173,14 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
{
if ($resource->getMorphClass() === 'App\Models\ServiceApplication') {
$uuid = $resource->uuid;
$server = $resource->service->server;
$environment_variables = $resource->service->environment_variables;
$uuid = data_get($resource, 'uuid');
$server = data_get($resource, 'service.server');
$environment_variables = data_get($resource, 'service.environment_variables');
$type = $resource->serviceType();
} else if ($resource->getMorphClass() === 'App\Models\Application') {
$uuid = $resource->uuid;
$server = $resource->destination->server;
$environment_variables = $resource->environment_variables;
$uuid = data_get($resource, 'uuid');
$server = data_get($resource, 'destination.server');
$environment_variables = data_get($resource, 'environment_variables');
$type = $resource->serviceType();
}
if (is_null($server) || is_null($type)) {
@@ -234,7 +234,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
}
return $payload;
}
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null)
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null)
{
$labels = collect([]);
if ($serviceLabels) {
@@ -247,7 +247,6 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
$url = Url::fromString($domain);
$host = $url->getHost();
$path = $url->getPath();
// $stripped_path = str($path)->replaceEnd('/', '');
$schema = $url->getScheme();
$port = $url->getPort();
@@ -273,7 +272,7 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
}
return $labels->sort();
}
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false)
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
@@ -331,7 +330,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$http_label = "http-{$loop}-{$uuid}-{$service_name}";
$https_label = "https-{$loop}-{$uuid}-{$service_name}";
}
if (str($image)->contains('ghost')) {
$labels->push("traefik.http.middlewares.redir-ghost.redirectregex.regex=^{$path}/(.*)");
$labels->push("traefik.http.middlewares.redir-ghost.redirectregex.replacement=/$1");
}
if ($schema === 'https') {
// Set labels for https
$labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
@@ -341,9 +343,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
}
if ($path !== '/') {
if ($is_stripprefix_enabled) {
$middlewares = collect([]);
if ($is_stripprefix_enabled && !str($image)->contains('ghost')) {
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
$middlewares = collect(["{$https_label}-stripprefix"]);
$middlewares->push("{$https_label}-stripprefix");
}
if ($is_gzip_enabled) {
$middlewares->push('gzip');
@@ -354,6 +357,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@@ -369,6 +375,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@@ -396,9 +405,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
}
if ($path !== '/') {
if ($is_stripprefix_enabled) {
$middlewares = collect([]);
if ($is_stripprefix_enabled && !str($image)->contains('ghost')) {
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
$middlewares = collect(["{$http_label}-stripprefix"]);
$middlewares->push("{$https_label}-stripprefix");
}
if ($is_gzip_enabled) {
$middlewares->push('gzip');
@@ -409,6 +419,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
@@ -424,6 +437,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
@@ -449,13 +465,34 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
$appUuid = $appUuid . '-pr-' . $pull_request_id;
}
$labels = collect([]);
if ($application->fqdn) {
if ($pull_request_id !== 0) {
if ($pull_request_id === 0) {
if ($application->fqdn) {
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
$labels = $labels->merge(fqdnLabelsForTraefik(
uuid: $appUuid,
domains: $domains,
onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
// Add Caddy labels
$labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network,
uuid: $appUuid,
domains: $domains,
onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
}
} else {
if (data_get($preview,'fqdn')) {
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
} else {
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
$domains = collect([]);
}
// Add Traefik labels
$labels = $labels->merge(fqdnLabelsForTraefik(
uuid: $appUuid,
domains: $domains,
@@ -474,6 +511,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
}
return $labels->all();
}

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