Compare commits

...

167 Commits

Author SHA1 Message Date
Andras Bacsai
2fdf52929c Merge pull request #1670 from coollabsio/next
v4.0.0-beta.199
2024-01-22 11:02:30 +01:00
Andras Bacsai
8128dfc061 Update resource limits helper links 2024-01-22 10:47:47 +01:00
Andras Bacsai
2b394d6fea fix: show container on logs/executecontainer command views
fix: exclude containers with restart: no from hc
feat: add compose to predefined docker network
service: add glitchtip
2024-01-21 14:30:03 +01:00
Andras Bacsai
964ded1d0b fix: redis custom conf 2024-01-21 12:06:51 +01:00
Andras Bacsai
838c3830d6 Merge pull request #1664 from coollabsio/next
v4.0.0-beta.198
2024-01-18 15:06:12 +01:00
Andras Bacsai
2db93bd9b9 Add echo statement for queue cleanup and update cleanup message 2024-01-18 14:56:12 +01:00
Andras Bacsai
2f82dedd4f Add call to 'cleanup:queue' command in Init.php and remove 'cleanup:queue' command from Kernel.php 2024-01-18 14:49:47 +01:00
Andras Bacsai
8106602d15 Add CleanupQueue command to clean up Redis queue keys 2024-01-18 14:47:17 +01:00
Andras Bacsai
3d0bf6b472 Update resource name in notification messages 2024-01-18 14:03:51 +01:00
Andras Bacsai
ba7a7e9695 Update server and version configurations 2024-01-18 13:33:57 +01:00
Andras Bacsai
dd0ad04384 Merge pull request #1662 from coollabsio/next
Do not report server is not ready
2024-01-18 12:45:05 +01:00
Andras Bacsai
910a1f43a9 Do not report server is not ready 2024-01-18 12:44:20 +01:00
Andras Bacsai
e2f959ce4c Merge pull request #1659 from coollabsio/next
v4.0.0-beta.197
2024-01-18 12:14:50 +01:00
Andras Bacsai
68fe886fb0 Add restart functionality to proxy deployment 2024-01-18 12:14:11 +01:00
Andras Bacsai
4631c73809 Update general.blade.php with build server option and network section 2024-01-18 12:05:48 +01:00
Andras Bacsai
77558b37da refactor build server 2024-01-18 11:40:13 +01:00
Andras Bacsai
e060409a76 fix: links 2024-01-18 11:24:07 +01:00
Andras Bacsai
af01bc3e77 fix: service deletion bug! 2024-01-17 15:48:01 +01:00
Andras Bacsai
1e158badfc Update button text in by-ip.blade.php 2024-01-17 15:41:38 +01:00
Andras Bacsai
c620bb58ed Add build pack selection and show/hide static site options 2024-01-17 15:41:32 +01:00
Andras Bacsai
8a91395472 Update server model to use 'coolify' instead of 'coolify-overlay' for the name field 2024-01-17 14:11:46 +01:00
Andras Bacsai
c5f3398b73 Update build server and swarm support messages 2024-01-17 14:06:41 +01:00
Andras Bacsai
3878527de8 Update server unreachable notifications to include automatic revival 2024-01-17 14:02:54 +01:00
Andras Bacsai
4abcb2d5b9 Update notification labels in Discord and Telegram settings 2024-01-17 12:23:58 +01:00
Andras Bacsai
a635e51486 fix: server status job 2024-01-17 11:52:56 +01:00
Andras Bacsai
b6ce2e9122 typo 2024-01-17 11:39:45 +01:00
Andras Bacsai
8c60dd5523 typo 2024-01-17 11:21:47 +01:00
Andras Bacsai
94e2d951c4 Revert commented out code and execute remote command to remove Docker container 2024-01-16 15:45:54 +01:00
Andras Bacsai
381e24bea5 fix: git pull command for deploy key based previews 2024-01-16 15:45:19 +01:00
Andras Bacsai
2b1e35980f empty nixpacks type result in error 2024-01-16 15:26:44 +01:00
Andras Bacsai
a42c8da344 fix: proxy ui view
feat: build server 🌮
2024-01-16 15:19:14 +01:00
Andras Bacsai
7a0e415ecf fix: checkbox click 2024-01-16 12:28:59 +01:00
Andras Bacsai
d721f4809a fix ui 2024-01-16 12:20:40 +01:00
Andras Bacsai
22431eee9a fix: change proxy view 2024-01-16 11:32:56 +01:00
Andras Bacsai
c058c0a766 Update release version to 4.0.0-beta.197 2024-01-16 08:38:39 +01:00
Andras Bacsai
1724c0d3ff Merge pull request #1658 from coollabsio/next
v4.0.0-beta.196
2024-01-15 20:22:57 +01:00
Andras Bacsai
0b8f48230f Remove unnecessary echo statements in Server.php 2024-01-15 20:22:13 +01:00
Andras Bacsai
e8d84b7067 Update server and version configurations 2024-01-15 19:57:29 +01:00
Andras Bacsai
5236bbc757 Merge pull request #1655 from coollabsio/next
v4.0.0-beta.195
2024-01-15 14:30:43 +01:00
Andras Bacsai
094e1d1bba Fix condition for merge_request actions in webhooks.php 2024-01-15 14:29:54 +01:00
Andras Bacsai
68b25523d6 Fix action condition in merge_request webhook handler 2024-01-15 14:27:45 +01:00
Andras Bacsai
bdc478d5f5 Update daisyui version in package.json and package-lock.json 2024-01-15 13:46:38 +01:00
Andras Bacsai
002472d7c6 Update version numbers and dependencies 2024-01-15 13:40:32 +01:00
Andras Bacsai
0d65bf62b9 Merge pull request #1654 from coollabsio/next
v4.0.0-beta.194
2024-01-15 13:24:01 +01:00
Andras Bacsai
01c7e76071 only add gzip + https redirect once 2024-01-15 13:23:28 +01:00
Andras Bacsai
884ae0efb0 Merge pull request #1653 from coollabsio/next
v4.0.0-beta.193
2024-01-15 13:03:40 +01:00
Andras Bacsai
8e7040bf7c Update Docker Compose commands to include SOURCE_COMMIT environment variable 2024-01-15 12:59:21 +01:00
Andras Bacsai
059e6a88eb Replace comma with pipe in customLabels 2024-01-15 12:30:49 +01:00
Andras Bacsai
9947158f7e fix gzip compression 2024-01-15 12:12:34 +01:00
Andras Bacsai
61aa9e8766 Merge pull request #1651 from coollabsio/next
v4.0.0-beta.192
2024-01-15 11:53:34 +01:00
Andras Bacsai
75813a289c check domains againts cloudflare ip range 2024-01-15 11:37:26 +01:00
Andras Bacsai
af11d8cf3d Merge pull request #1650 from coollabsio/next
v4.0.0-beta.191
2024-01-15 11:06:15 +01:00
Andras Bacsai
48990db699 Add link to documentation for further help 2024-01-15 11:00:09 +01:00
Andras Bacsai
da71353bfa Update application and proxy configuration 2024-01-15 10:49:39 +01:00
Andras Bacsai
0f5559bc61 fix: service stack view 2024-01-15 10:19:37 +01:00
Andras Bacsai
1afb509c33 add domain validation + custom dns servers
add new guides / docs
2024-01-15 10:03:15 +01:00
Andras Bacsai
bccca6e874 add docs to server validation 2024-01-15 09:03:46 +01:00
Andras Bacsai
083dc15053 Update version and add OpenSSH server detection and PermitRootLogin check 2024-01-15 08:40:46 +01:00
Andras Bacsai
1b6d376472 refactor: compose file and install script 2024-01-12 21:26:51 +01:00
Andras Bacsai
891deee05a Update version numbers to 4.0.0-beta.191 2024-01-12 15:42:05 +01:00
Andras Bacsai
5ffbba908b Refactor healthcheck test in StartPostgresql.php 2024-01-12 15:41:49 +01:00
Andras Bacsai
f762959c9f Refactor select.blade.php file 2024-01-12 15:41:46 +01:00
Andras Bacsai
90a5a23fd9 Fix initial username placeholder in PostgreSQL database view 2024-01-12 15:41:43 +01:00
Andras Bacsai
94e87141ff Merge pull request #1639 from coollabsio/next
Fix resource limits for CPU set
2024-01-12 14:37:34 +01:00
Andras Bacsai
fceaf3e94b Fix resource limits for CPU set 2024-01-12 14:30:25 +01:00
Andras Bacsai
3be554cb55 Merge pull request #1638 from coollabsio/next
v4.0.0-beta.190
2024-01-12 14:20:13 +01:00
Andras Bacsai
27b18fbedf Refactor database and application start scripts 2024-01-12 14:15:15 +01:00
Andras Bacsai
5e7c6906b3 fix: cpuset limits was determined in a way that apps only used 1 CPU max, ehh, sorry. 2024-01-12 13:47:01 +01:00
Andras Bacsai
d05ffe32a3 Merge pull request #1630 from coollabsio/next
v4.0.0-beta.189
2024-01-12 12:51:00 +01:00
Andras Bacsai
f1298d1db4 do not send notification of scheduled task failed 2024-01-12 12:44:08 +01:00
Andras Bacsai
408738e08d Add service status to Index.php and update resource display in index.blade.php 2024-01-12 11:31:51 +01:00
Andras Bacsai
8d04fbdb74 feat: search between resources 2024-01-12 11:25:20 +01:00
Andras Bacsai
dccb31d17e fix: service deletion command 2024-01-12 09:11:36 +01:00
Andras Bacsai
f61210287e Update release version to 4.0.0-beta.189 2024-01-12 08:56:59 +01:00
Andras Bacsai
18ad7220f0 10 mins server check -> 5 mins 2024-01-12 08:49:03 +01:00
Andras Bacsai
79e0df1d43 fix: cleanup docker stuffs before upgrading 2024-01-12 08:45:24 +01:00
Andras Bacsai
a2f53085e5 fix: preview deployments with nixpacks 2024-01-12 08:38:08 +01:00
Andras Bacsai
c5782252ea Merge branch 'fix' into next 2024-01-11 19:00:13 +01:00
Andras Bacsai
bf3d88facd Merge pull request #1632 from coollabsio/fix
v4.0.0-beta.188
2024-01-11 18:59:23 +01:00
Andras Bacsai
e45b0bf715 Update version numbers to 4.0.0-beta.188 2024-01-11 18:55:20 +01:00
Andras Bacsai
9db6c12eea fix missing a end tag 2024-01-11 18:54:55 +01:00
Andras Bacsai
3a391b69e8 fix: restart should not update config hash 2024-01-11 14:34:48 +01:00
Andras Bacsai
cc1fb83c79 cleanup 2024-01-11 14:25:55 +01:00
Andras Bacsai
efa5dd28f1 fix: load profile and set envs on remote cmd 2024-01-11 14:25:42 +01:00
Andras Bacsai
f1eddae379 cleanup 2024-01-11 14:24:54 +01:00
Andras Bacsai
34febe670d fix: load profile on remote commands 2024-01-11 14:13:43 +01:00
Andras Bacsai
3137131a1a Merge pull request #1628 from coollabsio/next
v4.0.0-beta.187
2024-01-11 12:57:01 +01:00
Andras Bacsai
1b6546d26c fix: nixpacks envs
fix: append logs
2024-01-11 12:56:02 +01:00
Andras Bacsai
b9f820cef4 fix: save cmd output propely (merge) 2024-01-11 12:33:28 +01:00
Andras Bacsai
d8639f58d7 Merge pull request #1625 from coollabsio/next
v4.0.0-beta.186
2024-01-11 11:56:00 +01:00
Andras Bacsai
cf9be9355f fix: upload limit on ui 2024-01-11 11:55:17 +01:00
Andras Bacsai
e36bb11ba8 fix: undead endpoint 2024-01-11 11:34:05 +01:00
Andras Bacsai
190beb3d3f fix: escape build envs properly for nixpacks + docker build 2024-01-11 11:32:32 +01:00
Andras Bacsai
890a6925d1 fix: email verification / forgot password 2024-01-11 08:52:30 +01:00
Andras Bacsai
d03b8420f8 Merge pull request #1627 from coollabsio/fix
fix: Email verification and forgot-password
2024-01-11 08:47:05 +01:00
Andras Bacsai
cbd3c880c3 Add email verification and password reset functionality 2024-01-11 07:16:28 +01:00
Andras Bacsai
6b24001876 feat: import backups 2024-01-10 15:42:54 +01:00
Andras Bacsai
6bb45430c9 fix: submit error on postgresql 2024-01-10 14:41:05 +01:00
Andras Bacsai
bc6b4ed850 Merge branch 'stooit-feat/db-import' into next 2024-01-10 14:37:30 +01:00
Andras Bacsai
8a63ef5da9 Merge branch 'feat/db-import' of github.com:stooit/coolify into stooit-feat/db-import 2024-01-10 14:36:59 +01:00
Andras Bacsai
e324866a27 Refactor database configuration layout 2024-01-10 14:34:04 +01:00
Andras Bacsai
0e5f733657 Add import tab to database configuration view 2024-01-10 14:33:21 +01:00
Andras Bacsai
c5932ed337 smaller ui fixes 2024-01-10 12:41:29 +01:00
Andras Bacsai
ef428f844f fix: do not include thegameplan.json into build image 2024-01-10 12:26:27 +01:00
Andras Bacsai
eb8b752a6e Merge pull request #1607 from stooit/feat/scheduled-tasks-cron
feat: Scheduled tasks (cron)
2024-01-10 12:08:47 +01:00
Andras Bacsai
ce0b38035c Merge branch 'next' into feat/scheduled-tasks-cron 2024-01-10 12:03:48 +01:00
Andras Bacsai
562a8f1fac Merge pull request #1614 from RayBB/fix-typos
fix typos
2024-01-10 11:56:29 +01:00
Andras Bacsai
68f1621757 Merge branch 'main' into fix-typos 2024-01-10 11:55:33 +01:00
Andras Bacsai
e3087573bb Merge pull request #1618 from Illyism/patch-1
Update README.md
2024-01-10 11:52:52 +01:00
Andras Bacsai
7869f223a3 Update version numbers to 4.0.0-beta.186 2024-01-10 11:52:39 +01:00
Andras Bacsai
0e99f27108 Merge pull request #1621 from coollabsio/next
v4.0.0-beta.185
2024-01-10 11:09:41 +01:00
Andras Bacsai
f445a8c312 fix: update navbar on build_pack change 2024-01-10 11:07:53 +01:00
Andras Bacsai
845fc191d4 Remove ray debug statement in updatedApplicationBuildPack method 2024-01-10 11:00:11 +01:00
Andras Bacsai
95d0d72e0d Fix application build pack logic 2024-01-10 11:00:06 +01:00
Andras Bacsai
76f695036c fix: static buildpack should set port 80 2024-01-10 10:58:31 +01:00
Andras Bacsai
225bf06736 Update version numbers and remove unused files 2024-01-10 10:50:07 +01:00
Andras Bacsai
3a287ae974 Update Dockerfile to use serversideup/php:8.2-fpm-nginx image 2024-01-10 09:25:04 +01:00
Andras Bacsai
e5c61b9f9f Merge pull request #1620 from coollabsio/next
fix: fix php-pgsql version to 8.2
2024-01-09 19:05:46 +01:00
Andras Bacsai
32bc876dfc fix: php pgsql to 8.2 2024-01-09 19:04:12 +01:00
Andras Bacsai
c9b3d2a43d Update PHP version in Dockerfiles 2024-01-09 15:23:37 +01:00
Andras Bacsai
f343210e7c hmm 2024-01-09 15:22:06 +01:00
Andras Bacsai
4a42bff0dc Update version numbers to 4.0.0-beta.185 2024-01-09 15:18:33 +01:00
Andras Bacsai
36931b5b18 Merge pull request #1612 from coollabsio/next
v4.0.0-beta.184
2024-01-09 14:58:01 +01:00
Andras Bacsai
3b080abada remove rays 2024-01-09 14:48:26 +01:00
Andras Bacsai
7feba4bbaa fix: remove traefik debug in dev mode 2024-01-09 14:27:55 +01:00
Andras Bacsai
eef8c756df fix: settings menu 2024-01-09 14:27:42 +01:00
Andras Bacsai
9ed30cb0dc Remove unnecessary proxy configurations 2024-01-09 13:13:55 +01:00
Andras Bacsai
6bc43bd999 fix: sort and rename (unique part) of labels 2024-01-09 13:00:07 +01:00
Andras Bacsai
e7683ee9a5 fix: service labels without ports (unknown ports) 2024-01-09 12:49:03 +01:00
Andras Bacsai
ee71aeaa36 fix: use ip for sslip in dev if remote server is used 2024-01-09 12:48:46 +01:00
Andras Bacsai
404c664500 fix: traefik labels 2024-01-09 12:29:45 +01:00
Andras Bacsai
aa80392b46 small fix 2024-01-09 12:25:50 +01:00
Andras Bacsai
c9509ef658 fix: show framework based notification in build logs 2024-01-09 12:19:39 +01:00
Andras Bacsai
31e08a24c9 fix: healthy status 2024-01-09 08:42:37 +01:00
Ilias Ism
14b32e30cd Update README.md 2024-01-08 23:47:11 +01:00
Raymond Berger
5aaad66fe5 fix data naming conflict 2024-01-08 21:59:26 +01:00
Andras Bacsai
b6745c691b fix: queue retry
fix: nixpacks builds
2024-01-08 16:33:34 +01:00
Ray Berger
5ee29c6072 fix typos 2024-01-07 22:32:54 +00:00
Andras Bacsai
b69584fe26 Merge branch 'main' into next 2024-01-07 16:25:13 +01:00
Andras Bacsai
4c3907c296 refactor routes 2024-01-07 16:23:41 +01:00
Andras Bacsai
bf44b4b949 version++ 2024-01-07 13:34:47 +01:00
Andras Bacsai
bee7a2357b Merge pull request #1608 from RayBB/patch-1
fix small typo
2024-01-07 13:34:11 +01:00
Andras Bacsai
98704fc3c2 Merge pull request #1605 from coollabsio/next
v4.0.0-beta.183
2024-01-06 11:39:51 +01:00
Andras Bacsai
e286e78ddc fix: database env variables 2024-01-06 11:38:12 +01:00
Stuart Rowlands
557e1407d0 Added database import feature. 2024-01-06 15:24:57 +10:00
Raymond Berger
3c99f24b5a fix small typo 2024-01-05 15:10:30 +01:00
Stuart Rowlands
512197021b Minor cleanup. 2024-01-05 15:26:51 +10:00
Stuart Rowlands
e233ec05b5 Merge branch 'main' into feat/scheduled-tasks-cron 2024-01-05 15:08:20 +10:00
Stuart Rowlands
d0e3a20a65 Merge branch 'main' of github.com:stooit/coolify 2024-01-05 15:08:06 +10:00
Stuart Rowlands
e2e6813632 Functional scheduled executions.
Display last executions.
Support for Services.
2024-01-05 15:06:36 +10:00
Andras Bacsai
963c519c38 feat: add www-non-www redirects to traefik 2024-01-04 14:14:40 +01:00
Andras Bacsai
d04513d817 Merge pull request #1603 from coollabsio/next
v4.0.0-beta.181
2024-01-04 13:08:18 +01:00
Andras Bacsai
64a7f27e37 fix: file storage save 2024-01-04 13:03:46 +01:00
Andras Bacsai
65652142b2 Merge pull request #1599 from coollabsio/next
v4.0.0-beta.181
2024-01-03 21:26:44 +01:00
Andras Bacsai
7691319c59 Refactor Dockerfile copying logic in ApplicationDeploymentJob.php 2024-01-03 21:15:58 +01:00
Andras Bacsai
206bd50d00 Update nixpacks build command in ApplicationDeploymentJob.php 2024-01-03 21:14:02 +01:00
Andras Bacsai
6159783a73 fix: nixpacks buildpack 2024-01-03 21:03:46 +01:00
Andras Bacsai
ed5f831c86 Merge pull request #1598 from coollabsio/next
v4.0.0-beta.180
2024-01-03 13:42:29 +01:00
Andras Bacsai
65be83e75d fix: only add restart policy if its empty (compose) 2024-01-03 13:37:14 +01:00
Andras Bacsai
25a471b045 Update Dockerfile with new versions of PACK and NIXPACKS 2024-01-03 13:29:23 +01:00
Andras Bacsai
60c7a29aa8 fix: nixpacks cache 2024-01-03 13:20:24 +01:00
Andras Bacsai
11ab6669a0 Merge pull request #1597 from coollabsio/next
v4.0.0-beta.179
2024-01-02 21:13:34 +01:00
Andras Bacsai
53965ab8ed fix: set deployment failed if new container is not healthy 2024-01-02 21:07:16 +01:00
Andras Bacsai
ea271ca079 fix: delete serviceApps & serviceDatabases
fix: cleanup trashed data
2024-01-02 21:03:51 +01:00
Stuart Rowlands
9bbe9567c7 Start scheduled task job execution. 2024-01-01 18:23:58 -08:00
Stuart Rowlands
7913a639b5 Complete add/edit/delete for scheduled tasks.
Refactor views.
2024-01-01 18:23:29 -08:00
Stuart Rowlands
adecf328fc WIP start of scheduled tasks. 2024-01-01 10:33:16 -08:00
256 changed files with 5583 additions and 2809 deletions

View File

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

View File

@@ -57,7 +57,6 @@ class StartMariadb
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -69,6 +68,9 @@ class StartMariadb
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',

View File

@@ -64,7 +64,6 @@ class StartMongodb
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -76,6 +75,9 @@ class StartMongodb
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
@@ -121,7 +123,7 @@ class StartMongodb
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()

View File

@@ -57,7 +57,6 @@ class StartMysql
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -69,6 +68,9 @@ class StartMysql
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',

View File

@@ -50,12 +50,8 @@ class StartPostgresql
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'pg_isready',
'-d',
$this->database->postgres_db,
'-U',
$this->database->postgres_user,
"CMD-SHELL",
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1"
],
'interval' => '5s',
'timeout' => '5s',
@@ -67,7 +63,6 @@ class StartPostgresql
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -79,6 +74,9 @@ class StartPostgresql
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
ray('Log Drain Enabled');
$docker_compose['services'][$container_name]['logging'] = [

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,8 +10,10 @@ class DeleteService
use AsAction;
public function handle(Service $service)
{
StopService::run($service);
$server = data_get($service, 'server');
if ($server->isFunctional()) {
StopService::run($service);
}
$storagesToDelete = collect([]);
$service->environment_variables()->delete();
@@ -21,14 +23,14 @@ class DeleteService
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
}
$application->delete();
$application->forceDelete();
}
foreach ($service->databases()->get() as $database) {
$storages = $database->persistentStorages()->get();
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
}
$database->delete();
$database->forceDelete();
}
foreach ($storagesToDelete as $storage) {
$commands[] = "docker volume rm -f $storage->name";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,23 +2,68 @@
namespace App\Http\Controllers;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\StandalonePostgresql;
use App\Events\TestEvent;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse;
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse;
use Illuminate\Support\Facades\Password;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
public function realtime_test() {
if (auth()->user()?->currentTeam()->id !== 0) {
return redirect(RouteServiceProvider::HOME);
}
TestEvent::dispatch();
return 'Look at your other tab.';
}
public function verify() {
return view('auth.verify-email');
}
public function email_verify(EmailVerificationRequest $request) {
$request->fulfill();
$name = request()->user()?->name;
send_internal_notification("User {$name} verified their email address.");
return redirect(RouteServiceProvider::HOME);
}
public function forgot_password(Request $request) {
if (is_transactional_emails_active()) {
$arrayOfRequest = $request->only(Fortify::email());
$request->merge([
'email' => Str::lower($arrayOfRequest['email']),
]);
$type = set_transanctional_email_settings();
if (!$type) {
return response()->json(['message' => 'Transactional emails are not active'], 400);
}
$request->validate([Fortify::email() => 'required|email']);
$status = Password::broker(config('fortify.passwords'))->sendResetLink(
$request->only(Fortify::email())
);
if ($status == Password::RESET_LINK_SENT) {
return app(SuccessfulPasswordResetLinkRequestResponse::class, ['status' => $status]);
}
if ($status == Password::RESET_THROTTLED) {
return response('Already requested a password reset in the past minutes.', 400);
}
return app(FailedPasswordResetLinkRequestResponse::class, ['status' => $status]);
}
return response()->json(['message' => 'Transactional emails are not active'], 400);
}
public function link()
{
$token = request()->get('token');
@@ -51,90 +96,7 @@ class Controller extends BaseController
return redirect()->route('login')->with('error', 'Invalid credentials.');
}
public function license()
{
if (!isCloud()) {
abort(404);
}
return view('settings.license', [
'settings' => InstanceSettings::get(),
]);
}
public function force_passoword_reset()
{
return view('auth.force-password-reset');
}
public function boarding()
{
if (currentTeam()->boarding || isDev()) {
return view('boarding');
} else {
return redirect()->route('dashboard');
}
}
public function settings()
{
if (isInstanceAdmin()) {
$settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first();
if ($database) {
if ($database->status !== 'running') {
$database->status = 'running';
$database->save();
}
$s3s = S3Storage::whereTeamId(0)->get();
}
return view('settings.configuration', [
'settings' => $settings,
'database' => $database,
's3s' => $s3s ?? [],
]);
} else {
return redirect()->route('dashboard');
}
}
public function team()
{
$invitations = [];
if (auth()->user()->isAdminFromSession()) {
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
return view('team.index', [
'invitations' => $invitations,
]);
}
public function storages()
{
$s3 = S3Storage::ownedByCurrentTeam()->get();
return view('team.storages.all', [
's3' => $s3,
]);
}
public function storages_show()
{
$storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->firstOrFail();
return view('team.storages.show', [
'storage' => $storage,
]);
}
public function members()
{
$invitations = [];
if (auth()->user()->isAdminFromSession()) {
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
return view('team.members', [
'invitations' => $invitations,
]);
}
public function acceptInvitation()
public function accept_invitation()
{
try {
$resetPassword = request()->query('reset-password');
@@ -169,7 +131,7 @@ class Controller extends BaseController
}
}
public function revokeInvitation()
public function revoke_invitation()
{
try {
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();

View File

@@ -1,84 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
class DatabaseController extends Controller
{
use AuthorizesRequests, ValidatesRequests;
public function configuration()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
return view('project.database.configuration', ['database' => $database]);
}
public function executions()
{
$backup_uuid = request()->route('backup_uuid');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
$backup = $database->scheduledBackups->where('uuid', $backup_uuid)->first();
if (!$backup) {
return redirect()->route('dashboard');
}
$executions = collect($backup->executions)->sortByDesc('created_at');
return view('project.database.backups.executions', [
'database' => $database,
'backup' => $backup,
'executions' => $executions,
's3s' => currentTeam()->s3s,
]);
}
public function backups()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
// No backups for redis
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $database->uuid,
]);
}
return view('project.database.backups.all', [
'database' => $database,
's3s' => currentTeam()->s3s,
]);
}
}

View File

@@ -30,6 +30,7 @@ use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
use Throwable;
use Visus\Cuid2\Cuid2;
use Yosymfony\Toml\Toml;
class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{
@@ -55,7 +56,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private GithubApp|GitlabApp|string $source = 'other';
private StandaloneDocker|SwarmDocker $destination;
// Deploy to Server
private Server $server;
// Build Server
private Server $build_server;
private bool $use_build_server = false;
// Save original server between phases
private Server $original_server;
private Server $mainServer;
private ?ApplicationPreview $preview = null;
private ?string $git_type = null;
@@ -71,8 +78,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private bool $is_debug_enabled;
private $build_args;
private $env_args;
private $env_nixpacks_args;
private $docker_compose;
private $docker_compose_base64;
private ?string $nixpacks_plan = null;
private ?string $nixpacks_type = null;
private string $dockerfile_location = '/Dockerfile';
private string $docker_compose_location = '/docker-compose.yml';
private ?string $docker_compose_custom_start_command = null;
@@ -164,11 +174,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$ips = collect([]);
if (count($allContainers) > 0) {
$allContainers = $allContainers[0];
$allContainers = collect($allContainers)->sort()->values();
foreach ($allContainers as $container) {
$containerName = data_get($container, 'Name');
if ($containerName === 'coolify-proxy') {
continue;
}
if (preg_match('/-(\d{12})/', $containerName)) {
continue;
}
$containerIp = data_get($container, 'IPv4Address');
if ($containerName && $containerIp) {
$containerIp = str($containerIp)->before('/');
@@ -188,6 +202,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Check custom port
['repository' => $this->customRepository, 'port' => $this->customPort] = $this->application->customRepository();
if (data_get($this->application, 'settings.is_build_server_enabled')) {
$teamId = data_get($this->application, 'environment.project.team.id');
$buildServers = Server::buildServers($teamId)->get();
if ($buildServers->count() === 0) {
$this->application_deployment_queue->addLogEntry("Build server feature activated, but no suitable build server found. Using the deployment server.");
$this->build_server = $this->server;
$this->original_server = $this->server;
} else {
$this->application_deployment_queue->addLogEntry("Build server feature activated and found a suitable build server. Using it to build your application - if needed.");
$this->build_server = $buildServers->random();
$this->original_server = $this->server;
$this->use_build_server = true;
}
} else {
// Set build server & original_server to the same as deployment server
$this->build_server = $this->server;
$this->original_server = $this->server;
}
ray($this->build_server);
try {
if ($this->restart_only && $this->application->build_pack !== 'dockerimage') {
$this->just_restart();
@@ -195,7 +228,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
dispatch(new ContainerStatusJob($this->server));
}
$this->next(ApplicationDeploymentStatus::FINISHED->value);
$this->application->isConfigurationChanged(true);
$this->application->isConfigurationChanged(false);
return;
} else if ($this->application->dockerfile) {
$this->deploy_simple_dockerfile();
@@ -217,7 +250,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->server->isProxyShouldRun()) {
dispatch(new ContainerStatusJob($this->server));
}
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage' && !$this->application->destination->server->isSwarm()) {
// Otherwise built image needs to be pushed before from the build server.
if (!$this->use_build_server) {
$this->push_to_docker_registry();
}
$this->next(ApplicationDeploymentStatus::FINISHED->value);
@@ -226,23 +260,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->fail($e);
throw $e;
} finally {
if (isset($this->docker_compose_base64)) {
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
$composeFileName = "$this->configuration_dir/docker-compose.yml";
if ($this->pull_request_id !== 0) {
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
}
$this->execute_remote_command(
[
"mkdir -p $this->configuration_dir"
],
[
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
]
);
if ($this->use_build_server) {
$this->server = $this->build_server;
} else {
$this->write_deployment_configurations();
}
$this->execute_remote_command(
[
@@ -261,42 +282,72 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
}
private function push_to_docker_registry()
private function write_deployment_configurations()
{
try {
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
if (isset($this->docker_compose_base64)) {
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
$composeFileName = "$this->configuration_dir/docker-compose.yml";
if ($this->pull_request_id !== 0) {
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
}
$this->execute_remote_command(
[
"echo '\n----------------------------------------'",
"mkdir -p $this->configuration_dir"
],
["echo -n 'Pushing image to docker registry ({$this->production_image_name}).'"],
[
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
]
);
if ($this->application->docker_registry_image_tag) {
// Tag image with latest
if ($this->use_build_server) {
$this->server = $this->build_server;
}
}
}
private function push_to_docker_registry($forceFail = false)
{
ray((str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()));
if (
$this->application->docker_registry_image_name &&
$this->application->build_pack !== 'dockerimage' &&
!$this->application->destination->server->isSwarm() &&
!$this->restart_only &&
!(str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged())
) {
try {
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
$this->execute_remote_command(
['echo -n "Tagging and pushing image with latest tag."'],
[
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
],
[
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true
],
);
if ($this->application->docker_registry_image_tag) {
// Tag image with latest
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
],
[
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
],
);
}
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.'");
} catch (Exception $e) {
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.'");
if ($forceFail) {
throw $e;
}
ray($e);
}
$this->execute_remote_command([
"echo -n 'Image pushed to docker registry.'"
]);
} catch (Exception $e) {
if ($this->application->destination->server->isSwarm()) {
throw $e;
}
$this->execute_remote_command(
["echo -n 'Failed to push image to docker registry. Please check debug logs for more information.'"],
);
ray($e);
}
}
private function generate_image_names()
@@ -332,17 +383,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function just_restart()
{
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
],
);
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
$this->create_workdir();
$this->generate_compose_file();
$this->rolling_update();
@@ -386,16 +434,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function deploy_simple_dockerfile()
{
if ($this->use_build_server) {
$this->server = $this->build_server;
}
$dockerfile_base64 = base64_encode($this->application->dockerfile);
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->application->name}.'"
],
);
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
$this->prepare_builder_image();
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir$this->dockerfile_location")
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > {$this->workdir}{$this->dockerfile_location}")
],
);
$this->generate_image_names();
@@ -411,11 +458,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->dockerImage = $this->application->docker_registry_image_name;
$this->dockerImageTag = $this->application->docker_registry_image_tag;
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'");
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"
],
);
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.");
$this->generate_image_names();
$this->prepare_builder_image();
$this->generate_compose_file();
@@ -466,7 +509,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
);
}
@@ -485,24 +528,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
]);
}
if (isset($this->docker_compose_base64)) {
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
$composeFileName = "$this->configuration_dir/docker-compose.yml";
if ($this->pull_request_id !== 0) {
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
}
$this->execute_remote_command(
[
"mkdir -p $this->configuration_dir"
],
[
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
]
);
}
$this->write_deployment_configurations();
// Start compose file
if ($this->docker_compose_custom_start_command) {
$this->execute_remote_command(
@@ -510,21 +536,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
);
}
$this->application_deployment_queue->addLogEntry("New container started.");
}
private function deploy_dockerfile_buildpack()
{
if ($this->use_build_server) {
$this->server = $this->build_server;
}
if (data_get($this->application, 'dockerfile_location')) {
$this->dockerfile_location = $this->application->dockerfile_location;
}
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
],
);
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->clone_repository();
@@ -544,11 +569,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function deploy_nixpacks_buildpack()
{
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
],
);
if ($this->use_build_server) {
$this->server = $this->build_server;
}
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
@@ -557,17 +581,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->create_workdir();
$this->execute_remote_command([
"echo 'No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.'",
]);
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->rolling_update();
return;
}
if ($this->application->isConfigurationChanged()) {
$this->execute_remote_command([
"echo 'Configuration changed. Rebuilding image.'",
]);
$this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
}
}
$this->clone_repository();
@@ -575,17 +595,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_nixpacks_confs();
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
// $this->add_build_env_variables_to_dockerfile();
$this->build_image();
$this->rolling_update();
}
private function deploy_static_buildpack()
{
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
],
);
if ($this->use_build_server) {
$this->server = $this->build_server;
}
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
@@ -597,11 +616,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->rolling_update();
}
private function framework_based_notification()
{
// Laravel old env variables
if ($this->pull_request_id === 0) {
$nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
$nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
} else {
$nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
}
if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) {
$this->application_deployment_queue->addLogEntry("There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/frameworks/laravel#requirements", 'stderr');
}
}
private function rolling_update()
{
if ($this->server->isSwarm()) {
if ($this->build_pack !== 'dockerimage') {
$this->push_to_docker_registry();
$this->push_to_docker_registry(forceFail: true);
}
$this->application_deployment_queue->addLogEntry("Rolling update started.");
$this->execute_remote_command(
@@ -611,28 +644,26 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
} else {
if ($this->use_build_server) {
$this->push_to_docker_registry(forceFail: true);
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
if (count($this->application->ports_mappings_array) > 0) {
$this->execute_remote_command(
[
"echo '\n----------------------------------------'",
],
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
);
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
$this->stop_running_container(force: true);
$this->start_by_compose_file();
} else {
$this->execute_remote_command(
[
"echo '\n----------------------------------------'",
],
["echo -n 'Rolling update started.'"],
);
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Rolling update started.");
$this->start_by_compose_file();
$this->health_check();
$this->stop_running_container();
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
}
}
$this->framework_based_notification();
}
private function health_check()
{
@@ -646,40 +677,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// ray('New container name: ', $this->container_name);
if ($this->container_name) {
$counter = 1;
$this->execute_remote_command(
[
"echo 'Waiting for healthcheck to pass on the new container.'"
]
);
$this->application_deployment_queue->addLogEntry("Waiting for healthcheck to pass on the new container.");
if ($this->full_healthcheck_url) {
$this->execute_remote_command(
[
"echo 'Healthcheck URL (inside the container): {$this->full_healthcheck_url}'"
]
);
$this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}");
}
while ($counter < $this->application->health_check_retries) {
$this->execute_remote_command(
[
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
"hidden" => true,
"save" => "health_check"
"save" => "health_check",
"append" => false
],
);
$this->execute_remote_command(
[
"echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'"
],
);
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
$this->application_deployment_queue->addLogEntry("Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}");
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') {
$this->newVersionIsHealthy = true;
$this->application->update(['status' => 'running']);
$this->execute_remote_command(
[
"echo 'New container is healthy.'"
],
);
$this->application_deployment_queue->addLogEntry("New container is healthy.");
break;
}
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') {
$this->newVersionIsHealthy = false;
break;
}
$counter++;
@@ -690,11 +710,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function deploy_pull_request()
{
if ($this->use_build_server) {
$this->server = $this->build_server;
}
$this->newVersionIsHealthy = true;
$this->generate_image_names();
$this->execute_remote_command([
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.'",
]);
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.");
$this->prepare_builder_image();
$this->clone_repository();
$this->set_base_dir();
@@ -703,13 +724,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_nixpacks_confs();
}
$this->generate_compose_file();
// Needs separate preview variables
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
if ($this->application->build_pack !== 'nixpacks') {
$this->add_build_env_variables_to_dockerfile();
}
$this->build_image();
$this->stop_running_container();
if ($this->application->destination->server->isSwarm()) {
ray("{$this->workdir}{$this->docker_compose_location}");
$this->push_to_docker_registry();
$this->execute_remote_command(
[
@@ -717,10 +740,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
} else {
$this->execute_remote_command(
["echo -n 'Starting preview deployment.'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
);
$this->application_deployment_queue->addLogEntry("Starting preview deployment.");
if ($this->use_build_server) {
$this->execute_remote_command(
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
);
}
}
}
private function create_workdir()
@@ -737,16 +766,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Get user home directory
$this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server);
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
if ($this->dockerConfigFileExists === 'OK') {
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
if ($this->use_build_server) {
if ($this->dockerConfigFileExists === 'NOK') {
throw new RuntimeException('Docker config file (~/.docker/config.json) not found on the build server. Please run "docker login" to login to the docker registry on the server.');
}
$runCommand = "docker run -d --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
} else {
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
if ($this->dockerConfigFileExists === 'OK') {
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
} else {
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
}
}
$this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage.");
$this->execute_remote_command(
[
"echo -n 'Preparing container with helper image: $helperImage.'",
],
[
$runCommand,
"hidden" => true,
@@ -764,19 +797,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$destination = StandaloneDocker::find($destination_id);
$server = $destination->server;
if ($server->team_id !== $this->mainServer->team_id) {
$this->execute_remote_command(
[
"echo -n 'Skipping deployment to {$server->name}. Not in the same team?!'",
],
);
$this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");
continue;
}
$this->server = $server;
$this->execute_remote_command(
[
"echo -n 'Deploying to {$this->server->name}.'",
],
);
$this->application_deployment_queue->addLogEntry("Deploying to {$this->server->name}.");
$this->prepare_builder_image();
$this->generate_image_names();
$this->rolling_update();
@@ -784,11 +809,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function set_base_dir()
{
$this->execute_remote_command(
[
"echo -n 'Setting base directory to {$this->workdir}.'"
],
);
$this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}.");
}
private function check_git_if_build_needed()
{
@@ -821,7 +842,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
}
if ($this->saved_outputs->get('git_commit_sha')) {
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
}
@@ -862,24 +882,36 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function generate_nixpacks_confs()
{
$nixpacks_command = $this->nixpacks_build_cmd();
$this->application_deployment_queue->addLogEntry("Generating nixpacks configuration with: $nixpacks_command");
$this->execute_remote_command(
[
"echo -n 'Generating nixpacks configuration with: $nixpacks_command'",
],
[executeInDocker($this->deployment_uuid, $nixpacks_command)],
[executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
[executeInDocker($this->deployment_uuid, "rm -f {$this->workdir}/.nixpacks/Dockerfile")]
[executeInDocker($this->deployment_uuid, $nixpacks_command), "save" => "nixpacks_plan", "hidden" => true],
[executeInDocker($this->deployment_uuid, "nixpacks detect {$this->workdir}"), "save" => "nixpacks_type", "hidden" => true],
);
if ($this->saved_outputs->get('nixpacks_type')) {
$this->nixpacks_type = $this->saved_outputs->get('nixpacks_type');
if (str($this->nixpacks_type)->isEmpty()) {
throw new RuntimeException('Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers');
}
}
if ($this->saved_outputs->get('nixpacks_plan')) {
$this->nixpacks_plan = $this->saved_outputs->get('nixpacks_plan');
if ($this->nixpacks_plan) {
$this->application_deployment_queue->addLogEntry("Found application type: {$this->nixpacks_type}.");
$this->application_deployment_queue->addLogEntry("If you need further customization, please check the documentation of Nixpacks: https://nixpacks.com/docs/providers/{$this->nixpacks_type}");
$parsed = Toml::Parse($this->nixpacks_plan);
// Do any modifications here
$this->generate_env_variables();
$merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', [])));
data_set($parsed, 'variables', $merged_envs->toArray());
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
}
}
}
private function nixpacks_build_cmd()
{
$this->generate_env_variables();
$cacheKey = $this->application->uuid;
if ($this->pull_request_id !== 0) {
$cacheKey = "{$this->application->uuid}-pr-{$this->pull_request_id}";
}
$nixpacks_command = "nixpacks build --cache-key '{$cacheKey}' -o {$this->workdir} {$this->env_args} --no-error-without-start";
$this->generate_nixpacks_env_variables();
$nixpacks_command = "nixpacks plan -f toml {$this->env_nixpacks_args}";
if ($this->application->build_command) {
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
}
@@ -892,21 +924,34 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$nixpacks_command .= " {$this->workdir}";
return $nixpacks_command;
}
private function generate_nixpacks_env_variables()
{
$this->env_nixpacks_args = collect([]);
if ($this->pull_request_id === 0) {
foreach ($this->application->nixpacks_environment_variables as $env) {
$this->env_nixpacks_args->push("--env {$env->key}={$env->value}");
}
} else {
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
$this->env_nixpacks_args->push("--env {$env->key}={$env->value}");
}
}
$this->env_nixpacks_args = $this->env_nixpacks_args->implode(' ');
}
private function generate_env_variables()
{
$this->env_args = collect([]);
if ($this->pull_request_id === 0) {
foreach ($this->application->nixpacks_environment_variables as $env) {
$this->env_args->push("--env {$env->key}={$env->value}");
foreach ($this->application->build_environment_variables as $env) {
$this->env_args->put($env->key, $env->value);
}
} else {
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
$this->env_args->push("--env {$env->key}={$env->value}");
foreach ($this->application->build_environment_variables_preview as $env) {
$this->env_args->put($env->key, $env->value);
}
}
$this->env_args = $this->env_args->implode(' ');
$this->env_args->put('SOURCE_COMMIT', $this->commit);
}
private function generate_compose_file()
@@ -959,7 +1004,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'mem_swappiness' => $this->application->limits_memory_swappiness,
'mem_reservation' => $this->application->limits_memory_reservation,
'cpus' => (float) $this->application->limits_cpus,
'cpuset' => $this->application->limits_cpuset,
'cpu_shares' => $this->application->limits_cpu_shares,
]
],
@@ -971,6 +1015,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
]
];
if (!is_null($this->application->limits_cpuset)) {
data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset);
}
if ($this->server->isSwarm()) {
data_forget($docker_compose, 'services.' . $this->container_name . '.container_name');
data_forget($docker_compose, 'services.' . $this->container_name . '.expose');
@@ -1131,11 +1178,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
}
// Add PORT if not exists, use the first port as default
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$environment_variables->push("PORT={$ports[0]}");
} if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('SOURCE_COMMIT'))->isEmpty()) {
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$environment_variables->push("SOURCE_COMMIT={$this->commit}");
} else {
$environment_variables->push("SOURCE_COMMIT=unknown");
}
}
return $environment_variables->all();
@@ -1170,9 +1220,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function pull_latest_image($image)
{
$this->application_deployment_queue->addLogEntry("Pulling latest image ($image) from the registry.");
$this->execute_remote_command(
["echo -n 'Pulling latest image ($image) from the registry.'"],
[
executeInDocker($this->deployment_uuid, "docker pull {$image}"), "hidden" => true
]
@@ -1180,22 +1229,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function build_image()
{
$this->application_deployment_queue->addLogEntry("----------------------------------------");
if ($this->application->build_pack === 'static') {
$this->execute_remote_command([
"echo -n 'Static deployment. Copying static assets to the image.'",
]);
$this->application_deployment_queue->addLogEntry("Static deployment. Copying static assets to the image.");
} else {
$this->execute_remote_command(
[
"echo -n 'Building docker image started.'",
],
["echo -n 'To check the current progress, click on Show Debug Logs.'"]
);
$this->application_deployment_queue->addLogEntry("Building docker image started.");
$this->application_deployment_queue->addLogEntry("To check the current progress, click on Show Debug Logs.");
}
if ($this->application->settings->is_static || $this->application->build_pack === 'static') {
if ($this->application->static_image) {
$this->pull_latest_image($this->application->static_image);
$this->application_deployment_queue->addLogEntry("Continuing with the building process.");
}
if ($this->application->build_pack === 'static') {
$dockerfile = base64_encode("FROM {$this->application->static_image}
@@ -1222,9 +1267,36 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
}");
} else {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
]);
if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true
]);
} else {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true
]);
}
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "rm /artifacts/thegameplan.json"), "hidden" => true]);
} else {
if ($this->force_rebuild) {
$build_command = "docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}";
$base64_build_command = base64_encode($build_command);
} else {
$build_command = "docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}";
$base64_build_command = base64_encode($build_command);
}
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
]
);
}
$dockerfile = base64_encode("FROM {$this->application->static_image}
WORKDIR /usr/share/nginx/html/
@@ -1249,6 +1321,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
}");
}
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile")
@@ -1257,24 +1331,59 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
],
[
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
]
);
} else {
// Pure Dockerfile based deployment
if ($this->application->dockerfile) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --pull $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
]);
$build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
]
);
} else {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
]);
if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true
]);
} else {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true
]);
}
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "rm /artifacts/thegameplan.json"), "hidden" => true]);
} else {
if ($this->force_rebuild) {
$build_command = "docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
$base64_build_command = base64_encode($build_command);
} else {
$build_command = "docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
$base64_build_command = base64_encode($build_command);
}
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
]
);
}
}
}
$this->execute_remote_command([
"echo -n 'Building docker image completed.'",
]);
$this->application_deployment_queue->addLogEntry("Building docker image completed.");
}
private function stop_running_container(bool $force = false)
@@ -1295,6 +1404,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
});
} else {
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::FAILED->value,
]);
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
);
@@ -1308,11 +1420,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} build"), "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
);
}
$this->application_deployment_queue->addLogEntry("New images built.");
@@ -1324,16 +1436,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
);
} else {
if ($this->docker_compose_location) {
if ($this->use_build_server) {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
);
}
}
@@ -1345,11 +1457,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]);
if ($this->pull_request_id === 0) {
foreach ($this->application->build_environment_variables as $env) {
$this->build_args->push("--build-arg {$env->key}=\"{$env->value}\"");
$value = escapeshellarg($env->value);
$this->build_args->push("--build-arg {$env->key}={$value}");
}
} else {
foreach ($this->application->build_environment_variables_preview as $env) {
$this->build_args->push("--build-arg {$env->key}=\"{$env->value}\"");
$value = escapeshellarg($env->value);
$this->build_args->push("--build-arg {$env->key}={$value}");
}
}
@@ -1397,13 +1511,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
public function failed(Throwable $exception): void
{
$this->execute_remote_command(
["echo 'Oops something is not okay, are you okay? 😢'", 'type' => 'err'],
["echo '{$exception->getMessage()}'", 'type' => 'err'],
);
$this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr');
$this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr');
if ($this->application->build_pack !== 'dockercompose') {
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
$this->execute_remote_command(
["echo -n 'Deployment failed. Removing the new version of your application.'", 'type' => 'err'],
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
);
}

View File

@@ -27,6 +27,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
@@ -37,15 +40,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return $this->server->uuid;
}
public function __construct(public Server $server)
{
$this->handle();
}
public function handle()
{
if (!$this->server->isServerReady($this->tries)) {
return 'Server is not reachable.';
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
try {
if ($this->server->isSwarm()) {
@@ -162,6 +160,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
// Notify user that this container should not be there.
}
}
if (data_get($container,'Name') === '/coolify-db') {
$foundDatabases[] = 0;
}
}
$serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) {
@@ -212,7 +214,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -274,7 +274,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{
$this->skipBoarding();
return redirect()->route(
'project.resources.new',
'project.resource.create',
[
'project_uuid' => $this->createdProject->uuid,
'environment_name' => 'production',

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Livewire\CommandCenter;
use App\Models\Server;
use Livewire\Component;
class Index extends Component
{
public $servers = [];
public function mount() {
$this->servers = Server::isReachable()->get();
}
public function render()
{
return view('livewire.command-center.index');
}
}

View File

@@ -22,6 +22,10 @@ class ForcePasswordReset extends Component
{
$this->email = auth()->user()->email;
}
public function render()
{
return view('livewire.force-password-reset')->layout('layouts.simple');
}
public function submit()
{
try {

View File

@@ -5,21 +5,19 @@ namespace App\Livewire\Profile;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Form extends Component
class Index extends Component
{
public int $userId;
public string $email;
#[Validate('required')]
public string $name;
public function mount()
{
$this->userId = auth()->user()->id;
$this->name = auth()->user()->name;
$this->email = auth()->user()->email;
}
public function submit()
{
@@ -34,4 +32,8 @@ class Form extends Component
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.profile.index');
}
}

View File

@@ -27,7 +27,7 @@ class AddEnvironment extends Component
'project_id' => $this->project->id,
]);
return redirect()->route('project.resources', [
return redirect()->route('project.resource.index', [
'project_uuid' => $this->project->uuid,
'environment_name' => $environment->name,
]);

View File

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

View File

@@ -4,13 +4,14 @@ namespace App\Livewire\Project\Application;
use App\Models\Application;
use App\Models\Server;
use App\Models\StandaloneDocker;
use Livewire\Component;
class Configuration extends Component
{
public Application $application;
public $servers;
protected $listeners = ['build_pack_updated' => '$refresh'];
public function mount()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();

View File

@@ -1,15 +1,15 @@
<?php
namespace App\Livewire\Project\Application;
namespace App\Livewire\Project\Application\Deployment;
use App\Models\Application;
use Illuminate\Support\Collection;
use Livewire\Component;
class Deployments extends Component
class Index extends Component
{
public Application $application;
public Array|Collection $deployments = [];
public array|Collection $deployments = [];
public int $deployments_count = 0;
public string $current_url;
public int $skip = 0;
@@ -19,11 +19,28 @@ class Deployments extends Component
protected $queryString = ['pull_request_id'];
public function mount()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
if (!$application) {
return redirect()->route('dashboard');
}
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40);
$this->application = $application;
$this->deployments = $deployments;
$this->deployments_count = $count;
$this->current_url = url()->current();
$this->show_pull_request_only();
$this->show_more();
}
private function show_pull_request_only() {
private function show_pull_request_only()
{
if ($this->pull_request_id) {
$this->deployments = $this->deployments->where('pull_request_id', $this->pull_request_id);
}
@@ -57,4 +74,8 @@ class Deployments extends Component
$this->show_pull_request_only();
$this->show_more();
}
public function render()
{
return view('livewire.project.application.deployment.index');
}
}

View File

@@ -1,35 +1,20 @@
<?php
namespace App\Http\Controllers;
namespace App\Livewire\Project\Application\Deployment;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Livewire\Component;
class ApplicationController extends Controller
class Show extends Component
{
use AuthorizesRequests, ValidatesRequests;
public Application $application;
public ApplicationDeploymentQueue $application_deployment_queue;
public string $deployment_uuid;
public $isKeepAliveOn = true;
protected $listeners = ['refreshQueue'];
public function deployments()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
if (!$application) {
return redirect()->route('dashboard');
}
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40);
return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]);
}
public function deployment()
{
public function mount() {
$deploymentUuid = request()->route('deployment_uuid');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
@@ -46,7 +31,7 @@ class ApplicationController extends Controller
}
// $activity = Activity::where('properties->type_uuid', '=', $deploymentUuid)->first();
// if (!$activity) {
// return redirect()->route('project.application.deployments', [
// return redirect()->route('project.application.deployment.index', [
// 'project_uuid' => $project->uuid,
// 'environment_name' => $environment->name,
// 'application_uuid' => $application->uuid,
@@ -54,17 +39,32 @@ class ApplicationController extends Controller
// }
$application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first();
if (!$application_deployment_queue) {
return redirect()->route('project.application.deployments', [
return redirect()->route('project.application.deployment.index', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'application_uuid' => $application->uuid,
]);
}
return view('project.application.deployment', [
'application' => $application,
// 'activity' => $activity,
'application_deployment_queue' => $application_deployment_queue,
'deployment_uuid' => $deploymentUuid,
]);
$this->application = $application;
$this->application_deployment_queue = $application_deployment_queue;
$this->deployment_uuid = $deploymentUuid;
}
public function refreshQueue()
{
$this->application_deployment_queue->refresh();
}
public function polling()
{
$this->dispatch('deploymentFinished');
$this->application_deployment_queue->refresh();
if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') {
$this->isKeepAliveOn = false;
}
}
public function render()
{
return view('livewire.project.application.deployment.show');
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace App\Livewire\Project\Application;
use App\Models\ApplicationDeploymentQueue;
use Livewire\Component;
class DeploymentLogs extends Component
{
public ApplicationDeploymentQueue $application_deployment_queue;
public $isKeepAliveOn = true;
protected $listeners = ['refreshQueue'];
public function refreshQueue()
{
$this->application_deployment_queue->refresh();
}
public function polling()
{
$this->dispatch('deploymentFinished');
$this->application_deployment_queue->refresh();
if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') {
$this->isKeepAliveOn = false;
}
}
}

View File

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

View File

@@ -39,7 +39,7 @@ class Heading extends Component
} else {
dispatch(new ServerStatusJob($this->application->destination->server));
}
if ($showNotification) $this->dispatch('success', 'Application status updated.');
if ($showNotification) $this->dispatch('success', "Application status updated.");
}
public function force_deploy_without_cache()
@@ -60,7 +60,7 @@ class Heading extends Component
force_rebuild: false,
is_new_deployment: true,
);
return redirect()->route('project.application.deployment', [
return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
@@ -74,7 +74,11 @@ class Heading extends Component
return;
}
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
$this->dispatch('error', 'Please set a Docker image name first.');
$this->dispatch('error', 'To deploy to a Swarm cluster you must set a Docker image name first.');
return;
}
if (data_get($this->application, 'settings.is_build_server_enabled') && is_null($this->application->docker_registry_image_name)) {
$this->dispatch('error', 'To use a build server you must set a Docker image name first.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
return;
}
$this->setDeploymentUuid();
@@ -83,7 +87,7 @@ class Heading extends Component
deployment_uuid: $this->deploymentUuid,
force_rebuild: $force_rebuild,
);
return redirect()->route('project.application.deployment', [
return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
@@ -113,7 +117,7 @@ class Heading extends Component
restart_only: true,
is_new_deployment: true,
);
return redirect()->route('project.application.deployment', [
return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
@@ -128,7 +132,7 @@ class Heading extends Component
deployment_uuid: $this->deploymentUuid,
restart_only: true,
);
return redirect()->route('project.application.deployment', [
return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,

View File

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

View File

@@ -29,7 +29,7 @@ class Rollback extends Component
commit: $commit,
force_rebuild: false,
);
return redirect()->route('project.application.deployment', [
return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $deployment_uuid,

View File

@@ -8,7 +8,7 @@ use App\Models\Server;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class CloneProject extends Component
class CloneMe extends Component
{
public string $project_uuid;
public string $environment_name;
@@ -41,7 +41,7 @@ class CloneProject extends Component
public function render()
{
return view('livewire.project.clone-project');
return view('livewire.project.clone-me');
}
public function selectServer($server_id, $destination_id)
@@ -152,7 +152,7 @@ class CloneProject extends Component
}
$newService->parse();
}
return redirect()->route('project.resources', [
return redirect()->route('project.resource.index', [
'project_uuid' => $newProject->uuid,
'environment_name' => $newEnvironment->name,
]);

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Livewire\Project\Database\Backup;
use Livewire\Component;
class Execution extends Component
{
public $database;
public $backup;
public $executions;
public $s3s;
public function mount() {
$backup_uuid = request()->route('backup_uuid');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
$backup = $database->scheduledBackups->where('uuid', $backup_uuid)->first();
if (!$backup) {
return redirect()->route('dashboard');
}
$executions = collect($backup->executions)->sortByDesc('created_at');
$this->database = $database;
$this->backup = $backup;
$this->executions = $executions;
$this->s3s = currentTeam()->s3s;
}
public function render()
{
return view('livewire.project.database.backup.execution');
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Livewire\Project\Database\Backup;
use Livewire\Component;
class Index extends Component
{
public $database;
public $s3s;
public function mount() {
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
// No backups for redis
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $database->uuid,
]);
}
$this->database = $database;
$this->s3s = currentTeam()->s3s;
}
public function render()
{
return view('livewire.project.database.backup.index');
}
}

View File

@@ -52,7 +52,7 @@ class BackupEdit extends Component
$url = $url->getPath() . "#{$url->getFragment()}";
return redirect($url);
} else {
return redirect()->route('project.database.backups.all', $this->parameters);
return redirect()->route('project.database.backup.index', $this->parameters);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Livewire\Project\Database;
use Livewire\Component;
class Configuration extends Component
{
public $database;
public function mount() {
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
$this->database = $database;
}
public function render()
{
return view('livewire.project.database.configuration');
}
}

View File

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

View File

@@ -25,6 +25,6 @@ class DeleteProject extends Component
return $this->dispatch('error', 'Project has resources defined, please delete them first.');
}
$project->delete();
return redirect()->route('projects');
return redirect()->route('project.index');
}
}

View File

@@ -12,6 +12,15 @@ class Edit extends Component
'project.name' => 'required|min:3|max:255',
'project.description' => 'nullable|string|max:255',
];
public function mount() {
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
$this->project = $project;
}
public function submit()
{

View File

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

View File

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

View File

@@ -55,7 +55,7 @@ class Select extends Component
public function updatedSelectedEnvironment()
{
return redirect()->route('project.resources.new', [
return redirect()->route('project.resource.create', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->selectedEnvironment,
]);
@@ -104,7 +104,7 @@ class Select extends Component
if ($this->includeSwarm) {
$this->servers = $this->allServers;
} else {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
}
}
public function setType(string $type)
@@ -120,13 +120,13 @@ class Select extends Component
case 'mongodb':
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
break;
}
if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) {
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
}
if ($type === "existing-postgresql") {
$this->current_step = $type;
@@ -157,7 +157,7 @@ class Select extends Component
public function setDestination(string $destination_uuid)
{
$this->destination_uuid = $destination_uuid;
return redirect()->route('project.resources.new', [
return redirect()->route('project.resource.create', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name'],
'type' => $this->type,

View File

@@ -1,52 +1,18 @@
<?php
namespace App\Http\Controllers;
namespace App\Livewire\Project\Resource;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandaloneDocker;
use Illuminate\Support\Str;
use Livewire\Component;
class ProjectController extends Controller
class Create extends Component
{
public function all()
{
return view('projects', [
'projects' => Project::ownedByCurrentTeam()->get(),
'servers' => Server::ownedByCurrentTeam()->count(),
]);
}
public function edit()
{
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
return view('project.edit', ['project' => $project]);
}
public function show()
{
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
$project->load(['environments']);
return view('project.show', ['project' => $project]);
}
public function new()
{
public $type;
public function mount() {
$services = getServiceTemplates();
$type = Str::of(request()->query('type'));
$type = str(request()->query('type'));
$destination_uuid = request()->query('destination');
$server_id = request()->query('server_id');
@@ -81,14 +47,14 @@ class ProjectController extends Controller
$oneClickService = data_get($services, "$oneClickServiceName.compose");
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
if ($oneClickDotEnvs) {
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
$oneClickDotEnvs = str(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
return !empty($value);
});
}
if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
$service = Service::create([
'name' => "$oneClickServiceName-" . Str::random(10),
'name' => "$oneClickServiceName-" . str()->random(10),
'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
@@ -99,8 +65,8 @@ class ProjectController extends Controller
$service->save();
if ($oneClickDotEnvs?->count() > 0) {
$oneClickDotEnvs->each(function ($value) use ($service) {
$key = Str::before($value, '=');
$value = Str::of(Str::after($value, '='));
$key = str()->before($value, '=');
$value = str(str()->after($value, '='));
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
@@ -123,24 +89,10 @@ class ProjectController extends Controller
]);
}
}
return view('project.new', [
'type' => $type->value()
]);
$this->type = $type->value();
}
public function resources()
public function render()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
if (!$environment) {
return redirect()->route('dashboard');
}
return view('project.resources', [
'project' => $project,
'environment' => $environment
]);
return view('livewire.project.resource.create');
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace App\Livewire\Project\Resource;
use App\Models\Environment;
use App\Models\Project;
use Livewire\Component;
class Index extends Component
{
public Project $project;
public Environment $environment;
public $applications = [];
public $postgresqls = [];
public $redis = [];
public $mongodbs = [];
public $mysqls = [];
public $mariadbs = [];
public $services = [];
public function mount()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
if (!$environment) {
return redirect()->route('dashboard');
}
$this->project = $project;
$this->environment = $environment;
$this->applications = $environment->applications->sortBy('name');
$this->applications = $this->applications->map(function ($application) {
if (data_get($application, 'environment.project.uuid')) {
$application->hrefLink = route('project.application.configuration', [
'project_uuid' => data_get($application, 'environment.project.uuid'),
'environment_name' => data_get($application, 'environment.name'),
'application_uuid' => data_get($application, 'uuid')
]);
}
return $application;
});
$this->postgresqls = $environment->postgresqls->sortBy('name');
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
if (data_get($postgresql, 'environment.project.uuid')) {
$postgresql->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($postgresql, 'environment.project.uuid'),
'environment_name' => data_get($postgresql, 'environment.name'),
'database_uuid' => data_get($postgresql, 'uuid')
]);
}
return $postgresql;
});
$this->redis = $environment->redis->sortBy('name');
$this->redis = $this->redis->map(function ($redis) {
if (data_get($redis, 'environment.project.uuid')) {
$redis->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($redis, 'environment.project.uuid'),
'environment_name' => data_get($redis, 'environment.name'),
'database_uuid' => data_get($redis, 'uuid')
]);
}
return $redis;
});
$this->mongodbs = $environment->mongodbs->sortBy('name');
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
if (data_get($mongodb, 'environment.project.uuid')) {
$mongodb->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($mongodb, 'environment.project.uuid'),
'environment_name' => data_get($mongodb, 'environment.name'),
'database_uuid' => data_get($mongodb, 'uuid')
]);
}
return $mongodb;
});
$this->mysqls = $environment->mysqls->sortBy('name');
$this->mysqls = $this->mysqls->map(function ($mysql) {
if (data_get($mysql, 'environment.project.uuid')) {
$mysql->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($mysql, 'environment.project.uuid'),
'environment_name' => data_get($mysql, 'environment.name'),
'database_uuid' => data_get($mysql, 'uuid')
]);
}
return $mysql;
});
$this->mariadbs = $environment->mariadbs->sortBy('name');
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
if (data_get($mariadb, 'environment.project.uuid')) {
$mariadb->hrefLink = route('project.database.configuration', [
'project_uuid' => data_get($mariadb, 'environment.project.uuid'),
'environment_name' => data_get($mariadb, 'environment.name'),
'database_uuid' => data_get($mariadb, 'uuid')
]);
}
return $mariadb;
});
$this->services = $environment->services->sortBy('name');
$this->services = $this->services->map(function ($service) {
if (data_get($service, 'environment.project.uuid')) {
$service->hrefLink = route('project.service.configuration', [
'project_uuid' => data_get($service, 'environment.project.uuid'),
'environment_name' => data_get($service, 'environment.name'),
'service_uuid' => data_get($service, 'uuid')
]);
$service->status = serviceStatus($service);
}
return $service;
});
}
public function render()
{
return view('livewire.project.resource.index');
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use Livewire\Component;
class Configuration extends Component
{
public Service $service;
public $applications;
public $databases;
public array $parameters;
public array $query;
public function getListeners()
{
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'checkStatus',
"refreshStacks",
"checkStatus",
];
}
public function render()
{
return view('livewire.project.service.configuration');
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function checkStatus()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStacks();
$this->dispatch('serviceStatusChanged');
}
public function refreshStacks()
{
$this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) {
$application->refresh();
});
$this->databases = $this->service->databases->sort();
$this->databases->each(function ($database) {
$database->refresh();
});
}
}

View File

@@ -2,53 +2,51 @@
namespace App\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Collection;
use Livewire\Component;
class Index extends Component
{
public Service $service;
public $applications;
public $databases;
public ?ServiceApplication $serviceApplication = null;
public ?ServiceDatabase $serviceDatabase = null;
public array $parameters;
public array $query;
public function getListeners()
public Collection $services;
public $s3s;
protected $listeners = ['generateDockerCompose'];
public function mount()
{
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'checkStatus',
"refreshStacks",
"checkStatus",
];
try {
$this->services = collect([]);
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
if ($service) {
$this->serviceApplication = $service;
$this->serviceApplication->getFilesFromServer();
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
$this->serviceDatabase->getFilesFromServer();
}
$this->s3s = currentTeam()->s3s;
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function generateDockerCompose()
{
$this->service->parse();
}
public function render()
{
return view('livewire.project.service.index');
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function checkStatus()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStacks();
$this->dispatch('serviceStatusChanged');
}
public function refreshStacks()
{
$this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) {
$application->refresh();
});
$this->databases = $this->service->databases->sort();
$this->databases->each(function ($database) {
$database->refresh();
});
}
}

View File

@@ -1,52 +0,0 @@
<?php
namespace App\Livewire\Project\Service;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Collection;
use Livewire\Component;
class Show extends Component
{
public Service $service;
public ?ServiceApplication $serviceApplication = null;
public ?ServiceDatabase $serviceDatabase = null;
public array $parameters;
public array $query;
public Collection $services;
public $s3s;
protected $listeners = ['generateDockerCompose'];
public function mount()
{
try {
$this->services = collect([]);
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
if ($service) {
$this->serviceApplication = $service;
$this->serviceApplication->getFilesFromServer();
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
$this->serviceDatabase->getFilesFromServer();
}
$this->s3s = currentTeam()->s3s;
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function generateDockerCompose()
{
$this->service->parse();
}
public function render()
{
return view('livewire.project.service.show');
}
}

View File

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

View File

@@ -25,7 +25,7 @@ class Danger extends Component
{
try {
DeleteResourceJob::dispatchSync($this->resource);
return redirect()->route('project.resources', [
return redirect()->route('project.resource.index', [
'project_uuid' => $this->projectUuid,
'environment_name' => $this->environmentName
]);

View File

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

View File

@@ -139,6 +139,18 @@ class All extends Component
case 'standalone-postgresql':
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'standalone-redis':
$environment->standalone_redis_id = $this->resource->id;
break;
case 'standalone-mongodb':
$environment->standalone_mongodb_id = $this->resource->id;
break;
case 'standalone-mysql':
$environment->standalone_mysql_id = $this->resource->id;
break;
case 'standalone-mariadb':
$environment->standalone_mariadb_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;

View File

@@ -10,7 +10,6 @@ use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Support\Sleep;
use Livewire\Component;
class ExecuteContainerCommand extends Component
@@ -80,21 +79,21 @@ class ExecuteContainerCommand extends Component
$this->resource = $resource;
$this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid;
if (str(data_get($this,'resource.status'))->startsWith('running')) {
// if (!str(data_get($this,'resource.status'))->startsWith('exited')) {
$this->containers->push($this->container);
}
// }
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource->applications()->get()->each(function ($application) {
if (str(data_get($application, 'status'))->contains('running')) {
// if (str(data_get($application, 'status'))->contains('running')) {
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
}
// }
});
$this->resource->databases()->get()->each(function ($database) {
if (str(data_get($database, 'status'))->contains('running')) {
// if (str(data_get($database, 'status'))->contains('running')) {
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
}
// }
});
$this->server = $this->resource->server;
@@ -109,8 +108,7 @@ class ExecuteContainerCommand extends Component
$this->validate();
try {
// Wrap command to prevent escaped execution in the host.
$cmd = 'sh -c "' . str_replace('"', '\"', $this->command) . '"';
$cmd = 'sh -c "if [ -f ~/.profile ]; then . ~/.profile; fi; ' . str_replace('"', '\"', $this->command) . '"';
if (!empty($this->workDir)) {
$exec = "docker exec -w {$this->workDir} {$this->container} {$cmd}";
} else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Livewire\Project;
use App\Models\Project;
use Livewire\Component;
class Show extends Component
{
public Project $project;
public function mount() {
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
$project->load(['environments']);
$this->project = $project;
}
public function render()
{
return view('livewire.project.show');
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Livewire\PrivateKey;
namespace App\Livewire\Security\PrivateKey;
use App\Models\PrivateKey;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;

View File

@@ -1,11 +1,11 @@
<?php
namespace App\Livewire\PrivateKey;
namespace App\Livewire\Security\PrivateKey;
use App\Models\PrivateKey;
use Livewire\Component;
class Change extends Component
class Show extends Component
{
public PrivateKey $private_key;
public $public_key;
@@ -24,6 +24,7 @@ class Change extends Component
public function mount()
{
try {
$this->private_key = PrivateKey::ownedByCurrentTeam(['name', 'description', 'private_key', 'is_git_related'])->whereUuid(request()->private_key_uuid)->firstOrFail();
$this->public_key = $this->private_key->publicKey();
}catch(\Throwable $e) {
return handleError($e, $this);

View File

@@ -19,7 +19,7 @@ class Delete extends Component
return;
}
$this->server->delete();
return redirect()->route('server.all');
return redirect()->route('server.index');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -15,7 +15,7 @@ class Show extends Component
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
return redirect()->route('server.index');
}
} catch (\Throwable $e) {
return handleError($e, $this);

View File

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

View File

@@ -6,7 +6,7 @@ use App\Models\Server;
use Illuminate\Database\Eloquent\Collection;
use Livewire\Component;
class All extends Component
class Index extends Component
{
public ?Collection $servers = null;
@@ -15,6 +15,6 @@ class All extends Component
}
public function render()
{
return view('livewire.server.all');
return view('livewire.server.index');
}
}

View File

@@ -43,7 +43,7 @@ class LogDrains extends Component
try {
$server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($server)) {
return redirect()->route('server.all');
return redirect()->route('server.index');
}
$this->server = $server;
} catch (\Throwable $e) {

View File

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

View File

@@ -17,7 +17,7 @@ class Show extends Component
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
return redirect()->route('server.index');
}
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
} catch (\Throwable $e) {

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ class Logs extends Component
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
return redirect()->route('server.index');
}
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -20,7 +20,7 @@ class Show extends Component
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
return redirect()->route('server.index');
}
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -17,7 +17,7 @@ class Show extends Component
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
return redirect()->route('server.index');
}
} catch (\Throwable $e) {

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Livewire\Settings;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\StandalonePostgresql;
use Livewire\Component;
class Index extends Component
{
public InstanceSettings $settings;
public StandalonePostgresql $database;
public $s3s;
public function mount()
{
if (isInstanceAdmin()) {
$settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($database) {
if ($database->status !== 'running') {
$database->status = 'running';
$database->save();
}
$this->database = $database;
}
$this->settings = $settings;
$this->s3s = $s3s;
} else {
return redirect()->route('dashboard');
}
}
public function render()
{
return view('livewire.settings.index');
}
}

View File

@@ -1,15 +1,16 @@
<?php
namespace App\Livewire;
namespace App\Livewire\Settings;
use App\Actions\License\CheckResaleLicense;
use App\Models\InstanceSettings;
use Livewire\Component;
class CheckLicense extends Component
class License extends Component
{
public InstanceSettings|null $settings = null;
public InstanceSettings $settings;
public string|null $instance_id = null;
protected $rules = [
'settings.resale_license' => 'nullable',
'settings.is_resale_license_active' => 'nullable',
@@ -20,12 +21,17 @@ class CheckLicense extends Component
'settings.is_resale_license_active' => 'Is License Active',
];
public function mount()
{
public function mount () {
if (!isCloud()) {
abort(404);
}
$this->instance_id = config('app.id');
$this->settings = InstanceSettings::get();
}
public function render()
{
return view('livewire.settings.license')->layout('layouts.subscription');
}
public function submit()
{
$this->validate();

View File

@@ -6,7 +6,7 @@ use App\Models\InstanceSettings;
use App\Providers\RouteServiceProvider;
use Livewire\Component;
class Show extends Component
class Index extends Component
{
public InstanceSettings $settings;
public bool $alreadySubscribed = false;
@@ -26,6 +26,6 @@ class Show extends Component
}
public function render()
{
return view('livewire.subscription.show')->layout('layouts.subscription');
return view('livewire.subscription.index')->layout('layouts.subscription');
}
}

View File

@@ -1,29 +0,0 @@
<?php
namespace App\Livewire\Team;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class Delete extends Component
{
public function delete()
{
$currentTeam = currentTeam();
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) {
return;
}
$user->teams()->detach($currentTeam);
$session = DB::table('sessions')->where('user_id', $user->id)->first();
if ($session) {
DB::table('sessions')->where('id', $session->id)->delete();
}
});
refreshSession();
return redirect()->route('team.index');
}
}

View File

@@ -1,35 +0,0 @@
<?php
namespace App\Livewire\Team;
use App\Models\Team;
use Livewire\Component;
class Form extends Component
{
public Team $team;
protected $rules = [
'team.name' => 'required|min:3|max:255',
'team.description' => 'nullable|min:3|max:255',
];
protected $validationAttributes = [
'team.name' => 'name',
'team.description' => 'description',
];
public function mount()
{
$this->team = currentTeam();
}
public function submit()
{
$this->validate();
try {
$this->team->save();
refreshSession();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Livewire\Team;
use App\Models\Team;
use App\Models\TeamInvitation;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class Index extends Component
{
public $invitations = [];
public Team $team;
protected $rules = [
'team.name' => 'required|min:3|max:255',
'team.description' => 'nullable|min:3|max:255',
];
protected $validationAttributes = [
'team.name' => 'name',
'team.description' => 'description',
];
public function mount() {
$this->team = currentTeam();
if (auth()->user()->isAdminFromSession()) {
$this->invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
}
public function render()
{
return view('livewire.team.index');
}
public function submit()
{
$this->validate();
try {
$this->team->save();
refreshSession();
$this->dispatch('success', 'Team updated successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function delete()
{
$currentTeam = currentTeam();
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) {
return;
}
$user->teams()->detach($currentTeam);
$session = DB::table('sessions')->where('user_id', $user->id)->first();
if ($session) {
DB::table('sessions')->where('id', $session->id)->delete();
}
});
refreshSession();
return redirect()->route('team.index');
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Livewire\Team\Member;
use App\Models\TeamInvitation;
use Livewire\Component;
class Index extends Component
{
public $invitations = [];
public function mount() {
if (auth()->user()->isAdminFromSession()) {
$this->invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
}
public function render()
{
return view('livewire.team.member.index');
}
}

View File

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

View File

@@ -65,7 +65,7 @@ class Create extends Component
$this->storage->team_id = currentTeam()->id;
$this->storage->testConnection();
$this->storage->save();
return redirect()->route('team.storages.show', $this->storage->uuid);
return redirect()->route('team.storage.show', $this->storage->uuid);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -43,7 +43,7 @@ class Form extends Component
{
try {
$this->storage->delete();
return redirect()->route('team.storages.all');
return redirect()->route('team.storage.index');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Livewire\Team\Storage;
use App\Models\S3Storage;
use Livewire\Component;
class Index extends Component
{
public $s3;
public function mount() {
$this->s3 = S3Storage::ownedByCurrentTeam()->get();
}
public function render()
{
return view('livewire.team.storage.index');
}
}

View File

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

View File

@@ -315,6 +315,11 @@ class Application extends BaseModel
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'like', 'NIXPACKS_%');
}
public function scheduled_tasks(): HasMany
{
return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc');
}
public function private_key()
{
return $this->belongsTo(PrivateKey::class);
@@ -424,8 +429,8 @@ class Application extends BaseModel
}
public function isConfigurationChanged($save = false)
{
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->health_check_path . $this->health_check_port . $this->health_check_host . $this->health_check_method . $this->health_check_return_code . $this->health_check_scheme . $this->health_check_response_text . $this->health_check_interval . $this->health_check_timeout . $this->health_check_retries . $this->health_check_start_period . $this->health_check_enabled . $this->limits_memory . $this->limits_swap . $this->limits_swappiness . $this->limits_reservation . $this->limits_cpus . $this->limits_cpuset . $this->limits_cpu_shares . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
if ($this->pull_request_id === 0) {
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
$newConfigHash .= json_encode($this->environment_variables->all());
} else {
$newConfigHash .= json_encode($this->environment_variables_preview->all());
@@ -637,7 +642,6 @@ class Application extends BaseModel
'mem_swappiness' => $this->limits_memory_swappiness,
'mem_reservation' => $this->limits_memory_reservation,
'cpus' => (float) $this->limits_cpus,
'cpuset' => $this->limits_cpuset,
'cpu_shares' => $this->limits_cpu_shares,
]
],
@@ -649,6 +653,9 @@ class Application extends BaseModel
]
]
];
if (!is_null($this->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->limits_cpuset);
}
if ($server->isSwarm()) {
data_forget($docker_compose, 'services.' . $container_name . '.container_name');
data_forget($docker_compose, 'services.' . $container_name . '.expose');
@@ -755,7 +762,6 @@ class Application extends BaseModel
// if (count($this->ports_mappings_array) > 0) {
// $deployment->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
$containers = getCurrentApplicationContainerStatus($server, $this->id, $pullRequestId);
ray($containers);
// if ($pullRequestId === 0) {
// $containers = $containers->filter(function ($container) use ($containerName) {
// return data_get($container, 'Names') !== $containerName;
@@ -867,7 +873,6 @@ class Application extends BaseModel
} else {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base);
}
ray($git_clone_command);
if ($exec_in_docker) {
$commands = collect([
executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"),
@@ -1061,7 +1066,7 @@ class Application extends BaseModel
$customLabels = base64_decode($this->custom_labels);
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
ray('custom_labels contains non-ascii characters');
$customLabels = str(implode(",", generateLabelsApplication($this, $preview)))->replace(',', "\n");
$customLabels = str(implode("|", generateLabelsApplication($this, $preview)))->replace("|", "\n");
}
$this->custom_labels = base64_encode($customLabels);
$this->save();

View File

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

View File

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

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