Compare commits

...

291 Commits

Author SHA1 Message Date
Andras Bacsai
de7380fb0c Merge pull request #2423 from coollabsio/next
v4.0.0-beta.297
2024-06-11 13:28:49 +02:00
Andras Bacsai
35bf4a111c Merge branch 'next' of github.com:coollabsio/coolify into next 2024-06-11 13:17:17 +02:00
Andras Bacsai
d5be00d29d chore: Add ApplicationDeploymentJob and pint.json 2024-06-11 13:17:15 +02:00
andrasbacsai
e63710dfb4 Fix styling 2024-06-11 11:13:36 +00:00
Andras Bacsai
8b95b3c1bf fix: just restart 2024-06-11 13:12:53 +02:00
Andras Bacsai
b8ec3f5704 Merge pull request #2412 from Dogacel/patch-1
fix: show proper error message on installation if OS is not supported for "archarm" / Orange Pi OS
2024-06-11 12:38:34 +02:00
andrasbacsai
5dc79159b4 Fix styling 2024-06-11 10:38:24 +00:00
Andras Bacsai
5368c49e68 Merge branch 'next' into patch-1 2024-06-11 12:38:22 +02:00
Andras Bacsai
8731d0416b chore: Add isAnyDeploymentInprogress function to check if any deployments are in progress 2024-06-11 12:37:39 +02:00
Andras Bacsai
d05151466c Merge pull request #2415 from tuarrep/patch-2
Fix Application `isDeploymentInprogress` method
2024-06-11 12:25:21 +02:00
Andras Bacsai
367a373904 feat: Update homepage.yaml with environment variables and volumes 2024-06-11 12:19:30 +02:00
Andras Bacsai
533587ce4e Merge pull request #2416 from cksidharthan/feat/add-homepage
Feat/add homepage
2024-06-11 12:14:33 +02:00
Andras Bacsai
db0ddee9f4 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-06-11 11:59:10 +02:00
Andras Bacsai
d5bad67e17 feat: Add logos for new sponsors
Added logos for new sponsors: Hetzner, Logto, BC Direct, QuantCDN, and Arcjet.
2024-06-11 11:59:08 +02:00
Andras Bacsai
9e4c8c52d2 Merge pull request #2434 from dotfrag/fix/install-script-arch
fix: install.sh do not reinstall packages on arch
2024-06-11 11:39:50 +02:00
Andras Bacsai
8ca84ee6f4 Merge pull request #2440 from holasoyender/next
fix: Setup script doesnt link to the correct source code file
2024-06-11 11:38:38 +02:00
Andras Bacsai
5ebbd769e8 Merge pull request #2438 from Thijmen/php-codestyle
Add PHP Codestyles
2024-06-11 11:37:17 +02:00
andrasbacsai
47051127af Fix styling 2024-06-11 09:36:42 +00:00
Andras Bacsai
7ad2e1ca05 Merge branch 'next' into php-codestyle 2024-06-11 11:36:07 +02:00
Andras Bacsai
7345ccbbee feat: easily redirect between www-and-non-www domains 2024-06-11 11:32:08 +02:00
holasoyender
5524f80ea3 fix: Setup script doesnt link to the correct source code file 2024-06-10 23:36:57 +02:00
Thijmen
d86274cc37 Fix styling 2024-06-10 20:43:34 +00:00
Thijmen Stavenuiter
41fb6a1fc9 Set on push 2024-06-10 22:42:56 +02:00
Thijmen Stavenuiter
8e9842df14 Add workflow (thanks to https://github.com/spatie/package-skeleton-laravel/blob/main/.github/workflows/fix-php-code-style-issues.yml) 2024-06-10 22:38:39 +02:00
Thijmen Stavenuiter
4083f4db9f Add laravel/pint as dev dependency 2024-06-10 22:31:56 +02:00
Andras Bacsai
a125c0032b chore: Remove commented out code for docker container removal 2024-06-10 21:57:44 +02:00
Andras Bacsai
2e4fd1530c fix: multiline build args 2024-06-10 21:57:13 +02:00
dotfrag
be08a2650c fix: install.sh do not reinstall packages on arch 2024-06-10 22:17:31 +03:00
Andras Bacsai
e08dc777df show errors in install script 2024-06-10 13:54:19 +02:00
Andras Bacsai
239f7fb35d chore: Update logo file path in logto.yaml 2024-06-10 13:03:09 +02:00
Andras Bacsai
1cb8354aca Merge pull request #2400 from Idorobots/fix-firefly-mariadb-healthcheck
Fix firefly mariadb healthcheck preventing app startup.
2024-06-10 13:02:53 +02:00
Andras Bacsai
2465b4ffd7 chore: Update logo file path in logto.yaml 2024-06-10 13:01:55 +02:00
Andras Bacsai
4f73ea0879 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-06-10 13:01:41 +02:00
Andras Bacsai
f4eb17f616 Merge pull request #2394 from Idorobots/fix-install-on-manjaro-arm
Adds support for installing on ARM-based Manjaro distributions.
2024-06-10 13:01:43 +02:00
Andras Bacsai
b5ce738ba2 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-06-10 12:58:29 +02:00
Andras Bacsai
3155d4afdb Merge pull request #2386 from Yarmeli/fix-logto-healthcheck
Fix postgres health check throwing fatal errors
2024-06-10 12:58:22 +02:00
Andras Bacsai
f444696b84 chore: Update page title in resource index view 2024-06-10 12:57:03 +02:00
Andras Bacsai
bdde9b063c Merge pull request #2381 from webdevcody/persist-name-on-failed-submit-2
Persist Registration Name & Email on password validation errors
2024-06-10 12:52:00 +02:00
Andras Bacsai
ef98bd107f Merge branch 'next' into persist-name-on-failed-submit-2 2024-06-10 12:51:14 +02:00
Andras Bacsai
dfb259d822 Merge pull request #2380 from OG-Jons/refactor/remove-duplications-from-resource-overview
refactor: replaces duplications in code with a single function
2024-06-10 12:50:05 +02:00
Andras Bacsai
13c34fd26d Merge branch 'next' into refactor/remove-duplications-from-resource-overview 2024-06-10 12:48:33 +02:00
Andras Bacsai
b3bde5782a Update version to 4.0.0-beta.297 2024-06-10 12:10:28 +02:00
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
Sidharthan
82e7348cf2 feat: add homepage template 2024-06-09 21:39:36 +00:00
Andras Bacsai
d1128c7a1e fix: multiline variable should be literal + should be multiline in bash with \ 2024-06-09 22:37:23 +02:00
Nicolas Perraut
5674879e23 Fix Application isDeploymentInprogress method 2024-06-09 22:12:13 +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
Doğaç Eldenk
d6c2c7ef02 Merge branch 'next' into patch-1 2024-06-09 13:21:28 -05:00
Doğaç Eldenk
2af1ccd8b2 fix error message not appearing in Orange OS 2024-06-09 13:19:59 -05: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
Kajetan Rzepecki
00ed54799f Fix Firefly III MariaDB health check. 2024-06-09 13:14:07 +02:00
Kajetan Rzepecki
fae77231a5 Allow installing on ARM-based Manjaro distribution
Manjaro ARM lists `manjaro-arm` as the OS type, so the install.sh script would fail early without any message indicating a problem.
2024-06-08 18:23:13 +02:00
Yarmeli
2083941361 Fix postgres health check throwing fatal errors 2024-06-08 15:31:59 +01:00
Cody Seibert
5ec517e3bc more clean up 2024-06-07 22:05:42 -04:00
Cody Seibert
780a3b1827 simplify registration form 2024-06-07 22:02:01 -04:00
Cody Seibert
61aa086cb5 also adding id to fix field level validations 2024-06-07 21:38:49 -04:00
Cody Seibert
0a08f9d3f8 adding logic to show the previous submitted name and email when registration fails validation 2024-06-07 21:19:09 -04:00
OG-Jons
c3de13e0d8 refactor: replaces duplications in code with a single function 2024-06-08 00:00:33 +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
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
617 changed files with 10073 additions and 5902 deletions

View File

@@ -1,17 +1,28 @@
name: Bug report name: Bug report
description: Create a new bug report description: 'Create a new bug report.'
title: '[Bug]: ' title: '[Bug]: '
body: 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 - type: textarea
attributes: attributes:
label: Description label: Description
description: A clear and concise description of the problem description: A clear and concise description of the problem
validations:
required: true
- type: textarea - type: textarea
attributes: attributes:
label: Minimal Reproduction (if possible, example repository) 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: validations:
required: true required: true
- type: textarea - type: textarea

View File

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

View File

@@ -0,0 +1,25 @@
name: Fix PHP code style issues
on: [push]
permissions:
contents: write
jobs:
php-code-styling:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Fix PHP code style issues
uses: aglipanci/laravel-pint-action@2.4
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Fix styling

View File

@@ -29,7 +29,7 @@ jobs:
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: docker/prod-ssu/Dockerfile file: docker/prod/Dockerfile
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
@@ -51,7 +51,7 @@ jobs:
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: docker/prod-ssu/Dockerfile file: docker/prod/Dockerfile
platforms: linux/aarch64 platforms: linux/aarch64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 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 # About the Project
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
@@ -28,19 +31,26 @@ https://coolify.io/sponsorships
Thank you so much! Thank you so much!
Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)! Special thanks to our biggest sponsors!
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a> <a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="200"/></a>
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="200"/></a>
<a href="https://bc.direct/?utm_source=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
<a href="https://www.quantcdn.io/?utm_source=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="200"/></a>
<a href="https://arcjet.com/?utm_source=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
## Github Sponsors ($40+) ## 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://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"> <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> <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://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://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://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://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/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> <a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>

View File

@@ -3,17 +3,17 @@
namespace App\Actions\Application; namespace App\Actions\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\StandaloneDocker;
use App\Notifications\Application\StatusChanged;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
class StopApplication class StopApplication
{ {
use AsAction; use AsAction;
public function handle(Application $application) public function handle(Application $application)
{ {
if ($application->destination->server->isSwarm()) { if ($application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server); instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
return; return;
} }
@@ -23,7 +23,7 @@ class StopApplication
$servers->push($server); $servers->push($server);
}); });
foreach ($servers as $server) { foreach ($servers as $server) {
if (!$server->isFunctional()) { if (! $server->isFunctional()) {
return 'Server is not functional'; return 'Server is not functional';
} }
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0); $containers = getCurrentApplicationContainerStatus($server, $application->id, 0);

View File

@@ -9,12 +9,13 @@ use Lorisleiva\Actions\Concerns\AsAction;
class StopApplicationOneServer class StopApplicationOneServer
{ {
use AsAction; use AsAction;
public function handle(Application $application, Server $server) public function handle(Application $application, Server $server)
{ {
if ($application->destination->server->isSwarm()) { if ($application->destination->server->isSwarm()) {
return; return;
} }
if (!$server->isFunctional()) { if (! $server->isFunctional()) {
return 'Server is not functional'; return 'Server is not functional';
} }
try { try {
@@ -32,6 +33,7 @@ class StopApplicationOneServer
} }
} catch (\Exception $e) { } catch (\Exception $e) {
ray($e->getMessage()); ray($e->getMessage());
return $e->getMessage(); return $e->getMessage();
} }
} }

View File

@@ -14,6 +14,7 @@ use Spatie\Activitylog\Models\Activity;
class PrepareCoolifyTask class PrepareCoolifyTask
{ {
protected Activity $activity; protected Activity $activity;
protected CoolifyTaskArgs $remoteProcessArgs; protected CoolifyTaskArgs $remoteProcessArgs;
public function __construct(CoolifyTaskArgs $remoteProcessArgs) public function __construct(CoolifyTaskArgs $remoteProcessArgs)
@@ -28,12 +29,12 @@ class PrepareCoolifyTask
->withProperties($properties) ->withProperties($properties)
->performedOn($remoteProcessArgs->model) ->performedOn($remoteProcessArgs->model)
->event($remoteProcessArgs->type) ->event($remoteProcessArgs->type)
->log("[]"); ->log('[]');
} else { } else {
$this->activity = activity() $this->activity = activity()
->withProperties($remoteProcessArgs->toArray()) ->withProperties($remoteProcessArgs->toArray())
->event($remoteProcessArgs->type) ->event($remoteProcessArgs->type)
->log("[]"); ->log('[]');
} }
} }
@@ -42,6 +43,7 @@ class PrepareCoolifyTask
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data); $job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data);
dispatch($job); dispatch($job);
$this->activity->refresh(); $this->activity->refresh();
return $this->activity; return $this->activity;
} }
} }

View File

@@ -60,7 +60,7 @@ class RunRemoteProcess
$decoded = json_decode( $decoded = json_decode(
data_get($activity, 'description'), data_get($activity, 'description'),
associative: true, associative: true,
flags: JSON_THROW_ON_ERROR flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE
); );
} catch (\JsonException $exception) { } catch (\JsonException $exception) {
return ''; return '';
@@ -69,7 +69,7 @@ class RunRemoteProcess
return collect($decoded) return collect($decoded)
->sortBy(fn ($i) => $i['order']) ->sortBy(fn ($i) => $i['order'])
->map(fn ($i) => $i['output']) ->map(fn ($i) => $i['output'])
->implode(""); ->implode('');
} }
public function __invoke(): ProcessResult public function __invoke(): ProcessResult
@@ -91,7 +91,7 @@ class RunRemoteProcess
if ($processResult->exitCode() == 0) { if ($processResult->exitCode() == 0) {
$status = ProcessStatus::FINISHED; $status = ProcessStatus::FINISHED;
} }
if ($processResult->exitCode() != 0 && !$this->ignore_errors) { if ($processResult->exitCode() != 0 && ! $this->ignore_errors) {
$status = ProcessStatus::ERROR; $status = ProcessStatus::ERROR;
} }
// if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) { // if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
@@ -109,14 +109,14 @@ class RunRemoteProcess
'status' => $status->value, 'status' => $status->value,
]); ]);
$this->activity->save(); $this->activity->save();
if ($processResult->exitCode() != 0 && !$this->ignore_errors) { if ($processResult->exitCode() != 0 && ! $this->ignore_errors) {
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode()); throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
} }
if ($this->call_event_on_finish) { if ($this->call_event_on_finish) {
try { try {
if ($this->call_event_data) { if ($this->call_event_data) {
event(resolve("App\\Events\\$this->call_event_on_finish", [ event(resolve("App\\Events\\$this->call_event_on_finish", [
"data" => $this->call_event_data, 'data' => $this->call_event_data,
])); ]));
} else { } else {
event(resolve("App\\Events\\$this->call_event_on_finish", [ event(resolve("App\\Events\\$this->call_event_on_finish", [
@@ -127,6 +127,7 @@ class RunRemoteProcess
ray($e); ray($e);
} }
} }
return $processResult; return $processResult;
} }
@@ -164,8 +165,7 @@ class RunRemoteProcess
public function encodeOutput($type, $output) 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[] = [ $outputStack[] = [
'type' => $type, 'type' => $type,
'output' => $output, 'output' => $output,
@@ -174,15 +174,16 @@ class RunRemoteProcess
'order' => $this->getLatestCounter(), '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 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) { if ($description === null || count($description) === 0) {
return 1; return 1;
} }
return end($description)['order'] + 1; return end($description)['order'] + 1;
} }

View File

@@ -4,24 +4,25 @@ namespace App\Actions\Database;
use App\Models\StandaloneClickhouse; use App\Models\StandaloneClickhouse;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartClickhouse class StartClickhouse
{ {
use AsAction; use AsAction;
public StandaloneClickhouse $database; public StandaloneClickhouse $database;
public array $commands = []; public array $commands = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandaloneClickhouse $database) public function handle(StandaloneClickhouse $database)
{ {
$this->database = $database; $this->database = $database;
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -29,6 +30,7 @@ class StartClickhouse
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
@@ -56,7 +58,7 @@ class StartClickhouse
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s' 'start_period' => '5s',
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@@ -64,27 +66,27 @@ class StartClickhouse
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ],
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
] ],
] ],
]; ];
if (!is_null($this->database->limits_cpuset)) { if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => "true", 'fluentd-async' => 'true',
'fluentd-sub-second-precision' => "true", 'fluentd-sub-second-precision' => 'true',
] ],
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@@ -93,6 +95,11 @@ class StartClickhouse
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $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) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
@@ -105,6 +112,7 @@ class StartClickhouse
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@@ -113,12 +121,13 @@ class StartClickhouse
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
} }
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@@ -135,6 +144,7 @@ class StartClickhouse
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }

View File

@@ -69,19 +69,19 @@ class StartDatabaseProxy
} }
if ($type === 'App\Models\StandaloneRedis') { if ($type === 'App\Models\StandaloneRedis') {
$internalPort = 6379; $internalPort = 6379;
} else if ($type === 'App\Models\StandalonePostgresql') { } elseif ($type === 'App\Models\StandalonePostgresql') {
$internalPort = 5432; $internalPort = 5432;
} else if ($type === 'App\Models\StandaloneMongodb') { } elseif ($type === 'App\Models\StandaloneMongodb') {
$internalPort = 27017; $internalPort = 27017;
} else if ($type === 'App\Models\StandaloneMysql') { } elseif ($type === 'App\Models\StandaloneMysql') {
$internalPort = 3306; $internalPort = 3306;
} else if ($type === 'App\Models\StandaloneMariadb') { } elseif ($type === 'App\Models\StandaloneMariadb') {
$internalPort = 3306; $internalPort = 3306;
} else if ($type === 'App\Models\StandaloneKeydb') { } elseif ($type === 'App\Models\StandaloneKeydb') {
$internalPort = 6379; $internalPort = 6379;
} else if ($type === 'App\Models\StandaloneDragonfly') { } elseif ($type === 'App\Models\StandaloneDragonfly') {
$internalPort = 6379; $internalPort = 6379;
} else if ($type === 'App\Models\StandaloneClickhouse') { } elseif ($type === 'App\Models\StandaloneClickhouse') {
$internalPort = 9000; $internalPort = 9000;
} }
$configuration_dir = database_proxy_dir($database->uuid); $configuration_dir = database_proxy_dir($database->uuid);
@@ -101,7 +101,7 @@ class StartDatabaseProxy
} }
} }
EOF; EOF;
$dockerfile = <<< EOF $dockerfile = <<< 'EOF'
FROM nginx:stable-alpine FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/nginx.conf COPY nginx.conf /etc/nginx/nginx.conf
@@ -113,7 +113,7 @@ class StartDatabaseProxy
'context' => $configuration_dir, 'context' => $configuration_dir,
'dockerfile' => 'Dockerfile', 'dockerfile' => 'Dockerfile',
], ],
'image' => "nginx:stable-alpine", 'image' => 'nginx:stable-alpine',
'container_name' => $proxyContainerName, 'container_name' => $proxyContainerName,
'restart' => RESTART_MODE, 'restart' => RESTART_MODE,
'ports' => [ 'ports' => [
@@ -130,17 +130,17 @@ class StartDatabaseProxy
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 3, 'retries' => 3,
'start_period' => '1s' 'start_period' => '1s',
], ],
] ],
], ],
'networks' => [ 'networks' => [
$network => [ $network => [
'external' => true, 'external' => true,
'name' => $network, 'name' => $network,
'attachable' => true, 'attachable' => true,
] ],
] ],
]; ];
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2)); $dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf); $nginxconf_base64 = base64_encode($nginxconf);

View File

@@ -3,19 +3,19 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\StandaloneDragonfly; use App\Models\StandaloneDragonfly;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartDragonfly class StartDragonfly
{ {
use AsAction; use AsAction;
public StandaloneDragonfly $database; public StandaloneDragonfly $database;
public array $commands = [];
public string $configuration_dir;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneDragonfly $database) public function handle(StandaloneDragonfly $database)
{ {
@@ -24,7 +24,7 @@ class StartDragonfly
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}"; $startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -32,6 +32,7 @@ class StartDragonfly
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
@@ -47,7 +48,7 @@ class StartDragonfly
$this->database->destination->network, $this->database->destination->network,
], ],
'ulimits' => [ 'ulimits' => [
'memlock'=> '-1' 'memlock' => '-1',
], ],
'labels' => [ 'labels' => [
'coolify.managed' => 'true', 'coolify.managed' => 'true',
@@ -57,7 +58,7 @@ class StartDragonfly
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s' 'start_period' => '5s',
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@@ -65,27 +66,27 @@ class StartDragonfly
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ],
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
] ],
] ],
]; ];
if (!is_null($this->database->limits_cpuset)) { if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => "true", 'fluentd-async' => 'true',
'fluentd-sub-second-precision' => "true", 'fluentd-sub-second-precision' => 'true',
] ],
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@@ -94,6 +95,11 @@ class StartDragonfly
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $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) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
@@ -106,6 +112,7 @@ class StartDragonfly
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@@ -114,12 +121,13 @@ class StartDragonfly
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
} }
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@@ -136,6 +144,7 @@ class StartDragonfly
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }

View File

@@ -5,17 +5,18 @@ namespace App\Actions\Database;
use App\Models\StandaloneKeydb; use App\Models\StandaloneKeydb;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartKeydb class StartKeydb
{ {
use AsAction; use AsAction;
public StandaloneKeydb $database; public StandaloneKeydb $database;
public array $commands = [];
public string $configuration_dir;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneKeydb $database) public function handle(StandaloneKeydb $database)
{ {
@@ -24,7 +25,7 @@ class StartKeydb
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes"; $startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -32,6 +33,7 @@ class StartKeydb
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->add_custom_keydb(); $this->add_custom_keydb();
@@ -55,7 +57,7 @@ class StartKeydb
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s' 'start_period' => '5s',
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@@ -63,27 +65,27 @@ class StartKeydb
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ],
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
] ],
] ],
]; ];
if (!is_null($this->database->limits_cpuset)) { if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => "true", 'fluentd-async' => 'true',
'fluentd-sub-second-precision' => "true", 'fluentd-sub-second-precision' => 'true',
] ],
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@@ -92,13 +94,18 @@ class StartKeydb
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $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) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) { if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir . '/keydb.conf', 'source' => $this->configuration_dir.'/keydb.conf',
'target' => '/etc/keydb/keydb.conf', 'target' => '/etc/keydb/keydb.conf',
'read_only' => true, 'read_only' => true,
]; ];
@@ -113,6 +120,7 @@ class StartKeydb
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@@ -121,12 +129,13 @@ class StartKeydb
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
} }
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@@ -143,6 +152,7 @@ class StartKeydb
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@@ -159,6 +169,7 @@ class StartKeydb
return $environment_variables->all(); return $environment_variables->all();
} }
private function add_custom_keydb() private function add_custom_keydb()
{ {
if (is_null($this->database->keydb_conf) || empty($this->database->keydb_conf)) { if (is_null($this->database->keydb_conf) || empty($this->database->keydb_conf)) {

View File

@@ -4,15 +4,17 @@ namespace App\Actions\Database;
use App\Models\StandaloneMariadb; use App\Models\StandaloneMariadb;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartMariadb class StartMariadb
{ {
use AsAction; use AsAction;
public StandaloneMariadb $database; public StandaloneMariadb $database;
public array $commands = []; public array $commands = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandaloneMariadb $database) public function handle(StandaloneMariadb $database)
@@ -20,7 +22,7 @@ class StartMariadb
$this->database = $database; $this->database = $database;
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -28,6 +30,7 @@ class StartMariadb
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql(); $this->add_custom_mysql();
@@ -45,11 +48,11 @@ class StartMariadb
'coolify.managed' => 'true', 'coolify.managed' => 'true',
], ],
'healthcheck' => [ 'healthcheck' => [
'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"], 'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s' 'start_period' => '5s',
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@@ -57,27 +60,27 @@ class StartMariadb
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ],
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
] ],
] ],
]; ];
if (!is_null($this->database->limits_cpuset)) { if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => "true", 'fluentd-async' => 'true',
'fluentd-sub-second-precision' => "true", 'fluentd-sub-second-precision' => 'true',
] ],
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@@ -86,13 +89,18 @@ class StartMariadb
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $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) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) { if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir . '/custom-config.cnf', 'source' => $this->configuration_dir.'/custom-config.cnf',
'target' => '/etc/mysql/conf.d/custom-config.cnf', 'target' => '/etc/mysql/conf.d/custom-config.cnf',
'read_only' => true, 'read_only' => true,
]; ];
@@ -106,6 +114,7 @@ class StartMariadb
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@@ -114,12 +123,13 @@ class StartMariadb
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
} }
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@@ -136,6 +146,7 @@ class StartMariadb
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@@ -160,8 +171,10 @@ class StartMariadb
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}"); $environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
} }
return $environment_variables->all(); return $environment_variables->all();
} }
private function add_custom_mysql() private function add_custom_mysql()
{ {
if (is_null($this->database->mariadb_conf) || empty($this->database->mariadb_conf)) { if (is_null($this->database->mariadb_conf) || empty($this->database->mariadb_conf)) {

View File

@@ -4,25 +4,27 @@ namespace App\Actions\Database;
use App\Models\StandaloneMongodb; use App\Models\StandaloneMongodb;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartMongodb class StartMongodb
{ {
use AsAction; use AsAction;
public StandaloneMongodb $database; public StandaloneMongodb $database;
public array $commands = []; public array $commands = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandaloneMongodb $database) public function handle(StandaloneMongodb $database)
{ {
$this->database = $database; $this->database = $database;
$startCommand = "mongod"; $startCommand = 'mongod';
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -30,6 +32,7 @@ class StartMongodb
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->add_custom_mongo_conf(); $this->add_custom_mongo_conf();
@@ -50,14 +53,14 @@ class StartMongodb
], ],
'healthcheck' => [ 'healthcheck' => [
'test' => [ 'test' => [
"CMD", 'CMD',
"echo", 'echo',
"ok" 'ok',
], ],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s' 'start_period' => '5s',
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@@ -65,27 +68,27 @@ class StartMongodb
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ],
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
] ],
] ],
]; ];
if (!is_null($this->database->limits_cpuset)) { if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => "true", 'fluentd-async' => 'true',
'fluentd-sub-second-precision' => "true", 'fluentd-sub-second-precision' => 'true',
] ],
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@@ -94,22 +97,27 @@ class StartMongodb
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $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) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) { if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir . '/mongod.conf', 'source' => $this->configuration_dir.'/mongod.conf',
'target' => '/etc/mongo/mongod.conf', 'target' => '/etc/mongo/mongod.conf',
'read_only' => true, 'read_only' => true,
]; ];
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf'; $docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf';
} }
$this->add_default_database(); $this->add_default_database();
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d', 'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d',
'target' => '/docker-entrypoint-initdb.d', 'target' => '/docker-entrypoint-initdb.d',
'read_only' => true, 'read_only' => true,
]; ];
@@ -123,6 +131,7 @@ class StartMongodb
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@@ -131,12 +140,13 @@ class StartMongodb
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
} }
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@@ -153,6 +163,7 @@ class StartMongodb
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@@ -174,8 +185,10 @@ class StartMongodb
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}"); $environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
} }
return $environment_variables->all(); return $environment_variables->all();
} }
private function add_custom_mongo_conf() private function add_custom_mongo_conf()
{ {
if (is_null($this->database->mongo_conf) || empty($this->database->mongo_conf)) { if (is_null($this->database->mongo_conf) || empty($this->database->mongo_conf)) {
@@ -186,6 +199,7 @@ class StartMongodb
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
} }
private function add_default_database() private function add_default_database()
{ {
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});"; $content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";

View File

@@ -4,15 +4,17 @@ namespace App\Actions\Database;
use App\Models\StandaloneMysql; use App\Models\StandaloneMysql;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartMysql class StartMysql
{ {
use AsAction; use AsAction;
public StandaloneMysql $database; public StandaloneMysql $database;
public array $commands = []; public array $commands = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandaloneMysql $database) public function handle(StandaloneMysql $database)
@@ -20,7 +22,7 @@ class StartMysql
$this->database = $database; $this->database = $database;
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -28,6 +30,7 @@ class StartMysql
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql(); $this->add_custom_mysql();
@@ -45,11 +48,11 @@ class StartMysql
'coolify.managed' => 'true', 'coolify.managed' => 'true',
], ],
'healthcheck' => [ 'healthcheck' => [
'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"], 'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s' 'start_period' => '5s',
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@@ -57,27 +60,27 @@ class StartMysql
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ],
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
] ],
] ],
]; ];
if (!is_null($this->database->limits_cpuset)) { if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => "true", 'fluentd-async' => 'true',
'fluentd-sub-second-precision' => "true", 'fluentd-sub-second-precision' => 'true',
] ],
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@@ -86,13 +89,18 @@ class StartMysql
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $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) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) { if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir . '/custom-config.cnf', 'source' => $this->configuration_dir.'/custom-config.cnf',
'target' => '/etc/mysql/conf.d/custom-config.cnf', 'target' => '/etc/mysql/conf.d/custom-config.cnf',
'read_only' => true, 'read_only' => true,
]; ];
@@ -106,7 +114,8 @@ class StartMysql
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()
@@ -114,12 +123,13 @@ class StartMysql
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
} }
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@@ -136,6 +146,7 @@ class StartMysql
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@@ -160,8 +171,10 @@ class StartMysql
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}"); $environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
} }
return $environment_variables->all(); return $environment_variables->all();
} }
private function add_custom_mysql() private function add_custom_mysql()
{ {
if (is_null($this->database->mysql_conf) || empty($this->database->mysql_conf)) { if (is_null($this->database->mysql_conf) || empty($this->database->mysql_conf)) {

View File

@@ -4,31 +4,35 @@ namespace App\Actions\Database;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartPostgresql class StartPostgresql
{ {
use AsAction; use AsAction;
public StandalonePostgresql $database; public StandalonePostgresql $database;
public array $commands = []; public array $commands = [];
public array $init_scripts = []; public array $init_scripts = [];
public string $configuration_dir; public string $configuration_dir;
public function handle(StandalonePostgresql $database) public function handle(StandalonePostgresql $database)
{ {
$this->database = $database; $this->database = $database;
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir",
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/" "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/",
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->generate_init_scripts(); $this->generate_init_scripts();
@@ -49,13 +53,13 @@ class StartPostgresql
], ],
'healthcheck' => [ 'healthcheck' => [
'test' => [ 'test' => [
"CMD-SHELL", 'CMD-SHELL',
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1" "psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1",
], ],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s' 'start_period' => '5s',
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@@ -63,27 +67,27 @@ class StartPostgresql
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ],
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
] ],
] ],
]; ];
if (!is_null($this->database->limits_cpuset)) { if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => "true", 'fluentd-async' => 'true',
'fluentd-sub-second-precision' => "true", 'fluentd-sub-second-precision' => 'true',
] ],
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@@ -92,6 +96,11 @@ class StartPostgresql
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $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) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
@@ -100,15 +109,15 @@ class StartPostgresql
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $init_script, 'source' => $init_script,
'target' => '/docker-entrypoint-initdb.d/' . basename($init_script), 'target' => '/docker-entrypoint-initdb.d/'.basename($init_script),
'read_only' => true, 'read_only' => true,
]; ];
} }
} }
if (!is_null($this->database->postgres_conf) && !empty($this->database->postgres_conf)) { if (! is_null($this->database->postgres_conf) && ! empty($this->database->postgres_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir . '/custom-postgres.conf', 'source' => $this->configuration_dir.'/custom-postgres.conf',
'target' => '/etc/postgresql/postgresql.conf', 'target' => '/etc/postgresql/postgresql.conf',
'read_only' => true, 'read_only' => true,
]; ];
@@ -127,6 +136,7 @@ class StartPostgresql
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@@ -135,12 +145,13 @@ class StartPostgresql
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
} }
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@@ -157,6 +168,7 @@ class StartPostgresql
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@@ -181,6 +193,7 @@ class StartPostgresql
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}"); $environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
} }
return $environment_variables->all(); return $environment_variables->all();
} }
@@ -197,6 +210,7 @@ class StartPostgresql
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; $this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
} }
} }
private function add_custom_conf() private function add_custom_conf()
{ {
if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) { if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) {
@@ -204,7 +218,7 @@ class StartPostgresql
} }
$filename = 'custom-postgres.conf'; $filename = 'custom-postgres.conf';
$content = $this->database->postgres_conf; $content = $this->database->postgres_conf;
if (!str($content)->contains('listen_addresses')) { if (! str($content)->contains('listen_addresses')) {
$content .= "\nlisten_addresses = '*'"; $content .= "\nlisten_addresses = '*'";
$this->database->postgres_conf = $content; $this->database->postgres_conf = $content;
$this->database->save(); $this->database->save();

View File

@@ -5,17 +5,18 @@ namespace App\Actions\Database;
use App\Models\StandaloneRedis; use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartRedis class StartRedis
{ {
use AsAction; use AsAction;
public StandaloneRedis $database; public StandaloneRedis $database;
public array $commands = [];
public string $configuration_dir;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneRedis $database) public function handle(StandaloneRedis $database)
{ {
@@ -24,7 +25,7 @@ class StartRedis
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; $startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir().'/'.$container_name;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -32,6 +33,7 @@ class StartRedis
]; ];
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables(); $environment_variables = $this->generate_environment_variables();
$this->add_custom_redis(); $this->add_custom_redis();
@@ -54,12 +56,12 @@ class StartRedis
'test' => [ 'test' => [
'CMD-SHELL', 'CMD-SHELL',
'redis-cli', 'redis-cli',
'ping' 'ping',
], ],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
'retries' => 10, 'retries' => 10,
'start_period' => '5s' 'start_period' => '5s',
], ],
'mem_limit' => $this->database->limits_memory, 'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
@@ -67,27 +69,27 @@ class StartRedis
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ],
], ],
'networks' => [ 'networks' => [
$this->database->destination->network => [ $this->database->destination->network => [
'external' => true, 'external' => true,
'name' => $this->database->destination->network, 'name' => $this->database->destination->network,
'attachable' => true, 'attachable' => true,
] ],
] ],
]; ];
if (!is_null($this->database->limits_cpuset)) { if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
} }
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [ $docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',
'options' => [ 'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => "true", 'fluentd-async' => 'true',
'fluentd-sub-second-precision' => "true", 'fluentd-sub-second-precision' => 'true',
] ],
]; ];
} }
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
@@ -96,13 +98,18 @@ class StartRedis
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages; $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) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) { if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir . '/redis.conf', 'source' => $this->configuration_dir.'/redis.conf',
'target' => '/usr/local/etc/redis/redis.conf', 'target' => '/usr/local/etc/redis/redis.conf',
'read_only' => true, 'read_only' => true,
]; ];
@@ -117,6 +124,7 @@ class StartRedis
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'"; $this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
@@ -125,12 +133,13 @@ class StartRedis
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
} }
} }
return $local_persistent_volumes; return $local_persistent_volumes;
} }
@@ -147,6 +156,7 @@ class StartRedis
'external' => false, 'external' => false,
]; ];
} }
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
@@ -163,6 +173,7 @@ class StartRedis
return $environment_variables->all(); return $environment_variables->all();
} }
private function add_custom_redis() private function add_custom_redis()
{ {
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) { if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {

View File

@@ -19,7 +19,7 @@ class StopDatabase
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database) public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
{ {
$server = $database->destination->server; $server = $database->destination->server;
if (!$server->isFunctional()) { if (! $server->isFunctional()) {
return 'Server is not functional'; return 'Server is not functional';
} }
instant_remote_process( instant_remote_process(

View File

@@ -6,31 +6,31 @@ use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy; use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy; use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck; use App\Actions\Shared\ComplexStatusCheck;
use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Models\Server; use App\Models\Server;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted; use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped; use App\Notifications\Container\ContainerStopped;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
class GetContainersStatus class GetContainersStatus
{ {
use AsAction; use AsAction;
public $applications; public $applications;
public $server; public $server;
public function handle(Server $server) public function handle(Server $server)
{ {
if (isDev()) { // if (isDev()) {
$server = Server::find(0); // $server = Server::find(0);
} // }
$this->server = $server; $this->server = $server;
if (!$this->server->isFunctional()) { if (! $this->server->isFunctional()) {
return 'Server is not ready.'; return 'Server is not ready.';
}; }
$this->applications = $this->server->applications(); $this->applications = $this->server->applications();
$skip_these_applications = collect([]); $skip_these_applications = collect([]);
foreach ($this->applications as $application) { foreach ($this->applications as $application) {
@@ -43,7 +43,7 @@ class GetContainersStatus
} }
} }
$this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) { $this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) {
return !$skip_these_applications->pluck('id')->contains($value->id); return ! $skip_these_applications->pluck('id')->contains($value->id);
}); });
$this->old_way(); $this->old_way();
// if ($this->server->isSwarm()) { // if ($this->server->isSwarm()) {
@@ -135,7 +135,7 @@ class GetContainersStatus
return data_get($value, 'name') === "$uuid-proxy"; return data_get($value, 'name') === "$uuid-proxy";
} }
})->first(); })->first();
if (!$foundTcpProxy) { if (! $foundTcpProxy) {
StartDatabaseProxy::run($service_db); StartDatabaseProxy::run($service_db);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server)); // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
} }
@@ -154,13 +154,13 @@ class GetContainersStatus
if ($isPublic) { if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) { $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
// TODO: fix this with sentinel // TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid"; return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else { } else {
return data_get($value, 'name') === "$uuid-proxy"; return data_get($value, 'name') === "$uuid-proxy";
} }
})->first(); })->first();
if (!$foundTcpProxy) { if (! $foundTcpProxy) {
StartDatabaseProxy::run($database); StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
} }
@@ -179,13 +179,13 @@ class GetContainersStatus
$subType = data_get($labels, 'coolify.service.subType'); $subType = data_get($labels, 'coolify.service.subType');
$subId = data_get($labels, 'coolify.service.subId'); $subId = data_get($labels, 'coolify.service.subId');
$service = $services->where('id', $serviceLabelId)->first(); $service = $services->where('id', $serviceLabelId)->first();
if (!$service) { if (! $service) {
continue; continue;
} }
if ($subType === 'application') { if ($subType === 'application') {
$service = $service->applications()->where('id', $subId)->first(); $service = $service->applications()->where('id', $subId)->first();
} else { } else {
$service = $service->databases()->where('id', $subId)->first(); $service = $service->databases()->where('id', $subId)->first();
} }
if ($service) { if ($service) {
$foundServices[] = "$service->id-$service->name"; $foundServices[] = "$service->id-$service->name";
@@ -223,17 +223,29 @@ class GetContainersStatus
} }
$name = data_get($exitedService, 'name'); $name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn'); $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'); $projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid'); $serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name'); $environmentName = data_get($service, 'environment.name');
if ($projectUuid && $serviceUuid && $environmentName) { if ($projectUuid && $serviceUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid; $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
} else { } else {
$url = null; $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']); $exitedService->update(['status' => 'exited']);
} }
@@ -255,12 +267,12 @@ class GetContainersStatus
$environment = data_get($application, 'environment.name'); $environment = data_get($application, 'environment.name');
if ($projectUuid && $applicationUuid && $environment) { if ($projectUuid && $applicationUuid && $environment) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid; $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
} else { } else {
$url = null; $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); $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) { foreach ($notRunningApplicationPreviews as $previewId) {
@@ -280,12 +292,12 @@ class GetContainersStatus
$applicationUuid = data_get($preview, 'application.uuid'); $applicationUuid = data_get($preview, 'application.uuid');
if ($projectUuid && $applicationUuid && $environmentName) { if ($projectUuid && $applicationUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid; $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
} else { } else {
$url = null; $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); $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) { foreach ($notRunningDatabases as $database) {
@@ -305,24 +317,24 @@ class GetContainersStatus
$databaseUuid = data_get($database, 'uuid'); $databaseUuid = data_get($database, 'uuid');
if ($projectUuid && $databaseUuid && $environmentName) { if ($projectUuid && $databaseUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid; $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
} else { } else {
$url = null; $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 // Check if proxy is running
$this->server->proxyType(); $this->server->proxyType();
$foundProxyContainer = $containers->filter(function ($value, $key) { $foundProxyContainer = $containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
// TODO: fix this with sentinel // TODO: fix this with sentinel
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik'; return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else { } else {
return data_get($value, 'name') === 'coolify-proxy'; return data_get($value, 'name') === 'coolify-proxy';
} }
})->first(); })->first();
if (!$foundProxyContainer) { if (! $foundProxyContainer) {
try { try {
$shouldStart = CheckProxy::run($this->server); $shouldStart = CheckProxy::run($this->server);
if ($shouldStart) { if ($shouldStart) {
@@ -341,9 +353,11 @@ class GetContainersStatus
} catch (\Exception $e) { } catch (\Exception $e) {
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage()); // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
return handleError($e); return handleError($e);
} }
} }
private function old_way() private function old_way()
{ {
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
@@ -351,8 +365,8 @@ class GetContainersStatus
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false); $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
} else { } else {
// Precheck for containers // Precheck for containers
$containers = instant_remote_process(["docker container ls -q"], $this->server, false); $containers = instant_remote_process(['docker container ls -q'], $this->server, false);
if (!$containers) { if (! $containers) {
return; return;
} }
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false); $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
@@ -380,6 +394,7 @@ class GetContainersStatus
data_set($container, 'State.Health.Status', 'unhealthy'); data_set($container, 'State.Health.Status', 'unhealthy');
} }
} }
return $container; return $container;
}); });
} }
@@ -442,19 +457,21 @@ class GetContainersStatus
if ($database_id) { if ($database_id) {
$service_db = ServiceDatabase::where('id', $database_id)->first(); $service_db = ServiceDatabase::where('id', $database_id)->first();
if ($service_db) { if ($service_db) {
$uuid = $service_db->service->uuid; $uuid = data_get($service_db, 'service.uuid');
$isPublic = data_get($service_db, 'is_public'); if ($uuid) {
if ($isPublic) { $isPublic = data_get($service_db, 'is_public');
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) { if ($isPublic) {
if ($this->server->isSwarm()) { $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid"; if ($this->server->isSwarm()) {
} else { return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
return data_get($value, 'Name') === "/$uuid-proxy"; } 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));
} }
} }
} }
@@ -476,7 +493,7 @@ class GetContainersStatus
return data_get($value, 'Name') === "/$uuid-proxy"; return data_get($value, 'Name') === "/$uuid-proxy";
} }
})->first(); })->first();
if (!$foundTcpProxy) { if (! $foundTcpProxy) {
StartDatabaseProxy::run($database); StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
} }
@@ -495,13 +512,13 @@ class GetContainersStatus
$subType = data_get($labels, 'coolify.service.subType'); $subType = data_get($labels, 'coolify.service.subType');
$subId = data_get($labels, 'coolify.service.subId'); $subId = data_get($labels, 'coolify.service.subId');
$service = $services->where('id', $serviceLabelId)->first(); $service = $services->where('id', $serviceLabelId)->first();
if (!$service) { if (! $service) {
continue; continue;
} }
if ($subType === 'application') { if ($subType === 'application') {
$service = $service->applications()->where('id', $subId)->first(); $service = $service->applications()->where('id', $subId)->first();
} else { } else {
$service = $service->databases()->where('id', $subId)->first(); $service = $service->databases()->where('id', $subId)->first();
} }
if ($service) { if ($service) {
$foundServices[] = "$service->id-$service->name"; $foundServices[] = "$service->id-$service->name";
@@ -539,17 +556,29 @@ class GetContainersStatus
} }
$name = data_get($exitedService, 'name'); $name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn'); $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'); $projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid'); $serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name'); $environmentName = data_get($service, 'environment.name');
if ($projectUuid && $serviceUuid && $environmentName) { if ($projectUuid && $serviceUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid; $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
} else { } else {
$url = null; $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']); $exitedService->update(['status' => 'exited']);
} }
@@ -571,12 +600,12 @@ class GetContainersStatus
$environment = data_get($application, 'environment.name'); $environment = data_get($application, 'environment.name');
if ($projectUuid && $applicationUuid && $environment) { if ($projectUuid && $applicationUuid && $environment) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid; $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
} else { } else {
$url = null; $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); $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) { foreach ($notRunningApplicationPreviews as $previewId) {
@@ -596,12 +625,12 @@ class GetContainersStatus
$applicationUuid = data_get($preview, 'application.uuid'); $applicationUuid = data_get($preview, 'application.uuid');
if ($projectUuid && $applicationUuid && $environmentName) { if ($projectUuid && $applicationUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid; $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
} else { } else {
$url = null; $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); $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) { foreach ($notRunningDatabases as $database) {
@@ -621,11 +650,11 @@ class GetContainersStatus
$databaseUuid = data_get($database, 'uuid'); $databaseUuid = data_get($database, 'uuid');
if ($projectUuid && $databaseUuid && $environmentName) { if ($projectUuid && $databaseUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid; $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
} else { } else {
$url = null; $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 // Check if proxy is running
@@ -637,7 +666,7 @@ class GetContainersStatus
return data_get($value, 'Name') === '/coolify-proxy'; return data_get($value, 'Name') === '/coolify-proxy';
} }
})->first(); })->first();
if (!$foundProxyContainer) { if (! $foundProxyContainer) {
try { try {
$shouldStart = CheckProxy::run($this->server); $shouldStart = CheckProxy::run($this->server);
if ($shouldStart) { if ($shouldStart) {

View File

@@ -16,12 +16,12 @@ class CreateNewUser implements CreatesNewUsers
/** /**
* Validate and create a newly registered user. * Validate and create a newly registered user.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function create(array $input): User public function create(array $input): User
{ {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
if (!$settings->is_registration_enabled) { if (! $settings->is_registration_enabled) {
abort(403); abort(403);
} }
Validator::make($input, [ Validator::make($input, [
@@ -66,6 +66,7 @@ class CreateNewUser implements CreatesNewUsers
} }
// Set session variable // Set session variable
session(['currentTeam' => $user->currentTeam = $team]); session(['currentTeam' => $user->currentTeam = $team]);
return $user; return $user;
} }
} }

View File

@@ -14,7 +14,7 @@ class ResetUserPassword implements ResetsUserPasswords
/** /**
* Validate and reset the user's forgotten password. * Validate and reset the user's forgotten password.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function reset(User $user, array $input): void public function reset(User $user, array $input): void
{ {

View File

@@ -14,7 +14,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
/** /**
* Validate and update the user's password. * Validate and update the user's password.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function update(User $user, array $input): void public function update(User $user, array $input): void
{ {

View File

@@ -13,7 +13,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
/** /**
* Validate and update the given user's profile information. * Validate and update the given user's profile information.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function update(User $user, array $input): void public function update(User $user, array $input): void
{ {
@@ -45,7 +45,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
/** /**
* Update the given verified user's profile information. * Update the given verified user's profile information.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
protected function updateVerifiedUser(User $user, array $input): void protected function updateVerifiedUser(User $user, array $input): void
{ {

View File

@@ -6,10 +6,10 @@ use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
class CheckResaleLicense class CheckResaleLicense
{ {
use AsAction; use AsAction;
public function handle() public function handle()
{ {
try { try {
@@ -18,6 +18,7 @@ class CheckResaleLicense
$settings->update([ $settings->update([
'is_resale_license_active' => true, 'is_resale_license_active' => true,
]); ]);
return; return;
} }
// if (!$settings->resale_license) { // if (!$settings->resale_license) {
@@ -38,6 +39,7 @@ class CheckResaleLicense
$settings->update([ $settings->update([
'is_resale_license_active' => true, 'is_resale_license_active' => true,
]); ]);
return; return;
} }
$data = Http::withHeaders([ $data = Http::withHeaders([
@@ -51,6 +53,7 @@ class CheckResaleLicense
$settings->update([ $settings->update([
'is_resale_license_active' => true, 'is_resale_license_active' => true,
]); ]);
return; return;
} }
if (data_get($data, 'license_key.status') === 'active') { if (data_get($data, 'license_key.status') === 'active') {

View File

@@ -2,13 +2,14 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
class CheckConfiguration class CheckConfiguration
{ {
use AsAction; use AsAction;
public function handle(Server $server, bool $reset = false) public function handle(Server $server, bool $reset = false)
{ {
$proxyType = $server->proxyType(); $proxyType = $server->proxyType();
@@ -22,12 +23,13 @@ class CheckConfiguration
]; ];
$proxy_configuration = instant_remote_process($payload, $server, false); $proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) { if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value; $proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
} }
if (!$proxy_configuration || is_null($proxy_configuration)) { if (! $proxy_configuration || is_null($proxy_configuration)) {
throw new \Exception("Could not generate proxy configuration"); throw new \Exception('Could not generate proxy configuration');
} }
return $proxy_configuration; return $proxy_configuration;
} }
} }

View File

@@ -8,18 +8,31 @@ use Lorisleiva\Actions\Concerns\AsAction;
class CheckProxy class CheckProxy
{ {
use AsAction; use AsAction;
public function handle(Server $server, $fromUI = false) 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; return false;
} }
['uptime' => $uptime, 'error' => $error] = $server->validateConnection(); ['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
if (!$uptime) { if (! $uptime) {
throw new \Exception($error); throw new \Exception($error);
} }
if (!$server->isProxyShouldRun()) { if (! $server->isProxyShouldRun()) {
if ($fromUI) { if ($fromUI) {
throw new \Exception("Proxy should not run. You selected the Custom Proxy."); throw new \Exception('Proxy should not run. You selected the Custom Proxy.');
} else { } else {
return false; return false;
} }
@@ -31,12 +44,14 @@ class CheckProxy
if ($status === 'running') { if ($status === 'running') {
return false; return false;
} }
return true; return true;
} else { } else {
$status = getContainerStatus($server, 'coolify-proxy'); $status = getContainerStatus($server, 'coolify-proxy');
if ($status === 'running') { if ($status === 'running') {
$server->proxy->set('status', 'running'); $server->proxy->set('status', 'running');
$server->save(); $server->save();
return false; return false;
} }
if ($server->settings->is_cloudflare_tunnel) { if ($server->settings->is_cloudflare_tunnel) {
@@ -65,6 +80,7 @@ class CheckProxy
return false; return false;
} }
} }
return true; return true;
} }
} }

View File

@@ -11,18 +11,19 @@ use Spatie\Activitylog\Models\Activity;
class StartProxy class StartProxy
{ {
use AsAction; use AsAction;
public function handle(Server $server, bool $async = true): string|Activity public function handle(Server $server, bool $async = true): string|Activity
{ {
try { try {
$proxyType = $server->proxyType(); $proxyType = $server->proxyType();
if (is_null($proxyType) || $proxyType === 'NONE') { if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) {
return 'OK'; return 'OK';
} }
$commands = collect([]); $commands = collect([]);
$proxy_path = $server->proxyPath(); $proxy_path = $server->proxyPath();
$configuration = CheckConfiguration::run($server); $configuration = CheckConfiguration::run($server);
if (!$configuration) { if (! $configuration) {
throw new \Exception("Configuration is not synced"); throw new \Exception('Configuration is not synced');
} }
SaveConfiguration::run($server, $configuration); SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration); $docker_compose_yml_base64 = base64_encode($configuration);
@@ -34,11 +35,11 @@ class StartProxy
"cd $proxy_path", "cd $proxy_path",
"echo 'Creating required Docker Compose file.'", "echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'", "echo 'Starting coolify-proxy.'",
"docker stack deploy -c docker-compose.yml coolify-proxy", 'docker stack deploy -c docker-compose.yml coolify-proxy',
"echo 'Proxy started successfully.'" "echo 'Proxy started successfully.'",
]); ]);
} else { } else {
$caddfile = "import /dynamic/*.caddy"; $caddfile = 'import /dynamic/*.caddy';
$commands = $commands->merge([ $commands = $commands->merge([
"mkdir -p $proxy_path/dynamic", "mkdir -p $proxy_path/dynamic",
"cd $proxy_path", "cd $proxy_path",
@@ -47,16 +48,17 @@ class StartProxy
"echo 'Pulling docker image.'", "echo 'Pulling docker image.'",
'docker compose pull', 'docker compose pull',
"echo 'Stopping existing coolify-proxy.'", "echo 'Stopping existing coolify-proxy.'",
"docker compose down -v --remove-orphans > /dev/null 2>&1", 'docker compose down -v --remove-orphans > /dev/null 2>&1',
"echo 'Starting coolify-proxy.'", "echo 'Starting coolify-proxy.'",
'docker compose up -d --remove-orphans', 'docker compose up -d --remove-orphans',
"echo 'Proxy started successfully.'" "echo 'Proxy started successfully.'",
]); ]);
$commands = $commands->merge(connectProxyToNetworks($server)); $commands = $commands->merge(connectProxyToNetworks($server));
} }
if ($async) { if ($async) {
$activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server); $activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
return $activity; return $activity;
} else { } else {
instant_remote_process($commands, $server); instant_remote_process($commands, $server);
@@ -64,6 +66,7 @@ class StartProxy
$server->proxy->set('type', $proxyType); $server->proxy->set('type', $proxyType);
$server->save(); $server->save();
ProxyStarted::dispatch($server); ProxyStarted::dispatch($server);
return 'OK'; return 'OK';
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -2,12 +2,13 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server; use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class CleanupDocker class CleanupDocker
{ {
use AsAction; use AsAction;
public function handle(Server $server, bool $force = true) public function handle(Server $server, bool $force = true)
{ {
if ($force) { if ($force) {

View File

@@ -9,18 +9,19 @@ use Symfony\Component\Yaml\Yaml;
class ConfigureCloudflared class ConfigureCloudflared
{ {
use AsAction; use AsAction;
public function handle(Server $server, string $cloudflare_token) public function handle(Server $server, string $cloudflare_token)
{ {
try { try {
$config = [ $config = [
"services" => [ 'services' => [
"coolify-cloudflared" => [ 'coolify-cloudflared' => [
"container_name" => "coolify-cloudflared", 'container_name' => 'coolify-cloudflared',
"image" => "cloudflare/cloudflared:latest", 'image' => 'cloudflare/cloudflared:latest',
"restart" => RESTART_MODE, 'restart' => RESTART_MODE,
"network_mode" => "host", 'network_mode' => 'host',
"command" => "tunnel run", 'command' => 'tunnel run',
"environment" => [ 'environment' => [
"TUNNEL_TOKEN={$cloudflare_token}", "TUNNEL_TOKEN={$cloudflare_token}",
], ],
], ],
@@ -29,12 +30,12 @@ class ConfigureCloudflared
$config = Yaml::dump($config, 12, 2); $config = Yaml::dump($config, 12, 2);
$docker_compose_yml_base64 = base64_encode($config); $docker_compose_yml_base64 = base64_encode($config);
$commands = collect([ $commands = collect([
"mkdir -p /tmp/cloudflared", 'mkdir -p /tmp/cloudflared',
"cd /tmp/cloudflared", 'cd /tmp/cloudflared',
"echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null", "echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
"docker compose pull", 'docker compose pull',
"docker compose down -v --remove-orphans > /dev/null 2>&1", 'docker compose down -v --remove-orphans > /dev/null 2>&1',
"docker compose up -d --remove-orphans", 'docker compose up -d --remove-orphans',
]); ]);
instant_remote_process($commands, $server); instant_remote_process($commands, $server);
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -42,7 +43,7 @@ class ConfigureCloudflared
throw $e; throw $e;
} finally { } finally {
$commands = collect([ $commands = collect([
"rm -fr /tmp/cloudflared", 'rm -fr /tmp/cloudflared',
]); ]);
instant_remote_process($commands, $server); instant_remote_process($commands, $server);
} }

View File

@@ -2,20 +2,21 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use Lorisleiva\Actions\Concerns\AsAction;
class InstallDocker class InstallDocker
{ {
use AsAction; use AsAction;
public function handle(Server $server) public function handle(Server $server)
{ {
$supported_os_type = $server->validateOS(); $supported_os_type = $server->validateOS();
if (!$supported_os_type) { if (! $supported_os_type) {
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.'); throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
} }
ray('Installing Docker on server: ' . $server->name . ' (' . $server->ip . ')' . ' with OS type: ' . $supported_os_type); ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type);
$dockerVersion = '24.0'; $dockerVersion = '24.0';
$config = base64_encode('{ $config = base64_encode('{
"log-driver": "json-file", "log-driver": "json-file",
@@ -36,40 +37,41 @@ class InstallDocker
if (isDev() && $server->id === 0) { if (isDev() && $server->id === 0) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
"sleep 1", 'sleep 1',
"echo 'Installing Docker Engine...'", "echo 'Installing Docker Engine...'",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'", "echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"sleep 4", 'sleep 4',
"echo 'Restarting Docker Engine...'", "echo 'Restarting Docker Engine...'",
"ls -l /tmp" 'ls -l /tmp',
]); ]);
return remote_process($command, $server); return remote_process($command, $server);
} else { } else {
if ($supported_os_type->contains('debian')) { if ($supported_os_type->contains('debian')) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
"apt-get update -y", 'apt-get update -y',
"command -v curl >/dev/null || apt install -y curl", 'command -v curl >/dev/null || apt install -y curl',
"command -v wget >/dev/null || apt install -y wget", 'command -v wget >/dev/null || apt install -y wget',
"command -v git >/dev/null || apt install -y git", 'command -v git >/dev/null || apt install -y git',
"command -v jq >/dev/null || apt install -y jq", 'command -v jq >/dev/null || apt install -y jq',
]); ]);
} else if ($supported_os_type->contains('rhel')) { } elseif ($supported_os_type->contains('rhel')) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
"command -v curl >/dev/null || dnf install -y curl", 'command -v curl >/dev/null || dnf install -y curl',
"command -v wget >/dev/null || dnf install -y wget", 'command -v wget >/dev/null || dnf install -y wget',
"command -v git >/dev/null || dnf install -y git", 'command -v git >/dev/null || dnf install -y git',
"command -v jq >/dev/null || dnf install -y jq", 'command -v jq >/dev/null || dnf install -y jq',
]); ]);
} else if ($supported_os_type->contains('sles')) { } elseif ($supported_os_type->contains('sles')) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
"zypper update -y", 'zypper update -y',
"command -v curl >/dev/null || zypper install -y curl", 'command -v curl >/dev/null || zypper install -y curl',
"command -v wget >/dev/null || zypper install -y wget", 'command -v wget >/dev/null || zypper install -y wget',
"command -v git >/dev/null || zypper install -y git", 'command -v git >/dev/null || zypper install -y git',
"command -v jq >/dev/null || zypper install -y jq", 'command -v jq >/dev/null || zypper install -y jq',
]); ]);
} else { } else {
throw new \Exception('Unsupported OS'); throw new \Exception('Unsupported OS');
@@ -78,29 +80,30 @@ class InstallDocker
"echo 'Installing Docker Engine...'", "echo 'Installing Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}", "curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'", "echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-$(date +\"%Y%m%d-%H%M%S\")\"", 'test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json "/etc/docker/daemon.json.original-$(date +"%Y%m%d-%H%M%S")"',
"test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null", "test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null",
"echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null", "echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null",
"jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null", 'jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null',
"mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify", 'mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify',
"jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null", "jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null",
"mv /etc/docker/daemon.json.appended /etc/docker/daemon.json", 'mv /etc/docker/daemon.json.appended /etc/docker/daemon.json',
"echo 'Restarting Docker Engine...'", "echo 'Restarting Docker Engine...'",
"systemctl enable docker >/dev/null 2>&1 || true", 'systemctl enable docker >/dev/null 2>&1 || true',
"systemctl restart docker", 'systemctl restart docker',
]); ]);
if ($server->isSwarm()) { if ($server->isSwarm()) {
$command = $command->merge([ $command = $command->merge([
"docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true", 'docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true',
]); ]);
} else { } else {
$command = $command->merge([ $command = $command->merge([
"docker network create --attachable coolify >/dev/null 2>&1 || true", 'docker network create --attachable coolify >/dev/null 2>&1 || true',
]); ]);
$command = $command->merge([ $command = $command->merge([
"echo 'Done!'", "echo 'Done!'",
]); ]);
} }
return remote_process($command, $server); return remote_process($command, $server);
} }
} }

View File

@@ -2,21 +2,22 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server; use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class InstallLogDrain class InstallLogDrain
{ {
use AsAction; use AsAction;
public function handle(Server $server) public function handle(Server $server)
{ {
if ($server->settings->is_logdrain_newrelic_enabled) { if ($server->settings->is_logdrain_newrelic_enabled) {
$type = 'newrelic'; $type = 'newrelic';
} else if ($server->settings->is_logdrain_highlight_enabled) { } elseif ($server->settings->is_logdrain_highlight_enabled) {
$type = 'highlight'; $type = 'highlight';
} else if ($server->settings->is_logdrain_axiom_enabled) { } elseif ($server->settings->is_logdrain_axiom_enabled) {
$type = 'axiom'; $type = 'axiom';
} else if ($server->settings->is_logdrain_custom_enabled) { } elseif ($server->settings->is_logdrain_custom_enabled) {
$type = 'custom'; $type = 'custom';
} else { } else {
$type = 'none'; $type = 'none';
@@ -25,11 +26,12 @@ class InstallLogDrain
if ($type === 'none') { if ($type === 'none') {
$command = [ $command = [
"echo 'Stopping old Fluent Bit'", "echo 'Stopping old Fluent Bit'",
"docker rm -f coolify-log-drain || true", 'docker rm -f coolify-log-drain || true',
]; ];
return instant_remote_process($command, $server); return instant_remote_process($command, $server);
} else if ($type === 'newrelic') { } elseif ($type === 'newrelic') {
if (!$server->settings->is_logdrain_newrelic_enabled) { if (! $server->settings->is_logdrain_newrelic_enabled) {
throw new \Exception('New Relic log drain is not enabled.'); throw new \Exception('New Relic log drain is not enabled.');
} }
$config = base64_encode(" $config = base64_encode("
@@ -59,11 +61,11 @@ class InstallLogDrain
# https://log-api.newrelic.com/log/v1 - US # https://log-api.newrelic.com/log/v1 - US
base_uri \${BASE_URI} base_uri \${BASE_URI}
"); ");
} else if ($type === 'highlight') { } elseif ($type === 'highlight') {
if (!$server->settings->is_logdrain_highlight_enabled) { if (! $server->settings->is_logdrain_highlight_enabled) {
throw new \Exception('Highlight log drain is not enabled.'); throw new \Exception('Highlight log drain is not enabled.');
} }
$config = base64_encode(" $config = base64_encode('
[SERVICE] [SERVICE]
Flush 5 Flush 5
Daemon off Daemon off
@@ -71,7 +73,7 @@ class InstallLogDrain
Parsers_File parsers.conf Parsers_File parsers.conf
[INPUT] [INPUT]
Name forward Name forward
tag \${HIGHLIGHT_PROJECT_ID} tag ${HIGHLIGHT_PROJECT_ID}
Buffer_Chunk_Size 1M Buffer_Chunk_Size 1M
Buffer_Max_Size 6M Buffer_Max_Size 6M
[OUTPUT] [OUTPUT]
@@ -79,9 +81,9 @@ class InstallLogDrain
Match * Match *
Host otel.highlight.io Host otel.highlight.io
Port 24224 Port 24224
"); ');
} else if ($type === 'axiom') { } elseif ($type === 'axiom') {
if (!$server->settings->is_logdrain_axiom_enabled) { if (! $server->settings->is_logdrain_axiom_enabled) {
throw new \Exception('Axiom log drain is not enabled.'); throw new \Exception('Axiom log drain is not enabled.');
} }
$config = base64_encode(" $config = base64_encode("
@@ -116,8 +118,8 @@ class InstallLogDrain
json_date_format iso8601 json_date_format iso8601
tls On tls On
"); ");
} else if ($type === 'custom') { } elseif ($type === 'custom') {
if (!$server->settings->is_logdrain_custom_enabled) { if (! $server->settings->is_logdrain_custom_enabled) {
throw new \Exception('Custom log drain is not enabled.'); throw new \Exception('Custom log drain is not enabled.');
} }
$config = base64_encode($server->settings->logdrain_custom_config); $config = base64_encode($server->settings->logdrain_custom_config);
@@ -133,7 +135,7 @@ class InstallLogDrain
Regex /^(?!\s*$).+/ Regex /^(?!\s*$).+/
"); ");
} }
$compose = base64_encode(" $compose = base64_encode('
services: services:
coolify-log-drain: coolify-log-drain:
image: cr.fluentbit.io/fluent/fluent-bit:2.0 image: cr.fluentbit.io/fluent/fluent-bit:2.0
@@ -147,7 +149,7 @@ services:
ports: ports:
- 127.0.0.1:24224:24224 - 127.0.0.1:24224:24224
restart: unless-stopped restart: unless-stopped
"); ');
$readme = base64_encode('# New Relic Log Drain $readme = base64_encode('# New Relic Log Drain
This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder. This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder.
@@ -160,11 +162,11 @@ Files:
$base_uri = $server->settings->logdrain_newrelic_base_uri; $base_uri = $server->settings->logdrain_newrelic_base_uri;
$base_path = config('coolify.base_config_path'); $base_path = config('coolify.base_config_path');
$config_path = $base_path . '/log-drains'; $config_path = $base_path.'/log-drains';
$fluent_bit_config = $config_path . '/fluent-bit.conf'; $fluent_bit_config = $config_path.'/fluent-bit.conf';
$parsers_config = $config_path . '/parsers.conf'; $parsers_config = $config_path.'/parsers.conf';
$compose_path = $config_path . '/docker-compose.yml'; $compose_path = $config_path.'/docker-compose.yml';
$readme_path = $config_path . '/README.md'; $readme_path = $config_path.'/README.md';
$command = [ $command = [
"echo 'Saving configuration'", "echo 'Saving configuration'",
"mkdir -p $config_path", "mkdir -p $config_path",
@@ -180,18 +182,18 @@ Files:
"echo LICENSE_KEY=$license_key >> $config_path/.env", "echo LICENSE_KEY=$license_key >> $config_path/.env",
"echo BASE_URI=$base_uri >> $config_path/.env", "echo BASE_URI=$base_uri >> $config_path/.env",
]; ];
} else if ($type === 'highlight') { } elseif ($type === 'highlight') {
$add_envs_command = [ $add_envs_command = [
"echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env", "echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env",
]; ];
} else if ($type === 'axiom') { } elseif ($type === 'axiom') {
$add_envs_command = [ $add_envs_command = [
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env", "echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env", "echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
]; ];
} else if ($type === 'custom') { } elseif ($type === 'custom') {
$add_envs_command = [ $add_envs_command = [
"touch $config_path/.env" "touch $config_path/.env",
]; ];
} else { } else {
throw new \Exception('Unknown log drain type.'); throw new \Exception('Unknown log drain type.');
@@ -203,6 +205,7 @@ Files:
"cd $config_path && docker compose up -d --remove-orphans", "cd $config_path && docker compose up -d --remove-orphans",
]; ];
$command = array_merge($command, $add_envs_command, $restart_command); $command = array_merge($command, $add_envs_command, $restart_command);
return instant_remote_process($command, $server); return instant_remote_process($command, $server);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e); return handleError($e);

View File

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

View File

@@ -2,53 +2,45 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Server; use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class UpdateCoolify class UpdateCoolify
{ {
use AsAction; use AsAction;
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 ?Server $server = null;
public ?string $latestVersion = null;
public ?string $currentVersion = null;
public function handle($manual_update = false)
{ {
try { try {
$this->async = $async;
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob'); ray('Running InstanceAutoUpdateJob');
$this->server = Server::find(0); $this->server = Server::find(0);
if (!$this->server) { if (! $this->server) {
return; return;
} }
CleanupDocker::run($this->server, false); CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify(); $this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version'); $this->currentVersion = config('version');
// if ($settings->next_channel) { if (! $manual_update) {
// ray('next channel enabled'); if (! $settings->is_auto_update_enabled) {
// $this->latestVersion = 'next'; return;
// }
if ($force) {
$this->update();
} else {
if (!$settings->is_auto_update_enabled) {
return 'Auto update is disabled';
} }
if ($this->latestVersion === $this->currentVersion) { if ($this->latestVersion === $this->currentVersion) {
return 'Already on latest version'; return;
} }
if (version_compare($this->latestVersion, $this->currentVersion, '<')) { if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
return 'Latest version is lower than current version?!'; return;
} }
$this->update();
} }
$this->update();
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
throw $e; throw $e;
} }
} }
@@ -56,34 +48,16 @@ class UpdateCoolify
private function update() private function update()
{ {
if (isDev()) { if (isDev()) {
ray("Running update on local docker container. Updating to $this->latestVersion"); remote_process([
if ($this->async) { 'sleep 10',
ray('Running async update'); ], $this->server);
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}");
return; 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);
} }
} }

View File

@@ -2,12 +2,13 @@
namespace App\Actions\Service; namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service; use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
class DeleteService class DeleteService
{ {
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
try { try {

View File

@@ -2,26 +2,27 @@
namespace App\Actions\Service; namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service; use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
class StartService class StartService
{ {
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
ray('Starting service: ' . $service->name); ray('Starting service: '.$service->name);
$service->saveComposeConfigs(); $service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir(); $commands[] = 'cd '.$service->workdir();
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'"; $commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo 'Creating Docker network.'"; $commands[] = "echo 'Creating Docker network.'";
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid"; $commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
$commands[] = "echo Starting service."; $commands[] = 'echo Starting service.';
$commands[] = "echo 'Pulling images.'"; $commands[] = "echo 'Pulling images.'";
$commands[] = "docker compose pull"; $commands[] = 'docker compose pull';
$commands[] = "echo 'Starting containers.'"; $commands[] = "echo 'Starting containers.'";
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build"; $commands[] = 'docker compose up -d --remove-orphans --force-recreate --build';
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true"; $commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
if (data_get($service, 'connect_to_docker_network')) { if (data_get($service, 'connect_to_docker_network')) {
$compose = data_get($service, 'docker_compose', []); $compose = data_get($service, 'docker_compose', []);
@@ -32,6 +33,7 @@ class StartService
} }
} }
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged'); $activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
return $activity; return $activity;
} }
} }

View File

@@ -2,20 +2,21 @@
namespace App\Actions\Service; namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service; use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
class StopService class StopService
{ {
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
try { try {
$server = $service->destination->server; $server = $service->destination->server;
if (!$server->isFunctional()) { if (! $server->isFunctional()) {
return 'Server is not functional'; return 'Server is not functional';
} }
ray('Stopping service: ' . $service->name); ray('Stopping service: '.$service->name);
$applications = $service->applications()->get(); $applications = $service->applications()->get();
foreach ($applications as $application) { foreach ($applications as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server); instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
@@ -33,6 +34,7 @@ class StopService
} catch (\Exception $e) { } catch (\Exception $e) {
echo $e->getMessage(); echo $e->getMessage();
ray($e->getMessage()); ray($e->getMessage());
return $e->getMessage(); return $e->getMessage();
} }

View File

@@ -8,18 +8,21 @@ use Lorisleiva\Actions\Concerns\AsAction;
class ComplexStatusCheck class ComplexStatusCheck
{ {
use AsAction; use AsAction;
public function handle(Application $application) public function handle(Application $application)
{ {
$servers = $application->additional_servers; $servers = $application->additional_servers;
$servers->push($application->destination->server); $servers->push($application->destination->server);
foreach ($servers as $server) { foreach ($servers as $server) {
$is_main_server = $application->destination->server->id === $server->id; $is_main_server = $application->destination->server->id === $server->id;
if (!$server->isFunctional()) { if (! $server->isFunctional()) {
if ($is_main_server) { if ($is_main_server) {
$application->update(['status' => 'exited:unhealthy']); $application->update(['status' => 'exited:unhealthy']);
continue; continue;
} else { } else {
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']); $application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
continue; continue;
} }
} }
@@ -44,9 +47,11 @@ class ComplexStatusCheck
} else { } else {
if ($is_main_server) { if ($is_main_server) {
$application->update(['status' => 'exited:unhealthy']); $application->update(['status' => 'exited:unhealthy']);
continue; continue;
} else { } else {
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']); $application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
continue; continue;
} }
} }

View File

@@ -8,17 +8,20 @@ use Lorisleiva\Actions\Concerns\AsAction;
class PullImage class PullImage
{ {
use AsAction; use AsAction;
public function handle(Service $resource) public function handle(Service $resource)
{ {
$resource->saveComposeConfigs(); $resource->saveComposeConfigs();
$commands[] = "cd " . $resource->workdir(); $commands[] = 'cd '.$resource->workdir();
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'"; $commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
$commands[] = "docker compose pull"; $commands[] = 'docker compose pull';
$server = data_get($resource, 'server'); $server = data_get($resource, 'server');
if (!$server) return; if (! $server) {
return;
}
instant_remote_process($commands, $resource->server); instant_remote_process($commands, $resource->server);
} }

View File

@@ -2,7 +2,6 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Server;
use App\Models\User; use App\Models\User;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@@ -29,9 +28,10 @@ class AdminRemoveUser extends Command
{ {
try { try {
$email = $this->argument('email'); $email = $this->argument('email');
$confirm = $this->confirm('Are you sure you want to remove user with email: ' . $email . '?'); $confirm = $this->confirm('Are you sure you want to remove user with email: '.$email.'?');
if (!$confirm) { if (! $confirm) {
$this->info('User removal cancelled.'); $this->info('User removal cancelled.');
return; return;
} }
$this->info("Removing user with email: $email"); $this->info("Removing user with email: $email");
@@ -40,6 +40,7 @@ class AdminRemoveUser extends Command
foreach ($teams as $team) { foreach ($teams as $team) {
if ($team->members->count() > 1) { if ($team->members->count() > 1) {
$this->error('User is a member of a team with more than one member. Please remove user from team first.'); $this->error('User is a member of a team with more than one member. Please remove user from team first.');
return; return;
} }
$team->delete(); $team->delete();
@@ -48,6 +49,7 @@ class AdminRemoveUser extends Command
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('Failed to remove user.'); $this->error('Failed to remove user.');
$this->error($e->getMessage()); $this->error($e->getMessage());
return; return;
} }
} }

View File

@@ -8,6 +8,7 @@ use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command class CleanupApplicationDeploymentQueue extends Command
{ {
protected $signature = 'cleanup:application-deployment-queue {--team-id=}'; protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
protected $description = 'CleanupApplicationDeploymentQueue'; protected $description = 'CleanupApplicationDeploymentQueue';
public function handle() public function handle()
@@ -15,10 +16,10 @@ class CleanupApplicationDeploymentQueue extends Command
$team_id = $this->option('team-id'); $team_id = $this->option('team-id');
$servers = \App\Models\Server::where('team_id', $team_id)->get(); $servers = \App\Models\Server::where('team_id', $team_id)->get();
foreach ($servers as $server) { foreach ($servers as $server) {
$deployments = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->where("server_id", $server->id)->get(); $deployments = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->where('server_id', $server->id)->get();
foreach ($deployments as $deployment) { foreach ($deployments as $deployment) {
$deployment->update(['status' => 'failed']); $deployment->update(['status' => 'failed']);
instant_remote_process(['docker rm -f ' . $deployment->deployment_uuid], $server, false); instant_remote_process(['docker rm -f '.$deployment->deployment_uuid], $server, false);
} }
} }
} }

View File

@@ -8,6 +8,7 @@ use Illuminate\Support\Facades\DB;
class CleanupDatabase extends Command class CleanupDatabase extends Command
{ {
protected $signature = 'cleanup:database {--yes}'; protected $signature = 'cleanup:database {--yes}';
protected $description = 'Cleanup database'; protected $description = 'Cleanup database';
public function handle() public function handle()

View File

@@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Redis;
class CleanupQueue extends Command class CleanupQueue extends Command
{ {
protected $signature = 'cleanup:queue'; protected $signature = 'cleanup:queue';
protected $description = 'Cleanup Queue'; protected $description = 'Cleanup Queue';
public function handle() public function handle()

View File

@@ -20,6 +20,7 @@ use Illuminate\Console\Command;
class CleanupStuckedResources extends Command class CleanupStuckedResources extends Command
{ {
protected $signature = 'cleanup:stucked-resources'; protected $signature = 'cleanup:stucked-resources';
protected $description = 'Cleanup Stucked Resources'; protected $description = 'Cleanup Stucked Resources';
public function handle() public function handle()
@@ -28,6 +29,7 @@ class CleanupStuckedResources extends Command
echo "Running cleanup stucked resources.\n"; echo "Running cleanup stucked resources.\n";
$this->cleanup_stucked_resources(); $this->cleanup_stucked_resources();
} }
private function cleanup_stucked_resources() private function cleanup_stucked_resources()
{ {
@@ -142,7 +144,7 @@ class CleanupStuckedResources extends Command
try { try {
$scheduled_tasks = ScheduledTask::all(); $scheduled_tasks = ScheduledTask::all();
foreach ($scheduled_tasks as $scheduled_task) { foreach ($scheduled_tasks as $scheduled_task) {
if (!$scheduled_task->service && !$scheduled_task->application) { if (! $scheduled_task->service && ! $scheduled_task->application) {
echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n"; echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n";
$scheduled_task->delete(); $scheduled_task->delete();
} }
@@ -155,19 +157,22 @@ class CleanupStuckedResources extends Command
try { try {
$applications = Application::all(); $applications = Application::all();
foreach ($applications as $application) { foreach ($applications as $application) {
if (!data_get($application, 'environment')) { if (! data_get($application, 'environment')) {
echo 'Application without environment: ' . $application->name . '\n'; echo 'Application without environment: '.$application->name.'\n';
$application->forceDelete(); $application->forceDelete();
continue; continue;
} }
if (!$application->destination()) { if (! $application->destination()) {
echo 'Application without destination: ' . $application->name . '\n'; echo 'Application without destination: '.$application->name.'\n';
$application->forceDelete(); $application->forceDelete();
continue; continue;
} }
if (!data_get($application, 'destination.server')) { if (! data_get($application, 'destination.server')) {
echo 'Application without server: ' . $application->name . '\n'; echo 'Application without server: '.$application->name.'\n';
$application->forceDelete(); $application->forceDelete();
continue; continue;
} }
} }
@@ -177,19 +182,22 @@ class CleanupStuckedResources extends Command
try { try {
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0); $postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
foreach ($postgresqls as $postgresql) { foreach ($postgresqls as $postgresql) {
if (!data_get($postgresql, 'environment')) { if (! data_get($postgresql, 'environment')) {
echo 'Postgresql without environment: ' . $postgresql->name . '\n'; echo 'Postgresql without environment: '.$postgresql->name.'\n';
$postgresql->forceDelete(); $postgresql->forceDelete();
continue; continue;
} }
if (!$postgresql->destination()) { if (! $postgresql->destination()) {
echo 'Postgresql without destination: ' . $postgresql->name . '\n'; echo 'Postgresql without destination: '.$postgresql->name.'\n';
$postgresql->forceDelete(); $postgresql->forceDelete();
continue; continue;
} }
if (!data_get($postgresql, 'destination.server')) { if (! data_get($postgresql, 'destination.server')) {
echo 'Postgresql without server: ' . $postgresql->name . '\n'; echo 'Postgresql without server: '.$postgresql->name.'\n';
$postgresql->forceDelete(); $postgresql->forceDelete();
continue; continue;
} }
} }
@@ -199,19 +207,22 @@ class CleanupStuckedResources extends Command
try { try {
$redis = StandaloneRedis::all(); $redis = StandaloneRedis::all();
foreach ($redis as $redis) { foreach ($redis as $redis) {
if (!data_get($redis, 'environment')) { if (! data_get($redis, 'environment')) {
echo 'Redis without environment: ' . $redis->name . '\n'; echo 'Redis without environment: '.$redis->name.'\n';
$redis->forceDelete(); $redis->forceDelete();
continue; continue;
} }
if (!$redis->destination()) { if (! $redis->destination()) {
echo 'Redis without destination: ' . $redis->name . '\n'; echo 'Redis without destination: '.$redis->name.'\n';
$redis->forceDelete(); $redis->forceDelete();
continue; continue;
} }
if (!data_get($redis, 'destination.server')) { if (! data_get($redis, 'destination.server')) {
echo 'Redis without server: ' . $redis->name . '\n'; echo 'Redis without server: '.$redis->name.'\n';
$redis->forceDelete(); $redis->forceDelete();
continue; continue;
} }
} }
@@ -222,19 +233,22 @@ class CleanupStuckedResources extends Command
try { try {
$mongodbs = StandaloneMongodb::all(); $mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) { foreach ($mongodbs as $mongodb) {
if (!data_get($mongodb, 'environment')) { if (! data_get($mongodb, 'environment')) {
echo 'Mongodb without environment: ' . $mongodb->name . '\n'; echo 'Mongodb without environment: '.$mongodb->name.'\n';
$mongodb->forceDelete(); $mongodb->forceDelete();
continue; continue;
} }
if (!$mongodb->destination()) { if (! $mongodb->destination()) {
echo 'Mongodb without destination: ' . $mongodb->name . '\n'; echo 'Mongodb without destination: '.$mongodb->name.'\n';
$mongodb->forceDelete(); $mongodb->forceDelete();
continue; continue;
} }
if (!data_get($mongodb, 'destination.server')) { if (! data_get($mongodb, 'destination.server')) {
echo 'Mongodb without server: ' . $mongodb->name . '\n'; echo 'Mongodb without server: '.$mongodb->name.'\n';
$mongodb->forceDelete(); $mongodb->forceDelete();
continue; continue;
} }
} }
@@ -245,19 +259,22 @@ class CleanupStuckedResources extends Command
try { try {
$mysqls = StandaloneMysql::all(); $mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) { foreach ($mysqls as $mysql) {
if (!data_get($mysql, 'environment')) { if (! data_get($mysql, 'environment')) {
echo 'Mysql without environment: ' . $mysql->name . '\n'; echo 'Mysql without environment: '.$mysql->name.'\n';
$mysql->forceDelete(); $mysql->forceDelete();
continue; continue;
} }
if (!$mysql->destination()) { if (! $mysql->destination()) {
echo 'Mysql without destination: ' . $mysql->name . '\n'; echo 'Mysql without destination: '.$mysql->name.'\n';
$mysql->forceDelete(); $mysql->forceDelete();
continue; continue;
} }
if (!data_get($mysql, 'destination.server')) { if (! data_get($mysql, 'destination.server')) {
echo 'Mysql without server: ' . $mysql->name . '\n'; echo 'Mysql without server: '.$mysql->name.'\n';
$mysql->forceDelete(); $mysql->forceDelete();
continue; continue;
} }
} }
@@ -268,19 +285,22 @@ class CleanupStuckedResources extends Command
try { try {
$mariadbs = StandaloneMariadb::all(); $mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) { foreach ($mariadbs as $mariadb) {
if (!data_get($mariadb, 'environment')) { if (! data_get($mariadb, 'environment')) {
echo 'Mariadb without environment: ' . $mariadb->name . '\n'; echo 'Mariadb without environment: '.$mariadb->name.'\n';
$mariadb->forceDelete(); $mariadb->forceDelete();
continue; continue;
} }
if (!$mariadb->destination()) { if (! $mariadb->destination()) {
echo 'Mariadb without destination: ' . $mariadb->name . '\n'; echo 'Mariadb without destination: '.$mariadb->name.'\n';
$mariadb->forceDelete(); $mariadb->forceDelete();
continue; continue;
} }
if (!data_get($mariadb, 'destination.server')) { if (! data_get($mariadb, 'destination.server')) {
echo 'Mariadb without server: ' . $mariadb->name . '\n'; echo 'Mariadb without server: '.$mariadb->name.'\n';
$mariadb->forceDelete(); $mariadb->forceDelete();
continue; continue;
} }
} }
@@ -291,19 +311,22 @@ class CleanupStuckedResources extends Command
try { try {
$services = Service::all(); $services = Service::all();
foreach ($services as $service) { foreach ($services as $service) {
if (!data_get($service, 'environment')) { if (! data_get($service, 'environment')) {
echo 'Service without environment: ' . $service->name . '\n'; echo 'Service without environment: '.$service->name.'\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
if (!$service->destination()) { if (! $service->destination()) {
echo 'Service without destination: ' . $service->name . '\n'; echo 'Service without destination: '.$service->name.'\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
if (!data_get($service, 'server')) { if (! data_get($service, 'server')) {
echo 'Service without server: ' . $service->name . '\n'; echo 'Service without server: '.$service->name.'\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
} }
@@ -313,9 +336,10 @@ class CleanupStuckedResources extends Command
try { try {
$serviceApplications = ServiceApplication::all(); $serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) { foreach ($serviceApplications as $service) {
if (!data_get($service, 'service')) { if (! data_get($service, 'service')) {
echo 'ServiceApplication without service: ' . $service->name . '\n'; echo 'ServiceApplication without service: '.$service->name.'\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
} }
@@ -325,9 +349,10 @@ class CleanupStuckedResources extends Command
try { try {
$serviceDatabases = ServiceDatabase::all(); $serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) { foreach ($serviceDatabases as $service) {
if (!data_get($service, 'service')) { if (! data_get($service, 'service')) {
echo 'ServiceDatabase without service: ' . $service->name . '\n'; echo 'ServiceDatabase without service: '.$service->name.'\n';
$service->forceDelete(); $service->forceDelete();
continue; continue;
} }
} }

View File

@@ -8,6 +8,7 @@ use Illuminate\Console\Command;
class CleanupUnreachableServers extends Command class CleanupUnreachableServers extends Command
{ {
protected $signature = 'cleanup:unreachable-servers'; protected $signature = 'cleanup:unreachable-servers';
protected $description = 'Cleanup Unreachable Servers (7 days)'; protected $description = 'Cleanup Unreachable Servers (7 days)';
public function handle() public function handle()
@@ -19,7 +20,7 @@ class CleanupUnreachableServers extends Command
echo "Cleanup unreachable server ($server->id) with name $server->name"; echo "Cleanup unreachable server ($server->id) with name $server->name";
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up..."); send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
$server->update([ $server->update([
'ip' => '1.2.3.4' 'ip' => '1.2.3.4',
]); ]);
} }
} }

View File

@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Process;
class Dev extends Command class Dev extends Command
{ {
protected $signature = 'dev:init'; protected $signature = 'dev:init';
protected $description = 'Init the app in dev mode'; protected $description = 'Init the app in dev mode';
public function handle() public function handle()
@@ -21,7 +22,7 @@ class Dev extends Command
} }
// Seed database if it's empty // Seed database if it's empty
$settings = InstanceSettings::find(0); $settings = InstanceSettings::find(0);
if (!$settings) { if (! $settings) {
echo "Initializing instance, seeding database.\n"; echo "Initializing instance, seeding database.\n";
Artisan::call('migrate --seed'); Artisan::call('migrate --seed');
} else { } else {

View File

@@ -2,12 +2,10 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Jobs\DatabaseBackupStatusJob;
use App\Jobs\SendConfirmationForWaitlistJob; use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution;
use App\Models\Server; use App\Models\Server;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use App\Models\Team; use App\Models\Team;
@@ -49,7 +47,9 @@ class Emails extends Command
* Execute the console command. * Execute the console command.
*/ */
private ?MailMessage $mail = null; private ?MailMessage $mail = null;
private ?string $email = null; private ?string $email = null;
public function handle() public function handle()
{ {
$type = select( $type = select(
@@ -73,21 +73,22 @@ class Emails extends Command
); );
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection']; $emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
if (isDev()) { if (isDev()) {
$this->email = "test@example.com"; $this->email = 'test@example.com';
} else { } else {
if (!in_array($type, $emailsGathered)) { if (! in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to:'); $this->email = text('Email Address to send to:');
} }
} }
set_transanctional_email_settings(); set_transanctional_email_settings();
$this->mail = new MailMessage(); $this->mail = new MailMessage();
$this->mail->subject("Test Email"); $this->mail->subject('Test Email');
switch ($type) { switch ($type) {
case 'updates': case 'updates':
$teams = Team::all(); $teams = Team::all();
if (!$teams || $teams->isEmpty()) { if (! $teams || $teams->isEmpty()) {
echo 'No teams found.' . PHP_EOL; echo 'No teams found.'.PHP_EOL;
return; return;
} }
$emails = []; $emails = [];
@@ -99,7 +100,7 @@ class Emails extends Command
} }
} }
$emails = array_unique($emails); $emails = array_unique($emails);
$this->info("Sending to " . count($emails) . " emails."); $this->info('Sending to '.count($emails).' emails.');
foreach ($emails as $email) { foreach ($emails as $email) {
$this->info($email); $this->info($email);
} }
@@ -111,7 +112,7 @@ class Emails extends Command
$unsubscribeUrl = route('unsubscribe.marketing.emails', [ $unsubscribeUrl = route('unsubscribe.marketing.emails', [
'token' => encrypt($email), 'token' => encrypt($email),
]); ]);
$this->mail->view('emails.updates', ["unsubscribeUrl" => $unsubscribeUrl]); $this->mail->view('emails.updates', ['unsubscribeUrl' => $unsubscribeUrl]);
$this->sendEmail($email); $this->sendEmail($email);
} }
} }
@@ -157,7 +158,7 @@ class Emails extends Command
case 'application-deployment-failed': case 'application-deployment-failed':
$application = Application::all()->first(); $application = Application::all()->first();
$preview = ApplicationPreview::all()->first(); $preview = ApplicationPreview::all()->first();
if (!$preview) { if (! $preview) {
$preview = ApplicationPreview::create([ $preview = ApplicationPreview::create([
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => 1, 'pull_request_id' => 1,
@@ -178,7 +179,7 @@ class Emails extends Command
case 'backup-failed': case 'backup-failed':
$backup = ScheduledDatabaseBackup::all()->first(); $backup = ScheduledDatabaseBackup::all()->first();
$db = StandalonePostgresql::all()->first(); $db = StandalonePostgresql::all()->first();
if (!$backup) { if (! $backup) {
$backup = ScheduledDatabaseBackup::create([ $backup = ScheduledDatabaseBackup::create([
'enabled' => true, 'enabled' => true,
'frequency' => 'daily', 'frequency' => 'daily',
@@ -188,14 +189,14 @@ class Emails extends Command
'team_id' => 0, 'team_id' => 0,
]); ]);
} }
$output = 'Because of an error, the backup of the database ' . $db->name . ' failed.'; $output = 'Because of an error, the backup of the database '.$db->name.' failed.';
$this->mail = (new BackupFailed($backup, $db, $output))->toMail(); $this->mail = (new BackupFailed($backup, $db, $output))->toMail();
$this->sendEmail(); $this->sendEmail();
break; break;
case 'backup-success': case 'backup-success':
$backup = ScheduledDatabaseBackup::all()->first(); $backup = ScheduledDatabaseBackup::all()->first();
$db = StandalonePostgresql::all()->first(); $db = StandalonePostgresql::all()->first();
if (!$backup) { if (! $backup) {
$backup = ScheduledDatabaseBackup::create([ $backup = ScheduledDatabaseBackup::create([
'enabled' => true, 'enabled' => true,
'frequency' => 'daily', 'frequency' => 'daily',
@@ -244,8 +245,9 @@ class Emails extends Command
$this->mail->view('emails.before-trial-conversion'); $this->mail->view('emails.before-trial-conversion');
$this->mail->subject('Trial period has been added for all subscription plans.'); $this->mail->subject('Trial period has been added for all subscription plans.');
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get(); $teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
if (!$teams || $teams->isEmpty()) { if (! $teams || $teams->isEmpty()) {
echo 'No teams found.' . PHP_EOL; echo 'No teams found.'.PHP_EOL;
return; return;
} }
$emails = []; $emails = [];
@@ -257,7 +259,7 @@ class Emails extends Command
} }
} }
$emails = array_unique($emails); $emails = array_unique($emails);
$this->info("Sending to " . count($emails) . " emails."); $this->info('Sending to '.count($emails).' emails.');
foreach ($emails as $email) { foreach ($emails as $email) {
$this->info($email); $this->info($email);
} }
@@ -271,7 +273,7 @@ class Emails extends Command
case 'realusers-server-lost-connection': case 'realusers-server-lost-connection':
$serverId = text('Server Id'); $serverId = text('Server Id');
$server = Server::find($serverId); $server = Server::find($serverId);
if (!$server) { if (! $server) {
throw new Exception('Server not found'); throw new Exception('Server not found');
} }
$admins = []; $admins = [];
@@ -281,7 +283,7 @@ class Emails extends Command
$admins[] = $member->email; $admins[] = $member->email;
} }
} }
$this->info('Sending to ' . count($admins) . ' admins.'); $this->info('Sending to '.count($admins).' admins.');
foreach ($admins as $admin) { foreach ($admins as $admin) {
$this->info($admin); $this->info($admin);
} }
@@ -289,14 +291,15 @@ class Emails extends Command
$this->mail->view('emails.server-lost-connection', [ $this->mail->view('emails.server-lost-connection', [
'name' => $server->name, 'name' => $server->name,
]); ]);
$this->mail->subject('Action required: Server ' . $server->name . ' lost connection.'); $this->mail->subject('Action required: Server '.$server->name.' lost connection.');
foreach ($admins as $email) { foreach ($admins as $email) {
$this->sendEmail($email); $this->sendEmail($email);
} }
break; break;
} }
} }
private function sendEmail(string $email = null)
private function sendEmail(?string $email = null)
{ {
if ($email) { if ($email) {
$this->email = $email; $this->email = $email;
@@ -307,7 +310,7 @@ class Emails extends Command
fn (Message $message) => $message fn (Message $message) => $message
->to($this->email) ->to($this->email)
->subject($this->mail->subject) ->subject($this->mail->subject)
->html((string)$this->mail->render()) ->html((string) $this->mail->render())
); );
$this->info("Email sent to $this->email successfully. 📧"); $this->info("Email sent to $this->email successfully. 📧");
} }

View File

@@ -7,7 +7,9 @@ use Illuminate\Console\Command;
class Horizon extends Command class Horizon extends Command
{ {
protected $signature = 'start:horizon'; protected $signature = 'start:horizon';
protected $description = 'Start Horizon'; protected $description = 'Start Horizon';
public function handle() public function handle()
{ {
if (config('coolify.is_horizon_enabled')) { if (config('coolify.is_horizon_enabled')) {

View File

@@ -15,6 +15,7 @@ use Illuminate\Support\Facades\Http;
class Init extends Command class Init extends Command
{ {
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}'; protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
protected $description = 'Cleanup instance related stuffs'; protected $description = 'Cleanup instance related stuffs';
public function handle() public function handle()
@@ -26,6 +27,7 @@ class Init extends Command
if ($cleanup_deployments) { if ($cleanup_deployments) {
echo "Running cleanup deployments.\n"; echo "Running cleanup deployments.\n";
$this->cleanup_in_progress_application_deployments(); $this->cleanup_in_progress_application_deployments();
return; return;
} }
if ($full_cleanup) { if ($full_cleanup) {
@@ -35,7 +37,7 @@ class Init extends Command
$this->cleanup_stucked_helper_containers(); $this->cleanup_stucked_helper_containers();
$this->call('cleanup:queue'); $this->call('cleanup:queue');
$this->call('cleanup:stucked-resources'); $this->call('cleanup:stucked-resources');
if (!isCloud()) { if (! isCloud()) {
try { try {
$server = Server::find(0)->first(); $server = Server::find(0)->first();
$server->setupDynamicProxyConfiguration(); $server->setupDynamicProxyConfiguration();
@@ -45,13 +47,14 @@ class Init extends Command
} }
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
if (!is_null(env('AUTOUPDATE', null))) { if (! is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) { if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]); $settings->update(['is_auto_update_enabled' => true]);
} else { } else {
$settings->update(['is_auto_update_enabled' => false]); $settings->update(['is_auto_update_enabled' => false]);
} }
} }
return; return;
} }
$this->cleanup_stucked_helper_containers(); $this->cleanup_stucked_helper_containers();
@@ -66,7 +69,7 @@ class Init extends Command
echo "Restoring coolify db backup\n"; echo "Restoring coolify db backup\n";
$database->restore(); $database->restore();
$scheduledBackup = ScheduledDatabaseBackup::find(0); $scheduledBackup = ScheduledDatabaseBackup::find(0);
if (!$scheduledBackup) { if (! $scheduledBackup) {
ScheduledDatabaseBackup::create([ ScheduledDatabaseBackup::create([
'id' => 0, 'id' => 0,
'enabled' => true, 'enabled' => true,
@@ -82,6 +85,7 @@ class Init extends Command
echo "Error in restoring coolify db backup: {$e->getMessage()}\n"; echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
} }
} }
private function cleanup_stucked_helper_containers() private function cleanup_stucked_helper_containers()
{ {
$servers = Server::all(); $servers = Server::all();
@@ -91,6 +95,7 @@ class Init extends Command
} }
} }
} }
private function alive() private function alive()
{ {
$id = config('app.id'); $id = config('app.id');
@@ -99,6 +104,7 @@ class Init extends Command
$do_not_track = data_get($settings, 'do_not_track'); $do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) { if ($do_not_track == true) {
echo "Skipping alive as do_not_track is enabled\n"; echo "Skipping alive as do_not_track is enabled\n";
return; return;
} }
try { try {

View File

@@ -3,6 +3,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use function Termwind\ask; use function Termwind\ask;
use function Termwind\render; use function Termwind\render;
use function Termwind\style; use function Termwind\style;
@@ -32,6 +33,7 @@ class NotifyDemo extends Command
if (blank($channel)) { if (blank($channel)) {
$this->showHelp(); $this->showHelp();
return; return;
} }

View File

@@ -35,6 +35,7 @@ class RootChangeEmail extends Command
$this->info('Root user\'s email updated successfully.'); $this->info('Root user\'s email updated successfully.');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('Failed to update root user\'s email.'); $this->error('Failed to update root user\'s email.');
return; return;
} }
} }

View File

@@ -34,6 +34,7 @@ class RootResetPassword extends Command
$passwordAgain = password('Again'); $passwordAgain = password('Again');
if ($password != $passwordAgain) { if ($password != $passwordAgain) {
$this->error('Passwords do not match.'); $this->error('Passwords do not match.');
return; return;
} }
$this->info('Updating root password...'); $this->info('Updating root password...');
@@ -42,6 +43,7 @@ class RootResetPassword extends Command
$this->info('Root password updated successfully.'); $this->info('Root password updated successfully.');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('Failed to update root password.'); $this->error('Failed to update root password.');
return; return;
} }
} }

View File

@@ -7,7 +7,9 @@ use Illuminate\Console\Command;
class Scheduler extends Command class Scheduler extends Command
{ {
protected $signature = 'start:scheduler'; protected $signature = 'start:scheduler';
protected $description = 'Start Scheduler'; protected $description = 'Start Scheduler';
public function handle() public function handle()
{ {
if (config('coolify.is_scheduler_enabled')) { if (config('coolify.is_scheduler_enabled')) {

View File

@@ -48,11 +48,13 @@ class ServicesDelete extends Command
$this->deleteServer(); $this->deleteServer();
} }
} }
private function deleteServer() private function deleteServer()
{ {
$servers = Server::all(); $servers = Server::all();
if ($servers->count() === 0) { if ($servers->count() === 0) {
$this->error('There are no applications to delete.'); $this->error('There are no applications to delete.');
return; return;
} }
$serversToDelete = multiselect( $serversToDelete = multiselect(
@@ -64,19 +66,21 @@ class ServicesDelete extends Command
$toDelete = $servers->where('id', $server)->first(); $toDelete = $servers->where('id', $server)->first();
if ($toDelete) { if ($toDelete) {
$this->info($toDelete); $this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?"); $confirmed = confirm('Are you sure you want to delete all selected resources?');
if (!$confirmed) { if (! $confirmed) {
break; break;
} }
$toDelete->delete(); $toDelete->delete();
} }
} }
} }
private function deleteApplication() private function deleteApplication()
{ {
$applications = Application::all(); $applications = Application::all();
if ($applications->count() === 0) { if ($applications->count() === 0) {
$this->error('There are no applications to delete.'); $this->error('There are no applications to delete.');
return; return;
} }
$applicationsToDelete = multiselect( $applicationsToDelete = multiselect(
@@ -88,19 +92,21 @@ class ServicesDelete extends Command
$toDelete = $applications->where('id', $application)->first(); $toDelete = $applications->where('id', $application)->first();
if ($toDelete) { if ($toDelete) {
$this->info($toDelete); $this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources? "); $confirmed = confirm('Are you sure you want to delete all selected resources? ');
if (!$confirmed) { if (! $confirmed) {
break; break;
} }
DeleteResourceJob::dispatch($toDelete); DeleteResourceJob::dispatch($toDelete);
} }
} }
} }
private function deleteDatabase() private function deleteDatabase()
{ {
$databases = StandalonePostgresql::all(); $databases = StandalonePostgresql::all();
if ($databases->count() === 0) { if ($databases->count() === 0) {
$this->error('There are no databases to delete.'); $this->error('There are no databases to delete.');
return; return;
} }
$databasesToDelete = multiselect( $databasesToDelete = multiselect(
@@ -112,19 +118,21 @@ class ServicesDelete extends Command
$toDelete = $databases->where('id', $database)->first(); $toDelete = $databases->where('id', $database)->first();
if ($toDelete) { if ($toDelete) {
$this->info($toDelete); $this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?"); $confirmed = confirm('Are you sure you want to delete all selected resources?');
if (!$confirmed) { if (! $confirmed) {
return; return;
} }
DeleteResourceJob::dispatch($toDelete); DeleteResourceJob::dispatch($toDelete);
} }
} }
} }
private function deleteService() private function deleteService()
{ {
$services = Service::all(); $services = Service::all();
if ($services->count() === 0) { if ($services->count() === 0) {
$this->error('There are no services to delete.'); $this->error('There are no services to delete.');
return; return;
} }
$servicesToDelete = multiselect( $servicesToDelete = multiselect(
@@ -136,8 +144,8 @@ class ServicesDelete extends Command
$toDelete = $services->where('id', $service)->first(); $toDelete = $services->where('id', $service)->first();
if ($toDelete) { if ($toDelete) {
$this->info($toDelete); $this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?"); $confirmed = confirm('Are you sure you want to delete all selected resources?');
if (!$confirmed) { if (! $confirmed) {
return; return;
} }
DeleteResourceJob::dispatch($toDelete); DeleteResourceJob::dispatch($toDelete);

View File

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

View File

@@ -33,30 +33,31 @@ class SyncBunny extends Command
$that = $this; $that = $this;
$only_template = $this->option('templates'); $only_template = $this->option('templates');
$only_version = $this->option('release'); $only_version = $this->option('release');
$bunny_cdn = "https://cdn.coollabs.io"; $bunny_cdn = 'https://cdn.coollabs.io';
$bunny_cdn_path = "coolify"; $bunny_cdn_path = 'coolify';
$bunny_cdn_storage_name = "coolcdn"; $bunny_cdn_storage_name = 'coolcdn';
$parent_dir = realpath(dirname(__FILE__) . '/../../..'); $parent_dir = realpath(dirname(__FILE__).'/../../..');
$compose_file = "docker-compose.yml"; $compose_file = 'docker-compose.yml';
$compose_file_prod = "docker-compose.prod.yml"; $compose_file_prod = 'docker-compose.prod.yml';
$install_script = "install.sh"; $install_script = 'install.sh';
$upgrade_script = "upgrade.sh"; $upgrade_script = 'upgrade.sh';
$production_env = ".env.production"; $production_env = '.env.production';
$service_template = "service-templates.json"; $service_template = 'service-templates.json';
$versions = "versions.json"; $versions = 'versions.json';
PendingRequest::macro('storage', function ($fileName) use ($that) { PendingRequest::macro('storage', function ($fileName) use ($that) {
$headers = [ $headers = [
'AccessKey' => env('BUNNY_STORAGE_API_KEY'), 'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
'Accept' => 'application/json', 'Accept' => 'application/json',
'Content-Type' => 'application/octet-stream' 'Content-Type' => 'application/octet-stream',
]; ];
$fileStream = fopen($fileName, "r"); $fileStream = fopen($fileName, 'r');
$file = fread($fileStream, filesize($fileName)); $file = fread($fileStream, filesize($fileName));
$that->info('Uploading: ' . $fileName); $that->info('Uploading: '.$fileName);
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw(); return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
}); });
PendingRequest::macro('purge', function ($url) use ($that) { PendingRequest::macro('purge', function ($url) use ($that) {
@@ -64,20 +65,21 @@ class SyncBunny extends Command
'AccessKey' => env('BUNNY_API_KEY'), 'AccessKey' => env('BUNNY_API_KEY'),
'Accept' => 'application/json', 'Accept' => 'application/json',
]; ];
$that->info('Purging: ' . $url); $that->info('Purging: '.$url);
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [ return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
"url" => $url, 'url' => $url,
"async" => false 'async' => false,
]); ]);
}); });
try { try {
if (!$only_template && !$only_version) { if (! $only_template && ! $only_version) {
$this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.'); $this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
} }
if ($only_template) { if ($only_template) {
$this->info('About to sync service-templates.json to BunnyCDN.'); $this->info('About to sync service-templates.json to BunnyCDN.');
$confirmed = confirm("Are you sure you want to sync?"); $confirmed = confirm('Are you sure you want to sync?');
if (!$confirmed) { if (! $confirmed) {
return; return;
} }
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
@@ -85,15 +87,16 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
]); ]);
$this->info('Service template uploaded & purged...'); $this->info('Service template uploaded & purged...');
return; return;
} else if ($only_version) { } elseif ($only_version) {
$this->info('About to sync versions.json to BunnyCDN.'); $this->info('About to sync versions.json to BunnyCDN.');
$file = file_get_contents("$parent_dir/$versions"); $file = file_get_contents("$parent_dir/$versions");
$json = json_decode($file, true); $json = json_decode($file, true);
$actual_version = data_get($json, 'coolify.v4.version'); $actual_version = data_get($json, 'coolify.v4.version');
$confirmed = confirm("Are you sure you want to sync to {$actual_version}?"); $confirmed = confirm("Are you sure you want to sync to {$actual_version}?");
if (!$confirmed) { if (! $confirmed) {
return; return;
} }
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
@@ -101,10 +104,10 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]); ]);
$this->info('versions.json uploaded & purged...'); $this->info('versions.json uploaded & purged...');
return; return;
} }
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"), $pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"), $pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
@@ -119,9 +122,9 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
]); ]);
$this->info("All files uploaded & purged..."); $this->info('All files uploaded & purged...');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->error("Error: " . $e->getMessage()); $this->error('Error: '.$e->getMessage());
} }
} }
} }

View File

@@ -13,7 +13,9 @@ use Illuminate\Support\Str;
class WaitlistInvite extends Command class WaitlistInvite extends Command
{ {
public Waitlist|User|null $next_patient = null; public Waitlist|User|null $next_patient = null;
public string|null $password = null;
public ?string $password = null;
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
@@ -38,7 +40,9 @@ class WaitlistInvite extends Command
$this->main(); $this->main();
} }
} }
private function main() {
private function main()
{
if ($this->argument('email')) { if ($this->argument('email')) {
if ($this->option('only-email')) { if ($this->option('only-email')) {
$this->next_patient = User::whereEmail($this->argument('email'))->first(); $this->next_patient = User::whereEmail($this->argument('email'))->first();
@@ -50,8 +54,9 @@ class WaitlistInvite extends Command
} else { } else {
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first(); $this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
} }
if (!$this->next_patient) { if (! $this->next_patient) {
$this->error("{$this->argument('email')} not found in the waitlist."); $this->error("{$this->argument('email')} not found in the waitlist.");
return; return;
} }
} else { } else {
@@ -60,6 +65,7 @@ class WaitlistInvite extends Command
if ($this->next_patient) { if ($this->next_patient) {
if ($this->option('only-email')) { if ($this->option('only-email')) {
$this->send_email(); $this->send_email();
return; return;
} }
$this->register_user(); $this->register_user();
@@ -69,10 +75,11 @@ class WaitlistInvite extends Command
$this->info('No verified user found in the waitlist. 👀'); $this->info('No verified user found in the waitlist. 👀');
} }
} }
private function register_user() private function register_user()
{ {
$already_registered = User::whereEmail($this->next_patient->email)->first(); $already_registered = User::whereEmail($this->next_patient->email)->first();
if (!$already_registered) { if (! $already_registered) {
$this->password = Str::password(); $this->password = Str::password();
User::create([ User::create([
'name' => Str::of($this->next_patient->email)->before('@'), 'name' => Str::of($this->next_patient->email)->before('@'),
@@ -85,11 +92,13 @@ class WaitlistInvite extends Command
throw new \Exception('User already registered'); throw new \Exception('User already registered');
} }
} }
private function remove_from_waitlist() private function remove_from_waitlist()
{ {
$this->next_patient->delete(); $this->next_patient->delete();
$this->info("User removed from waitlist successfully."); $this->info('User removed from waitlist successfully.');
} }
private function send_email() private function send_email()
{ {
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password"); $token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
@@ -100,6 +109,6 @@ class WaitlistInvite extends Command
]); ]);
$mail->subject('Congratulations! You are invited to join Coolify Cloud.'); $mail->subject('Congratulations! You are invited to join Coolify Cloud.');
send_user_an_email($mail, $this->next_patient->email); send_user_an_email($mail, $this->next_patient->email);
$this->info("Email sent successfully. 📧"); $this->info('Email sent successfully. 📧');
} }
} }

View File

@@ -4,14 +4,14 @@ namespace App\Console;
use App\Jobs\CheckLogDrainContainerJob; use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CleanupInstanceStuffsJob; use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\PullCoolifyImageJob;
use App\Jobs\PullHelperImageJob; use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob; use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\ServerStatusJob; use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask; use App\Models\ScheduledTask;
use App\Models\Server; use App\Models\Server;
@@ -22,6 +22,7 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel class Kernel extends ConsoleKernel
{ {
private $all_servers; private $all_servers;
protected function schedule(Schedule $schedule): void protected function schedule(Schedule $schedule): void
{ {
$this->all_servers = Server::all(); $this->all_servers = Server::all();
@@ -29,34 +30,34 @@ class Kernel extends ConsoleKernel
// Instance Jobs // Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute(); $schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); $schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
// Server Jobs // Server Jobs
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
// $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule); $this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes(); $schedule->command('uploads:clear')->everyTwoMinutes();
} else { } else {
// Instance Jobs // Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily(); $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 CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); // $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs // Server Jobs
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->pull_helper_image($schedule); $this->pull_images($schedule);
$this->check_scheduled_tasks($schedule); $this->check_scheduled_tasks($schedule);
$schedule->command('cleanup:database --yes')->daily(); $schedule->command('cleanup:database --yes')->daily();
$schedule->command('uploads:clear')->everyTwoMinutes(); $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'); $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) { foreach ($servers as $server) {
@@ -66,6 +67,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer(); $schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
} }
} }
private function check_resources($schedule) private function check_resources($schedule)
{ {
if (isCloud()) { if (isCloud()) {
@@ -87,16 +89,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer(); $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) private function check_scheduled_backups($schedule)
{ {
$scheduled_backups = ScheduledDatabaseBackup::all(); $scheduled_backups = ScheduledDatabaseBackup::all();
@@ -104,12 +97,13 @@ class Kernel extends ConsoleKernel
return; return;
} }
foreach ($scheduled_backups as $scheduled_backup) { foreach ($scheduled_backups as $scheduled_backup) {
if (!$scheduled_backup->enabled) { if (! $scheduled_backup->enabled) {
continue; continue;
} }
if (is_null(data_get($scheduled_backup, 'database'))) { if (is_null(data_get($scheduled_backup, 'database'))) {
ray('database not found'); ray('database not found');
$scheduled_backup->delete(); $scheduled_backup->delete();
continue; continue;
} }
@@ -135,9 +129,10 @@ class Kernel extends ConsoleKernel
$service = $scheduled_task->service; $service = $scheduled_task->service;
$application = $scheduled_task->application; $application = $scheduled_task->application;
if (!$application && !$service) { if (! $application && ! $service) {
ray('application/service attached to scheduled task does not exist'); ray('application/service attached to scheduled task does not exist');
$scheduled_task->delete(); $scheduled_task->delete();
continue; continue;
} }
if ($application) { if ($application) {
@@ -161,7 +156,7 @@ class Kernel extends ConsoleKernel
protected function commands(): void protected function commands(): void
{ {
$this->load(__DIR__ . '/Commands'); $this->load(__DIR__.'/Commands');
require base_path('routes/console.php'); require base_path('routes/console.php');
} }

View File

@@ -12,18 +12,18 @@ use Spatie\LaravelData\Data;
class CoolifyTaskArgs extends Data class CoolifyTaskArgs extends Data
{ {
public function __construct( public function __construct(
public string $server_uuid, public string $server_uuid,
public string $command, public string $command,
public string $type, public string $type,
public ?string $type_uuid = null, public ?string $type_uuid = null,
public ?int $process_id = null, public ?int $process_id = null,
public ?Model $model = null, public ?Model $model = null,
public ?string $status = null , public ?string $status = null,
public bool $ignore_errors = false, public bool $ignore_errors = false,
public $call_event_on_finish = null, public $call_event_on_finish = null,
public $call_event_data = null public $call_event_data = null
) { ) {
if(is_null($status)){ if (is_null($status)) {
$this->status = ProcessStatus::QUEUED->value; $this->status = ProcessStatus::QUEUED->value;
} }
} }

View File

@@ -9,7 +9,7 @@ use Spatie\LaravelData\Data;
class ServerMetadata extends Data class ServerMetadata extends Data
{ {
public function __construct( public function __construct(
public ?ProxyTypes $type, public ?ProxyTypes $type,
public ?ProxyStatus $status public ?ProxyStatus $status
) { ) {
} }

View File

@@ -2,9 +2,7 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class ApplicationStatusChanged implements ShouldBroadcast class ApplicationStatusChanged implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId; public $teamId;
public function __construct($teamId = null) public function __construct($teamId = null)
{ {
if (is_null($teamId)) { if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null; $teamId = auth()->user()->currentTeam()->id ?? null;
} }
if (is_null($teamId)) { if (is_null($teamId)) {
throw new \Exception("Team id is null"); throw new \Exception('Team id is null');
} }
$this->teamId = $teamId; $this->teamId = $teamId;
} }

View File

@@ -2,9 +2,7 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class BackupCreated implements ShouldBroadcast class BackupCreated implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId; public $teamId;
public function __construct($teamId = null) public function __construct($teamId = null)
{ {
if (is_null($teamId)) { if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null; $teamId = auth()->user()->currentTeam()->id ?? null;
} }
if (is_null($teamId)) { if (is_null($teamId)) {
throw new \Exception("Team id is null"); throw new \Exception('Team id is null');
} }
$this->teamId = $teamId; $this->teamId = $teamId;
} }

View File

@@ -2,9 +2,7 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class DatabaseStatusChanged implements ShouldBroadcast class DatabaseStatusChanged implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $userId; public $userId;
public function __construct($userId = null) public function __construct($userId = null)
{ {
if (is_null($userId)) { if (is_null($userId)) {
$userId = auth()->user()->id ?? null; $userId = auth()->user()->id ?? null;
} }
if (is_null($userId)) { if (is_null($userId)) {
throw new \Exception("User id is null"); throw new \Exception('User id is null');
} }
$this->userId = $userId; $this->userId = $userId;
} }

View File

@@ -9,6 +9,7 @@ use Illuminate\Queue\SerializesModels;
class ProxyStarted class ProxyStarted
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public $data) public function __construct(public $data)
{ {

View File

@@ -2,9 +2,7 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class ProxyStatusChanged implements ShouldBroadcast class ProxyStatusChanged implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId; public $teamId;
public function __construct($teamId = null) public function __construct($teamId = null)
{ {
if (is_null($teamId)) { if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null; $teamId = auth()->user()->currentTeam()->id ?? null;
} }
if (is_null($teamId)) { if (is_null($teamId)) {
throw new \Exception("Team id is null"); throw new \Exception('Team id is null');
} }
$this->teamId = $teamId; $this->teamId = $teamId;
} }

View File

@@ -2,9 +2,7 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class ServiceStatusChanged implements ShouldBroadcast class ServiceStatusChanged implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $userId; public $userId;
public function __construct($userId = null) public function __construct($userId = null)
{ {
if (is_null($userId)) { if (is_null($userId)) {
$userId = auth()->user()->id ?? null; $userId = auth()->user()->id ?? null;
} }
if (is_null($userId)) { if (is_null($userId)) {
throw new \Exception("User id is null"); throw new \Exception('User id is null');
} }
$this->userId = $userId; $this->userId = $userId;
} }

View File

@@ -2,9 +2,7 @@
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
@@ -13,7 +11,9 @@ use Illuminate\Queue\SerializesModels;
class TestEvent implements ShouldBroadcast class TestEvent implements ShouldBroadcast
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId; public $teamId;
public function __construct() public function __construct()
{ {
$this->teamId = auth()->user()->currentTeam()->id; $this->teamId = auth()->user()->currentTeam()->id;

View File

@@ -13,7 +13,6 @@ use Throwable;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
/** /**
* A list of exception types with their corresponding custom log levels. * A list of exception types with their corresponding custom log levels.
* *
@@ -22,14 +21,16 @@ class Handler extends ExceptionHandler
protected $levels = [ protected $levels = [
// //
]; ];
/** /**
* A list of the exception types that are not reported. * A list of the exception types that are not reported.
* *
* @var array<int, class-string<\Throwable>> * @var array<int, class-string<\Throwable>>
*/ */
protected $dontReport = [ protected $dontReport = [
ProcessException::class ProcessException::class,
]; ];
/** /**
* A list of the inputs that are never flashed to the session on validation exceptions. * A list of the inputs that are never flashed to the session on validation exceptions.
* *
@@ -40,6 +41,7 @@ class Handler extends ExceptionHandler
'password', 'password',
'password_confirmation', 'password_confirmation',
]; ];
private InstanceSettings $settings; private InstanceSettings $settings;
protected function unauthenticated($request, AuthenticationException $exception) protected function unauthenticated($request, AuthenticationException $exception)
@@ -47,8 +49,10 @@ class Handler extends ExceptionHandler
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) { if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
return response()->json(['message' => $exception->getMessage()], 401); return response()->json(['message' => $exception->getMessage()], 401);
} }
return redirect()->guest($exception->redirectTo() ?? route('login')); return redirect()->guest($exception->redirectTo() ?? route('login'));
} }
/** /**
* Register the exception handling callbacks for the application. * Register the exception handling callbacks for the application.
*/ */
@@ -72,7 +76,7 @@ class Handler extends ExceptionHandler
$scope->setUser( $scope->setUser(
[ [
'email' => $email, 'email' => $email,
'instanceAdmin' => $instanceAdmin 'instanceAdmin' => $instanceAdmin,
] ]
); );
} }

View File

@@ -6,5 +6,4 @@ use Exception;
class ProcessException extends Exception class ProcessException extends Exception
{ {
} }

View File

@@ -27,18 +27,20 @@ class Deploy extends Controller
return invalid_token(); return invalid_token();
} }
$servers = Server::whereTeamId($teamId)->get(); $servers = Server::whereTeamId($teamId)->get();
$deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $servers->pluck("id"))->get([ $deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get([
"id", 'id',
"application_id", 'application_id',
"application_name", 'application_name',
"deployment_url", 'deployment_url',
"pull_request_id", 'pull_request_id',
"server_name", 'server_name',
"server_id", 'server_id',
"status" 'status',
])->sortBy('id')->toArray(); ])->sortBy('id')->toArray();
return response()->json($deployments_per_server, 200); return response()->json($deployments_per_server, 200);
} }
public function deploy(Request $request) public function deploy(Request $request)
{ {
$teamId = get_team_id_from_token(); $teamId = get_team_id_from_token();
@@ -54,11 +56,13 @@ class Deploy extends Controller
} }
if ($tags) { if ($tags) {
return $this->by_tags($tags, $teamId, $force); return $this->by_tags($tags, $teamId, $force);
} else if ($uuids) { } elseif ($uuids) {
return $this->by_uuids($uuids, $teamId, $force); return $this->by_uuids($uuids, $teamId, $force);
} }
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400); return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
} }
private function by_uuids(string $uuid, int $teamId, bool $force = false) private function by_uuids(string $uuid, int $teamId, bool $force = false)
{ {
$uuids = explode(',', $uuid); $uuids = explode(',', $uuid);
@@ -82,10 +86,13 @@ class Deploy extends Controller
} }
if ($deployments->count() > 0) { if ($deployments->count() > 0) {
$payload->put('deployments', $deployments->toArray()); $payload->put('deployments', $deployments->toArray());
return response()->json($payload->toArray(), 200); return response()->json($payload->toArray(), 200);
} }
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
return response()->json(['error' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
} }
public function by_tags(string $tags, int $team_id, bool $force = false) public function by_tags(string $tags, int $team_id, bool $force = false)
{ {
$tags = explode(',', $tags); $tags = explode(',', $tags);
@@ -99,7 +106,7 @@ class Deploy extends Controller
$payload = collect(); $payload = collect();
foreach ($tags as $tag) { foreach ($tags as $tag) {
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first(); $found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
if (!$found_tag) { if (! $found_tag) {
// $message->push("Tag {$tag} not found."); // $message->push("Tag {$tag} not found.");
continue; continue;
} }
@@ -107,6 +114,7 @@ class Deploy extends Controller
$services = $found_tag->services()->get(); $services = $found_tag->services()->get();
if ($applications->count() === 0 && $services->count() === 0) { if ($applications->count() === 0 && $services->count() === 0) {
$message->push("No resources found for tag {$tag}."); $message->push("No resources found for tag {$tag}.");
continue; continue;
} }
foreach ($applications as $resource) { foreach ($applications as $resource) {
@@ -127,11 +135,13 @@ class Deploy extends Controller
if ($deployments->count() > 0) { if ($deployments->count() > 0) {
$payload->put('details', $deployments->toArray()); $payload->put('details', $deployments->toArray());
} }
return response()->json($payload->toArray(), 200); return response()->json($payload->toArray(), 200);
} }
return response()->json(['error' => "No resources found with this tag.", 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404); return response()->json(['error' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
} }
public function deploy_resource($resource, bool $force = false): array public function deploy_resource($resource, bool $force = false): array
{ {
$message = null; $message = null;
@@ -148,58 +158,59 @@ class Deploy extends Controller
force_rebuild: $force, force_rebuild: $force,
); );
$message = "Application {$resource->name} deployment queued."; $message = "Application {$resource->name} deployment queued.";
} else if ($type === 'App\Models\StandalonePostgresql') { } elseif ($type === 'App\Models\StandalonePostgresql') {
StartPostgresql::run($resource); StartPostgresql::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneRedis') { } elseif ($type === 'App\Models\StandaloneRedis') {
StartRedis::run($resource); StartRedis::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneKeydb') { } elseif ($type === 'App\Models\StandaloneKeydb') {
StartKeydb::run($resource); StartKeydb::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneDragonfly') { } elseif ($type === 'App\Models\StandaloneDragonfly') {
StartDragonfly::run($resource); StartDragonfly::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneClickhouse') { } elseif ($type === 'App\Models\StandaloneClickhouse') {
StartClickhouse::run($resource); StartClickhouse::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMongodb') { } elseif ($type === 'App\Models\StandaloneMongodb') {
StartMongodb::run($resource); StartMongodb::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMysql') { } elseif ($type === 'App\Models\StandaloneMysql') {
StartMysql::run($resource); StartMysql::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMariadb') { } elseif ($type === 'App\Models\StandaloneMariadb') {
StartMariadb::run($resource); StartMariadb::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$message = "Database {$resource->name} started."; $message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\Service') { } elseif ($type === 'App\Models\Service') {
StartService::run($resource); StartService::run($resource);
$message = "Service {$resource->name} started. It could take a while, be patient."; $message = "Service {$resource->name} started. It could take a while, be patient.";
} }
return ['message' => $message, 'deployment_uuid' => $deployment_uuid]; return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
} }
} }

View File

@@ -38,7 +38,7 @@ class Domains extends Controller
'ip' => $settings->public_ipv6, 'ip' => $settings->public_ipv6,
]); ]);
} }
if (!$settings->public_ipv4 && !$settings->public_ipv6) { if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
$domains->push([ $domains->push([
'domain' => $fqdn, 'domain' => $fqdn,
'ip' => $ip, 'ip' => $ip,
@@ -74,7 +74,7 @@ class Domains extends Controller
'ip' => $settings->public_ipv6, 'ip' => $settings->public_ipv6,
]); ]);
} }
if (!$settings->public_ipv4 && !$settings->public_ipv6) { if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
$domains->push([ $domains->push([
'domain' => $fqdn, 'domain' => $fqdn,
'ip' => $ip, 'ip' => $ip,

View File

@@ -15,8 +15,10 @@ class Project extends Controller
return invalid_token(); return invalid_token();
} }
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get(); $projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
return response()->json($projects); return response()->json($projects);
} }
public function project_by_uuid(Request $request) public function project_by_uuid(Request $request)
{ {
$teamId = get_team_id_from_token(); $teamId = get_team_id_from_token();
@@ -24,8 +26,10 @@ class Project extends Controller
return invalid_token(); return invalid_token();
} }
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']); $project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
return response()->json($project); return response()->json($project);
} }
public function environment_details(Request $request) public function environment_details(Request $request)
{ {
$teamId = get_team_id_from_token(); $teamId = get_team_id_from_token();
@@ -34,6 +38,7 @@ class Project extends Controller
} }
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first(); $project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']); $environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
return response()->json($environment); return response()->json($environment);
} }
} }

View File

@@ -30,9 +30,10 @@ class Resources extends Controller
$payload['status'] = $resource->status; $payload['status'] = $resource->status;
} }
$payload['type'] = $resource->type(); $payload['type'] = $resource->type();
return $payload; return $payload;
}); });
return response()->json($resources); return response()->json($resources);
} }
} }

View File

@@ -17,10 +17,13 @@ class Server extends Controller
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) { $servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
$server['is_reachable'] = $server->settings->is_reachable; $server['is_reachable'] = $server->settings->is_reachable;
$server['is_usable'] = $server->settings->is_usable; $server['is_usable'] = $server->settings->is_usable;
return $server; return $server;
}); });
return response()->json($servers); return response()->json($servers);
} }
public function server_by_uuid(Request $request) public function server_by_uuid(Request $request)
{ {
$with_resources = $request->query('resources'); $with_resources = $request->query('resources');
@@ -47,11 +50,13 @@ class Server extends Controller
} else { } else {
$payload['status'] = $resource->status; $payload['status'] = $resource->status;
} }
return $payload; return $payload;
}); });
} else { } else {
$server->load(['settings']); $server->load(['settings']);
} }
return response()->json($server); return response()->json($server);
} }
} }

View File

@@ -14,8 +14,10 @@ class Team extends Controller
return invalid_token(); return invalid_token();
} }
$teams = auth()->user()->teams; $teams = auth()->user()->teams;
return response()->json($teams); return response()->json($teams);
} }
public function team_by_id(Request $request) public function team_by_id(Request $request)
{ {
$id = $request->id; $id = $request->id;
@@ -26,10 +28,12 @@ class Team extends Controller
$teams = auth()->user()->teams; $teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first(); $team = $teams->where('id', $id)->first();
if (is_null($team)) { if (is_null($team)) {
return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api-reference/get-team-by-teamid"], 404); return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
} }
return response()->json($team); return response()->json($team);
} }
public function members_by_id(Request $request) public function members_by_id(Request $request)
{ {
$id = $request->id; $id = $request->id;
@@ -40,10 +44,12 @@ class Team extends Controller
$teams = auth()->user()->teams; $teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first(); $team = $teams->where('id', $id)->first();
if (is_null($team)) { if (is_null($team)) {
return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api-reference/get-team-by-teamid-members"], 404); return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
} }
return response()->json($team->members); return response()->json($team->members);
} }
public function current_team(Request $request) public function current_team(Request $request)
{ {
$teamId = get_team_id_from_token(); $teamId = get_team_id_from_token();
@@ -51,8 +57,10 @@ class Team extends Controller
return invalid_token(); return invalid_token();
} }
$team = auth()->user()->currentTeam(); $team = auth()->user()->currentTeam();
return response()->json($team); return response()->json($team);
} }
public function current_team_members(Request $request) public function current_team_members(Request $request)
{ {
$teamId = get_team_id_from_token(); $teamId = get_team_id_from_token();
@@ -60,6 +68,7 @@ class Team extends Controller
return invalid_token(); return invalid_token();
} }
$team = auth()->user()->currentTeam(); $team = auth()->user()->currentTeam();
return response()->json($team->members); return response()->json($team->members);
} }
} }

View File

@@ -14,40 +14,49 @@ use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse; use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse;
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse; use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse;
use Illuminate\Support\Facades\Password; use Laravel\Fortify\Fortify;
class Controller extends BaseController class Controller extends BaseController
{ {
use AuthorizesRequests, ValidatesRequests; use AuthorizesRequests, ValidatesRequests;
public function realtime_test() { public function realtime_test()
{
if (auth()->user()?->currentTeam()->id !== 0) { if (auth()->user()?->currentTeam()->id !== 0) {
return redirect(RouteServiceProvider::HOME); return redirect(RouteServiceProvider::HOME);
} }
TestEvent::dispatch(); TestEvent::dispatch();
return 'Look at your other tab.'; return 'Look at your other tab.';
} }
public function verify() {
public function verify()
{
return view('auth.verify-email'); return view('auth.verify-email');
} }
public function email_verify(EmailVerificationRequest $request) {
public function email_verify(EmailVerificationRequest $request)
{
$request->fulfill(); $request->fulfill();
$name = request()->user()?->name; $name = request()->user()?->name;
// send_internal_notification("User {$name} verified their email address."); // send_internal_notification("User {$name} verified their email address.");
return redirect(RouteServiceProvider::HOME); return redirect(RouteServiceProvider::HOME);
} }
public function forgot_password(Request $request) {
public function forgot_password(Request $request)
{
if (is_transactional_emails_active()) { if (is_transactional_emails_active()) {
$arrayOfRequest = $request->only(Fortify::email()); $arrayOfRequest = $request->only(Fortify::email());
$request->merge([ $request->merge([
'email' => Str::lower($arrayOfRequest['email']), 'email' => Str::lower($arrayOfRequest['email']),
]); ]);
$type = set_transanctional_email_settings(); $type = set_transanctional_email_settings();
if (!$type) { if (! $type) {
return response()->json(['message' => 'Transactional emails are not active'], 400); return response()->json(['message' => 'Transactional emails are not active'], 400);
} }
$request->validate([Fortify::email() => 'required|email']); $request->validate([Fortify::email() => 'required|email']);
@@ -60,10 +69,13 @@ class Controller extends BaseController
if ($status == Password::RESET_THROTTLED) { if ($status == Password::RESET_THROTTLED) {
return response('Already requested a password reset in the past minutes.', 400); return response('Already requested a password reset in the past minutes.', 400);
} }
return app(FailedPasswordResetLinkRequestResponse::class, ['status' => $status]); return app(FailedPasswordResetLinkRequestResponse::class, ['status' => $status]);
} }
return response()->json(['message' => 'Transactional emails are not active'], 400); return response()->json(['message' => 'Transactional emails are not active'], 400);
} }
public function link() public function link()
{ {
$token = request()->get('token'); $token = request()->get('token');
@@ -72,7 +84,7 @@ class Controller extends BaseController
$email = Str::of($decrypted)->before('@@@'); $email = Str::of($decrypted)->before('@@@');
$password = Str::of($decrypted)->after('@@@'); $password = Str::of($decrypted)->after('@@@');
$user = User::whereEmail($email)->first(); $user = User::whereEmail($email)->first();
if (!$user) { if (! $user) {
return redirect()->route('login'); return redirect()->route('login');
} }
if (Hash::check($password, $user->password)) { if (Hash::check($password, $user->password)) {
@@ -90,9 +102,11 @@ class Controller extends BaseController
} }
Auth::login($user); Auth::login($user);
session(['currentTeam' => $team]); session(['currentTeam' => $team]);
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
} }
return redirect()->route('login')->with('error', 'Invalid credentials.'); return redirect()->route('login')->with('error', 'Invalid credentials.');
} }
@@ -108,11 +122,12 @@ class Controller extends BaseController
if ($resetPassword) { if ($resetPassword) {
$user->update([ $user->update([
'password' => Hash::make($invitationUuid), 'password' => Hash::make($invitationUuid),
'force_password_reset' => true 'force_password_reset' => true,
]); ]);
} }
if ($user->teams()->where('team_id', $invitation->team->id)->exists()) { if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
$invitation->delete(); $invitation->delete();
return redirect()->route('team.index'); return redirect()->route('team.index');
} }
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]); $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
@@ -121,6 +136,7 @@ class Controller extends BaseController
return redirect()->route('login'); return redirect()->route('login');
} }
refreshSession($invitation->team); refreshSession($invitation->team);
return redirect()->route('team.index'); return redirect()->route('team.index');
} else { } else {
abort(401); abort(401);
@@ -143,6 +159,7 @@ class Controller extends BaseController
abort(401); abort(401);
} }
$invitation->delete(); $invitation->delete();
return redirect()->route('team.index'); return redirect()->route('team.index');
} catch (\Throwable $e) { } catch (\Throwable $e) {
throw $e; throw $e;

View File

@@ -12,34 +12,35 @@ class MagicController extends Controller
public function servers() public function servers()
{ {
return response()->json([ return response()->json([
'servers' => Server::isUsable()->get() 'servers' => Server::isUsable()->get(),
]); ]);
} }
public function destinations() public function destinations()
{ {
return response()->json([ return response()->json([
'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name') 'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name'),
]); ]);
} }
public function projects() public function projects()
{ {
return response()->json([ return response()->json([
'projects' => Project::ownedByCurrentTeam()->get() 'projects' => Project::ownedByCurrentTeam()->get(),
]); ]);
} }
public function environments() public function environments()
{ {
$project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first(); $project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first();
if (!$project) { if (! $project) {
return response()->json([ return response()->json([
'environments' => [] 'environments' => [],
]); ]);
} }
return response()->json([ return response()->json([
'environments' => $project->environments 'environments' => $project->environments,
]); ]);
} }
@@ -49,8 +50,9 @@ class MagicController extends Controller
['name' => request()->query('name') ?? generate_random_name()], ['name' => request()->query('name') ?? generate_random_name()],
['team_id' => currentTeam()->id] ['team_id' => currentTeam()->id]
); );
return response()->json([ return response()->json([
'project_uuid' => $project->uuid 'project_uuid' => $project->uuid,
]); ]);
} }
@@ -60,6 +62,7 @@ class MagicController extends Controller
['name' => request()->query('name') ?? generate_random_name()], ['name' => request()->query('name') ?? generate_random_name()],
['project_id' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->firstOrFail()->id] ['project_id' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->firstOrFail()->id]
); );
return response()->json([ return response()->json([
'environment_name' => $environment->name, 'environment_name' => $environment->name,
]); ]);
@@ -75,6 +78,7 @@ class MagicController extends Controller
); );
auth()->user()->teams()->attach($team, ['role' => 'admin']); auth()->user()->teams()->attach($team, ['role' => 'admin']);
refreshSession(); refreshSession();
return redirect(request()->header('Referer')); return redirect(request()->header('Referer'));
} }
} }

View File

@@ -2,15 +2,15 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class OauthController extends Controller { class OauthController extends Controller
{
public function redirect(string $provider) public function redirect(string $provider)
{ {
$socialite_provider = get_socialite_provider($provider); $socialite_provider = get_socialite_provider($provider);
return $socialite_provider->redirect(); return $socialite_provider->redirect();
} }
@@ -19,16 +19,18 @@ class OauthController extends Controller {
try { try {
$oauthUser = get_socialite_provider($provider)->user(); $oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first(); $user = User::whereEmail($oauthUser->email)->first();
if (!$user) { if (! $user) {
$user = User::create([ $user = User::create([
'name' => $oauthUser->name, 'name' => $oauthUser->name,
'email' => $oauthUser->email, 'email' => $oauthUser->email,
]); ]);
} }
Auth::login($user); Auth::login($user);
return redirect('/'); return redirect('/');
} catch (\Exception $e) { } catch (\Exception $e) {
ray($e->getMessage()); ray($e->getMessage());
return redirect()->route('login')->withErrors([__('auth.failed.callback')]); return redirect()->route('login')->withErrors([__('auth.failed.callback')]);
} }
} }

View File

@@ -2,14 +2,11 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Http\JsonResponse;
use Pion\Laravel\ChunkUpload\Exceptions\UploadFailedException;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException; use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException;
use Pion\Laravel\ChunkUpload\Handler\AbstractHandler;
use Pion\Laravel\ChunkUpload\Handler\HandlerFactory; use Pion\Laravel\ChunkUpload\Handler\HandlerFactory;
use Pion\Laravel\ChunkUpload\Receiver\FileReceiver; use Pion\Laravel\ChunkUpload\Receiver\FileReceiver;
@@ -21,7 +18,7 @@ class UploadController extends BaseController
if (is_null($resource)) { if (is_null($resource)) {
return response()->json(['error' => 'You do not have permission for this database'], 500); return response()->json(['error' => 'You do not have permission for this database'], 500);
} }
$receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request)); $receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request));
if ($receiver->isUploaded() === false) { if ($receiver->isUploaded() === false) {
throw new UploadMissingFileException(); throw new UploadMissingFileException();
@@ -34,9 +31,10 @@ class UploadController extends BaseController
} }
$handler = $save->handler(); $handler = $save->handler();
return response()->json([ return response()->json([
"done" => $handler->getPercentageDone(), 'done' => $handler->getPercentageDone(),
'status' => true 'status' => true,
]); ]);
} }
// protected function saveFileToS3($file) // protected function saveFileToS3($file)
@@ -64,19 +62,20 @@ class UploadController extends BaseController
{ {
$mime = str_replace('/', '-', $file->getMimeType()); $mime = str_replace('/', '-', $file->getMimeType());
$filePath = "upload/{$resource->uuid}"; $filePath = "upload/{$resource->uuid}";
$finalPath = storage_path("app/" . $filePath); $finalPath = storage_path('app/'.$filePath);
$file->move($finalPath, 'restore'); $file->move($finalPath, 'restore');
return response()->json([ return response()->json([
'mime_type' => $mime 'mime_type' => $mime,
]); ]);
} }
protected function createFilename(UploadedFile $file) protected function createFilename(UploadedFile $file)
{ {
$extension = $file->getClientOriginalExtension(); $extension = $file->getClientOriginalExtension();
$filename = str_replace("." . $extension, "", $file->getClientOriginalName()); // Filename without extension $filename = str_replace('.'.$extension, '', $file->getClientOriginalName()); // Filename without extension
$filename .= "_" . md5(time()) . "." . $extension; $filename .= '_'.md5(time()).'.'.$extension;
return $filename; return $filename;
} }

View File

@@ -20,25 +20,26 @@ class Bitbucket extends Controller
$epoch = now()->valueOf(); $epoch = now()->valueOf();
$data = [ $data = [
'attributes' => $request->attributes->all(), 'attributes' => $request->attributes->all(),
'request' => $request->request->all(), 'request' => $request->request->all(),
'query' => $request->query->all(), 'query' => $request->query->all(),
'server' => $request->server->all(), 'server' => $request->server->all(),
'files' => $request->files->all(), 'files' => $request->files->all(),
'cookies' => $request->cookies->all(), 'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(), 'headers' => $request->headers->all(),
'content' => $request->getContent(), 'content' => $request->getContent(),
]; ];
$json = json_encode($data); $json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Bitbicket::manual_bitbucket", $json); Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Bitbicket::manual_bitbucket", $json);
return; return;
} }
$return_payloads = collect([]); $return_payloads = collect([]);
$payload = $request->collect(); $payload = $request->collect();
$headers = $request->headers->all(); $headers = $request->headers->all();
$x_bitbucket_token = data_get($headers, 'x-hub-signature.0', ""); $x_bitbucket_token = data_get($headers, 'x-hub-signature.0', '');
$x_bitbucket_event = data_get($headers, 'x-event-key.0', ""); $x_bitbucket_event = data_get($headers, 'x-event-key.0', '');
$handled_events = collect(['repo:push', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']); $handled_events = collect(['repo:push', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']);
if (!$handled_events->contains($x_bitbucket_event)) { if (! $handled_events->contains($x_bitbucket_event)) {
return response([ return response([
'status' => 'failed', 'status' => 'failed',
'message' => 'Nothing to do. Event not handled.', 'message' => 'Nothing to do. Event not handled.',
@@ -48,13 +49,13 @@ class Bitbucket extends Controller
$branch = data_get($payload, 'push.changes.0.new.name'); $branch = data_get($payload, 'push.changes.0.new.name');
$full_name = data_get($payload, 'repository.full_name'); $full_name = data_get($payload, 'repository.full_name');
$commit = data_get($payload, 'push.changes.0.new.target.hash'); $commit = data_get($payload, 'push.changes.0.new.target.hash');
if (!$branch) { if (! $branch) {
return response([ return response([
'status' => 'failed', 'status' => 'failed',
'message' => 'Nothing to do. No branch found in the request.', 'message' => 'Nothing to do. No branch found in the request.',
]); ]);
} }
ray('Manual webhook bitbucket push event with branch: ' . $branch); ray('Manual webhook bitbucket push event with branch: '.$branch);
} }
if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') { if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
$branch = data_get($payload, 'pullrequest.destination.branch.name'); $branch = data_get($payload, 'pullrequest.destination.branch.name');
@@ -76,30 +77,32 @@ class Bitbucket extends Controller
$webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket'); $webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket');
$payload = $request->getContent(); $payload = $request->getContent();
list($algo, $hash) = explode('=', $x_bitbucket_token, 2); [$algo, $hash] = explode('=', $x_bitbucket_token, 2);
$payloadHash = hash_hmac($algo, $payload, $webhook_secret); $payloadHash = hash_hmac($algo, $payload, $webhook_secret);
if (!hash_equals($hash, $payloadHash) && !isDev()) { if (! hash_equals($hash, $payloadHash) && ! isDev()) {
$return_payloads->push([ $return_payloads->push([
'application' => $application->name, 'application' => $application->name,
'status' => 'failed', 'status' => 'failed',
'message' => 'Invalid token.', 'message' => 'Invalid signature.',
]); ]);
ray('Invalid signature'); ray('Invalid signature');
continue; continue;
} }
$isFunctional = $application->destination->server->isFunctional(); $isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) { if (! $isFunctional) {
$return_payloads->push([ $return_payloads->push([
'application' => $application->name, 'application' => $application->name,
'status' => 'failed', 'status' => 'failed',
'message' => 'Server is not functional.', 'message' => 'Server is not functional.',
]); ]);
ray('Server is not functional: ' . $application->destination->server->name); ray('Server is not functional: '.$application->destination->server->name);
continue; continue;
} }
if ($x_bitbucket_event === 'repo:push') { if ($x_bitbucket_event === 'repo:push') {
if ($application->isDeployable()) { if ($application->isDeployable()) {
ray('Deploying ' . $application->name . ' with branch ' . $branch); ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);
queue_application_deployment( queue_application_deployment(
application: $application, application: $application,
@@ -123,10 +126,10 @@ class Bitbucket extends Controller
} }
if ($x_bitbucket_event === 'pullrequest:created') { if ($x_bitbucket_event === 'pullrequest:created') {
if ($application->isPRDeployable()) { if ($application->isPRDeployable()) {
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) { if (! $found) {
ApplicationPreview::create([ ApplicationPreview::create([
'git_type' => 'bitbucket', 'git_type' => 'bitbucket',
'application_id' => $application->id, 'application_id' => $application->id,
@@ -178,9 +181,11 @@ class Bitbucket extends Controller
} }
} }
ray($return_payloads); ray($return_payloads);
return response($return_payloads); return response($return_payloads);
} catch (Exception $e) { } catch (Exception $e) {
ray($e); ray($e);
return handleError($e); return handleError($e);
} }
} }

View File

@@ -0,0 +1,228 @@
<?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

@@ -33,20 +33,22 @@ class Github extends Controller
})->first(); })->first();
if ($github_delivery_found) { if ($github_delivery_found) {
ray('Webhook already found'); ray('Webhook already found');
return; return;
} }
$data = [ $data = [
'attributes' => $request->attributes->all(), 'attributes' => $request->attributes->all(),
'request' => $request->request->all(), 'request' => $request->request->all(),
'query' => $request->query->all(), 'query' => $request->query->all(),
'server' => $request->server->all(), 'server' => $request->server->all(),
'files' => $request->files->all(), 'files' => $request->files->all(),
'cookies' => $request->cookies->all(), 'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(), 'headers' => $request->headers->all(),
'content' => $request->getContent(), 'content' => $request->getContent(),
]; ];
$json = json_encode($data); $json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::manual_{$x_github_delivery}", $json); Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::manual_{$x_github_delivery}", $json);
return; return;
} }
$x_github_event = Str::lower($request->header('X-GitHub-Event')); $x_github_event = Str::lower($request->header('X-GitHub-Event'));
@@ -71,7 +73,7 @@ class Github extends Controller
$removed_files = data_get($payload, 'commits.*.removed'); $removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified'); $modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray('Manual Webhook GitHub Push Event with branch: ' . $branch); ray('Manual Webhook GitHub Push Event with branch: '.$branch);
} }
if ($x_github_event === 'pull_request') { if ($x_github_event === 'pull_request') {
$action = data_get($payload, 'action'); $action = data_get($payload, 'action');
@@ -80,9 +82,9 @@ class Github extends Controller
$pull_request_html_url = data_get($payload, 'pull_request.html_url'); $pull_request_html_url = data_get($payload, 'pull_request.html_url');
$branch = data_get($payload, 'pull_request.head.ref'); $branch = data_get($payload, 'pull_request.head.ref');
$base_branch = data_get($payload, 'pull_request.base.ref'); $base_branch = data_get($payload, 'pull_request.base.ref');
ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id);
} }
if (!$branch) { if (! $branch) {
return response('Nothing to do. No branch found in the request.'); return response('Nothing to do. No branch found in the request.');
} }
$applications = Application::where('git_repository', 'like', "%$full_name%"); $applications = Application::where('git_repository', 'like', "%$full_name%");
@@ -101,29 +103,31 @@ class Github extends Controller
foreach ($applications as $application) { foreach ($applications as $application) {
$webhook_secret = data_get($application, 'manual_webhook_secret_github'); $webhook_secret = data_get($application, 'manual_webhook_secret_github');
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) { if (! hash_equals($x_hub_signature_256, $hmac) && ! isDev()) {
ray('Invalid signature'); ray('Invalid signature');
$return_payloads->push([ $return_payloads->push([
'application' => $application->name, 'application' => $application->name,
'status' => 'failed', 'status' => 'failed',
'message' => 'Invalid token.', 'message' => 'Invalid signature.',
]); ]);
continue; continue;
} }
$isFunctional = $application->destination->server->isFunctional(); $isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) { if (! $isFunctional) {
$return_payloads->push([ $return_payloads->push([
'application' => $application->name, 'application' => $application->name,
'status' => 'failed', 'status' => 'failed',
'message' => 'Server is not functional.', 'message' => 'Server is not functional.',
]); ]);
continue; continue;
} }
if ($x_github_event === 'push') { if ($x_github_event === 'push') {
if ($application->isDeployable()) { if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) { if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying ' . $application->name . ' with branch ' . $branch); ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);
queue_application_deployment( queue_application_deployment(
application: $application, application: $application,
@@ -165,7 +169,7 @@ class Github extends Controller
if ($application->isPRDeployable()) { if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) { if (! $found) {
ApplicationPreview::create([ ApplicationPreview::create([
'git_type' => 'github', 'git_type' => 'github',
'application_id' => $application->id, 'application_id' => $application->id,
@@ -218,12 +222,15 @@ class Github extends Controller
} }
} }
ray($return_payloads); ray($return_payloads);
return response($return_payloads); return response($return_payloads);
} catch (Exception $e) { } catch (Exception $e) {
ray($e->getMessage()); ray($e->getMessage());
return handleError($e); return handleError($e);
} }
} }
public function normal(Request $request) public function normal(Request $request)
{ {
try { try {
@@ -239,20 +246,22 @@ class Github extends Controller
})->first(); })->first();
if ($github_delivery_found) { if ($github_delivery_found) {
ray('Webhook already found'); ray('Webhook already found');
return; return;
} }
$data = [ $data = [
'attributes' => $request->attributes->all(), 'attributes' => $request->attributes->all(),
'request' => $request->request->all(), 'request' => $request->request->all(),
'query' => $request->query->all(), 'query' => $request->query->all(),
'server' => $request->server->all(), 'server' => $request->server->all(),
'files' => $request->files->all(), 'files' => $request->files->all(),
'cookies' => $request->cookies->all(), 'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(), 'headers' => $request->headers->all(),
'content' => $request->getContent(), 'content' => $request->getContent(),
]; ];
$json = json_encode($data); $json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::normal_{$x_github_delivery}", $json); Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::normal_{$x_github_delivery}", $json);
return; return;
} }
$x_github_event = Str::lower($request->header('X-GitHub-Event')); $x_github_event = Str::lower($request->header('X-GitHub-Event'));
@@ -270,7 +279,7 @@ class Github extends Controller
$webhook_secret = data_get($github_app, 'webhook_secret'); $webhook_secret = data_get($github_app, 'webhook_secret');
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
if (config('app.env') !== 'local') { if (config('app.env') !== 'local') {
if (!hash_equals($x_hub_signature_256, $hmac)) { if (! hash_equals($x_hub_signature_256, $hmac)) {
return response('Invalid signature.'); return response('Invalid signature.');
} }
} }
@@ -280,6 +289,7 @@ class Github extends Controller
if ($action === 'new_permissions_accepted') { if ($action === 'new_permissions_accepted') {
GithubAppPermissionJob::dispatch($github_app); GithubAppPermissionJob::dispatch($github_app);
} }
return response('cool'); return response('cool');
} }
if ($x_github_event === 'push') { if ($x_github_event === 'push') {
@@ -292,7 +302,7 @@ class Github extends Controller
$removed_files = data_get($payload, 'commits.*.removed'); $removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified'); $modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch); ray('Webhook GitHub Push Event: '.$id.' with branch: '.$branch);
} }
if ($x_github_event === 'pull_request') { if ($x_github_event === 'pull_request') {
$action = data_get($payload, 'action'); $action = data_get($payload, 'action');
@@ -301,9 +311,9 @@ class Github extends Controller
$pull_request_html_url = data_get($payload, 'pull_request.html_url'); $pull_request_html_url = data_get($payload, 'pull_request.html_url');
$branch = data_get($payload, 'pull_request.head.ref'); $branch = data_get($payload, 'pull_request.head.ref');
$base_branch = data_get($payload, 'pull_request.base.ref'); $base_branch = data_get($payload, 'pull_request.base.ref');
ray('Webhook GitHub Pull Request Event: ' . $id . ' with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); ray('Webhook GitHub Pull Request Event: '.$id.' with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id);
} }
if (!$id || !$branch) { if (! $id || ! $branch) {
return response('Nothing to do. No id or branch found.'); return response('Nothing to do. No id or branch found.');
} }
$applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false); $applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false);
@@ -322,20 +332,21 @@ class Github extends Controller
foreach ($applications as $application) { foreach ($applications as $application) {
$isFunctional = $application->destination->server->isFunctional(); $isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) { if (! $isFunctional) {
$return_payloads->push([ $return_payloads->push([
'status' => 'failed', 'status' => 'failed',
'message' => 'Server is not functional.', 'message' => 'Server is not functional.',
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
'application_name' => $application->name, 'application_name' => $application->name,
]); ]);
continue; continue;
} }
if ($x_github_event === 'push') { if ($x_github_event === 'push') {
if ($application->isDeployable()) { if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) { if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying ' . $application->name . ' with branch ' . $branch); ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);
queue_application_deployment( queue_application_deployment(
application: $application, application: $application,
@@ -377,7 +388,7 @@ class Github extends Controller
if ($application->isPRDeployable()) { if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) { if (! $found) {
ApplicationPreview::create([ ApplicationPreview::create([
'git_type' => 'github', 'git_type' => 'github',
'application_id' => $application->id, 'application_id' => $application->id,
@@ -410,11 +421,12 @@ class Github extends Controller
if ($action === 'closed' || $action === 'close') { if ($action === 'closed' || $action === 'close') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) { 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); ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
$found->delete(); $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([ $return_payloads->push([
'application' => $application->name, 'application' => $application->name,
'status' => 'success', 'status' => 'success',
@@ -430,13 +442,15 @@ class Github extends Controller
} }
} }
} }
ray($return_payloads);
return response($return_payloads); return response($return_payloads);
} catch (Exception $e) { } catch (Exception $e) {
ray($e->getMessage()); ray($e->getMessage());
return handleError($e); return handleError($e);
} }
} }
public function redirect(Request $request) public function redirect(Request $request)
{ {
try { try {
@@ -464,11 +478,13 @@ class Github extends Controller
$github_app->webhook_secret = $webhook_secret; $github_app->webhook_secret = $webhook_secret;
$github_app->private_key_id = $private_key->id; $github_app->private_key_id = $private_key->id;
$github_app->save(); $github_app->save();
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (Exception $e) { } catch (Exception $e) {
return handleError($e); return handleError($e);
} }
} }
public function install(Request $request) public function install(Request $request)
{ {
try { try {
@@ -478,16 +494,17 @@ class Github extends Controller
$epoch = now()->valueOf(); $epoch = now()->valueOf();
$data = [ $data = [
'attributes' => $request->attributes->all(), 'attributes' => $request->attributes->all(),
'request' => $request->request->all(), 'request' => $request->request->all(),
'query' => $request->query->all(), 'query' => $request->query->all(),
'server' => $request->server->all(), 'server' => $request->server->all(),
'files' => $request->files->all(), 'files' => $request->files->all(),
'cookies' => $request->cookies->all(), 'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(), 'headers' => $request->headers->all(),
'content' => $request->getContent(), 'content' => $request->getContent(),
]; ];
$json = json_encode($data); $json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::install_{$installation_id}", $json); Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::install_{$installation_id}", $json);
return; return;
} }
$source = $request->get('source'); $source = $request->get('source');
@@ -497,6 +514,7 @@ class Github extends Controller
$github_app->installation_id = $installation_id; $github_app->installation_id = $installation_id;
$github_app->save(); $github_app->save();
} }
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (Exception $e) { } catch (Exception $e) {
return handleError($e); return handleError($e);

View File

@@ -21,16 +21,17 @@ class Gitlab extends Controller
$epoch = now()->valueOf(); $epoch = now()->valueOf();
$data = [ $data = [
'attributes' => $request->attributes->all(), 'attributes' => $request->attributes->all(),
'request' => $request->request->all(), 'request' => $request->request->all(),
'query' => $request->query->all(), 'query' => $request->query->all(),
'server' => $request->server->all(), 'server' => $request->server->all(),
'files' => $request->files->all(), 'files' => $request->files->all(),
'cookies' => $request->cookies->all(), 'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(), 'headers' => $request->headers->all(),
'content' => $request->getContent(), 'content' => $request->getContent(),
]; ];
$json = json_encode($data); $json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitlab::manual_gitlab", $json); Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitlab::manual_gitlab", $json);
return; return;
} }
$return_payloads = collect([]); $return_payloads = collect([]);
@@ -39,11 +40,12 @@ class Gitlab extends Controller
$x_gitlab_token = data_get($headers, 'x-gitlab-token.0'); $x_gitlab_token = data_get($headers, 'x-gitlab-token.0');
$x_gitlab_event = data_get($payload, 'object_kind'); $x_gitlab_event = data_get($payload, 'object_kind');
$allowed_events = ['push', 'merge_request']; $allowed_events = ['push', 'merge_request'];
if (!in_array($x_gitlab_event, $allowed_events)) { if (! in_array($x_gitlab_event, $allowed_events)) {
$return_payloads->push([ $return_payloads->push([
'status' => 'failed', 'status' => 'failed',
'message' => 'Event not allowed. Only push and merge_request events are allowed.', 'message' => 'Event not allowed. Only push and merge_request events are allowed.',
]); ]);
return response($return_payloads); return response($return_payloads);
} }
@@ -53,18 +55,19 @@ class Gitlab extends Controller
if (Str::isMatch('/refs\/heads\/*/', $branch)) { if (Str::isMatch('/refs\/heads\/*/', $branch)) {
$branch = Str::after($branch, 'refs/heads/'); $branch = Str::after($branch, 'refs/heads/');
} }
if (!$branch) { if (! $branch) {
$return_payloads->push([ $return_payloads->push([
'status' => 'failed', 'status' => 'failed',
'message' => 'Nothing to do. No branch found in the request.', 'message' => 'Nothing to do. No branch found in the request.',
]); ]);
return response($return_payloads); return response($return_payloads);
} }
$added_files = data_get($payload, 'commits.*.added'); $added_files = data_get($payload, 'commits.*.added');
$removed_files = data_get($payload, 'commits.*.removed'); $removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified'); $modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray('Manual Webhook GitLab Push Event with branch: ' . $branch); ray('Manual Webhook GitLab Push Event with branch: '.$branch);
} }
if ($x_gitlab_event === 'merge_request') { if ($x_gitlab_event === 'merge_request') {
$action = data_get($payload, 'object_attributes.action'); $action = data_get($payload, 'object_attributes.action');
@@ -73,14 +76,15 @@ class Gitlab extends Controller
$full_name = data_get($payload, 'project.path_with_namespace'); $full_name = data_get($payload, 'project.path_with_namespace');
$pull_request_id = data_get($payload, 'object_attributes.iid'); $pull_request_id = data_get($payload, 'object_attributes.iid');
$pull_request_html_url = data_get($payload, 'object_attributes.url'); $pull_request_html_url = data_get($payload, 'object_attributes.url');
if (!$branch) { if (! $branch) {
$return_payloads->push([ $return_payloads->push([
'status' => 'failed', 'status' => 'failed',
'message' => 'Nothing to do. No branch found in the request.', 'message' => 'Nothing to do. No branch found in the request.',
]); ]);
return response($return_payloads); return response($return_payloads);
} }
ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id);
} }
$applications = Application::where('git_repository', 'like', "%$full_name%"); $applications = Application::where('git_repository', 'like', "%$full_name%");
if ($x_gitlab_event === 'push') { if ($x_gitlab_event === 'push') {
@@ -90,6 +94,7 @@ class Gitlab extends Controller
'status' => 'failed', 'status' => 'failed',
'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.", 'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.",
]); ]);
return response($return_payloads); return response($return_payloads);
} }
} }
@@ -100,6 +105,7 @@ class Gitlab extends Controller
'status' => 'failed', 'status' => 'failed',
'message' => "Nothing to do. No applications found with branch '$base_branch'.", 'message' => "Nothing to do. No applications found with branch '$base_branch'.",
]); ]);
return response($return_payloads); return response($return_payloads);
} }
} }
@@ -109,26 +115,28 @@ class Gitlab extends Controller
$return_payloads->push([ $return_payloads->push([
'application' => $application->name, 'application' => $application->name,
'status' => 'failed', 'status' => 'failed',
'message' => 'Invalid token.', 'message' => 'Invalid signature.',
]); ]);
ray('Invalid signature'); ray('Invalid signature');
continue; continue;
} }
$isFunctional = $application->destination->server->isFunctional(); $isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) { if (! $isFunctional) {
$return_payloads->push([ $return_payloads->push([
'application' => $application->name, 'application' => $application->name,
'status' => 'failed', 'status' => 'failed',
'message' => 'Server is not functional', 'message' => 'Server is not functional',
]); ]);
ray('Server is not functional: ' . $application->destination->server->name); ray('Server is not functional: '.$application->destination->server->name);
continue; continue;
} }
if ($x_gitlab_event === 'push') { if ($x_gitlab_event === 'push') {
if ($application->isDeployable()) { if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) { if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying ' . $application->name . ' with branch ' . $branch); ray('Deploying '.$application->name.' with branch '.$branch);
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);
queue_application_deployment( queue_application_deployment(
application: $application, application: $application,
@@ -163,7 +171,7 @@ class Gitlab extends Controller
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
'application_name' => $application->name, 'application_name' => $application->name,
]); ]);
ray('Deployments disabled for ' . $application->name); ray('Deployments disabled for '.$application->name);
} }
} }
if ($x_gitlab_event === 'merge_request') { if ($x_gitlab_event === 'merge_request') {
@@ -171,7 +179,7 @@ class Gitlab extends Controller
if ($application->isPRDeployable()) { if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) { if (! $found) {
ApplicationPreview::create([ ApplicationPreview::create([
'git_type' => 'gitlab', 'git_type' => 'gitlab',
'application_id' => $application->id, 'application_id' => $application->id,
@@ -188,7 +196,7 @@ class Gitlab extends Controller
is_webhook: true, is_webhook: true,
git_type: 'gitlab' git_type: 'gitlab'
); );
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
$return_payloads->push([ $return_payloads->push([
'application' => $application->name, 'application' => $application->name,
'status' => 'success', 'status' => 'success',
@@ -200,9 +208,9 @@ class Gitlab extends Controller
'status' => 'failed', 'status' => 'failed',
'message' => 'Preview deployments disabled', 'message' => 'Preview deployments disabled',
]); ]);
ray('Preview deployments disabled for ' . $application->name); ray('Preview deployments disabled for '.$application->name);
} }
} else if ($action === 'closed' || $action === 'close') { } elseif ($action === 'closed' || $action === 'close' || $action === 'merge') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) { if ($found) {
$found->delete(); $found->delete();
@@ -214,6 +222,7 @@ class Gitlab extends Controller
'status' => 'success', 'status' => 'success',
'message' => 'Preview Deployment closed', 'message' => 'Preview Deployment closed',
]); ]);
return response($return_payloads); return response($return_payloads);
} }
$return_payloads->push([ $return_payloads->push([
@@ -230,9 +239,11 @@ class Gitlab extends Controller
} }
} }
} }
return response($return_payloads); return response($return_payloads);
} catch (Exception $e) { } catch (Exception $e) {
ray($e->getMessage()); ray($e->getMessage());
return handleError($e); return handleError($e);
} }
} }

View File

@@ -26,16 +26,17 @@ class Stripe extends Controller
$epoch = now()->valueOf(); $epoch = now()->valueOf();
$data = [ $data = [
'attributes' => $request->attributes->all(), 'attributes' => $request->attributes->all(),
'request' => $request->request->all(), 'request' => $request->request->all(),
'query' => $request->query->all(), 'query' => $request->query->all(),
'server' => $request->server->all(), 'server' => $request->server->all(),
'files' => $request->files->all(), 'files' => $request->files->all(),
'cookies' => $request->cookies->all(), 'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(), 'headers' => $request->headers->all(),
'content' => $request->getContent(), 'content' => $request->getContent(),
]; ];
$json = json_encode($data); $json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Stripe::events_stripe", $json); Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Stripe::events_stripe", $json);
return; return;
} }
$webhookSecret = config('subscription.stripe_webhook_secret'); $webhookSecret = config('subscription.stripe_webhook_secret');
@@ -48,7 +49,7 @@ class Stripe extends Controller
); );
$webhook = Webhook::create([ $webhook = Webhook::create([
'type' => 'stripe', 'type' => 'stripe',
'payload' => $request->getContent() 'payload' => $request->getContent(),
]); ]);
$type = data_get($event, 'type'); $type = data_get($event, 'type');
$data = data_get($event, 'data.object'); $data = data_get($event, 'data.object');
@@ -65,20 +66,20 @@ class Stripe extends Controller
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
$team = Team::find($teamId); $team = Team::find($teamId);
$found = $team->members->where('id', $userId)->first(); $found = $team->members->where('id', $userId)->first();
if (!$found->isAdmin()) { if (! $found->isAdmin()) {
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
throw new Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); throw new Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
} }
$subscription = Subscription::where('team_id', $teamId)->first(); $subscription = Subscription::where('team_id', $teamId)->first();
if ($subscription) { if ($subscription) {
send_internal_notification('Old subscription activated for team: ' . $teamId); send_internal_notification('Old subscription activated for team: '.$teamId);
$subscription->update([ $subscription->update([
'stripe_subscription_id' => $subscriptionId, 'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId, 'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => true, 'stripe_invoice_paid' => true,
]); ]);
} else { } else {
send_internal_notification('New subscription for team: ' . $teamId); send_internal_notification('New subscription for team: '.$teamId);
Subscription::create([ Subscription::create([
'team_id' => $teamId, 'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId, 'stripe_subscription_id' => $subscriptionId,
@@ -95,7 +96,7 @@ class Stripe extends Controller
break; break;
} }
$subscription = Subscription::where('stripe_customer_id', $customerId)->first(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) { if (! $subscription) {
Sleep::for(5)->seconds(); Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
} }
@@ -106,34 +107,38 @@ class Stripe extends Controller
case 'invoice.payment_failed': case 'invoice.payment_failed':
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->first(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) { if (! $subscription) {
send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: ' . $customerId); send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId);
return response('No subscription found in Coolify.'); return response('No subscription found in Coolify.');
} }
$team = data_get($subscription, 'team'); $team = data_get($subscription, 'team');
if (!$team) { if (! $team) {
send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: ' . $customerId); send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
return response('No team found in Coolify.'); return response('No team found in Coolify.');
} }
if (!$subscription->stripe_invoice_paid) { if (! $subscription->stripe_invoice_paid) {
SubscriptionInvoiceFailedJob::dispatch($team); SubscriptionInvoiceFailedJob::dispatch($team);
send_internal_notification('Invoice payment failed: ' . $customerId); send_internal_notification('Invoice payment failed: '.$customerId);
} else { } else {
send_internal_notification('Invoice payment failed but already paid: ' . $customerId); send_internal_notification('Invoice payment failed but already paid: '.$customerId);
} }
break; break;
case 'payment_intent.payment_failed': case 'payment_intent.payment_failed':
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->first(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) { if (! $subscription) {
send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: ' . $customerId); send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId);
return response('No subscription found in Coolify.'); return response('No subscription found in Coolify.');
} }
if ($subscription->stripe_invoice_paid) { if ($subscription->stripe_invoice_paid) {
send_internal_notification('payment_intent.payment_failed but invoice is active for customer: ' . $customerId); send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId);
return; return;
} }
send_internal_notification('Subscription payment failed for customer: ' . $customerId); send_internal_notification('Subscription payment failed for customer: '.$customerId);
break; break;
case 'customer.subscription.updated': case 'customer.subscription.updated':
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
@@ -145,13 +150,19 @@ class Stripe extends Controller
break; break;
} }
$subscription = Subscription::where('stripe_customer_id', $customerId)->first(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) { if (! $subscription) {
Sleep::for(5)->seconds(); Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->first(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
} }
if (!$subscription) { if (! $subscription) {
send_internal_notification('No subscription found for: ' . $customerId); if ($status === 'incomplete_expired') {
return response("No subscription found", 400); 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);
} }
$trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended'); $trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended');
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end'); $cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
@@ -166,9 +177,11 @@ class Stripe extends Controller
$quantity = data_get($data, 'items.data.0.quantity', 10); $quantity = data_get($data, 'items.data.0.quantity', 10);
} }
$team = data_get($subscription, 'team'); $team = data_get($subscription, 'team');
$team->update([ if ($team) {
'custom_server_limit' => $quantity, $team->update([
]); 'custom_server_limit' => $quantity,
]);
}
ServerLimitCheckJob::dispatch($team); ServerLimitCheckJob::dispatch($team);
} }
$subscription->update([ $subscription->update([
@@ -181,7 +194,7 @@ class Stripe extends Controller
$subscription->update([ $subscription->update([
'stripe_invoice_paid' => false, 'stripe_invoice_paid' => false,
]); ]);
send_internal_notification('Subscription paused or incomplete for customer: ' . $customerId); send_internal_notification('Subscription paused or incomplete for customer: '.$customerId);
} }
// Trial ended but subscribed, reactive servers // Trial ended but subscribed, reactive servers
@@ -191,9 +204,9 @@ class Stripe extends Controller
} }
if ($feedback) { if ($feedback) {
$reason = "Cancellation feedback for {$customerId}: '" . $feedback . "'"; $reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
if ($comment) { if ($comment) {
$reason .= ' with comment: \'' . $comment . "'"; $reason .= ' with comment: \''.$comment."'";
} }
send_internal_notification($reason); send_internal_notification($reason);
} }
@@ -201,7 +214,7 @@ class Stripe extends Controller
if ($cancelAtPeriodEnd) { if ($cancelAtPeriodEnd) {
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id); // send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
} else { } else {
send_internal_notification('customer.subscription.updated for customer: ' . $customerId); send_internal_notification('customer.subscription.updated for customer: '.$customerId);
} }
} }
break; break;
@@ -210,7 +223,9 @@ class Stripe extends Controller
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team'); $team = data_get($subscription, 'team');
$team->trialEnded(); if ($team) {
$team->trialEnded();
}
$subscription->update([ $subscription->update([
'stripe_subscription_id' => null, 'stripe_subscription_id' => null,
'stripe_plan_id' => null, 'stripe_plan_id' => null,
@@ -218,15 +233,15 @@ class Stripe extends Controller
'stripe_invoice_paid' => false, 'stripe_invoice_paid' => false,
'stripe_trial_already_ended' => true, 'stripe_trial_already_ended' => true,
]); ]);
send_internal_notification('customer.subscription.deleted for customer: ' . $customerId); send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
break; break;
case 'customer.subscription.trial_will_end': case 'customer.subscription.trial_will_end':
// Not used for now // Not used for now
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team'); $team = data_get($subscription, 'team');
if (!$team) { if (! $team) {
throw new Exception('No team found for subscription: ' . $subscription->id); throw new Exception('No team found for subscription: '.$subscription->id);
} }
SubscriptionTrialEndsSoonJob::dispatch($team); SubscriptionTrialEndsSoonJob::dispatch($team);
break; break;
@@ -234,8 +249,8 @@ class Stripe extends Controller
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team'); $team = data_get($subscription, 'team');
if (!$team) { if (! $team) {
throw new Exception('No team found for subscription: ' . $subscription->id); throw new Exception('No team found for subscription: '.$subscription->id);
} }
$team->trialEnded(); $team->trialEnded();
$subscription->update([ $subscription->update([
@@ -243,19 +258,20 @@ class Stripe extends Controller
'stripe_invoice_paid' => false, 'stripe_invoice_paid' => false,
]); ]);
SubscriptionTrialEndedJob::dispatch($team); SubscriptionTrialEndedJob::dispatch($team);
send_internal_notification('Subscription paused for customer: ' . $customerId); send_internal_notification('Subscription paused for customer: '.$customerId);
break; break;
default: default:
// Unhandled event type // Unhandled event type
} }
} catch (Exception $e) { } catch (Exception $e) {
if ($type !== 'payment_intent.payment_failed') { if ($type !== 'payment_intent.payment_failed') {
send_internal_notification("Subscription webhook ($type) failed: " . $e->getMessage()); send_internal_notification("Subscription webhook ($type) failed: ".$e->getMessage());
} }
$webhook->update([ $webhook->update([
'status' => 'failed', 'status' => 'failed',
'failure_reason' => $e->getMessage(), 'failure_reason' => $e->getMessage(),
]); ]);
return response($e->getMessage(), 400); return response($e->getMessage(), 400);
} }
} }

View File

@@ -17,41 +17,49 @@ class Waitlist extends Controller
try { try {
$found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
if ($found) { if ($found) {
if (!$found->verified) { if (! $found->verified) {
if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) { if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) {
$found->verified = true; $found->verified = true;
$found->save(); $found->save();
send_internal_notification('Waitlist confirmed: ' . $email); send_internal_notification('Waitlist confirmed: '.$email);
return 'Thank you for confirming your email address. We will notify you when you are next in line.'; return 'Thank you for confirming your email address. We will notify you when you are next in line.';
} else { } else {
$found->delete(); $found->delete();
send_internal_notification('Waitlist expired: ' . $email); send_internal_notification('Waitlist expired: '.$email);
return 'Your confirmation code has expired. Please sign up again.'; return 'Your confirmation code has expired. Please sign up again.';
} }
} }
} }
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} catch (Exception $e) { } catch (Exception $e) {
send_internal_notification('Waitlist confirmation failed: ' . $e->getMessage()); send_internal_notification('Waitlist confirmation failed: '.$e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
} }
public function cancel(Request $request) public function cancel(Request $request)
{ {
$email = request()->get('email'); $email = request()->get('email');
$confirmation_code = request()->get('confirmation_code'); $confirmation_code = request()->get('confirmation_code');
try { try {
$found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
if ($found && !$found->verified) { if ($found && ! $found->verified) {
$found->delete(); $found->delete();
send_internal_notification('Waitlist cancelled: ' . $email); send_internal_notification('Waitlist cancelled: '.$email);
return 'Your email address has been removed from the waitlist.'; return 'Your email address has been removed from the waitlist.';
} }
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} catch (Exception $e) { } catch (Exception $e) {
send_internal_notification('Waitlist cancellation failed: ' . $e->getMessage()); send_internal_notification('Waitlist cancellation failed: '.$e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
} }

View File

@@ -44,7 +44,7 @@ class Kernel extends HttpKernel
'api' => [ 'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api', \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
], ],
]; ];

View File

@@ -20,16 +20,19 @@ class CheckForcePasswordReset
auth()->logout(); auth()->logout();
request()->session()->invalidate(); request()->session()->invalidate();
request()->session()->regenerateToken(); request()->session()->regenerateToken();
return $next($request); return $next($request);
} }
$force_password_reset = auth()->user()->force_password_reset; $force_password_reset = auth()->user()->force_password_reset;
if ($force_password_reset) { if ($force_password_reset) {
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') { if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
return $next($request); return $next($request);
} }
return redirect()->route('auth.force-password-reset'); return redirect()->route('auth.force-password-reset');
} }
} }
return $next($request); return $next($request);
} }
} }

View File

@@ -5,8 +5,8 @@ namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class DecideWhatToDoWithUser class DecideWhatToDoWithUser
{ {
@@ -16,33 +16,37 @@ class DecideWhatToDoWithUser
$currentTeam = auth()->user()?->recreate_personal_team(); $currentTeam = auth()->user()?->recreate_personal_team();
refreshSession($currentTeam); refreshSession($currentTeam);
} }
if(auth()?->user()?->currentTeam()){ if (auth()?->user()?->currentTeam()) {
refreshSession(auth()->user()->currentTeam()); refreshSession(auth()->user()->currentTeam());
} }
if (!auth()->user() || !isCloud() || isInstanceAdmin()) { if (! auth()->user() || ! isCloud() || isInstanceAdmin()) {
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) { if (! isCloud() && showBoarding() && ! in_array($request->path(), allowedPathsForBoardingAccounts())) {
return redirect()->route('onboarding'); return redirect()->route('onboarding');
} }
return $next($request); return $next($request);
} }
if (!auth()->user()->hasVerifiedEmail()) { if (! auth()->user()->hasVerifiedEmail()) {
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) { if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
return $next($request); return $next($request);
} }
return redirect()->route('verify.email'); return redirect()->route('verify.email');
} }
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) { if (! isSubscriptionActive() && ! isSubscriptionOnGracePeriod()) {
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) { if (! in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) { if (Str::startsWith($request->path(), 'invitations')) {
return $next($request); return $next($request);
} }
return redirect()->route('subscription.index'); return redirect()->route('subscription.index');
} }
} }
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) { if (showBoarding() && ! in_array($request->path(), allowedPathsForBoardingAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) { if (Str::startsWith($request->path(), 'invitations')) {
return $next($request); return $next($request);
} }
return redirect()->route('onboarding'); return redirect()->route('onboarding');
} }
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') { if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
@@ -51,6 +55,7 @@ class DecideWhatToDoWithUser
if (isSubscriptionActive() && $request->routeIs('subscription.index')) { if (isSubscriptionActive() && $request->routeIs('subscription.index')) {
return redirect(RouteServiceProvider::HOME); return redirect(RouteServiceProvider::HOME);
} }
return $next($request); return $next($request);
} }
} }

View File

@@ -13,6 +13,6 @@ class PreventRequestsDuringMaintenance extends Middleware
*/ */
protected $except = [ protected $except = [
'webhooks/*', 'webhooks/*',
'/api/health' '/api/health',
]; ];
} }

View File

@@ -13,7 +13,7 @@ class RedirectIfAuthenticated
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/ */
public function handle(Request $request, Closure $next, string ...$guards): Response public function handle(Request $request, Closure $next, string ...$guards): Response
{ {
@@ -24,6 +24,7 @@ class RedirectIfAuthenticated
return redirect(RouteServiceProvider::HOME); return redirect(RouteServiceProvider::HOME);
} }
} }
return $next($request); return $next($request);
} }
} }

View File

@@ -20,7 +20,7 @@ class TrustProxies extends Middleware
* @var int * @var int
*/ */
protected $headers = protected $headers =
Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_PROTO |

File diff suppressed because it is too large Load Diff

View File

@@ -12,11 +12,12 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public string $build_logs_url; public string $build_logs_url;
public string $body; public string $body;
public function __construct( public function __construct(
@@ -31,25 +32,28 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
{ {
try { try {
if ($this->application->is_public_repository()) { if ($this->application->is_public_repository()) {
ray('Public repository. Skipping comment update.');
return; return;
} }
if ($this->status === ProcessStatus::CLOSED) { if ($this->status === ProcessStatus::CLOSED) {
$this->delete_comment(); $this->delete_comment();
return; return;
} else if ($this->status === ProcessStatus::IN_PROGRESS) { } elseif ($this->status === ProcessStatus::IN_PROGRESS) {
$this->body = "The preview deployment is in progress. 🟡\n\n"; $this->body = "The preview deployment is in progress. 🟡\n\n";
} else if ($this->status === ProcessStatus::FINISHED) { } elseif ($this->status === ProcessStatus::FINISHED) {
$this->body = "The preview deployment is ready. 🟢\n\n"; $this->body = "The preview deployment is ready. 🟢\n\n";
if ($this->preview->fqdn) { if ($this->preview->fqdn) {
$this->body .= "[Open Preview]({$this->preview->fqdn}) | "; $this->body .= "[Open Preview]({$this->preview->fqdn}) | ";
} }
} else if ($this->status === ProcessStatus::ERROR) { } elseif ($this->status === ProcessStatus::ERROR) {
$this->body = "The preview deployment failed. 🔴\n\n"; $this->body = "The preview deployment failed. 🔴\n\n";
} }
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; $this->build_logs_url = base_url()."/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n"; $this->body .= '[Open Build Logs]('.$this->build_logs_url.")\n\n\n";
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET"; $this->body .= 'Last updated at: '.now()->toDateTimeString().' CET';
ray('Updating comment', $this->body); ray('Updating comment', $this->body);
if ($this->preview->pull_request_issue_comment_id) { if ($this->preview->pull_request_issue_comment_id) {
@@ -59,7 +63,8 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e); ray($e);
throw $e;
return $e;
} }
} }
@@ -82,6 +87,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
$this->preview->pull_request_issue_comment_id = $data['id']; $this->preview->pull_request_issue_comment_id = $data['id'];
$this->preview->save(); $this->preview->save();
} }
private function delete_comment() private function delete_comment()
{ {
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete'); githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');

View File

@@ -10,19 +10,23 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class ApplicationRestartJob implements ShouldBeEncrypted, ShouldQueue
class ApplicationRestartJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand; use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 3600; public $timeout = 3600;
public $tries = 1; public $tries = 1;
public string $applicationDeploymentQueueId; public string $applicationDeploymentQueueId;
public function __construct(string $applicationDeploymentQueueId) public function __construct(string $applicationDeploymentQueueId)
{ {
$this->applicationDeploymentQueueId = $applicationDeploymentQueueId; $this->applicationDeploymentQueueId = $applicationDeploymentQueueId;
} }
public function handle() {
public function handle()
{
ray('Restarting application'); ray('Restarting application');
} }
} }

View File

@@ -15,13 +15,14 @@ use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Sleep; use Illuminate\Support\Sleep;
class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server) public function __construct(public Server $server)
{ {
} }
public function middleware(): array public function middleware(): array
{ {
return [(new WithoutOverlapping($this->server->id))->dontRelease()]; return [(new WithoutOverlapping($this->server->id))->dontRelease()];
@@ -31,6 +32,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
{ {
return $this->server->id; return $this->server->id;
} }
public function healthcheck() public function healthcheck()
{ {
$status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false); $status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false);
@@ -40,15 +42,16 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
return false; return false;
} }
} }
public function handle(): void
public function handle()
{ {
// ray("checking log drain statuses for {$this->server->id}"); // ray("checking log drain statuses for {$this->server->id}");
try { try {
if (!$this->server->isFunctional()) { if (! $this->server->isFunctional()) {
return; return;
}; }
$containers = instant_remote_process(["docker container ls -q"], $this->server, false); $containers = instant_remote_process(['docker container ls -q'], $this->server, false);
if (!$containers) { if (! $containers) {
return; return;
} }
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server); $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
@@ -57,7 +60,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
$foundLogDrainContainer = $containers->filter(function ($value, $key) { $foundLogDrainContainer = $containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-log-drain'; return data_get($value, 'Name') === '/coolify-log-drain';
})->first(); })->first();
if (!$foundLogDrainContainer || !$this->healthcheck()) { if (! $foundLogDrainContainer || ! $this->healthcheck()) {
ray('Log drain container not found or unhealthy. Restarting...'); ray('Log drain container not found or unhealthy. Restarting...');
InstallLogDrain::run($this->server); InstallLogDrain::run($this->server);
Sleep::for(10)->seconds(); Sleep::for(10)->seconds();
@@ -66,11 +69,12 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server)); $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
$this->server->update(['log_drain_notification_sent' => false]); $this->server->update(['log_drain_notification_sent' => false]);
} }
return; return;
} }
if (!$this->server->log_drain_notification_sent) { if (! $this->server->log_drain_notification_sent) {
ray('Log drain container still unhealthy. Sending notification...'); 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]); $this->server->update(['log_drain_notification_sent' => true]);
} }
} else { } else {
@@ -80,9 +84,12 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
} }
} }
} catch (\Throwable $e) { } 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()); ray($e->getMessage());
handleError($e);
return handleError($e);
} }
} }
} }

View File

@@ -10,7 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@@ -23,7 +23,7 @@ class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted
try { try {
CheckResaleLicense::run(); CheckResaleLicense::run();
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('CheckResaleLicenseJob failed with: ' . $e->getMessage()); send_internal_notification('CheckResaleLicenseJob failed with: '.$e->getMessage());
ray($e); ray($e);
throw $e; throw $e;
} }

View File

@@ -11,7 +11,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class CleanupHelperContainersJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@@ -22,18 +22,18 @@ class CleanupHelperContainersJob implements ShouldQueue, ShouldBeUnique, ShouldB
public function handle(): void public function handle(): void
{ {
try { try {
ray('Cleaning up helper containers on ' . $this->server->name); ray('Cleaning up helper containers on '.$this->server->name);
$containers = instant_remote_process(['docker container ps --filter "ancestor=ghcr.io/coollabsio/coolify-helper:next" --filter "ancestor=ghcr.io/coollabsio/coolify-helper:latest" --format \'{{json .}}\''], $this->server, false); $containers = instant_remote_process(['docker container ps --filter "ancestor=ghcr.io/coollabsio/coolify-helper:next" --filter "ancestor=ghcr.io/coollabsio/coolify-helper:latest" --format \'{{json .}}\''], $this->server, false);
$containers = format_docker_command_output_to_json($containers); $containers = format_docker_command_output_to_json($containers);
if ($containers->count() > 0) { if ($containers->count() > 0) {
foreach ($containers as $container) { foreach ($containers as $container) {
$containerId = data_get($container,'ID'); $containerId = data_get($container, 'ID');
ray('Removing container ' . $containerId); ray('Removing container '.$containerId);
instant_remote_process(['docker container rm -f ' . $containerId], $this->server, false); instant_remote_process(['docker container rm -f '.$containerId], $this->server, false);
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('CleanupHelperContainersJob failed with error: ' . $e->getMessage()); send_internal_notification('CleanupHelperContainersJob failed with error: '.$e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
} }
} }

View File

@@ -12,7 +12,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@@ -31,13 +31,13 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE
try { try {
// $this->cleanup_waitlist(); // $this->cleanup_waitlist();
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage()); send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
} }
try { try {
$this->cleanup_invitation_link(); $this->cleanup_invitation_link();
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage()); send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
} }
} }
@@ -49,6 +49,7 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE
$item->delete(); $item->delete();
} }
} }
private function cleanup_invitation_link() private function cleanup_invitation_link()
{ {
$invitation = TeamInvitation::all(); $invitation = TeamInvitation::all();

View File

@@ -12,18 +12,21 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 4; public $tries = 4;
public function backoff(): int public function backoff(): int
{ {
return isDev() ? 1 : 3; return isDev() ? 1 : 3;
} }
public function __construct(public Server $server) public function __construct(public Server $server)
{ {
} }
public function middleware(): array public function middleware(): array
{ {
return [(new WithoutOverlapping($this->server->uuid))]; return [(new WithoutOverlapping($this->server->uuid))];

View File

@@ -11,7 +11,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
class CoolifyTask implements ShouldQueue, ShouldBeEncrypted class CoolifyTask implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@@ -20,7 +20,7 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
*/ */
public function __construct( public function __construct(
public Activity $activity, public Activity $activity,
public bool $ignore_errors = false, public bool $ignore_errors = false,
public $call_event_on_finish = null, public $call_event_on_finish = null,
public $call_event_data = null public $call_event_data = null
) { ) {
@@ -35,7 +35,7 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
'activity' => $this->activity, 'activity' => $this->activity,
'ignore_errors' => $this->ignore_errors, 'ignore_errors' => $this->ignore_errors,
'call_event_on_finish' => $this->call_event_on_finish, 'call_event_on_finish' => $this->call_event_on_finish,
'call_event_data' => $this->call_event_data 'call_event_data' => $this->call_event_data,
]); ]);
$remote_process(); $remote_process();

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