Compare commits

...

351 Commits

Author SHA1 Message Date
Andras Bacsai
59d6818f70 Merge pull request #1339 from coollabsio/next
v4.0.0-beta.93
2023-10-18 11:30:40 +02:00
Andras Bacsai
7678cd47df fix: add config_hash if its null (old deployments) 2023-10-18 11:26:01 +02:00
Andras Bacsai
b101fbacd4 fix: do not show configuration changed if config_hash is null 2023-10-18 11:22:56 +02:00
Andras Bacsai
a61a86dc3b feat: show if config is not applied 2023-10-18 11:20:40 +02:00
Andras Bacsai
0b3cde44c3 feat: able to customize docker labels on applications 2023-10-18 10:32:08 +02:00
Andras Bacsai
6b302ab786 Add restarting indicator to resources 2023-10-18 09:03:14 +02:00
Andras Bacsai
3c623f13e2 revert version 2023-10-17 20:54:54 +02:00
Andras Bacsai
da54c24e8d fix: setup:dev script & contribution guide 2023-10-17 20:54:26 +02:00
Andras Bacsai
1e39c3d5ab Merge pull request #1338 from coollabsio/next
v4.0.0-beta.92
2023-10-17 19:03:04 +02:00
Andras Bacsai
6071412986 fix: proxy start process 2023-10-17 19:00:23 +02:00
Andras Bacsai
ba7148206a Merge pull request #1336 from coollabsio/next
v4.0.0-beta.91
2023-10-17 15:41:30 +02:00
Andras Bacsai
59c5b22e6c fix: always start proxy if not NONE is selected 2023-10-17 15:40:47 +02:00
Andras Bacsai
be7f2ad9c4 ui: add helper to service domains 2023-10-17 15:34:20 +02:00
Andras Bacsai
62295ef573 Merge pull request #1335 from coollabsio/next
v4.0.0-beta.90
2023-10-17 14:45:26 +02:00
Andras Bacsai
ceb9fcf3b6 service: wordpress 2023-10-17 14:44:25 +02:00
Andras Bacsai
60282f7b6c fix: only include config.json if its exists and a file 2023-10-17 14:23:07 +02:00
Andras Bacsai
f14b0a3411 Merge pull request #1334 from coollabsio/next
v4.0.0-beta.89
2023-10-17 14:06:12 +02:00
Andras Bacsai
30af317bd9 fix: show docker build logs 2023-10-17 14:04:21 +02:00
Andras Bacsai
95faa1c3ad fix: noindex meta tag 2023-10-17 13:28:33 +02:00
Andras Bacsai
fb280afe41 Merge pull request #1332 from coollabsio/next
v4.0.0-beta.88
2023-10-17 12:41:45 +02:00
Andras Bacsai
fd488a561a feat: use docker login credentials from server 2023-10-17 12:35:04 +02:00
Andras Bacsai
ab57a5d8ef Merge pull request #1331 from coollabsio/next
v4.0.0-beta.87
2023-10-17 12:14:03 +02:00
Andras Bacsai
f5ae222a6e fix: add internal domain names during build process 2023-10-17 11:23:49 +02:00
Andras Bacsai
5d95d8b79a fix: cancel any deployments + queue next 2023-10-17 11:10:33 +02:00
Andras Bacsai
fbb5f2ca2e fix: generate fqdn if you deleted a service app, but it requires fqdn 2023-10-17 10:37:39 +02:00
Andras Bacsai
16cbca36c1 add trademark policy 2023-10-17 10:37:26 +02:00
Andras Bacsai
24a578bedb Merge pull request #1327 from theh2so4/main
[+] Update
2023-10-17 10:31:25 +02:00
Andras Bacsai
36dc479772 fix: service status check is a bit better 2023-10-17 10:17:03 +02:00
Andras Bacsai
83d6e488e4 Merge pull request #1330 from seii/fix/raspbian-support
Add Raspbian support to install.sh
2023-10-17 09:50:57 +02:00
Seii
a4d358d512 Add Raspbian support to install.sh 2023-10-16 22:11:51 -06:00
TheH2SO4
c0c197101d [+] Update
💄 **Styling**:

-> ℹ️ **Alphabetical order**: Changed the order of the templates and set them on a alphabetical order.
-> ℹ️ **More accurate descriptions**: Created more accurate descriptions for the templates: `Appsmith, Appwrite, Fider, Ghost, Umami` and `Uptime Kuma`.
2023-10-15 23:48:33 +02:00
Andras Bacsai
62e39ccc7f Merge pull request #1326 from coollabsio/next
v4.0.0-beta.86
2023-10-15 16:56:21 +02:00
Andras Bacsai
a88a016137 fix: build image before starting dockerfile buildpacks 2023-10-15 16:54:16 +02:00
Andras Bacsai
f4c8986ab3 Merge pull request #1324 from coollabsio/next
v4.0.0-beta.85
2023-10-14 17:40:08 +02:00
Andras Bacsai
e286eae53b Merge pull request #1322 from scmmishra/fix/redis-default-url
fix: generated redis URL string
2023-10-14 17:36:48 +02:00
Andras Bacsai
bc3e59e4ef fix: delete resource if there was an error
fix: do not refresh on containerStatusJob (db view)
2023-10-14 17:26:16 +02:00
Andras Bacsai
17ebc650c9 Merge pull request #1323 from coollabsio/next
v4.0.0-beta.84
2023-10-14 14:24:20 +02:00
Andras Bacsai
0ef386b4a8 fix: stopping a resource is now job based
ui: show status on project
2023-10-14 14:22:07 +02:00
Shivam Mishra
26fce85bb0 fix: redis URL generated 2023-10-14 12:10:12 +05:30
Andras Bacsai
2a079e3365 Merge pull request #1321 from coollabsio/next
v4.0.0-beta.83
2023-10-13 21:54:26 +02:00
Andras Bacsai
5fb5ed75c4 Merge pull request #1315 from scmmishra/patch-1
fix: docker hub URL for redis
2023-10-13 21:46:46 +02:00
Andras Bacsai
a2008fe9d1 typo 2023-10-13 21:44:04 +02:00
Andras Bacsai
3c96485e3d fix: custom dockerfile location for dockerfile buildpack
fix: able to use custom port for git cloning
2023-10-13 21:39:49 +02:00
Andras Bacsai
1c97d47ea0 fix: turn off static deployment if you switch buildpacks 2023-10-13 21:08:59 +02:00
Shivam Mishra
8f8f5878dd fix: docker hub URL 2023-10-13 22:34:46 +05:30
Andras Bacsai
6e6f39dc1f Merge pull request #1314 from coollabsio/next
v4.0.0-beta.82
2023-10-13 16:38:52 +02:00
Andras Bacsai
d2d1f984e1 fix 2023-10-13 16:32:59 +02:00
Andras Bacsai
d635e5dbae fix: backup database one-by-one. 2023-10-13 15:45:24 +02:00
Andras Bacsai
49c56524e1 fix: dev containerjobs 2023-10-13 15:17:04 +02:00
Andras Bacsai
6ced607f2a fix: timeout for instant remote processes 2023-10-13 15:16:52 +02:00
Andras Bacsai
aaa2febef4 fix: docker cleanup jobs 2023-10-13 15:16:33 +02:00
Andras Bacsai
10e6eddcfe fix: db labels 2023-10-13 15:16:22 +02:00
Andras Bacsai
2639bf92ad fix: traefik dashboard ip 2023-10-13 14:35:02 +02:00
Andras Bacsai
59eae3a44e fix: proxy check for ports, do not kill anything listening on port 80/443 2023-10-13 14:25:30 +02:00
Andras Bacsai
5aa8ccfcf4 link to default redis conf 2023-10-13 09:42:38 +02:00
Andras Bacsai
aea7cc9638 fix: show database logs in case of its not healthy and running 2023-10-13 09:40:37 +02:00
Andras Bacsai
c970907c73 fix: no backup for redis 2023-10-13 09:39:40 +02:00
Andras Bacsai
38c6c1ee40 fix: urls should be password fields 2023-10-13 09:36:37 +02:00
Andras Bacsai
a6118f5daf fix 2023-10-13 09:34:11 +02:00
Andras Bacsai
b196c138d9 fix: server ip could be hostname in self-hosted 2023-10-13 09:31:44 +02:00
Andras Bacsai
8f9949160c feat: add custom redis conf 2023-10-12 17:29:29 +02:00
Andras Bacsai
beae0b545f init: redis 2023-10-12 17:18:33 +02:00
Andras Bacsai
b8dd7704b3 update resource delete command 2023-10-12 13:35:57 +02:00
Andras Bacsai
d913be66e6 Merge pull request #1312 from coollabsio/next
v4.0.0-beta.81
2023-10-12 12:23:03 +02:00
Andras Bacsai
63de538879 seeder update 2023-10-12 12:18:26 +02:00
Andras Bacsai
1d733b2282 seeder 2023-10-12 12:11:54 +02:00
Andras Bacsai
1d0ad51fdf fix: dockerfile location feature 2023-10-12 12:01:09 +02:00
Andras Bacsai
83d00bbe3c fix: make sure to use IP address 2023-10-12 11:41:37 +02:00
Andras Bacsai
c422b4dbcf fix: isCloud in production seeder 2023-10-12 11:36:44 +02:00
Andras Bacsai
767fd334dd Merge pull request #1310 from coollabsio/next
v4.0.0-beta.80
2023-10-12 09:47:39 +02:00
Andras Bacsai
972223f01b disable docker_compose deployments 2023-10-12 09:30:27 +02:00
Andras Bacsai
9318cac189 fix: service status check
fix: containerStatusJob
fix: service form
2023-10-12 09:12:46 +02:00
Andras Bacsai
7aa991fd7c fix: service check status 10 sec 2023-10-12 08:58:08 +02:00
Andras Bacsai
5c27f43b3d move autoupdate job to actions 2023-10-12 08:56:29 +02:00
Andras Bacsai
a2f4d4ed6d fix: make sure proxy wont start in NONE mode 2023-10-12 08:51:32 +02:00
Andras Bacsai
6aca2740fb fix 2023-10-11 15:46:59 +02:00
Andras Bacsai
cd13b5b83e version++ 2023-10-11 15:39:27 +02:00
Andras Bacsai
758dbafbf1 Merge pull request #1308 from coollabsio/next
v4.0.0-beta.79
2023-10-11 15:27:21 +02:00
Andras Bacsai
f6663661df disallow robots 2023-10-11 15:07:00 +02:00
Andras Bacsai
9666099408 commit 2023-10-11 14:43:34 +02:00
Andras Bacsai
d382af6860 fix 2023-10-11 14:40:41 +02:00
Andras Bacsai
4905454269 hm 2023-10-11 14:33:18 +02:00
Andras Bacsai
ed8bd37230 disallow robots 2023-10-11 14:31:59 +02:00
Andras Bacsai
ec1a7aa893 Merge pull request #1307 from coollabsio/next
v4.0.0-beta.78
2023-10-11 14:25:27 +02:00
Andras Bacsai
62adf2c5dc fix: boarding + verification 2023-10-11 14:24:19 +02:00
Andras Bacsai
3e4538de98 update command 2023-10-11 14:12:02 +02:00
Andras Bacsai
5a7b16ea5f command: delete server 2023-10-11 14:04:21 +02:00
Andras Bacsai
aa7bc40f85 fix: send unreachable/revived notifications 2023-10-11 13:52:46 +02:00
Andras Bacsai
4fd83dc727 version++ 2023-10-11 13:51:30 +02:00
Andras Bacsai
0e451f87a9 Merge pull request #1305 from coollabsio/next
v4.0.0-beta.77
2023-10-11 13:50:56 +02:00
Andras Bacsai
0b0ae55f0b fix 2023-10-11 13:47:14 +02:00
Andras Bacsai
40ec3d9753 fix 2023-10-11 13:34:51 +02:00
Andras Bacsai
9535c8df29 fix: check localhost connection 2023-10-11 13:30:36 +02:00
Andras Bacsai
6ca1d36d5d fix: cannot remove localhost 2023-10-11 13:12:29 +02:00
Andras Bacsai
f5d16c46cb version++ 2023-10-11 13:11:52 +02:00
Andras Bacsai
725c3fd547 Merge pull request #1304 from coollabsio/next
v4.0.0-beta.76
2023-10-11 12:57:35 +02:00
Andras Bacsai
dcfcee1db6 fix: public git 2023-10-11 12:56:57 +02:00
Andras Bacsai
9f8c44d96b version++ 2023-10-11 12:33:06 +02:00
Andras Bacsai
5b74fd34f5 fix: instant save build pack change 2023-10-11 12:12:25 +02:00
Andras Bacsai
5a4c9422b2 fix: only require registry image in case of dockerimage bp 2023-10-11 12:10:40 +02:00
Andras Bacsai
81b916724e Merge pull request #1303 from coollabsio/next
v4.0.0-beta.75
2023-10-11 12:06:15 +02:00
Andras Bacsai
9540f60fa2 fix: dashboard goto link 2023-10-11 12:04:30 +02:00
Andras Bacsai
8eb1686125 fix: transactional email link 2023-10-11 12:04:23 +02:00
Andras Bacsai
daf3710a5e fix: add new team button 2023-10-11 12:04:14 +02:00
Andras Bacsai
1067f37e4d fix: deleted team and it is the current one 2023-10-11 12:03:59 +02:00
Andras Bacsai
3b3c0b94e5 version++ 2023-10-11 12:03:39 +02:00
Andras Bacsai
8b0a0d67da Merge pull request #1302 from coollabsio/next
v4.0.0-beta.74
2023-10-11 11:09:19 +02:00
Andras Bacsai
5541c135df feat: proxy logs on the ui 2023-10-11 11:00:40 +02:00
Andras Bacsai
2552cb2208 ui: able to select environment on new resource 2023-10-11 10:19:03 +02:00
Andras Bacsai
f001e9bc34 improve dashboard 2023-10-11 10:08:37 +02:00
Andras Bacsai
f943fdc5be fix: use only ip addresses for servers 2023-10-11 09:57:35 +02:00
Andras Bacsai
a4f1fcba58 move subscription to livewire + show manage subscription button for people already subscribed once 2023-10-11 09:55:05 +02:00
Andras Bacsai
68091b44fc fix: contact link 2023-10-11 09:54:01 +02:00
Andras Bacsai
9f8caac91c dev: coolify proxy access logs exposed in dev 2023-10-11 09:31:30 +02:00
Andras Bacsai
8082dc1a01 fix: use port exposed for reverse proxy 2023-10-11 09:23:31 +02:00
Andras Bacsai
a71cf5bc66 cleanup 2023-10-10 15:36:06 +02:00
Andras Bacsai
0775074509 version++ 2023-10-10 14:34:38 +02:00
Andras Bacsai
242d2fb283 Merge pull request #1300 from coollabsio/next
v4.0.0-beta.73
2023-10-10 14:29:44 +02:00
Andras Bacsai
5646818965 version++ 2023-10-10 14:26:31 +02:00
Andras Bacsai
ffc5320940 fix: backupfailed notification is forced 2023-10-10 14:17:16 +02:00
Andras Bacsai
7c10c55b1c fix: only send email if transactional email set 2023-10-10 14:14:41 +02:00
Andras Bacsai
7c96b6207a Merge pull request #1299 from coollabsio/next
v4.0.0-beta.72
2023-10-10 14:06:11 +02:00
Andras Bacsai
0be8ffbdc9 feat: add dockerfile location 2023-10-10 14:02:43 +02:00
Andras Bacsai
24fa56762e fix: database backups 2023-10-10 13:10:43 +02:00
Andras Bacsai
84d8e35411 fix: tcp proxy for dbs 2023-10-10 11:42:35 +02:00
Andras Bacsai
3d3ccc435c ui: fix 2023-10-10 11:29:33 +02:00
Andras Bacsai
be3b01472e ui: fix 2023-10-10 11:28:57 +02:00
Andras Bacsai
de6f5b1105 fix: goto 2023-10-10 11:27:39 +02:00
Andras Bacsai
14d9c06dcd feat: able to deploy docker images 2023-10-10 11:16:38 +02:00
Andras Bacsai
8abfaa1967 fix: no env goto envs from dashboard 2023-10-10 10:57:56 +02:00
Andras Bacsai
46f7ae9588 ui: updated dashboard 2023-10-10 10:56:11 +02:00
Andras Bacsai
f2c32b9aeb fixes 2023-10-09 20:37:42 +02:00
Andras Bacsai
3dab1eb92e fix: server saving 2023-10-09 20:12:03 +02:00
Andras Bacsai
9c22e01716 wip: dockerimage 2023-10-09 15:49:48 +02:00
Andras Bacsai
d1c47a4062 version++ 2023-10-09 15:22:51 +02:00
Andras Bacsai
6c3f97d9ae Merge pull request #1297 from coollabsio/next
v4.0.0-beta.71
2023-10-09 15:19:35 +02:00
Andras Bacsai
ebd8e2ce40 fix: check connection 2023-10-09 15:08:28 +02:00
Andras Bacsai
b650f3f754 fix: contact docs 2023-10-09 14:52:24 +02:00
Andras Bacsai
f33ba40478 fix help 2023-10-09 14:48:51 +02:00
Andras Bacsai
5cea9c4603 isInstanceAdmin() 2023-10-09 14:38:44 +02:00
Andras Bacsai
d32832fabc update 2023-10-09 14:32:30 +02:00
Andras Bacsai
165f0a3d4a feat: add email verification for cloud 2023-10-09 14:20:55 +02:00
Andras Bacsai
f14995200b fix: do not reset unreachable count 2023-10-09 12:41:21 +02:00
Andras Bacsai
12bb2ecc4a update 2023-10-09 12:13:30 +02:00
Andras Bacsai
933ec5741d fix: server unreachable count 2023-10-09 12:07:42 +02:00
Andras Bacsai
8004a40139 updates 2023-10-09 11:49:38 +02:00
Andras Bacsai
a6209fbe5c package updates 2023-10-09 11:45:23 +02:00
Andras Bacsai
b47c327b55 Merge pull request #1296 from coollabsio/next
fix: small
2023-10-09 11:24:06 +02:00
Andras Bacsai
25434a7acd fix: small 2023-10-09 11:23:51 +02:00
Andras Bacsai
0de042dbac Merge pull request #1295 from coollabsio/next
v4.0.0-beta.70
2023-10-09 11:23:32 +02:00
Andras Bacsai
eb9e2203b0 fix: fqdn could be null 2023-10-09 11:10:04 +02:00
Andras Bacsai
dcaa7a6ad7 fix: server validation process 2023-10-09 11:00:18 +02:00
Andras Bacsai
5b584a6c6d Merge pull request #1292 from vaporii/main
corrected 'orAGnize' to 'orGAnize'
2023-10-09 09:24:16 +02:00
vaporii
c40ea6f1da corrected 'orAGnize' to 'orGAnize' 2023-10-08 15:01:09 -05:00
Andras Bacsai
61a7b9ac94 fix: able to set base dir for Dockerfile build pack 2023-10-07 12:54:19 +02:00
Andras Bacsai
9e81416fef fix: better unreachable/revived server statuses 2023-10-07 00:51:01 +02:00
Andras Bacsai
c58706e3e4 Merge pull request #1291 from adiologydev/next
fix(create): flex wrap on server & network selection
2023-10-06 20:55:15 +02:00
Andras Bacsai
45bca8649b fix: public repository names 2023-10-06 20:48:03 +02:00
Andras Bacsai
b095b88281 fix: contribution guide 2023-10-06 20:45:35 +02:00
Aditya Tripathi
cb41584137 fix(create): flex wrap on server & network selection 2023-10-06 22:31:20 +05:30
Andras Bacsai
6659153804 version++
fix: unreachable servers
2023-10-06 15:32:46 +02:00
Andras Bacsai
44c429a224 Merge pull request #1290 from coollabsio/next
v4.0.0-beta.69
2023-10-06 14:54:14 +02:00
Andras Bacsai
fe9c501c1d ui 2023-10-06 14:51:50 +02:00
Andras Bacsai
4e94b4a0c1 fix: private repository 2023-10-06 14:50:49 +02:00
Andras Bacsai
2f4d7c0e43 feat: deploy private repo with ssh key 2023-10-06 14:39:30 +02:00
Andras Bacsai
e443fc394a fix: select branch on other git 2023-10-06 13:52:15 +02:00
Andras Bacsai
5b56c50f03 feat: init version of any git deployment 2023-10-06 13:46:42 +02:00
Andras Bacsai
3adeb2f73f fix: set smtp notifications on by default 2023-10-06 12:32:38 +02:00
Andras Bacsai
9eaa13a08a update 2023-10-06 11:17:35 +02:00
Andras Bacsai
df5a4a9667 fix: contact details in emails
fix: pricing plans
2023-10-06 11:16:29 +02:00
Andras Bacsai
26048339d6 Merge pull request #1289 from coollabsio/next
v4.0.0-beta.68
2023-10-06 10:58:12 +02:00
Andras Bacsai
d85af3fefc fix: ui for self-hosted email settings 2023-10-06 10:57:35 +02:00
Andras Bacsai
575338609b fix 2023-10-06 10:47:48 +02:00
Andras Bacsai
d32e43ef37 fix: test emails only available for user owned smtp/resend 2023-10-06 10:42:32 +02:00
Andras Bacsai
a96ef1bfab use instance settigns by default 2023-10-06 10:30:32 +02:00
Andras Bacsai
1bfedf69f2 Merge pull request #1288 from coollabsio/next
v4.0.0-beta.67
2023-10-06 10:24:13 +02:00
Andras Bacsai
208fe7d87b revert 2023-10-06 10:11:04 +02:00
Andras Bacsai
a6d58b5d72 fix: decrease max horizon processes to get lower memory usage 2023-10-06 10:10:47 +02:00
Andras Bacsai
277b4276e6 small fixes 2023-10-06 10:08:50 +02:00
Andras Bacsai
f6adc9285a fix: services - do not remove unnecessary things for now 2023-10-06 10:08:46 +02:00
Andras Bacsai
f03bbe0e95 feat: basedir / monorepo initial support 2023-10-06 10:07:25 +02:00
Andras Bacsai
535375193c cloud: add shared email option to everyone 2023-10-05 14:46:39 +02:00
Andras Bacsai
d79c063fd6 ui: notifications 2023-10-05 14:44:17 +02:00
Andras Bacsai
35f45492e3 fix: email notifications subscription fixed 2023-10-05 14:37:16 +02:00
Andras Bacsai
ab8a7893d9 composer update 2023-10-05 13:57:59 +02:00
Andras Bacsai
76b8d048d4 fix: PR deployments use the first fqdn as base 2023-10-05 13:35:16 +02:00
Andras Bacsai
0e583334e7 version++ 2023-10-05 11:39:02 +02:00
Andras Bacsai
0ad8ca224f Merge pull request #1287 from coollabsio/next
v4.0.0-beta.66
2023-10-05 11:28:50 +02:00
Andras Bacsai
050e56f69a fix: traefik labelling in case of several http and https domain added 2023-10-05 11:27:50 +02:00
Andras Bacsai
6099ac11d9 version++ 2023-10-05 11:26:45 +02:00
Andras Bacsai
adac728a60 Merge pull request #1286 from coollabsio/next
v4.0.0-beta.65
2023-10-05 11:00:56 +02:00
Andras Bacsai
e2e64e36a0 feat: disable service, required version 2023-10-05 10:58:08 +02:00
Andras Bacsai
762af66cbf add deprecated templates in dev 2023-10-05 10:48:26 +02:00
Andras Bacsai
b08f525bd4 fix: move dev data to volumes to prevent permission issues 2023-10-05 08:54:03 +02:00
Andras Bacsai
5ae16b195c remove ray 2023-10-05 08:50:01 +02:00
Andras Bacsai
91db1953ff fix: delete event to deleting 2023-10-05 08:46:26 +02:00
Andras Bacsai
1c8f92d3b7 fix: remove SERVICE_ from deployable compose 2023-10-05 08:40:17 +02:00
Andras Bacsai
4075572dbc fix: visible version number 2023-10-05 08:32:20 +02:00
Andras Bacsai
2971e360d7 revert last commits 2023-10-04 18:06:25 +02:00
Andras Bacsai
32bb2780f2 rename composes 2023-10-04 15:43:29 +02:00
Andras Bacsai
af69575b29 fix: remove SERVICE_ stuff from raw compose
feat: multiport predefined compose
fix: use predefined name as prefix for fqdn
2023-10-04 15:40:08 +02:00
Andras Bacsai
d4a7d0d25f fix: traefik labels for multiport deployments 2023-10-04 15:39:23 +02:00
Andras Bacsai
45f9def0f6 fix: dev compose files 2023-10-04 14:40:33 +02:00
Andras Bacsai
5a90eed7ef fix: compose parser updated 2023-10-04 14:40:26 +02:00
Andras Bacsai
38e1f17edf feat: multiselect removable resources 2023-10-04 14:40:04 +02:00
Andras Bacsai
1651845e20 version++ 2023-10-04 11:54:56 +02:00
Andras Bacsai
38e96548b5 Merge pull request #1285 from coollabsio/next
v4.0.0-beta.64
2023-10-04 11:33:21 +02:00
Andras Bacsai
47e4126dca fix: compose magic 2023-10-04 11:32:27 +02:00
Andras Bacsai
e0b175ab07 version++ 2023-10-04 11:05:40 +02:00
Andras Bacsai
93ec785f4f Merge pull request #1284 from coollabsio/next
v4.0.0-beta.63
2023-10-04 11:01:05 +02:00
Andras Bacsai
e849addab8 dev: switch back to /data (volume errors) 2023-10-04 10:57:44 +02:00
Andras Bacsai
a5e6975dac fix: ui 2023-10-04 10:34:44 +02:00
Andras Bacsai
4ac8e1cc67 fix: services file/dir read from server
ui: fix storages layout
2023-10-04 09:58:39 +02:00
Andras Bacsai
1a5e3a7836 sync volume script 2023-10-03 15:48:07 +02:00
Andras Bacsai
4498d1ed4b fix: service logs visible if the whole service stack is not running 2023-10-03 15:08:23 +02:00
Andras Bacsai
c8b974820b Merge pull request #1282 from coollabsio/next
fix: volume names
2023-10-03 13:26:02 +02:00
Andras Bacsai
527373e297 fix: volume names 2023-10-03 13:25:41 +02:00
Andras Bacsai
a84be8dc33 Merge pull request #1281 from coollabsio/next
v4.0.0-beta.61
2023-10-03 13:14:39 +02:00
Andras Bacsai
bd856f7f67 fix: volume names in services 2023-10-03 13:14:11 +02:00
Andras Bacsai
8ff216e5fb revert 2023-10-03 12:20:09 +02:00
Andras Bacsai
5255311a2e hmm 2023-10-03 12:14:58 +02:00
Andras Bacsai
774a245e84 Merge pull request #1280 from coollabsio/next
v4.0.0-beta.60
2023-10-03 12:12:17 +02:00
Andras Bacsai
194675c838 update commands 2023-10-03 12:08:57 +02:00
Andras Bacsai
75862ca8de rename resource delete 2023-10-03 11:59:30 +02:00
Andras Bacsai
5580a4e704 feat: delete resource command 2023-10-03 11:56:56 +02:00
Andras Bacsai
51e601a303 fix: only use _ in volume names for services 2023-10-03 11:22:35 +02:00
Andras Bacsai
734e9fd68d more explanation 2023-10-03 11:01:01 +02:00
Andras Bacsai
09fc950ae8 fix: add _data to vite ignore 2023-10-03 11:00:52 +02:00
Andras Bacsai
cf6caa279d fix: ui 2023-10-03 09:02:36 +02:00
Andras Bacsai
68c976ab70 fix: show all storages in one place for services 2023-10-03 08:48:07 +02:00
Andras Bacsai
1560ab2a50 fix: UI 2023-10-03 08:22:03 +02:00
Andras Bacsai
e3a6458506 version++ 2023-10-03 08:16:19 +02:00
Andras Bacsai
1768b9374f fix: move /data to ./_data in dev 2023-10-03 08:14:49 +02:00
Andras Bacsai
51d0a30a6c Merge pull request #1279 from coollabsio/next
v4.0.0-beta.59
2023-10-02 18:03:07 +02:00
Andras Bacsai
9701c65297 fix: predefined content for files 2023-10-02 18:02:32 +02:00
Andras Bacsai
58e3bb2571 version++ 2023-10-02 18:01:24 +02:00
Andras Bacsai
31cbd1602d Merge pull request #1278 from coollabsio/next
v4.0.0-beta.58
2023-10-02 17:21:07 +02:00
Andras Bacsai
dd5723d596 service: uptime kume hc updated 2023-10-02 17:17:49 +02:00
Andras Bacsai
620f26a6f1 fix: add destination to new services 2023-10-02 17:12:50 +02:00
Andras Bacsai
540717e809 feat: attach Coolify defined networks to services 2023-10-02 16:57:55 +02:00
Andras Bacsai
d446cd4103 feat: reset root password 2023-10-02 16:38:05 +02:00
Andras Bacsai
7d1a76570c wip 2023-10-02 15:51:06 +02:00
Andras Bacsai
e18766ec21 fix: if waitlist is disabled, redirect to register 2023-10-02 15:09:57 +02:00
Andras Bacsai
3d0354cf7e add contribution guide 2023-10-02 14:58:29 +02:00
Andras Bacsai
af5b9fced1 Merge pull request #1277 from coollabsio/next
v4.0.0-beta.57
2023-10-02 14:19:24 +02:00
Andras Bacsai
ab5202515e fix: service status 2023-10-02 14:12:19 +02:00
Andras Bacsai
f863db7ea5 only 100 2023-10-02 14:01:54 +02:00
Andras Bacsai
3adc0bdd6e fix: only show last 1000 lines 2023-10-02 14:01:16 +02:00
Andras Bacsai
97027875bf feat: container logs 2023-10-02 13:38:16 +02:00
Andras Bacsai
fd9c13009f fix: always pull helper image in dev 2023-10-02 09:37:55 +02:00
Andras Bacsai
46a72fac47 Merge pull request #1276 from alexjustesen/patch-1
wee little missing spacing
2023-10-02 09:32:33 +02:00
Andras Bacsai
5d6ee04991 fix: new volumes for services should have - instead of _ 2023-10-02 09:18:32 +02:00
Andras Bacsai
d523becb29 fix: add uuid to volume names 2023-10-02 09:08:41 +02:00
Andras Bacsai
41672f75d0 fix: only parse expose in dockerfiles if ports_exposes is empty 2023-10-02 09:08:33 +02:00
Alex Justesen
acd8541e68 wee little spacing
noticed some missing spacing during the onboarding process.
2023-10-01 19:16:37 -04:00
Andras Bacsai
ed6af777a4 fix: show real volume names 2023-10-01 20:46:49 +02:00
Andras Bacsai
05f162f4e8 version++ 2023-10-01 20:29:35 +02:00
Andras Bacsai
5e0adc3777 Merge pull request #1275 from coollabsio/next
v4.0.0-beta.56
2023-10-01 18:16:24 +02:00
Andras Bacsai
7d06fc4403 fix: do not show subscription cancelled noti 2023-10-01 18:14:24 +02:00
Andras Bacsai
0e1bcceb8e feat: able to disable container healthchecks
fix: dockerfile based deployments have hc off by default
2023-10-01 18:14:13 +02:00
Andras Bacsai
a922f2fedf version++ 2023-10-01 18:13:34 +02:00
Andras Bacsai
1e0226c8ed Merge pull request #1274 from coollabsio/next
v4.0.0-beta.55
2023-10-01 17:40:31 +02:00
Andras Bacsai
5c45908087 fix: if app settings is not saved to db 2023-10-01 17:39:57 +02:00
Andras Bacsai
aefdc76805 fix: dockerfile expose is not overwritten 2023-10-01 17:27:12 +02:00
Andras Bacsai
b3c8c881b7 Revert "fix: services should have destination as well"
This reverts commit 9ab5a1f7bd.
2023-10-01 15:31:25 +02:00
Andras Bacsai
9ab5a1f7bd fix: services should have destination as well 2023-10-01 13:59:22 +02:00
Andras Bacsai
23968e7886 version++ 2023-10-01 13:28:33 +02:00
Andras Bacsai
390d24b6d7 Merge pull request #1273 from coollabsio/next
v4.0.0-beta.54
2023-10-01 12:36:10 +02:00
Andras Bacsai
e4296345b3 fix: public repo branch selection
fix: commit sha selection in source tabs
2023-10-01 12:29:50 +02:00
Andras Bacsai
bcffbe418b fix: preview deployments name, status etc 2023-10-01 12:02:44 +02:00
Andras Bacsai
bfbee4e78f version+ 2023-10-01 11:33:15 +02:00
Andras Bacsai
2bdb44dac3 Merge pull request #1272 from coollabsio/next
v4.0.0-beta.52
2023-09-30 20:55:24 +02:00
Andras Bacsai
4daa1b8c16 fix: coolify db backup 2023-09-30 20:54:05 +02:00
Andras Bacsai
febc399568 fix: not found base_branch in git webhooks 2023-09-30 20:47:07 +02:00
Andras Bacsai
9b9d4f9941 Merge pull request #1271 from coollabsio/next
v4.0.0-beta.52
2023-09-30 20:27:12 +02:00
Andras Bacsai
f49b87870c fix: backups are now working again 2023-09-30 20:26:42 +02:00
Andras Bacsai
ed49d4e3a0 Merge pull request #1264 from coollabsio/next
v4.0.0-beta.51
2023-09-30 16:02:09 +02:00
Andras Bacsai
7811c75139 fix: deploykey branch 2023-09-30 15:59:39 +02:00
Andras Bacsai
068a1b4bc4 fix: preselect branc on private repos 2023-09-30 15:57:30 +02:00
Andras Bacsai
c618d912db fix: if public repository does not have a main branch 2023-09-30 15:51:01 +02:00
Andras Bacsai
3d43f2127a fix: respect server fqdn 2023-09-30 15:39:40 +02:00
Andras Bacsai
79fde593a9 fix: service volume read from filesystem
fix: edit compose moved to dialog
2023-09-30 15:08:40 +02:00
Andras Bacsai
64ce41df0f fix: file/dir based volumes are now read from the server 2023-09-29 21:38:11 +02:00
Andras Bacsai
23e205b6cd fix: docker cleanup should be a job by server 2023-09-29 14:26:42 +02:00
Andras Bacsai
4161ea7eb6 fix: show source on all type of applications 2023-09-29 14:26:31 +02:00
Andras Bacsai
77037f8933 ui: fix previews to preview 2023-09-29 14:26:19 +02:00
Andras Bacsai
bdcc0c8de5 fix: only show manually added private keys on server view 2023-09-29 11:01:58 +02:00
Andras Bacsai
ac133875fa fix: remove private key in case you removed a github app 2023-09-29 11:01:40 +02:00
Andras Bacsai
f4819a849b remove plausible for now 2023-09-29 10:53:33 +02:00
Andras Bacsai
2cd1785a92 version++ 2023-09-29 10:53:28 +02:00
Andras Bacsai
797352a5db Merge pull request #1263 from coollabsio/next
v4.0.0-beta.50
2023-09-29 10:36:25 +02:00
Andras Bacsai
03a3405524 fix: localhost privatekey update 2023-09-29 10:32:32 +02:00
Andras Bacsai
925326b885 change sentry dsn 2023-09-29 10:32:23 +02:00
Andras Bacsai
ffdf51a490 version++ 2023-09-29 10:32:13 +02:00
Andras Bacsai
272d5547c8 Merge pull request #1262 from coollabsio/next
v4.0.0-beta.49
2023-09-29 10:21:54 +02:00
Andras Bacsai
fbaec769f0 fix: reporting handler 2023-09-29 10:21:11 +02:00
Andras Bacsai
d93640a8d9 Merge pull request #1261 from coollabsio/next
v4.0.0-beta.48
2023-09-29 09:51:51 +02:00
Andras Bacsai
3749970831 fix: update process if server has been renamed 2023-09-29 09:48:20 +02:00
Andras Bacsai
7d45bb1335 version check 2023-09-28 22:55:03 +02:00
Andras Bacsai
da04e0c027 fix: sync:bunny 2023-09-28 22:42:10 +02:00
Andras Bacsai
c637d2d90d version++ 2023-09-28 22:31:53 +02:00
Andras Bacsai
1bb8860f37 Merge pull request #1260 from coollabsio/next
v4.0.0-beta.47
2023-09-28 22:25:12 +02:00
Andras Bacsai
38a22dcf4d fix: service templates 2023-09-28 22:20:49 +02:00
Andras Bacsai
91e1eb7664 fix 2023-09-28 21:56:34 +02:00
Andras Bacsai
c7946e7551 fix: next helper image 2023-09-28 21:52:50 +02:00
Andras Bacsai
958645e37f Merge branch 'main' into next 2023-09-28 21:48:03 +02:00
Andras Bacsai
acdfa89ec1 Update coolify-helper.yml 2023-09-28 21:32:53 +02:00
Andras Bacsai
62ec85ee2e Update coolify-helper.yml 2023-09-28 21:31:50 +02:00
Andras Bacsai
8f24a66456 Update coolify-helper.yml 2023-09-28 21:26:55 +02:00
Andras Bacsai
0dd0888775 Update coolify-helper.yml 2023-09-28 21:23:25 +02:00
Andras Bacsai
5040ddea28 fix: sentry 4502634789 2023-09-28 13:46:53 +02:00
Andras Bacsai
1a04a57c01 fix: sentry 4504136641 2023-09-28 13:40:58 +02:00
Andras Bacsai
9b6c162224 fix: sentry 4510197209 2023-09-28 13:15:14 +02:00
Andras Bacsai
e22c5d22f5 fix: instance fqdn setting 2023-09-28 13:13:21 +02:00
Andras Bacsai
84e5b39830 fix: prevent sync version (it needs an option) 2023-09-28 13:07:52 +02:00
Andras Bacsai
cdb6964b0b fix: install script drops an error 2023-09-28 13:05:17 +02:00
Andras Bacsai
df4ecd47a7 fix: sync command 2023-09-28 11:54:20 +02:00
Andras Bacsai
4a84c7238a fix: cannot delete env with available services 2023-09-28 11:51:01 +02:00
Andras Bacsai
2b3057e1b4 version++ 2023-09-28 11:50:50 +02:00
Andras Bacsai
ae65172946 Merge pull request #1256 from coollabsio/next
v4.0.0-beta.46
2023-09-28 11:38:16 +02:00
Andras Bacsai
3a2d17bc05 fix: containerStatusJob 2023-09-28 11:33:16 +02:00
Andras Bacsai
4c1067cf36 baseurl for docs 2023-09-28 11:01:00 +02:00
Andras Bacsai
b046a3e9f7 fix: sslip for localhost 2023-09-28 10:53:00 +02:00
Andras Bacsai
199881c596 fixes 2023-09-28 09:54:21 +02:00
Andras Bacsai
99c8607ff4 fix: disable early updates 2023-09-27 22:43:32 +02:00
Andras Bacsai
e61fcc77f9 ui 2023-09-27 22:39:10 +02:00
Andras Bacsai
20dca179fb add umami 2023-09-27 22:35:15 +02:00
Andras Bacsai
0f542c65ae fix: manually create network for services 2023-09-27 22:35:10 +02:00
Andras Bacsai
d609fcaee1 fix: services 2023-09-27 21:51:06 +02:00
Andras Bacsai
fe8a7fc54f fix: services view 2023-09-27 21:14:13 +02:00
Andras Bacsai
398f122593 fix: aaaaaaaaaaaaaaaaa 2023-09-27 15:48:19 +02:00
Andras Bacsai
f0abdcc2da okay, now it is way better 2023-09-27 12:45:53 +02:00
Andras Bacsai
c9a278b750 update service templates 2023-09-26 16:32:05 +02:00
Andras Bacsai
6990c593a4 update 2023-09-26 16:27:48 +02:00
Andras Bacsai
8f54b51ecd update templates 2023-09-26 16:21:55 +02:00
Andras Bacsai
3eb628b773 fix: containerstatusjob 2023-09-26 15:07:33 +02:00
Andras Bacsai
fabb97330a puh, fixes 2023-09-26 14:45:52 +02:00
Andras Bacsai
03c9793d11 Merge pull request #1252 from khrj/patch-1
Remove space before ? from onboarding flow
2023-09-25 21:02:53 +02:00
Andras Bacsai
a4320b7cee cloud: marketing emails 2023-09-25 20:57:52 +02:00
Andras Bacsai
cbf9bc99ea ui: help 2023-09-25 18:07:20 +02:00
Andras Bacsai
0fbc382467 feat: image tag for services 2023-09-25 17:51:04 +02:00
Andras Bacsai
0f8ccac775 fix: new service template layout 2023-09-25 17:41:35 +02:00
Andras Bacsai
ee20c3339e fix: show real storage name on services 2023-09-25 17:14:19 +02:00
Andras Bacsai
0b11093d18 feat: services 2023-09-25 15:48:43 +02:00
Khushraj Rathod
de8118b59d Remove space before ? from onboarding flow 2023-09-25 19:04:11 +05:30
Andras Bacsai
58522b59b7 - package updates
- feat: service fixes
- ui: help menu
2023-09-25 12:49:55 +02:00
Andras Bacsai
356394c03d ui: update help modal 2023-09-25 11:15:35 +02:00
Andras Bacsai
3e4db2f5b2 ui: more visible feedback button 2023-09-25 09:34:32 +02:00
Andras Bacsai
872981b8b4 ui: a bit better error 2023-09-25 09:20:29 +02:00
Andras Bacsai
51c468ae0b fix: proxy configuration + starter 2023-09-25 09:17:42 +02:00
Andras Bacsai
80a797aec8 version++ 2023-09-24 21:56:57 +02:00
306 changed files with 7865 additions and 2799 deletions

View File

@@ -1,11 +1,3 @@
############################################################################################################
# Development Environment
# User and group id for the user that will run the application inside the container
# Run in your terminal: `id -u` and `id -g` and that's the results
USERID=
GROUPID=
############################################################################################################
APP_NAME=Coolify-localhost
APP_ID=development
APP_ENV=local
@@ -13,6 +5,7 @@ APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_PORT=8000
MUX_ENABLED=false
DUSK_DRIVER_URL=http://selenium:4444

View File

@@ -4,7 +4,7 @@ on:
push:
branches: [ "next" ]
paths:
- .github/workflows/coolify-helper.yml
- .github/workflows/coolify-helper-next.yml
- docker/coolify-helper/Dockerfile
env:

View File

@@ -77,7 +77,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:

31
CONTRIBUTION.md Normal file
View File

@@ -0,0 +1,31 @@
# Contributing
> "First, thanks for considering to contribute to my project.
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
You can ask for guidance anytime on our
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
## 1) Setup your development environment
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
## 2) Set your environment variables
- Copy [.env.development.example](./.env.development.example) to .env.
## 3) Start & setup Coolify
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
## 4) Start development
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
Mails are caught by Mailpit: `localhost:8025`

View File

@@ -36,7 +36,7 @@ You can find the installation script [here](./scripts/install.sh).
## Support
Contact us [here](https://docs.coollabs.io/contact).
Contact us [here](https://coolify.io/docs/contact).
## Recognitions

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Actions\Application;
use App\Models\Application;
use App\Notifications\Application\StatusChanged;
use Lorisleiva\Actions\Concerns\AsAction;
class StopApplication
{
use AsAction;
public function handle(Application $application)
{
$server = $application->destination->server;
$containers = getCurrentApplicationContainerStatus($server, $application->id);
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(
["docker rm -f {$containerName}"],
$server
);
}
}
// TODO: make notification for application
// $application->environment->project->team->notify(new StatusChanged($application));
}
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace App\Actions\Database;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartDatabaseProxy
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql $database)
{
$internalPort = null;
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
$internalPort = 6379;
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
$internalPort = 5432;
}
$containerName = "{$database->uuid}-proxy";
$configuration_dir = database_proxy_dir($database->uuid);
$nginxconf = <<<EOF
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
stream {
server {
listen $database->public_port;
proxy_pass $database->uuid:$internalPort;
}
}
EOF;
$dockerfile = <<< EOF
FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/nginx.conf
EOF;
$docker_compose = [
'version' => '3.8',
'services' => [
$containerName => [
'build' => [
'context' => $configuration_dir,
'dockerfile' => 'Dockerfile',
],
'image' => "nginx:stable-alpine",
'container_name' => $containerName,
'restart' => RESTART_MODE,
'ports' => [
"$database->public_port:$database->public_port",
],
'networks' => [
$database->destination->network,
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'stat /etc/nginx/nginx.conf || exit 1',
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 3,
'start_period' => '1s'
],
]
],
'networks' => [
$database->destination->network => [
'external' => true,
'name' => $database->destination->network,
'attachable' => true,
]
]
];
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf);
$dockerfile_base64 = base64_encode($dockerfile);
instant_remote_process([
"mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
"docker compose --project-directory {$configuration_dir} up --build -d",
], $database->destination->server);
}
}

View File

@@ -6,15 +6,18 @@ use App\Models\Server;
use App\Models\StandalonePostgresql;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartPostgresql
{
use AsAction;
public StandalonePostgresql $database;
public array $commands = [];
public array $init_scripts = [];
public string $configuration_dir;
public function __invoke(Server $server, StandalonePostgresql $database)
public function handle(Server $server, StandalonePostgresql $database)
{
$this->database = $database;
$container_name = $this->database->uuid;
@@ -41,6 +44,9 @@ class StartPostgresql
'networks' => [
$this->database->destination->network,
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => [
'CMD-SHELL',

View File

@@ -0,0 +1,160 @@
<?php
namespace App\Actions\Database;
use App\Models\Server;
use App\Models\StandaloneRedis;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartRedis
{
use AsAction;
public StandaloneRedis $database;
public array $commands = [];
public string $configuration_dir;
public function handle(Server $server, StandaloneRedis $database)
{
$this->database = $database;
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [
"echo '####### Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
];
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_redis();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
'command' => $startCommand,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => RESTART_MODE,
'networks' => [
$this->database->destination->network,
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'redis-cli',
'ping'
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
];
if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->redis_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/redis.conf',
'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 = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $server);
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
}
return $environment_variables->all();
}
private function add_custom_redis()
{
if (is_null($this->database->redis_conf)) {
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}";
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Actions\Database;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Notifications\Application\StatusChanged;
use Lorisleiva\Actions\Concerns\AsAction;
class StopDatabase
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql $database)
{
$server = $database->destination->server;
instant_remote_process(
["docker rm -f {$database->uuid}"],
$server
);
if ($database->is_public) {
StopDatabaseProxy::run($database);
}
// TODO: make notification for services
// $database->environment->project->team->notify(new StatusChanged($database));
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Actions\Database;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
class StopDatabaseProxy
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql $database)
{
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
$database->is_public = false;
$database->save();
}
}

View File

@@ -58,6 +58,11 @@ class CreateNewUser implements CreatesNewUsers
'password' => Hash::make($input['password']),
]);
$team = $user->teams()->first();
if (isCloud()) {
$user->sendVerificationEmail();
} else {
$user->markEmailAsVerified();
}
}
// Set session variable
session(['currentTeam' => $user->currentTeam = $team]);

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class CheckProxy
{
use AsAction;
public function handle(Server $server, $fromUI = false)
{
if (!$server->isProxyShouldRun()) {
if ($fromUI) {
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
} else {
return false;
}
}
$status = getContainerStatus($server, 'coolify-proxy');
if ($status === 'running') {
$server->proxy->set('status', 'running');
$server->save();
return false;
}
$ip = $server->ip;
if ($server->id === 0) {
$ip = 'host.docker.internal';
}
$connection80 = @fsockopen($ip, '80');
$connection443 = @fsockopen($ip, '443');
$port80 = is_resource($connection80) && fclose($connection80);
$port443 = is_resource($connection443) && fclose($connection443);
if ($port80) {
if ($fromUI) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
if ($port443) {
if ($fromUI) {
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
return true;
}
}

View File

@@ -10,52 +10,47 @@ use Spatie\Activitylog\Models\Activity;
class StartProxy
{
use AsAction;
public function handle(Server $server, bool $async = true): Activity|string
public function handle(Server $server, bool $async = true): string|Activity
{
$commands = collect([]);
$proxyType = $server->proxyType();
if ($proxyType === 'none') {
return 'OK';
try {
$proxyType = $server->proxyType();
$commands = collect([]);
$proxy_path = get_proxy_path();
$configuration = CheckConfiguration::run($server);
if (!$configuration) {
throw new \Exception("Configuration is not synced");
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
$commands = $commands->merge([
"mkdir -p $proxy_path && cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",
'docker compose pull',
"echo 'Stopping existing coolify-proxy.'",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
"echo 'Starting coolify-proxy.'",
'docker compose up -d --remove-orphans',
"echo 'Proxy started successfully.'"
]);
$commands = $commands->merge(connectProxyToNetworks($server));
if ($async) {
$activity = remote_process($commands, $server);
return $activity;
} else {
instant_remote_process($commands, $server);
$server->proxy->set('status', 'running');
$server->proxy->set('type', $proxyType);
$server->save();
return 'OK';
}
} catch(\Throwable $e) {
ray($e);
throw $e;
}
$proxy_path = get_proxy_path();
$configuration = CheckConfiguration::run($server);
if (!$configuration) {
throw new \Exception("Configuration is not synced");
}
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
$commands = $commands->merge([
"apt-get update > /dev/null 2>&1 || true",
"command -v lsof >/dev/null || echo '####### Installing lsof.'",
"command -v lsof >/dev/null || apt install -y lsof",
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
"mkdir -p $proxy_path && cd $proxy_path",
"echo '####### Creating Docker Compose file.'",
"echo '####### Pulling docker image.'",
'docker compose pull',
"echo '####### Stopping existing coolify-proxy.'",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
"command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
"command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
"command -v fuser >/dev/null && fuser -k 80/tcp || true",
"command -v fuser >/dev/null && fuser -k 443/tcp || true",
"systemctl disable nginx > /dev/null 2>&1 || true",
"systemctl disable apache2 > /dev/null 2>&1 || true",
"systemctl disable apache > /dev/null 2>&1 || true",
"echo '####### Starting coolify-proxy.'",
'docker compose up -d --remove-orphans',
"echo '####### Proxy installed successfully.'"
]);
$commands = $commands->merge(connectProxyToNetworks($server));
if (!$async) {
instant_remote_process($commands, $server);
return 'OK';
} else {
$activity = remote_process($commands, $server);
return $activity;
}
}
}

View File

@@ -2,12 +2,14 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use App\Models\StandaloneDocker;
class InstallDocker
{
public function __invoke(Server $server)
use AsAction;
public function handle(Server $server)
{
$dockerVersion = '24.0';
$config = base64_encode('{

View File

@@ -2,22 +2,23 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\InstanceSettings;
use App\Models\Server;
class UpdateCoolify
{
use AsAction;
public ?Server $server = null;
public ?string $latestVersion = null;
public ?string $currentVersion = null;
public function __invoke(bool $force)
public function handle(bool $force)
{
try {
$settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob');
$localhost_name = 'localhost';
$this->server = Server::where('name', $localhost_name)->first();
$this->server = Server::find(0)->first();
if (!$this->server) {
return;
}

View File

@@ -4,29 +4,30 @@ namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
use Symfony\Component\Yaml\Yaml;
class StartService
{
use AsAction;
public function handle(Service $service)
{
$workdir = service_configuration_dir() . "/{$service->uuid}";
$network = $service->destination->network;
$service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir();
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo '####### Creating Docker network.'";
$commands[] = "docker network create --attachable {$service->uuid} >/dev/null 2>/dev/null || true";
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
$commands[] = "echo '####### Pulling images.'";
$commands[] = "mkdir -p $workdir";
$commands[] = "cd $workdir";
$docker_compose_base64 = base64_encode($service->docker_compose);
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
$envs = $service->environment_variables()->get();
$commands[] = "rm -f .env || true";
foreach ($envs as $env) {
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
}
$commands[] = "docker compose pull --quiet";
$commands[] = "docker compose pull";
$commands[] = "echo '####### Starting containers.'";
$commands[] = "docker compose up -d >/dev/null 2>&1";
$commands[] = "docker compose up -d --remove-orphans --force-recreate";
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || 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} 2>/dev/null || true";
}
$activity = remote_process($commands, $service->server);
return $activity;
}

View File

@@ -4,6 +4,7 @@ namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
use App\Notifications\Application\StatusChanged;
class StopService
{
@@ -21,5 +22,8 @@ class StopService
$db->update(['status' => 'exited']);
}
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
// TODO: make notification for databases
// $service->environment->project->team->notify(new StatusChanged($service));
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
class Cloud extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cloud:unused-servers';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get Unused Servers from Cloud';
/**
* Execute the console command.
*/
public function handle()
{
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
$this->info($server->name);
});
}
}

View File

@@ -56,6 +56,7 @@ class Emails extends Command
$type = select(
'Which Email should be sent?',
options: [
'updates' => 'Send Update Email to all users',
'emails-test' => 'Test',
'application-deployment-success' => 'Application - Deployment Success',
'application-deployment-failed' => 'Application - Deployment Failed',
@@ -69,7 +70,7 @@ class Emails extends Command
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
],
);
$emailsGathered = ['realusers-before-trial','realusers-server-lost-connection'];
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
if (!in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to');
}
@@ -78,6 +79,38 @@ class Emails extends Command
$this->mail = new MailMessage();
$this->mail->subject("Test Email");
switch ($type) {
case 'updates':
$teams = Team::all();
if (!$teams || $teams->isEmpty()) {
echo 'No teams found.' . PHP_EOL;
return;
}
$emails = [];
foreach ($teams as $team) {
foreach ($team->members as $member) {
if ($member->email && $member->marketing_emails) {
$emails[] = $member->email;
}
}
}
$emails = array_unique($emails);
$this->info("Sending to " . count($emails) . " emails.");
foreach ($emails as $email) {
$this->info($email);
}
$confirmed = confirm('Are you sure?');
if ($confirmed) {
foreach ($emails as $email) {
$this->mail = new MailMessage();
$this->mail->subject('One-click Services, Docker Compose support');
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
'token' => encrypt($email),
]);
$this->mail->view('emails.updates',["unsubscribeUrl" => $unsubscribeUrl]);
$this->sendEmail($email);
}
}
break;
case 'emails-test':
$this->mail = (new Test())->toMail();
$this->sendEmail();
@@ -141,20 +174,20 @@ class Emails extends Command
$this->mail = (new BackupSuccess($backup, $db))->toMail();
$this->sendEmail();
break;
// case 'invitation-link':
// $user = User::all()->first();
// $invitation = TeamInvitation::whereEmail($user->email)->first();
// if (!$invitation) {
// $invitation = TeamInvitation::create([
// 'uuid' => Str::uuid(),
// 'email' => $user->email,
// 'team_id' => 1,
// 'link' => 'http://example.com',
// ]);
// }
// $this->mail = (new InvitationLink($user))->toMail();
// $this->sendEmail();
// break;
// case 'invitation-link':
// $user = User::all()->first();
// $invitation = TeamInvitation::whereEmail($user->email)->first();
// if (!$invitation) {
// $invitation = TeamInvitation::create([
// 'uuid' => Str::uuid(),
// 'email' => $user->email,
// 'team_id' => 1,
// 'link' => 'http://example.com',
// ]);
// }
// $this->mail = (new InvitationLink($user))->toMail();
// $this->sendEmail();
// break;
case 'waitlist-invitation-link':
$this->mail = new MailMessage();
$this->mail->view('emails.waitlist-invitation', [

View File

@@ -0,0 +1,139 @@
<?php
namespace App\Console\Commands;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandalonePostgresql;
use Illuminate\Console\Command;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\multiselect;
use function Laravel\Prompts\select;
class ResourcesDelete extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'resources:delete';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Delete a resource from the database';
/**
* Execute the console command.
*/
public function handle()
{
$resource = select(
'What resource do you want to delete?',
['Application', 'Database', 'Service', 'Server'],
);
if ($resource === 'Application') {
$this->deleteApplication();
} elseif ($resource === 'Database') {
$this->deleteDatabase();
} elseif ($resource === 'Service') {
$this->deleteService();
} elseif ($resource === 'Server') {
$this->deleteServer();
}
}
private function deleteServer()
{
$servers = Server::all();
if ($servers->count() === 0) {
$this->error('There are no applications to delete.');
return;
}
$serversToDelete = multiselect(
label: 'What server do you want to delete?',
options: $servers->pluck('name', 'id')->sortKeys(),
);
foreach ($serversToDelete as $server) {
$toDelete = $servers->where('id', $server)->first();
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
break;
}
$toDelete->delete();
}
}
private function deleteApplication()
{
$applications = Application::all();
if ($applications->count() === 0) {
$this->error('There are no applications to delete.');
return;
}
$applicationsToDelete = multiselect(
'What application do you want to delete?',
$applications->pluck('name', 'id')->sortKeys(),
);
foreach ($applicationsToDelete as $application) {
ray($application);
$toDelete = $applications->where('id', $application)->first();
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
if (!$confirmed) {
break;
}
$toDelete->delete();
}
}
private function deleteDatabase()
{
$databases = StandalonePostgresql::all();
if ($databases->count() === 0) {
$this->error('There are no databases to delete.');
return;
}
$databasesToDelete = multiselect(
'What database do you want to delete?',
$databases->pluck('name', 'id')->sortKeys(),
);
foreach ($databasesToDelete as $database) {
$toDelete = $databases->where('id', $database)->first();
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$toDelete->delete();
}
}
private function deleteService()
{
$services = Service::all();
if ($services->count() === 0) {
$this->error('There are no services to delete.');
return;
}
$servicesToDelete = multiselect(
'What service do you want to delete?',
$services->pluck('name', 'id')->sortKeys(),
);
foreach ($servicesToDelete as $service) {
$toDelete = $services->where('id', $service)->first();
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$toDelete->delete();
}
}
}

View File

@@ -7,6 +7,8 @@ use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
use function Laravel\Prompts\confirm;
class SyncBunny extends Command
{
/**
@@ -14,7 +16,7 @@ class SyncBunny extends Command
*
* @var string
*/
protected $signature = 'sync:bunny';
protected $signature = 'sync:bunny {--only-template} {--only-version}';
/**
* The console command description.
@@ -28,6 +30,9 @@ class SyncBunny extends Command
*/
public function handle()
{
$that = $this;
$only_template = $this->option('only-template');
$only_version = $this->option('only-version');
$bunny_cdn = "https://cdn.coollabs.io";
$bunny_cdn_path = "coolify";
$bunny_cdn_storage_name = "coolcdn";
@@ -39,51 +44,71 @@ class SyncBunny extends Command
$install_script = "install.sh";
$upgrade_script = "upgrade.sh";
$production_env = ".env.production";
$service_template = "service-templates.json";
$versions = "versions.json";
PendingRequest::macro('storage', function ($file) {
PendingRequest::macro('storage', function ($fileName) use($that) {
$headers = [
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
'Accept' => 'application/json',
'Content-Type' => 'application/octet-stream'
];
$fileStream = fopen($file, "r");
$file = fread($fileStream, filesize($file));
$fileStream = fopen($fileName, "r");
$file = fread($fileStream, filesize($fileName));
$that->info('Uploading: ' . $fileName);
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
});
PendingRequest::macro('purge', function ($url) {
PendingRequest::macro('purge', function ($url) use ($that) {
$headers = [
'AccessKey' => env('BUNNY_API_KEY'),
'Accept' => 'application/json',
];
ray('Purging: ' . $url);
$that->info('Purging: ' . $url);
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
"url" => $url,
"async" => false
]);
});
try {
$confirmed = confirm('Are you sure you want to sync?');
if (!$confirmed) {
return;
}
if ($only_template) {
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
]);
$this->info('Service template uploaded & purged...');
return;
}
if ($only_version) {
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]);
$this->info('versions.json uploaded & purged...');
return;
}
Http::pool(fn (Pool $pool) => [
$pool->storage(file: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(file: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
$pool->storage(file: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
$pool->storage(file: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
$pool->storage(file: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
$pool->storage(file: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
$pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
$pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
$pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
]);
ray("{$bunny_cdn}/{$bunny_cdn_path}");
Http::pool(fn (Pool $pool) => [
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file_prod"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$production_env"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]);
echo "All files uploaded & purged...\n";
$this->info("All files uploaded & purged...");
} catch (\Throwable $e) {
echo $e->getMessage();
$this->error("Error: " . $e->getMessage());
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use function Laravel\Prompts\password;
class UsersResetRoot extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'users:reset-root';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Reset Root Password';
/**
* Execute the console command.
*/
public function handle()
{
//
$this->info('You are about to reset the root password.');
$password = password('Give me a new password for root user: ');
$passwordAgain = password('Again');
if ($password != $passwordAgain) {
$this->error('Passwords do not match.');
return;
}
$this->info('Updating root password...');
try {
User::find(0)->update(['password' => Hash::make($password)]);
$this->info('Root password updated successfully.');
} catch (\Exception $e) {
$this->error('Failed to update root password.');
return;
}
}
}

View File

@@ -27,21 +27,32 @@ class Kernel extends ConsoleKernel
// $this->instance_auto_update($schedule);
// $this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->cleanup_servers($schedule);
$this->check_scheduled_backups($schedule);
} else {
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->cleanup_servers($schedule);
}
}
private function cleanup_servers($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
foreach ($servers as $server) {
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
}
}
private function check_resources($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
ray($servers);
if (isCloud()) {
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
} else {
$servers = Server::all();
}
foreach ($servers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
}

View File

@@ -55,10 +55,12 @@ class Handler extends ExceptionHandler
}
app('sentry')->configureScope(
function (Scope $scope) {
$email = auth()?->user() ? auth()->user()->email : 'guest';
$instanceAdmin = User::find(0)->email ?? 'admin@localhost';
$scope->setUser(
[
'email' => auth()->user()->email,
'instanceAdmin' => User::find(0)->email
'email' => $email,
'instanceAdmin' => $instanceAdmin
]
);
}

View File

@@ -46,15 +46,6 @@ class Controller extends BaseController
}
return redirect()->route('login')->with('error', 'Invalid credentials.');
}
public function subscription()
{
if (!isCloud()) {
abort(404);
}
return view('subscription.index', [
'settings' => InstanceSettings::get(),
]);
}
public function license()
{

View File

@@ -19,7 +19,7 @@ class DatabaseController extends Controller
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
@@ -37,7 +37,7 @@ class DatabaseController extends Controller
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
@@ -64,10 +64,18 @@ class DatabaseController extends Controller
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
$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

@@ -2,8 +2,12 @@
namespace App\Http\Controllers;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandaloneDocker;
use Illuminate\Support\Str;
class ProjectController extends Controller
{
@@ -41,9 +45,10 @@ class ProjectController extends Controller
public function new()
{
$type = request()->query('type');
$services = getServiceTemplates();
$type = Str::of(request()->query('type'));
$destination_uuid = request()->query('destination');
$server = requesT()->query('server');
$server_id = request()->query('server_id');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
@@ -54,15 +59,85 @@ class ProjectController extends Controller
return redirect()->route('dashboard');
}
if (in_array($type, DATABASE_TYPES)) {
$standalone_postgresql = create_standalone_postgresql($environment->id, $destination_uuid);
if ($type->value() === "postgresql") {
$database = create_standalone_postgresql($environment->id, $destination_uuid);
} else if ($type->value() === 'redis') {
$database = create_standalone_redis($environment->id, $destination_uuid);
}
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $standalone_postgresql->uuid,
'database_uuid' => $database->uuid,
]);
}
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
$oneClickServiceName = $type->after('one-click-service-')->value();
$oneClickService = data_get($services, "$oneClickServiceName.compose");
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
if ($oneClickDotEnvs) {
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
}
if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
$service = Service::create([
'name' => "$oneClickServiceName-" . Str::random(10),
'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
$service->name = "$oneClickServiceName-" . $service->uuid;
$service->save();
if ($oneClickDotEnvs?->count() > 0) {
$oneClickDotEnvs->each(function ($value) use ($service) {
$key = Str::before($value, '=');
$value = Str::of(Str::after($value, '='));
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
// TODO: make it shared with Service.php
switch ($command->value()) {
case 'PASSWORD':
$generatedValue = Str::password(symbols: false);
break;
case 'PASSWORD_64':
$generatedValue = Str::password(length: 64, symbols: false);
break;
case 'BASE64_64':
$generatedValue = Str::random(64);
break;
case 'BASE64_128':
$generatedValue = Str::random(128);
break;
case 'BASE64':
$generatedValue = Str::random(32);
break;
case 'USER':
$generatedValue = Str::random(16);
break;
}
}
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'service_id' => $service->id,
'is_build_time' => false,
'is_preview' => false,
]);
});
}
$service->parse(isNew: true);
return redirect()->route('project.service', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
}
}
return view('project.new', [
'type' => $type
'type' => $type->value()
]);
}

View File

@@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\PrivateKey;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
class ServerController extends Controller
{
use AuthorizesRequests, ValidatesRequests;
public function new_server()
{
$privateKeys = PrivateKey::ownedByCurrentTeam()->get();
if (!isCloud()) {
return view('server.create', [
'limit_reached' => false,
'private_keys' => $privateKeys,
]);
}
$team = currentTeam();
$servers = $team->servers->count();
['serverLimit' => $serverLimit] = $team->limits;
$limit_reached = $servers >= $serverLimit;
return view('server.create', [
'limit_reached' => $limit_reached,
'private_keys' => $privateKeys,
]);
}
}

View File

@@ -38,8 +38,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CheckForcePasswordReset::class,
\App\Http\Middleware\IsSubscriptionValid::class,
\App\Http\Middleware\IsBoardingFlow::class,
\App\Http\Middleware\DecideWhatToDoWithUser::class,
],

View File

@@ -76,7 +76,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
Team::find(currentTeam()->id)->update([
'show_boarding' => false
]);
ray(currentTeam());
refreshSession();
return redirect()->route('dashboard');
}
@@ -129,6 +128,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function selectExistingPrivateKey()
{
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
$this->privateKey = $this->createdPrivateKey->private_key;
$this->currentState = 'create-server';
}
public function createNewServer()
@@ -164,7 +164,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{
$this->validate([
'remoteServerName' => 'required',
'remoteServerHost' => 'required',
'remoteServerHost' => 'required|ip',
'remoteServerPort' => 'required|integer',
'remoteServerUser' => 'required',
]);
@@ -220,7 +220,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function installDocker()
{
$this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->createdServer);
$activity = InstallDocker::run($this->createdServer);
$this->emit('newMonitorActivity', $activity->id);
}
public function dockerInstalledOrSkipped()

View File

@@ -9,21 +9,13 @@ use Livewire\Component;
class Dashboard extends Component
{
public int $projects = 0;
public int $servers = 0;
public int $s3s = 0;
public int $resources = 0;
public $projects = [];
public $servers = [];
public function mount()
{
$this->servers = Server::ownedByCurrentTeam()->get()->count();
$this->s3s = S3Storage::ownedByCurrentTeam()->get()->count();
$projects = Project::ownedByCurrentTeam()->get();
foreach ($projects as $project) {
$this->resources += $project->applications->count();
$this->resources += $project->postgresqls->count();
}
$this->projects = $projects->count();
$this->servers = Server::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
}
// public function getIptables()
// {

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Dev;
use Livewire\Component;
class Compose extends Component
{
public string $compose = '';
public string $base64 = '';
public $services;
public function mount() {
$this->services = getServiceTemplates();
}
public function setService(string $selected) {
$this->base64 = data_get($this->services, $selected . '.compose');
if ($this->base64) {
$this->compose = base64_decode($this->base64);
}
}
public function updatedCompose($value) {
$this->base64 = base64_encode($value);
}
public function render()
{
return view('livewire.dev.compose');
}
}

View File

@@ -4,8 +4,8 @@ namespace App\Http\Livewire;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
use Route;
class Help extends Component
{
@@ -28,7 +28,7 @@ class Help extends Component
public function submit()
{
try {
$this->rateLimit(1, 60);
$this->rateLimit(3, 60);
$this->validate();
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
$debug = "Route: {$this->path}";
@@ -42,7 +42,7 @@ class Help extends Component
);
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
$this->emit('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -60,12 +60,16 @@ class DeploymentNavbar extends Component
$previous_logs[] = $new_log_entry;
$this->application_deployment_queue->update([
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
'current_process_id' => null,
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
]);
}
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->application_deployment_queue->update([
'current_process_id' => null,
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
]);
queue_next_deployment($this->application);
}
}
}

View File

@@ -3,12 +3,9 @@
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use App\Models\InstanceSettings;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Component;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
class General extends Component
{
@@ -17,14 +14,15 @@ class General extends Component
public Application $application;
public Collection $services;
public string $name;
public string|null $fqdn;
public ?string $fqdn = null;
public string $git_repository;
public string $git_branch;
public string|null $git_commit_sha;
public ?string $git_commit_sha = null;
public string $build_pack;
public string|null $wildcard_domain = null;
public string|null $server_wildcard_domain = null;
public string|null $global_wildcard_domain = null;
public $customLabels;
public bool $labelsChanged = false;
public bool $isConfigurationChanged = false;
public bool $is_static;
public bool $is_git_submodules_enabled;
@@ -52,6 +50,10 @@ class General extends Component
'application.ports_exposes' => 'required',
'application.ports_mappings' => 'nullable',
'application.dockerfile' => 'nullable',
'application.docker_registry_image_name' => 'nullable',
'application.docker_registry_image_tag' => 'nullable',
'application.dockerfile_location' => 'nullable',
'application.custom_labels' => 'nullable',
];
protected $validationAttributes = [
'application.name' => 'name',
@@ -70,9 +72,50 @@ class General extends Component
'application.ports_exposes' => 'Ports exposes',
'application.ports_mappings' => 'Ports mappings',
'application.dockerfile' => 'Dockerfile',
'application.docker_registry_image_name' => 'Docker registry image name',
'application.docker_registry_image_tag' => 'Docker registry image tag',
'application.dockerfile_location' => 'Dockerfile location',
'application.custom_labels' => 'Custom labels',
];
public function mount()
{
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
$this->application->isConfigurationChanged(true);
}
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
if (is_null(data_get($this->application, 'custom_labels'))) {
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
} else {
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
}
if (data_get($this->application, 'settings')) {
$this->is_static = $this->application->settings->is_static;
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
}
$this->checkLabelUpdates();
}
public function updatedApplicationBuildPack()
{
if ($this->application->build_pack !== 'nixpacks') {
$this->application->settings->is_static = $this->is_static = false;
$this->application->settings->save();
}
$this->submit();
}
public function checkLabelUpdates()
{
if (md5($this->application->custom_labels) !== md5(implode(",", generateLabelsApplication($this->application)))) {
$this->labelsChanged = true;
} else {
$this->labelsChanged = false;
}
}
public function instantSave()
{
// @TODO: find another way - if possible
@@ -91,58 +134,42 @@ class General extends Component
$this->application->settings->save();
$this->application->save();
$this->application->refresh();
$this->checkWildCardDomain();
$this->emit('success', 'Application settings updated!');
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
}
protected function checkWildCardDomain()
public function getWildcardDomain()
{
$coolify_instance_settings = InstanceSettings::get();
$this->server_wildcard_domain = data_get($this->application, 'destination.server.settings.wildcard_domain');
$this->global_wildcard_domain = data_get($coolify_instance_settings, 'wildcard_domain');
$this->wildcard_domain = $this->server_wildcard_domain ?? $this->global_wildcard_domain ?? null;
$server = data_get($this->application, 'destination.server');
if ($server) {
$fqdn = generateFqdn($server, $this->application->uuid);
$this->application->fqdn = $fqdn;
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
}
public function mount()
public function resetDefaultLabels($showToaster = true)
{
$this->is_static = $this->application->settings->is_static;
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
$this->checkWildCardDomain();
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
$this->submit($showToaster);
}
public function generateGlobalRandomDomain()
public function updatedApplicationFqdn()
{
// Set wildcard domain based on Global wildcard domain
$url = Url::fromString($this->global_wildcard_domain);
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path;
$this->application->save();
$this->emit('success', 'Application settings updated!');
$this->resetDefaultLabels(false);
$this->emit('success', 'Labels reseted to default!');
}
public function generateServerRandomDomain()
{
// Set wildcard domain based on Server wildcard domain
$url = Url::fromString($this->server_wildcard_domain);
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path;
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
public function submit()
public function submit($showToaster = true)
{
try {
$this->validate();
if (data_get($this->application, 'build_pack') === 'dockerimage') {
$this->validate([
'application.docker_registry_image_name' => 'required',
'application.docker_registry_image_tag' => 'required',
]);
}
if (data_get($this->application, 'fqdn')) {
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return Str::of($domain)->trim()->lower();
@@ -151,7 +178,7 @@ class General extends Component
}
if (data_get($this->application, 'dockerfile')) {
$port = get_port_from_dockerfile($this->application->dockerfile);
if ($port) {
if ($port && !$this->application->ports_exposes) {
$this->application->ports_exposes = $port;
}
}
@@ -161,10 +188,17 @@ class General extends Component
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
}
if (gettype($this->customLabels) === 'string') {
$this->customLabels = str($this->customLabels)->replace(',', "\n");
}
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
$this->application->save();
$this->emit('success', 'Application settings updated!');
$showToaster && $this->emit('success', 'Application settings updated!');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
}
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Jobs\ContainerStatusJob;
use App\Models\Application;
use Livewire\Component;
@@ -21,11 +22,13 @@ class Heading extends Component
public function check_status()
{
dispatch(new ContainerStatusJob($this->application->destination->server));
$this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
if ($this->application->destination->server->isFunctional()) {
dispatch(new ContainerStatusJob($this->application->destination->server));
$this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
}
}
public function force_deploy_without_cache()
@@ -57,22 +60,9 @@ class Heading extends Component
public function stop()
{
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
if ($containers->count() === 0) {
return;
}
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(
["docker rm -f {$containerName}"],
$this->application->destination->server
);
$this->application->status = 'exited';
$this->application->save();
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
}
}
StopApplication::run($this->application);
$this->application->status = 'exited';
$this->application->save();
$this->application->refresh();
}
}

View File

@@ -29,7 +29,8 @@ class Form extends Component
public function generate_real_url()
{
if (data_get($this->application, 'fqdn')) {
$url = Url::fromString($this->application->fqdn);
$firstFqdn = Str::of($this->application->fqdn)->before(',');
$url = Url::fromString($firstFqdn);
$host = $url->getHost();
$this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host);
}

View File

@@ -25,7 +25,7 @@ class Previews extends Component
public function load_prs()
{
try {
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
$this->rate_limit_remaining = $rate_limit_remaining;
$this->pull_requests = $data->sortBy('number')->values();
} catch (\Throwable $e) {
@@ -72,8 +72,7 @@ class Previews extends Component
public function stop(int $pull_request_id)
{
try {
$container_name = generateApplicationContainerName($this->application);
ray('Stopping container: ' . $container_name);
$container_name = generateApplicationContainerName($this->application, $pull_request_id);
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();

View File

@@ -8,6 +8,7 @@ class BackupEdit extends Component
{
public $backup;
public $s3s;
public ?string $status = null;
public array $parameters;
protected $rules = [
@@ -16,6 +17,7 @@ class BackupEdit extends Component
'backup.number_of_backups_locally' => 'required|integer|min:1',
'backup.save_s3' => 'required|boolean',
'backup.s3_storage_id' => 'nullable|integer',
'backup.databases_to_backup' => 'nullable',
];
protected $validationAttributes = [
'backup.enabled' => 'Enabled',
@@ -23,6 +25,7 @@ class BackupEdit extends Component
'backup.number_of_backups_locally' => 'Number of Backups Locally',
'backup.save_s3' => 'Save to S3',
'backup.s3_storage_id' => 'S3 Storage',
'backup.databases_to_backup' => 'Databases to Backup',
];
protected $messages = [
'backup.s3_storage_id' => 'Select a S3 Storage',
@@ -36,7 +39,6 @@ class BackupEdit extends Component
}
}
public function delete()
{
// TODO: Delete backup from server and add a confirmation modal
@@ -48,6 +50,7 @@ class BackupEdit extends Component
{
try {
$this->custom_validate();
$this->backup->save();
$this->backup->refresh();
$this->emit('success', 'Backup updated successfully');
@@ -70,9 +73,11 @@ class BackupEdit extends Component
public function submit()
{
ray($this->backup->s3_storage_id);
try {
$this->custom_validate();
if ($this->backup->databases_to_backup == '' || $this->backup->databases_to_backup === null) {
$this->backup->databases_to_backup = null;
}
$this->backup->save();
$this->backup->refresh();
$this->emit('success', 'Backup updated successfully');

View File

@@ -13,6 +13,6 @@ class BackupNow extends Component
dispatch(new DatabaseBackupJob(
backup: $this->backup
));
$this->emit('success', 'Backup queued. It will be available in a few minutes');
$this->emit('success', 'Backup queued. It will be available in a few minutes.');
}
}

View File

@@ -10,7 +10,7 @@ class CreateScheduledBackup extends Component
public $database;
public $frequency;
public bool $enabled = true;
public bool $save_s3 = true;
public bool $save_s3 = false;
public $s3_storage_id;
public $s3s;
@@ -32,7 +32,7 @@ class CreateScheduledBackup extends Component
$this->emit('error', 'Invalid Cron / Human expression.');
return;
}
ScheduledDatabaseBackup::create([
$payload = [
'enabled' => true,
'frequency' => $this->frequency,
'save_s3' => $this->save_s3,
@@ -40,7 +40,11 @@ class CreateScheduledBackup extends Component
'database_id' => $this->database->id,
'database_type' => $this->database->getMorphClass(),
'team_id' => currentTeam()->id,
]);
];
if ($this->database->type() === 'standalone-postgresql') {
$payload['databases_to_backup'] = $this->database->postgres_db;
}
ScheduledDatabaseBackup::create($payload);
$this->emit('refreshScheduledBackups');
} catch (\Throwable $e) {
handleError($e, $this);

View File

@@ -3,6 +3,8 @@
namespace App\Http\Livewire\Project\Database;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Database\StopDatabase;
use App\Jobs\ContainerStatusJob;
use Livewire\Component;
@@ -35,24 +37,20 @@ class Heading extends Component
public function stop()
{
instant_remote_process(
["docker rm -f {$this->database->uuid}"],
$this->database->destination->server
);
if ($this->database->is_public) {
stopPostgresProxy($this->database);
$this->database->is_public = false;
}
StopDatabase::run($this->database);
$this->database->status = 'exited';
$this->database->save();
$this->check_status();
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
}
public function start()
{
if ($this->database->type() === 'standalone-postgresql') {
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database);
$activity = StartPostgresql::run($this->database->destination->server, $this->database);
$this->emit('newMonitorActivity', $activity->id);
}
if ($this->database->type() === 'standalone-redis') {
$activity = StartRedis::run($this->database->destination->server, $this->database);
$this->emit('newMonitorActivity', $activity->id);
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Http\Livewire\Project\Database\Postgresql;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandalonePostgresql;
use Exception;
use Livewire\Component;
@@ -50,8 +52,9 @@ class General extends Component
$this->getDbUrl();
}
public function getDbUrl() {
if ($this->database->is_public) {
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->ip}:{$this->database->public_port}/{$this->database->postgres_db}";
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/{$this->database->postgres_db}";
} else {
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
}
@@ -66,10 +69,10 @@ class General extends Component
}
if ($this->database->is_public) {
$this->emit('success', 'Starting TCP proxy...');
startPostgresProxy($this->database);
StartDatabaseProxy::run($this->database);
$this->emit('success', 'Database is now publicly accessible.');
} else {
stopPostgresProxy($this->database);
StopDatabaseProxy::run($this->database);
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->getDbUrl();

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Http\Livewire\Project\Database\Redis;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandaloneRedis;
use Exception;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public StandaloneRedis $database;
public string $db_url;
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.redis_conf' => 'nullable',
'database.redis_password' => 'required',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
];
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.redis_conf' => 'Redis Configuration',
'database.redis_password' => 'Redis Password',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
];
public function submit() {
try {
$this->validate();
if ($this->database->redis_conf === "") {
$this->database->redis_conf = null;
}
$this->database->save();
$this->emit('success', 'Database updated successfully.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSave()
{
try {
if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.');
$this->database->is_public = false;
return;
}
if ($this->database->is_public) {
$this->emit('success', 'Starting TCP proxy...');
StartDatabaseProxy::run($this->database);
$this->emit('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->getDbUrl();
$this->database->save();
} catch(\Throwable $e) {
$this->database->is_public = !$this->database->is_public;
return handleError($e, $this);
}
}
public function refresh(): void
{
$this->database->refresh();
}
public function mount()
{
$this->getDbUrl();
}
public function getDbUrl() {
if ($this->database->is_public) {
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/0";
} else {
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->uuid}:6379/0";
}
}
public function render()
{
return view('livewire.project.database.redis.general');
}
}

View File

@@ -2,73 +2,140 @@
namespace App\Http\Livewire\Project\New;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Service;
use Livewire\Component;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class DockerCompose extends Component
{
public string $dockercompose = '';
public string $dockerComposeRaw = '';
public string $envFile = '';
public array $parameters;
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (isDev()) {
$this->dockercompose = 'services:
ghost:
documentation: https://ghost.org/docs/config
image: ghost:5
volumes:
- ghost-content-data:/var/lib/ghost/content
environment:
- url=$SERVICE_FQDN_GHOST
- database__client=mysql
- database__connection__host=mysql
- database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL
- database__connection__database=${MYSQL_DATABASE-ghost}
depends_on:
- mysql
mysql:
documentation: https://hub.docker.com/_/mysql
image: mysql:8.0
volumes:
- ghost-mysql-data:/var/lib/mysql
environment:
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
';
$this->dockerComposeRaw = 'services:
ghost:
image: ghost:5
volumes:
- ~/configs:/etc/configs/:ro
- ./var/lib/ghost/content:/tmp/ghost2/content:ro
- /var/lib/ghost/content:/tmp/ghost/content:rw
- ghost-content-data:/var/lib/ghost/content
- type: volume
source: mydata
target: /data
- type: bind
source: ./var/lib/ghost/data
target: /data
- type: bind
source: /tmp
target: /tmp
labels:
- "test.label=true"
ports:
- "3000"
- "3000-3005"
- "8000:8000"
- "9090-9091:8080-8081"
- "49100:22"
- "127.0.0.1:8001:8001"
- "127.0.0.1:5000-5010:5000-5010"
- "127.0.0.1::5000"
- "6060:6060/udp"
- "12400-12500:1240"
- target: 80
published: 8080
protocol: tcp
mode: host
networks:
- some-network
- other-network
environment:
- database__client=${DATABASE_CLIENT:-mysql}
- database__connection__database=${MYSQL_DATABASE:-ghost}
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
- test=${TEST:?true}
- url=$SERVICE_FQDN_GHOST
- database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL
depends_on:
- mysql
mysql:
image: mysql:8.0
volumes:
- ghost-mysql-data:/var/lib/mysql
environment:
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=$MYSQL_DATABASE
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
- SESSION_SECRET
minio:
image: minio/minio
environment:
RACK_ENV: development
A: $A
SHOW: ${SHOW}
SHOW1: ${SHOW2-show1}
SHOW2: ${SHOW3:-show2}
SHOW3: ${SHOW4?show3}
SHOW4: ${SHOW5:?show4}
SHOW5: ${SERVICE_USER_MINIO}
SHOW6: ${SERVICE_PASSWORD_MINIO}
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
SHOW8: ${SERVICE_BASE64_64_MINIO}
SHOW9: ${SERVICE_BASE64_128_MINIO}
SHOW10: ${SERVICE_BASE64_MINIO}
SHOW11:
';
}
}
public function submit()
{
$this->validate([
'dockercompose' => 'required'
]);
$server_id = $this->query['server_id'];
try {
$this->validate([
'dockerComposeRaw' => 'required'
]);
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$server_id = $this->query['server_id'];
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$service = Service::create([
'name' => 'service' . Str::random(10),
'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
$variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $variable) {
EnvironmentVariable::create([
'key' => $key,
'value' => $variable,
'is_build_time' => false,
'is_preview' => false,
'service_id' => $service->id,
]);
}
$service->name = "service-$service->uuid";
$service = Service::create([
'name' => 'service' . Str::random(10),
'docker_compose_raw' => $this->dockercompose,
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
$service->name = "service-$service->uuid";
$service->parse(isNew: true);
$service->parse(isNew: true);
return redirect()->route('project.service', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
return redirect()->route('project.service', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Http\Livewire\Project\New;
use App\Models\Application;
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class DockerImage extends Component
{
public string $dockerImage = '';
public array $parameters;
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
}
public function submit()
{
$this->validate([
'dockerImage' => 'required'
]);
$image = Str::of($this->dockerImage)->before(':');
if (Str::of($this->dockerImage)->contains(':')) {
$tag = Str::of($this->dockerImage)->after(':');
} else {
$tag = 'latest';
}
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (!$destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
}
if (!$destination) {
throw new \Exception('Destination not found. What?!');
}
$destination_class = $destination->getMorphClass();
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
ray($image,$tag);
$application = Application::create([
'name' => 'docker-image-' . new Cuid2(7),
'repository_project_id' => 0,
'git_repository' => "coollabsio/coolify",
'git_branch' => 'main',
'build_pack' => 'dockerimage',
'ports_exposes' => 80,
'docker_registry_image_name' => $image,
'docker_registry_image_tag' => $tag,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'health_check_enabled' => false,
]);
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->update([
'name' => 'docker-image-' . $application->uuid,
'fqdn' => $fqdn
]);
redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
}
public function render()
{
return view('livewire.project.new.docker-image');
}
}

View File

@@ -11,6 +11,7 @@ use App\Traits\SaveFromRedirect;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
use Spatie\Url\Url;
class GithubPrivateRepository extends Component
{
@@ -95,6 +96,7 @@ class GithubPrivateRepository extends Component
$this->loadBranchByPage();
}
}
$this->selected_branch_name = data_get($this->branches,'0.name');
}
protected function loadBranchByPage()
@@ -144,10 +146,9 @@ class GithubPrivateRepository extends Component
$application->settings->is_static = $this->is_static;
$application->settings->save();
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
if (isDev()) {
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
$application->save();

View File

@@ -11,6 +11,7 @@ use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Livewire\Component;
use Spatie\Url\Url;
use Illuminate\Support\Str;
class GithubPrivateRepositoryDeployKey extends Component
{
@@ -29,7 +30,7 @@ class GithubPrivateRepositoryDeployKey extends Component
public string $repository_url;
public string $branch;
protected $rules = [
'repository_url' => 'required|url',
'repository_url' => 'required',
'branch' => 'required|string',
'port' => 'required|numeric',
'is_static' => 'required|boolean',
@@ -43,10 +44,9 @@ class GithubPrivateRepositoryDeployKey extends Component
'publish_directory' => 'Publish directory',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp|null $git_source = null;
private string $git_host;
private GithubApp|GitlabApp|string $git_source = 'other';
private ?string $git_host = null;
private string $git_repository;
private string $git_branch;
public function mount()
{
@@ -93,29 +93,42 @@ class GithubPrivateRepositoryDeployKey extends Component
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'git_full_url' => "git@$this->git_host:$this->git_repository.git",
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
if ($this->git_source === 'other') {
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id,
];
} else {
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;
$application->settings->save();
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
if (isDev()) {
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_random_name($application->uuid);
$application->save();
@@ -134,18 +147,18 @@ class GithubPrivateRepositoryDeployKey extends Component
$this->repository_url_parsed = Url::fromString($this->repository_url);
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
if ($this->branch) {
$this->git_branch = $this->branch;
} else {
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
}
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
} elseif ($this->git_host == 'gitlab.com') {
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
} elseif ($this->git_host == 'bitbucket.org') {
// Not supported yet
return;
}
if (Str::of($this->repository_url)->startsWith('http')) {
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
$this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git');
} else {
$this->git_repository = $this->repository_url;
}
$this->git_source = 'other';
}
}

View File

@@ -26,6 +26,11 @@ class PublicGitRepository extends Component
public string $git_branch = 'main';
public int $rate_limit_remaining = 0;
public $rate_limit_reset = 0;
private object $repository_url_parsed;
public GithubApp|GitlabApp|string $git_source = 'other';
public string $git_host;
public string $git_repository;
protected $rules = [
'repository_url' => 'required|url',
'port' => 'required|numeric',
@@ -38,10 +43,6 @@ class PublicGitRepository extends Component
'is_static' => 'static',
'publish_directory' => 'publish directory',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp|null $git_source = null;
private string $git_host;
private string $git_repository;
public function mount()
{
@@ -64,25 +65,34 @@ class PublicGitRepository extends Component
}
$this->emit('success', 'Application settings updated!');
}
public function load_any_git()
{
$this->branch_found = true;
}
public function load_branch()
{
try {
$this->branch_found = false;
$this->validate([
'repository_url' => 'required|url'
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
try {
$this->branch_found = false;
$this->get_git_source();
$this->get_branch();
$this->selected_branch = $this->git_branch;
} catch (\Throwable $e) {
return handleError($e, $this);
}
if (!$this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
$this->get_branch();
} catch (\Throwable $e) {
ray($e->getMessage());
if (!$this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
$this->get_branch();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} else {
return handleError($e, $this);
}
}
@@ -97,21 +107,23 @@ class PublicGitRepository extends Component
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
} elseif ($this->git_host == 'gitlab.com') {
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
} elseif ($this->git_host == 'bitbucket.org') {
// Not supported yet
}
if (is_null($this->git_source)) {
throw new \Exception('Git source not found. What?!');
return;
}
$this->git_repository = $this->repository_url;
$this->git_source = 'other';
}
private function get_branch()
{
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branch_found = true;
if ($this->git_source === 'other') {
$this->branch_found = true;
return;
}
if ($this->git_source->getMorphClass() === 'App\Models\GithubApp') {
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branch_found = true;
}
}
public function submit()
@@ -122,9 +134,6 @@ class PublicGitRepository extends Component
$project_uuid = $this->parameters['project_uuid'];
$environment_name = $this->parameters['environment_name'];
$this->get_git_source();
$this->git_branch = $this->selected_branch ?? $this->git_branch;
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (!$destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
@@ -137,30 +146,42 @@ class PublicGitRepository extends Component
$project = Project::where('uuid', $project_uuid)->first();
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
$application_init = [
'name' => generate_application_name($this->git_repository, $this->git_branch),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
if ($this->git_source === 'other') {
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
];
} else {
$application_init = [
'name' => generate_application_name($this->git_repository, $this->git_branch),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;
$application->settings->save();
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
if (isDev()) {
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->save();
return redirect()->route('project.application.configuration', [

View File

@@ -2,13 +2,12 @@
namespace App\Http\Livewire\Project\New;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Countable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
use Route;
class Select extends Component
{
@@ -21,20 +20,35 @@ class Select extends Component
public Collection|array $standaloneDockers = [];
public Collection|array $swarmDockers = [];
public array $parameters;
public Collection|array $services = [];
public bool $loadingServices = true;
public bool $loading = false;
public $environments = [];
public ?string $selectedEnvironment = null;
public ?string $existingPostgresqlUrl = null;
protected $queryString = [
'server',
];
public function mount()
{
$this->parameters = get_route_parameters();
if (isDev()) {
$this->existingPostgresqlUrl = 'postgres://coolify:password@coolify-db:5432';
}
$projectUuid = data_get($this->parameters, 'project_uuid');
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
}
public function updatedSelectedEnvironment()
{
return redirect()->route('project.resources.new', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->selectedEnvironment,
]);
}
// public function addExistingPostgresql()
// {
// try {
@@ -44,9 +58,31 @@ class Select extends Component
// return handleError($e, $this);
// }
// }
public function loadThings()
{
$this->loadServices();
$this->loadServers();
}
public function loadServices(bool $forceReload = false)
{
try {
if ($forceReload) {
Cache::forget('services');
}
$this->services = getServiceTemplates();
$this->emit('success', 'Successfully loaded services.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->loadingServices = false;
}
}
public function setType(string $type)
{
$this->type = $type;
if ($this->loading) return;
$this->loading = true;
if ($type === "existing-postgresql") {
$this->current_step = $type;
return;
@@ -87,7 +123,7 @@ class Select extends Component
]);
}
public function load_servers()
public function loadServers()
{
$this->servers = Server::isUsable()->get();
}

View File

@@ -45,6 +45,9 @@ CMD ["nginx", "-g", "daemon off;"]
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$port = get_port_from_dockerfile($this->dockerfile);
if (!$port) {
$port = 80;
}
$application = Application::create([
'name' => 'dockerfile-' . new Cuid2(7),
'repository_project_id' => 0,
@@ -56,14 +59,12 @@ CMD ["nginx", "-g", "daemon off;"]
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'health_check_enabled' => false,
'source_id' => 0,
'source_type' => GithubApp::class
]);
$fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
if (isDev()) {
$fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->update([
'name' => 'dockerfile-' . $application->uuid,
'fqdn' => $fqdn

View File

@@ -8,23 +8,47 @@ use Livewire\Component;
class Application extends Component
{
public ServiceApplication $application;
public $parameters;
protected $rules = [
'application.human_name' => 'nullable',
'application.description' => 'nullable',
'application.fqdn' => 'nullable',
'application.image' => 'required',
'application.exclude_from_status' => 'required|boolean',
'application.required_fqdn' => 'required|boolean',
];
public function render()
{
ray($this->application->fileStorages()->get());
return view('livewire.project.service.application');
}
public function instantSave()
{
$this->submit();
}
public function delete()
{
try {
$this->application->delete();
$this->emit('success', 'Application deleted successfully.');
return redirect()->route('project.service', $this->parameters);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->parameters = get_route_parameters();
}
public function submit()
{
try {
$this->validate();
$this->application->save();
updateCompose($this->application);
$this->emit('success', 'Application saved successfully.');
} catch (\Throwable $e) {
ray($e);
return handleError($e, $this);
} finally {
$this->emit('generateDockerCompose');
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Livewire\Project\Service;
use Livewire\Component;
class ComposeModal extends Component
{
public string $raw;
public string $actual;
public function render()
{
return view('livewire.project.service.compose-modal');
}
public function submit() {
$this->emit('warning', "Saving new docker compose...");
$this->emit('saveCompose', $this->raw);
}
}

View File

@@ -2,26 +2,41 @@
namespace App\Http\Livewire\Project\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Livewire\Component;
class Database extends Component
{
public ServiceDatabase $database;
public $fileStorages;
protected $listeners = ["refreshFileStorages"];
protected $rules = [
'database.human_name' => 'nullable',
'database.description' => 'nullable',
'database.image' => 'required',
'database.exclude_from_status' => 'required|boolean',
];
public function render()
{
return view('livewire.project.service.database');
}
public function mount() {
$this->refreshFileStorages();
}
public function instantSave() {
$this->submit();
}
public function refreshFileStorages()
{
$this->fileStorages = $this->database->fileStorages()->get();
}
public function submit()
{
try {
$this->validate();
$this->database->save();
updateCompose($this->database);
$this->emit('success', 'Database saved successfully.');
} catch (\Throwable $e) {
ray($e);
} finally {

View File

@@ -3,17 +3,56 @@
namespace App\Http\Livewire\Project\Service;
use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Livewire\Component;
use Illuminate\Support\Str;
class FileStorage extends Component
{
public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase $service;
public string $fs_path;
public ?string $workdir = null;
protected $rules = [
'fileStorage.is_directory' => 'required',
'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required',
'fileStorage.content' => 'nullable',
];
public function mount()
{
$this->service = $this->fileStorage->service;
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
$this->workdir = $this->service->service->workdir();
$this->fs_path = Str::of($this->fileStorage->fs_path)->after('.');
} else {
$this->workdir = null;
$this->fs_path = $this->fileStorage->fs_path;
}
}
public function submit()
{
$original = $this->fileStorage->getOriginal();
try {
$this->validate();
if ($this->fileStorage->is_directory) {
$this->fileStorage->content = null;
}
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer($this->service);
$this->emit('success', 'File updated successfully.');
} catch (\Throwable $e) {
$this->fileStorage->setRawAttributes($original);
$this->fileStorage->save();
return handleError($e, $this);
}
}
public function instantSave()
{
$this->submit();
}
public function render()
{
return view('livewire.project.service.file-storage');

View File

@@ -2,45 +2,44 @@
namespace App\Http\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use Livewire\Component;
class Index extends Component
{
public Service $service;
public $applications;
public $databases;
public array $parameters;
public array $query;
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
'service.name' => 'required',
'service.description' => 'required',
];
protected $listeners = ["refreshStacks","checkStatus"];
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 render()
public function checkStatus()
{
return view('livewire.project.service.index');
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStacks();
}
public function save() {
$this->service->save();
$this->service->parse();
$this->service->refresh();
$this->emit('refreshEnvs');
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();
});
}
public function submit() {
try {
$this->validate();
$this->service->save();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -6,8 +6,8 @@ use Livewire\Component;
class Modal extends Component
{
public function serviceStatusUpdated() {
$this->emit('serviceStatusUpdated');
public function checkStatus() {
$this->emit('checkStatus');
}
public function render()
{

View File

@@ -13,21 +13,15 @@ class Navbar extends Component
public Service $service;
public array $parameters;
public array $query;
protected $listeners = ['serviceStatusUpdated'];
public function render()
{
return view('livewire.project.service.navbar');
}
public function serviceStatusUpdated()
public function checkStatus()
{
ray('serviceStatusUpdated');
$this->check_status();
}
public function check_status()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->service->refresh();
$this->emit('checkStatus');
}
public function deploy()
{
@@ -39,5 +33,7 @@ class Navbar extends Component
{
StopService::run($this->service);
$this->service->refresh();
$this->emit('success', 'Service stopped successfully.');
$this->checkStatus();
}
}

View File

@@ -11,8 +11,8 @@ use Livewire\Component;
class Show extends Component
{
public Service $service;
public ServiceApplication $serviceApplication;
public ServiceDatabase $serviceDatabase;
public ?ServiceApplication $serviceApplication = null;
public ?ServiceDatabase $serviceDatabase = null;
public array $parameters;
public array $query;
public Collection $services;
@@ -20,16 +20,23 @@ class Show extends Component
public function mount()
{
$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;
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
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();
}
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function generateDockerCompose()
{

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Livewire\Project\Service;
use Livewire\Component;
class StackForm extends Component
{
public $service;
protected $listeners = ["saveCompose"];
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
'service.name' => 'required',
'service.description' => 'nullable',
];
public function saveCompose($raw)
{
$this->service->docker_compose_raw = $raw;
$this->submit();
}
public function submit()
{
try {
$this->validate();
$this->service->save();
$this->service->parse();
$this->service->refresh();
$this->service->saveComposeConfigs();
$this->emit('refreshStacks');
$this->emit('refreshEnvs');
$this->emit('success', 'Service saved successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.service.stack-form');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Models\LocalPersistentVolume;
use Livewire\Component;
class Storage extends Component
{
protected $listeners = ['addNewVolume'];
public $resource;
public function render()
{
return view('livewire.project.service.storage');
}
public function addNewVolume($data)
{
try {
LocalPersistentVolume::create([
'name' => $data['name'],
'mount_path' => $data['mount_path'],
'host_path' => $data['host_path'],
'resource_id' => $this->resource->id,
'resource_type' => $this->resource->getMorphClass(),
]);
$this->resource->refresh();
$this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage');
$this->emit('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Http\Livewire\Project\Shared;
use App\Actions\Service\StopService;
use App\Jobs\StopResourceJob;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -10,7 +10,7 @@ class Danger extends Component
{
public $resource;
public array $parameters;
public string|null $modalId = null;
public ?string $modalId = null;
public function mount()
{
@@ -21,18 +21,7 @@ class Danger extends Component
public function delete()
{
try {
if ($this->resource->type() === 'service') {
$server = $this->resource->server;
StopService::run($this->resource);
} else {
$destination = data_get($this->resource, 'destination');
if ($destination) {
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
$server = $destination->server;
}
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
}
$this->resource->delete();
StopResourceJob::dispatchSync($this->resource);
return redirect()->route('project.resources', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name']

View File

@@ -9,13 +9,13 @@ class Add extends Component
public $parameters;
public bool $is_preview = false;
public string $key;
public string $value;
public ?string $value = null;
public bool $is_build_time = false;
protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [
'key' => 'required|string',
'value' => 'required|string',
'value' => 'nullable',
'is_build_time' => 'required|boolean',
];
protected $validationAttributes = [
@@ -32,6 +32,7 @@ class Add extends Component
public function submit()
{
$this->validate();
ray($this->key, $this->value, $this->is_build_time);
$this->emitUp('submit', [
'key' => $this->key,
'value' => $this->value,

View File

@@ -53,7 +53,6 @@ class All extends Component
$this->resource->environment_variables_preview()->delete();
} else {
$variables = parseEnvFormatToArray($this->variables);
ray($variables);
$existingVariables = $this->resource->environment_variables();
$this->resource->environment_variables()->delete();
}
@@ -76,6 +75,9 @@ 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 'service':
$environment->service_id = $this->resource->id;
break;
@@ -110,11 +112,16 @@ class All extends Component
$environment->is_build_time = $data['is_build_time'];
$environment->is_preview = $data['is_preview'];
if ($this->resource->type() === 'application') {
$environment->application_id = $this->resource->id;
}
if ($this->resource->type() === 'standalone-postgresql') {
$environment->standalone_postgresql_id = $this->resource->id;
switch ($this->resource->type()) {
case 'application':
$environment->application_id = $this->resource->id;
break;
case 'standalone-postgresql':
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;
}
$environment->save();
$this->refreshEnvs();

View File

@@ -5,17 +5,19 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class Show extends Component
{
public $parameters;
public ModelsEnvironmentVariable $env;
public ?string $modalId = null;
public bool $isDisabled = false;
public string $type;
protected $rules = [
'env.key' => 'required|string',
'env.value' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
];
protected $validationAttributes = [
@@ -26,6 +28,10 @@ class Show extends Component
public function mount()
{
$this->isDisabled = false;
if (Str::of($this->env->key)->startsWith('SERVICE_FQDN') || Str::of($this->env->key)->startsWith('SERVICE_URL')) {
$this->isDisabled = true;
}
$this->modalId = new Cuid2(7);
$this->parameters = get_route_parameters();
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use App\Models\Server;
use Illuminate\Support\Facades\Process;
use Livewire\Component;
class GetLogs extends Component
{
public string $outputs = '';
public string $errors = '';
public Server $server;
public ?string $container = null;
public ?bool $streamLogs = false;
public int $numberOfLines = 100;
public function doSomethingWithThisChunkOfOutput($output)
{
$this->outputs .= $output;
}
public function instantSave()
{
}
public function getLogs($refresh = false)
{
if ($this->container) {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
if ($refresh) {
$this->outputs = '';
}
Process::run($sshCommand, function (string $type, string $output) {
$this->doSomethingWithThisChunkOfOutput($output);
});
}
}
public function render()
{
return view('livewire.project.shared.get-logs');
}
}

View File

@@ -9,6 +9,7 @@ class HealthChecks extends Component
public $resource;
protected $rules = [
'resource.health_check_enabled' => 'boolean',
'resource.health_check_path' => 'string',
'resource.health_check_port' => 'nullable|string',
'resource.health_check_host' => 'string',
@@ -22,12 +23,19 @@ class HealthChecks extends Component
'resource.health_check_start_period' => 'integer',
];
public function instantSave()
{
$this->resource->save();
$this->emit('success', 'Health check updated.');
}
public function submit()
{
try {
$this->validate();
$this->resource->save();
$this->emit('saved');
$this->emit('success', 'Health check updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Livewire\Component;
class Logs extends Component
{
public ?string $type = null;
public Application|StandalonePostgresql|Service|StandaloneRedis $resource;
public Server $server;
public ?string $container = null;
public $parameters;
public $query;
public $status;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id);
if ($containers->count() > 0) {
$this->container = data_get($containers[0], 'Names');
}
} else if (data_get($this->parameters, 'database_uuid')) {
$this->type = 'database';
$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)) {
abort(404);
}
}
$this->resource = $resource;
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid;
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->status = $this->resource->status;
$this->server = $this->resource->server;
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
}
}
public function render()
{
return view('livewire.project.shared.logs');
}
}

View File

@@ -6,6 +6,7 @@ use Livewire\Component;
class Add extends Component
{
public $uuid;
public $parameters;
public string $name;
public string $mount_path;
@@ -31,8 +32,9 @@ class Add extends Component
public function submit()
{
$this->validate();
$this->emitUp('submit', [
'name' => $this->name,
$name = $this->uuid . '-' . $this->name;
$this->emit('addNewVolume', [
'name' => $name,
'mount_path' => $this->mount_path,
'host_path' => $this->host_path,
]);

View File

@@ -8,28 +8,10 @@ use Livewire\Component;
class All extends Component
{
public $resource;
protected $listeners = ['refreshStorages', 'submit'];
protected $listeners = ['refreshStorages'];
public function refreshStorages()
{
$this->resource->refresh();
}
public function submit($data)
{
try {
LocalPersistentVolume::create([
'name' => $data['name'],
'mount_path' => $data['mount_path'],
'host_path' => $data['host_path'],
'resource_id' => $this->resource->id,
'resource_type' => $this->resource->getMorphClass(),
]);
$this->resource->refresh();
$this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

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

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Livewire\Server;
use App\Models\PrivateKey;
use Livewire\Component;
class Create extends Component
{
public $private_keys = [];
public bool $limit_reached = false;
public function mount()
{
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
if (!isCloud()) {
$this->limit_reached = false;
return;
}
$team = currentTeam();
$servers = $team->servers->count();
['serverLimit' => $serverLimit] = $team->limits;
$this->limit_reached = $servers >= $serverLimit;
}
public function render()
{
return view('livewire.server.create');
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Server\Destination;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.destination.show');
}
}

View File

@@ -11,11 +11,12 @@ class Form extends Component
{
use AuthorizesRequests;
public Server $server;
public $uptime;
public $dockerVersion;
public string|null $wildcard_domain = null;
public bool $isValidConnection = false;
public bool $isValidDocker = false;
public ?string $wildcard_domain = null;
public int $cleanup_after_percentage;
public bool $dockerInstallationStarted = false;
protected $listeners = ['serverRefresh'];
protected $rules = [
'server.name' => 'required|min:6',
@@ -29,11 +30,11 @@ class Form extends Component
'wildcard_domain' => 'nullable|url',
];
protected $validationAttributes = [
'server.name' => 'name',
'server.description' => 'description',
'server.ip' => 'ip',
'server.user' => 'user',
'server.port' => 'port',
'server.name' => 'Name',
'server.description' => 'Description',
'server.ip' => 'IP address',
'server.user' => 'User',
'server.port' => 'Port',
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
'server.settings.is_reachable' => 'is reachable',
'server.settings.is_part_of_swarm' => 'is part of swarm'
@@ -44,37 +45,62 @@ class Form extends Component
$this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
}
public function instantSave() {
public function serverRefresh()
{
$this->validateServer();
}
public function instantSave()
{
refresh_server_connection($this->server->privateKey);
$this->validateServer();
$this->server->settings->save();
}
public function installDocker()
{
$this->emit('installDocker');
$this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->server);
$activity = InstallDocker::run($this->server);
$this->emit('newMonitorActivity', $activity->id);
}
public function validateServer()
public function checkLocalhostConnection()
{
$uptime = $this->server->validateConnection();
if ($uptime) {
$this->emit('success', 'Server is reachable.');
$this->server->settings->is_reachable = true;
$this->server->settings->is_usable = true;
$this->server->settings->save();
} else {
$this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
return;
}
}
public function validateServer($install = true)
{
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
$uptime = $this->server->validateConnection();
if ($uptime) {
$this->uptime = $uptime;
$this->emit('success', 'Server is reachable.');
$install && $this->emit('success', 'Server is reachable.');
} else {
$this->emit('error', 'Server is not reachable.');
$install && $this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
return;
}
if ($dockerVersion) {
$this->dockerVersion = $dockerVersion;
$this->emit('success', 'Docker Engine 23+ is installed!');
$dockerInstalled = $this->server->validateDockerEngine();
if ($dockerInstalled) {
$install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
} else {
$this->emit('error', 'No Docker Engine or older than 23 version installed.');
$install && $this->installDocker();
return;
}
$dockerVersion = $this->server->validateDockerEngineVersion();
if ($dockerVersion) {
$install && $this->emit('success', 'Docker Engine version is 23+.');
} else {
$install && $this->installDocker();
return;
}
} catch (\Throwable $e) {
return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
return handleError($e, $this);
} finally {
$this->emit('proxyStatusUpdated');
}
@@ -96,7 +122,14 @@ class Form extends Component
}
public function submit()
{
$this->validate();
if(isCloud() && !isDev()) {
$this->validate();
$this->validate([
'server.ip' => 'required|ip',
]);
} else {
$this->validate();
}
$uniqueIPs = Server::all()->reject(function (Server $server) {
return $server->id === $this->server->id;
})->pluck('ip')->toArray();
@@ -104,6 +137,7 @@ class Form extends Component
$this->emit('error', 'IP address is already in use by another team.');
return;
}
refresh_server_connection($this->server->privateKey);
$this->server->settings->wildcard_domain = $this->wildcard_domain;
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
$this->server->settings->save();

View File

@@ -11,13 +11,13 @@ class ByIp extends Component
{
public $private_keys;
public $limit_reached;
public int|null $private_key_id = null;
public ?int $private_key_id = null;
public $new_private_key_name;
public $new_private_key_description;
public $new_private_key_value;
public string $name;
public string|null $description = null;
public ?string $description = null;
public string $ip;
public string $user = 'root';
public int $port = 22;
@@ -26,16 +26,16 @@ class ByIp extends Component
protected $rules = [
'name' => 'required|string',
'description' => 'nullable|string',
'ip' => 'required',
'ip' => 'required|ip',
'user' => 'required|string',
'port' => 'required|integer',
];
protected $validationAttributes = [
'name' => 'name',
'description' => 'description',
'ip' => 'ip',
'user' => 'user',
'port' => 'port',
'name' => 'Name',
'description' => 'Description',
'ip' => 'IP Address',
'user' => 'User',
'port' => 'Port',
];
public function mount()

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Livewire\Server\PrivateKey;
use App\Models\PrivateKey;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $privateKeys = [];
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.private-key.show');
}
}

View File

@@ -38,8 +38,8 @@ class Proxy extends Component
public function select_proxy($proxy_type)
{
$this->server->proxy->type = $proxy_type;
$this->server->proxy->status = 'exited';
$this->server->proxy->set('status', 'exited');
$this->server->proxy->set('type', $proxy_type);
$this->server->save();
$this->selectedProxy = $this->server->proxy->type;
$this->emit('proxyStatusUpdated');

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Server\Proxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Livewire\Component;
@@ -11,18 +12,40 @@ class Deploy extends Component
public Server $server;
public bool $traefikDashboardAvailable = false;
public ?string $currentRoute = null;
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable'];
public ?string $serverIp = null;
public function mount() {
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated', "checkProxy", "startProxy"];
public function mount()
{
if ($this->server->id === 0) {
$this->serverIp = base_ip();
} else {
$this->serverIp = $this->server->ip;
}
$this->currentRoute = request()->route()->getName();
}
public function traefikDashboardAvailable(bool $data) {
public function traefikDashboardAvailable(bool $data)
{
$this->traefikDashboardAvailable = $data;
}
public function proxyStatusUpdated()
{
$this->server->refresh();
}
public function ip()
{
}
public function checkProxy()
{
try {
CheckProxy::run($this->server, true);
$this->emit('startProxyPolling');
$this->emit('proxyChecked');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function startProxy()
{
try {

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Server\Proxy;
use App\Models\Server;
use Livewire\Component;
class Logs extends Component
{
public ?Server $server = null;
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.proxy.logs');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Livewire\Server\Proxy;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $parameters = [];
protected $listeners = ['proxyStatusUpdated'];
public function proxyStatusUpdated()
{
$this->server->refresh();
}
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.proxy.show');
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Server\Proxy;
use App\Actions\Proxy\CheckProxy;
use App\Jobs\ContainerStatusJob;
use App\Models\Server;
use Livewire\Component;
@@ -9,26 +10,49 @@ use Livewire\Component;
class Status extends Component
{
public Server $server;
public bool $polling = false;
public int $numberOfPolls = 0;
protected $listeners = ['proxyStatusUpdated'];
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
public function startProxyPolling()
{
$this->polling = true;
}
public function proxyStatusUpdated()
{
$this->server->refresh();
}
public function getProxyStatus()
public function checkProxy(bool $notification = false)
{
try {
if ($this->server->isFunctional()) {
dispatch_sync(new ContainerStatusJob($this->server));
$this->emit('proxyStatusUpdated');
if ($this->polling) {
if ($this->numberOfPolls >= 10) {
$this->polling = false;
$this->numberOfPolls = 0;
$notification && $this->emit('error', 'Proxy is not running.');
return;
}
$this->numberOfPolls++;
}
CheckProxy::run($this->server, true);
$this->emit('proxyStatusUpdated');
if ($this->server->proxy->status === 'running') {
$this->polling = false;
$notification && $this->emit('success', 'Proxy is running.');
} else {
$notification && $this->emit('error', 'Proxy is not running.');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function getProxyStatusWithNoti()
public function getProxyStatus()
{
$this->emit('success', 'Refreshed proxy status.');
$this->getProxyStatus();
try {
dispatch_sync(new ContainerStatusJob($this->server));
$this->emit('proxyStatusUpdated');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -10,8 +10,10 @@ class Show extends Component
{
use AuthorizesRequests;
public ?Server $server = null;
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
@@ -21,6 +23,10 @@ class Show extends Component
return handleError($e, $this);
}
}
public function submit()
{
$this->emit('serverRefresh');
}
public function render()
{
return view('livewire.server.show');

View File

@@ -14,8 +14,8 @@ class ShowPrivateKey extends Component
public function setPrivateKey($newPrivateKeyId)
{
try {
refresh_server_connection($this->server->privateKey);
$oldPrivateKeyId = $this->server->private_key_id;
refresh_server_connection($this->server->privateKey);
$this->server->update([
'private_key_id' => $newPrivateKeyId
]);
@@ -35,31 +35,13 @@ class ShowPrivateKey extends Component
public function checkConnection()
{
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
$uptime = $this->server->validateConnection();
if ($uptime) {
$this->server->settings->update([
'is_reachable' => true
]);
$this->emit('success', 'Server is reachable with this private key.');
$this->emit('success', 'Server is reachable.');
} else {
$this->server->settings->update([
'is_reachable' => false,
'is_usable' => false
]);
$this->emit('error', 'Server is not reachable with this private key.');
$this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.');
return;
}
if ($dockerVersion) {
$this->server->settings->update([
'is_usable' => true
]);
$this->emit('success', 'Server is usable for Coolify.');
} else {
$this->server->settings->update([
'is_usable' => false
]);
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -78,10 +78,10 @@ class Backup extends Component
dispatch(new DatabaseBackupJob(
backup: $this->backup
));
$this->emit('success', 'Backup queued. It will be available in a few minutes');
$this->emit('success', 'Backup queued. It will be available in a few minutes.');
}
public function submit()
{
$this->emit('success', 'Backup updated successfully');
$this->emit('success', 'Backup updated successfully.');
}
}

View File

@@ -45,7 +45,12 @@ 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->next_channel = $this->next_channel;
if ($this->next_channel) {
$this->settings->next_channel = false;
$this->next_channel = false;
} else {
$this->settings->next_channel = $this->next_channel;
}
$this->settings->save();
$this->emit('success', 'Settings updated!');
}
@@ -68,7 +73,7 @@ class Configuration extends Component
{
$file = "$this->dynamic_config_path/coolify.yaml";
if (empty($this->settings->fqdn)) {
remote_process([
instant_remote_process([
"rm -f $file",
], $this->server);
} else {
@@ -124,7 +129,6 @@ class Configuration extends Component
];
}
$this->save_configuration_to_disk($traefik_dynamic_conf, $file);
dispatch(new ContainerStatusJob($this->server));
}
}
@@ -137,7 +141,7 @@ class Configuration extends Component
$yaml;
$base64 = base64_encode($yaml);
remote_process([
instant_remote_process([
"mkdir -p $this->dynamic_config_path",
"echo '$base64' | base64 -d > $file",
], $this->server);

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Livewire\Subscription;
use App\Models\InstanceSettings;
use Livewire\Component;
class Show extends Component
{
public InstanceSettings $settings;
public bool $alreadySubscribed = false;
public function mount() {
if (!isCloud()) {
return redirect('/');
}
$this->settings = InstanceSettings::get();
$this->alreadySubscribed = currentTeam()->subscription()->exists();
}
public function stripeCustomerPortal() {
$session = getStripeCustomerPortalSession(currentTeam());
if (is_null($session)) {
return;
}
return redirect($session->url);
}
public function render()
{
return view('livewire.subscription.show')->layout('layouts.subscription');
}
}

View File

@@ -64,7 +64,7 @@ class Create extends Component
}
$this->storage->team_id = currentTeam()->id;
$this->storage->testConnection();
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
$this->storage->is_usable = true;
$this->storage->save();
return redirect()->route('team.storages.show', $this->storage->uuid);
} catch (\Throwable $e) {

View File

@@ -9,6 +9,7 @@ class Form extends Component
{
public S3Storage $storage;
protected $rules = [
'storage.is_usable' => 'nullable|boolean',
'storage.name' => 'nullable|min:3|max:255',
'storage.description' => 'nullable|min:3|max:255',
'storage.region' => 'required|max:255',
@@ -18,6 +19,7 @@ class Form extends Component
'storage.endpoint' => 'required|url|max:255',
];
protected $validationAttributes = [
'storage.is_usable' => 'Is Usable',
'storage.name' => 'Name',
'storage.description' => 'Description',
'storage.region' => 'Region',

View File

@@ -5,10 +5,11 @@ namespace App\Http\Livewire;
use App\Actions\Server\UpdateCoolify;
use App\Models\InstanceSettings;
use Livewire\Component;
use Masmerise\Toaster\Toaster;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
class Upgrade extends Component
{
use WithRateLimiting;
public bool $showProgress = false;
public bool $isUpgradeAvailable = false;
public string $latestVersion = '';
@@ -31,11 +32,12 @@ class Upgrade extends Component
public function upgrade()
{
try {
$this->rateLimit(1, 30);
if ($this->showProgress) {
return;
}
$this->showProgress = true;
resolve(UpdateCoolify::class)(true);
UpdateCoolify::run(true);
$this->emit('success', "Upgrading to {$this->latestVersion} version...");
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
class VerifyEmail extends Component
{
use WithRateLimiting;
public function again() {
try {
$this->rateLimit(1, 300);
auth()->user()->sendVerificationEmail();
$this->emit('success', 'Email verification link sent!');
} catch(\Exception $e) {
ray($e);
return handleError($e,$this);
}
}
public function render()
{
return view('livewire.verify-email');
}
}

View File

@@ -23,6 +23,9 @@ class Index extends Component
}
public function mount()
{
if (config('coolify.waitlist') == false) {
return redirect()->route('register');
}
$this->waitingInLine = Waitlist::whereVerified(true)->count();
$this->users = User::count();
if (isDev()) {

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
class DecideWhatToDoWithUser
{
public function handle(Request $request, Closure $next): Response
{
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
return redirect('boarding');
}
return $next($request);
}
if (!auth()->user()->hasVerifiedEmail()) {
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
return $next($request);
}
return redirect('/verify');
}
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('subscription');
}
}
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('boarding');
}
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
return redirect('/');
}
if (isSubscriptionActive() && $request->path() === 'subscription') {
return redirect('/');
}
return $next($request);
}
}

View File

@@ -45,16 +45,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private string $commit;
private bool $force_rebuild;
private GithubApp|GitlabApp $source;
private ?string $dockerImage = null;
private ?string $dockerImageTag = null;
private GithubApp|GitlabApp|string $source = 'other';
private StandaloneDocker|SwarmDocker $destination;
private Server $server;
private ApplicationPreview|null $preview = null;
private string $container_name;
private string|null $currently_running_container_name = null;
private ?string $currently_running_container_name = null;
private string $basedir;
private string $workdir;
private ?string $build_pack = null;
private string $configuration_dir;
private string $build_workdir;
private string $build_image_name;
private string $production_image_name;
private bool $is_debug_enabled;
@@ -62,17 +66,23 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private $env_args;
private $docker_compose;
private $docker_compose_base64;
private string $dockerfile_location = '/Dockerfile';
private ?string $addHosts = null;
private $log_model;
private Collection $saved_outputs;
private string $serverUser = 'root';
private string $serverUserHomeDir = '/root';
private string $dockerConfigFileExists = 'NOK';
public $tries = 1;
public function __construct(int $application_deployment_queue_id)
{
ray()->clearScreen();
// ray()->clearScreen();
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
$this->log_model = $this->application_deployment_queue;
$this->application = Application::find($this->application_deployment_queue->application_id);
$this->build_pack = data_get($this->application, 'build_pack');
$this->application_deployment_queue_id = $application_deployment_queue_id;
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
@@ -80,16 +90,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->commit = $this->application_deployment_queue->commit;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->source = $this->application->source->getMorphClass()::where('id', $this->application->source->id)->first();
$source = data_get($this->application, 'source');
if ($source) {
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
}
$this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
$this->server = $this->destination->server;
$this->workdir = "/artifacts/{$this->deployment_uuid}";
$this->serverUser = $this->server->user;
$this->basedir = "/artifacts/{$this->deployment_uuid}";
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->container_name = generateApplicationContainerName($this->application);
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
savePrivateKeyToFs($this->server);
$this->saved_outputs = collect();
@@ -97,7 +110,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->pull_request_id !== 0) {
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
if ($this->application->fqdn) {
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
if (data_get($this->preview, 'fqdn')) {
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
}
$template = $this->application->preview_url_template;
$url = Url::fromString($this->application->fqdn);
$host = $url->getHost();
@@ -126,20 +141,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
// Generate custom host<->ip mapping
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
$allContainers = format_docker_command_output_to_json($allContainers);
$ips = collect([]);
if (count($allContainers) > 0) {
$allContainers = $allContainers[0];
foreach ($allContainers as $container) {
$containerName = data_get($container, 'Name');
if ($containerName === 'coolify-proxy') {
continue;
}
$containerIp = data_get($container, 'IPv4Address');
if ($containerName && $containerIp) {
$containerIp = str($containerIp)->before('/');
$ips->put($containerName, $containerIp->value());
}
}
}
$this->addHosts = $ips->map(function ($ip, $name) {
return "--add-host $name:$ip";
})->implode(' ');
// 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);
try {
if ($this->application->dockerfile) {
$this->deploy_simple_dockerfile();
} else if ($this->application->build_pack === 'dockerimage') {
$this->deploy_dockerimage_buildpack();
} else if ($this->application->build_pack === 'dockerfile') {
$this->deploy_dockerfile_buildpack();
} else {
if ($this->pull_request_id !== 0) {
$this->deploy_pull_request();
} else {
$this->deploy();
$this->deploy_nixpacks_buildpack();
}
}
if ($this->server->isProxyShouldRun()) {
dispatch(new ContainerStatusJob($this->server));
}
$this->next(ApplicationDeploymentStatus::FINISHED->value);
$this->application->isConfigurationChanged(true);
} catch (Exception $e) {
ray($e);
$this->fail($e);
@@ -167,41 +213,43 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
}
}
private function deploy_docker_compose()
{
$dockercompose_base64 = base64_encode($this->application->dockercompose);
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->application->name}.'"
],
);
$this->prepare_builder_image();
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
],
);
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
$this->save_environment_variables();
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(
["docker rm -f {$containerName}"],
$this->application->destination->server
);
}
}
}
$this->execute_remote_command(
["echo -n 'Starting services (could take a while)...'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
);
}
// private function deploy_docker_compose()
// {
// $dockercompose_base64 = base64_encode($this->application->dockercompose);
// $this->execute_remote_command(
// [
// "echo 'Starting deployment of {$this->application->name}.'"
// ],
// );
// $this->prepare_builder_image();
// $this->execute_remote_command(
// [
// executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
// ],
// );
// $this->build_image_name = Str::lower("{$this->application->git_repository}:build");
// $this->production_image_name = Str::lower("{$this->application->uuid}:latest");
// $this->save_environment_variables();
// $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
// ray($containers);
// if ($containers->count() > 0) {
// foreach ($containers as $container) {
// $containerName = data_get($container, 'Names');
// if ($containerName) {
// instant_remote_process(
// ["docker rm -f {$containerName}"],
// $this->application->destination->server
// );
// }
// }
// }
// $this->execute_remote_command(
// ["echo -n 'Starting services (could take a while)...'"],
// [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
// );
// }
private function save_environment_variables()
{
$envs = collect([]);
@@ -231,7 +279,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
@@ -239,7 +287,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->rolling_update();
}
private function deploy()
private function deploy_dockerimage_buildpack()
{
$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->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
$this->prepare_builder_image();
$this->generate_compose_file();
$this->rolling_update();
}
private function deploy_dockerfile_buildpack()
{
if (data_get($this->application, 'dockerfile_location')) {
$this->dockerfile_location = $this->application->dockerfile_location;
}
$this->execute_remote_command(
[
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
],
);
$this->prepare_builder_image();
$this->clone_repository();
$this->set_base_dir();
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
if (strlen($tag) > 128) {
$tag = $tag->substr(0, 128);
}
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
$this->cleanup_git();
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
$this->build_image();
$this->rolling_update();
}
private function deploy_nixpacks_buildpack()
{
$this->execute_remote_command(
[
@@ -248,7 +340,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
$this->prepare_builder_image();
$this->clone_repository();
$this->set_base_dir();
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
if (strlen($tag) > 128) {
$tag = $tag->substr(0, 128);
@@ -256,25 +348,28 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
if (!$this->force_rebuild) {
$this->execute_remote_command([
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
]);
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->execute_remote_command([
"echo 'Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped...'"
"echo 'No configuration changed & Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. 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->cleanup_git();
if ($this->application->build_pack === 'nixpacks') {
$this->generate_nixpacks_confs();
}
$this->generate_nixpacks_confs();
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
@@ -284,7 +379,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function rolling_update()
{
if (count($this->application->ports_mappings_array) > 0){
if (count($this->application->ports_mappings_array) > 0) {
$this->execute_remote_command(
["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"],
);
@@ -301,7 +396,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function health_check()
{
ray('New container name: ', $this->container_name);
if ($this->application->isHealthcheckDisabled()) {
$this->newVersionIsHealthy = true;
return;
}
// ray('New container name: ', $this->container_name);
if ($this->container_name) {
$counter = 0;
$this->execute_remote_command(
@@ -329,9 +428,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
$this->newVersionIsHealthy = true;
$this->execute_remote_command(
[
"echo 'New version of your application is healthy.'"
],
[
"echo 'Rolling update completed.'"
],
@@ -348,12 +444,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
$this->execute_remote_command([
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
]);
$this->prepare_builder_image();
$this->clone_repository();
$this->set_base_dir();
$this->cleanup_git();
if ($this->application->build_pack === 'nixpacks') {
$this->generate_nixpacks_confs();
@@ -366,18 +463,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->stop_running_container();
$this->execute_remote_command(
["echo -n 'Starting preview deployment.'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
);
}
private function prepare_builder_image()
{
$pull = "--pull=always";
if (isDev()) {
$pull = "--pull=never";
}
$helperImage = config('coolify.helper_image');
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
if ($this->dockerConfigFileExists === 'OK') {
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --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 {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
}
$this->execute_remote_command(
[
@@ -388,24 +486,31 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"hidden" => true,
],
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
],
);
}
private function set_base_dir()
{
$this->execute_remote_command(
[
"echo -n 'Setting base directory to {$this->workdir}.'"
],
);
}
private function clone_repository()
{
$this->execute_remote_command(
[
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '"
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
],
[
$this->importing_git_repository()
$this->importing_git_repository(), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git rev-parse HEAD"),
executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git rev-parse HEAD"),
"hidden" => true,
"save" => "git_commit_sha"
],
@@ -429,23 +534,28 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
if ($this->source->is_public) {
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}";
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->basedir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
} else {
$github_access_token = generate_github_installation_token($this->source);
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}"));
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->basedir}"));
}
if ($this->pull_request_id !== 0) {
$commands->push(executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
$commands->push(executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
}
return $commands->implode(' && ');
}
}
if ($this->application->deploymentType() === 'deploy_key') {
$port = 22;
preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches);
if (count($matches) === 1) {
$port = $matches[0];
}
$private_key = base64_encode($this->application->private_key->private_key);
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}";
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -p $port -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_repository} {$this->basedir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands = collect([
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
@@ -455,18 +565,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]);
return $commands->implode(' && ');
}
if ($this->application->deploymentType() === 'other') {
$git_clone_command = "{$git_clone_command} {$this->application->git_repository} {$this->basedir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
ray($commands);
return $commands->implode(' && ');
}
}
private function set_git_import_settings($git_clone_command)
{
if ($this->application->git_commit_sha !== 'HEAD') {
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1";
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1";
}
if ($this->application->settings->is_git_submodules_enabled) {
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git submodule update --init --recursive";
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git submodule update --init --recursive";
}
if ($this->application->settings->is_git_lfs_enabled) {
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git lfs pull";
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git lfs pull";
}
return $git_clone_command;
}
@@ -474,17 +591,24 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function cleanup_git()
{
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "rm -fr {$this->workdir}/.git")],
[executeInDocker($this->deployment_uuid, "rm -fr {$this->basedir}/.git")],
);
}
private function generate_nixpacks_confs()
{
$this->execute_remote_command(
[
"echo -n 'Generating nixpacks configuration.'",
]
);
$nixpacks_command = $this->nixpacks_build_cmd();
$this->execute_remote_command(
[
"echo -n Running: $nixpacks_command",
],
[$this->nixpacks_build_cmd()],
[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")]
);
@@ -493,7 +617,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function nixpacks_build_cmd()
{
$this->generate_env_variables();
$nixpacks_command = "nixpacks build -o {$this->workdir} {$this->env_args} --no-error-without-start";
$nixpacks_command = "nixpacks build --no-cache -o {$this->workdir} {$this->env_args} --no-error-without-start";
if ($this->application->build_command) {
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
}
@@ -504,7 +628,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
}
$nixpacks_command .= " {$this->workdir}";
return executeInDocker($this->deployment_uuid, $nixpacks_command);
return $nixpacks_command;
}
private function generate_env_variables()
@@ -531,6 +655,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables($ports);
$labels = generateLabelsApplication($this->application, $this->preview);
if (data_get($this->application, 'custom_labels')) {
$labels = str($this->application->custom_labels)->explode(',')->toArray();
}
$docker_compose = [
'version' => '3.8',
'services' => [
@@ -539,7 +667,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'container_name' => $this->container_name,
'restart' => RESTART_MODE,
'environment' => $environment_variables,
'labels' => generateLabelsApplication($this->application, $this->preview),
'labels' => $labels,
'expose' => $ports,
'networks' => [
$this->destination->network,
@@ -571,6 +699,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
]
];
if ($this->application->isHealthcheckDisabled()) {
data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck');
}
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
}
@@ -580,6 +711,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
// if ($this->build_pack === 'dockerfile') {
// $docker_compose['services'][$this->container_name]['build'] = [
// 'context' => $this->workdir,
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
// ];
// }
$this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
@@ -622,14 +759,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function generate_environment_variables($ports)
{
$environment_variables = collect();
ray('Generate Environment Variables')->green();
// ray('Generate Environment Variables')->green();
if ($this->pull_request_id === 0) {
ray($this->application->runtime_environment_variables)->green();
// ray($this->application->runtime_environment_variables)->green();
foreach ($this->application->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
}
} else {
ray($this->application->runtime_environment_variables_preview)->green();
// ray($this->application->runtime_environment_variables_preview)->green();
foreach ($this->application->runtime_environment_variables_preview as $env) {
$environment_variables->push("$env->key=$env->value");
}
@@ -643,7 +780,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function generate_healthcheck_commands()
{
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
return 'exit 0';
}
@@ -667,12 +804,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function build_image()
{
$this->execute_remote_command([
"echo -n 'Building docker image for your application.'",
"echo -n 'Building docker image for your application. To check the current progress, click on Show Debug Logs.'",
]);
if ($this->application->settings->is_static) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
]);
$dockerfile = base64_encode("FROM {$this->application->static_image}
@@ -705,12 +842,12 @@ 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 --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
]
);
} else {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
]);
}
}
@@ -736,7 +873,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
{
$this->execute_remote_command(
["echo -n 'Starting application (could take a while).'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
);
}
@@ -759,7 +896,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function add_build_env_variables_to_dockerfile()
{
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile'
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile'
]);
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
@@ -768,7 +905,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"),
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/{$this->dockerfile_location}"),
"hidden" => true
]);
}

View File

@@ -66,7 +66,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
private function update_comment()
{
['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [
'body' => $this->body,
], throwError: false);
if (data_get($data, 'message') === 'Not Found') {
@@ -77,7 +77,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
private function create_comment()
{
['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
'body' => $this->body,
]);
$this->preview->pull_request_issue_comment_id = $data['id'];

View File

@@ -2,11 +2,13 @@
namespace App\Jobs;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -26,13 +28,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
public $tries = 1;
public $timeout = 120;
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [new WithoutOverlapping($this->server->uuid)];
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
}
public function uniqueId(): string
@@ -40,32 +38,70 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return $this->server->uuid;
}
private function checkServerConnection()
public function __construct(public Server $server)
{
$uptime = instant_remote_process(['uptime'], $this->server, false);
if (!is_null($uptime)) {
return true;
if (isDev()) {
$this->handle();
}
}
public function handle(): void
public function handle()
{
try {
// ray("checking server status for {$this->server->id}");
// ray()->clearAll();
$serverUptimeCheckNumber = 0;
$serverUptimeCheckNumber = $this->server->unreachable_count;
$serverUptimeCheckNumberMax = 3;
while (true) {
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
$this->server->settings()->update(['is_reachable' => false]);
// ray('checking # ' . $serverUptimeCheckNumber);
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
if ($this->server->unreachable_email_sent === false) {
ray('Server unreachable, sending notification...');
$this->server->team->notify(new Unreachable($this->server));
return;
$this->server->update(['unreachable_email_sent' => true]);
}
$result = $this->checkServerConnection();
if ($result) {
break;
}
$serverUptimeCheckNumber++;
sleep(5);
$this->server->settings()->update([
'is_reachable' => false,
]);
$this->server->update([
'unreachable_count' => 0,
]);
return;
}
$result = $this->server->validateConnection();
if ($result) {
$this->server->settings()->update([
'is_reachable' => true,
]);
$this->server->update([
'unreachable_count' => 0,
]);
} else {
$serverUptimeCheckNumber++;
$this->server->settings()->update([
'is_reachable' => false,
]);
$this->server->update([
'unreachable_count' => $serverUptimeCheckNumber,
]);
return;
}
if (data_get($this->server, 'unreachable_email_sent') === true) {
ray('Server is reachable again, sending notification...');
$this->server->team->notify(new Revived($this->server));
$this->server->update(['unreachable_email_sent' => false]);
}
if (
data_get($this->server, 'settings.is_reachable') === false ||
data_get($this->server, 'settings.is_usable') === false
) {
$this->server->settings()->update([
'is_reachable' => true,
'is_usable' => true
]);
}
// $this->server->validateDockerEngine(true);
$containers = instant_remote_process(["docker container ls -q"], $this->server);
if (!$containers) {
return;
@@ -74,18 +110,26 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$containers = format_docker_command_output_to_json($containers);
$applications = $this->server->applications();
$databases = $this->server->databases();
$services = $this->server->services();
$services = $this->server->services()->get();
$previews = $this->server->previews();
$this->server->proxyType();
/// Check if proxy is running
$foundProxyContainer = $containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-proxy';
})->first();
if (!$foundProxyContainer) {
if ($this->server->isProxyShouldRun()) {
StartProxy::run($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
} else {
ray('Proxy could not be started.');
}
} catch (\Throwable $e) {
ray($e);
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
$this->server->save();
@@ -99,16 +143,16 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
foreach ($containers as $container) {
$containerStatus = data_get($container, 'State.Status');
$containerHealth = data_get($container, 'State.Health.Status','unhealthy');
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
$containerStatus = "$containerStatus ($containerHealth)";
$labels = data_get($container, 'Config.Labels');
$labels = Arr::undot(format_docker_labels_to_json($labels));
$labelId = data_get($labels, 'coolify.applicationId');
if ($labelId) {
if (str_contains($labelId, '-pr-')) {
$previewId = (int) Str::after($labelId, '-pr-');
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
$applicationId = (int) Str::before($labelId, '-pr-');
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $previewId)->first();
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
if ($preview) {
$foundApplicationPreviews[] = $preview->id;
$statusFromDb = $preview->status;
@@ -147,25 +191,29 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
}
$serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) {
$coolifyName = data_get($labels, 'coolify.name');
$serviceName = Str::of($coolifyName)->before('-');
$serviceUuid = Str::of($coolifyName)->after('-');
$service = $services->where('uuid', $serviceUuid)->first();
$subType = data_get($labels, 'coolify.service.subType');
$subId = data_get($labels, 'coolify.service.subId');
$service = $services->where('id', $serviceLabelId)->first();
if (!$service) {
continue;
}
if ($subType === 'application') {
$service = $service->applications()->where('id', $subId)->first();
} else {
$service = $service->databases()->where('id', $subId)->first();
}
if ($service) {
$foundService = $service->byName($serviceName);
if ($foundService) {
$foundServices[] = "$foundService->id-$serviceName";
$statusFromDb = $foundService->status;
if ($statusFromDb !== $containerStatus) {
// ray('Updating status: ' . $containerStatus);
$foundService->update(['status' => $containerStatus]);
}
$foundServices[] = "$service->id-$service->name";
$statusFromDb = $service->status;
if ($statusFromDb !== $containerStatus) {
// ray('Updating status: ' . $containerStatus);
$service->update(['status' => $containerStatus]);
}
}
}
}
$exitedServices = collect([]);
foreach ($services->get() as $service) {
foreach ($services as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
@@ -260,7 +308,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} catch (\Throwable $e) {
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
return handleError($e);
}
}
}

View File

@@ -31,7 +31,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
public ?string $container_name = null;
public ?ScheduledDatabaseBackupExecution $backup_log = null;
public string $backup_status;
public string $backup_status = 'failed';
public ?string $backup_location = null;
public string $backup_dir;
public string $backup_file;
@@ -61,70 +61,92 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
public function handle(): void
{
try {
if (data_get($this->database, 'status') !== 'running') {
$status = Str::of(data_get($this->database, 'status'));
if (!$status->startsWith('running') && $this->database->id !== 0) {
ray('database not running');
return;
}
$databaseType = $this->database->type();
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
if (is_null($databasesToBackup)) {
if ($databaseType === 'standalone-postgresql') {
$databasesToBackup = [$this->database->postgres_db];
} else {
return;
}
} else {
$databasesToBackup = explode(',', $databasesToBackup);
$databasesToBackup = array_map('trim', $databasesToBackup);
}
$this->container_name = $this->database->uuid;
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->container_name;
if ($this->database->name === 'coolify-db') {
$databasesToBackup = ['coolify'];
$this->container_name = "coolify-db";
$ip = Str::slug($this->server->ip);
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
}
$this->backup_file = "/pg_dump-" . Carbon::now()->timestamp . ".dump";
$this->backup_location = $this->backup_dir . $this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'filename' => $this->backup_location,
'scheduled_database_backup_id' => $this->backup->id,
]);
if ($this->database->type() === 'standalone-postgresql') {
$this->backup_standalone_postgresql();
foreach ($databasesToBackup as $database) {
$size = 0;
ray('Backing up ' . $database);
try {
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
$this->backup_location = $this->backup_dir . $this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
'filename' => $this->backup_location,
'scheduled_database_backup_id' => $this->backup->id,
]);
if ($databaseType === 'standalone-postgresql') {
$this->backup_standalone_postgresql($database);
}
$size = $this->calculate_size();
$this->remove_old_backups();
if ($this->backup->save_s3) {
$this->upload_to_s3();
}
$this->team->notify(new BackupSuccess($this->backup, $this->database));
$this->backup_log->update([
'status' => 'success',
'message' => $this->backup_output,
'size' => $size,
]);
} catch (\Throwable $e) {
$this->backup_log->update([
'status' => 'failed',
'message' => $this->backup_output,
'size' => $size,
'filename' => null
]);
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
throw $e;
}
}
$this->calculate_size();
$this->remove_old_backups();
if ($this->backup->save_s3) {
$this->upload_to_s3();
}
$this->save_backup_logs();
// TODO: Notify user
} catch (\Throwable $e) {
ray($e->getMessage());
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
throw $e;
}
}
private function backup_standalone_postgresql(): void
private function backup_standalone_postgresql(string $database): void
{
try {
ray($this->backup_dir);
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = "docker exec $this->container_name pg_dump -Fc -U {$this->database->postgres_user} > $this->backup_location";
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') {
$this->backup_output = null;
}
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
$this->backup_status = 'success';
$this->team->notify(new BackupSuccess($this->backup, $this->database));
} catch (\Throwable $e) {
$this->backup_status = 'failed';
$this->add_to_backup_output($e->getMessage());
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
} finally {
$this->backup_log->update([
'status' => $this->backup_status,
]);
throw $e;
}
}
@@ -137,9 +159,9 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
}
}
private function calculate_size(): void
private function calculate_size()
{
$this->size = instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server);
return instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server, false);
}
private function remove_old_backups(): void
@@ -163,11 +185,16 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
}
$key = $this->s3->key;
$secret = $this->s3->secret;
// $region = $this->s3->region;
// $region = $this->s3->region;
$bucket = $this->s3->bucket;
$endpoint = $this->s3->endpoint;
$this->s3->testConnection();
if (isDev()) {
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v coolify_coolify-data-dev:/data/coolify:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
} else {
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
}
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
instant_remote_process($commands, $this->server);
@@ -175,19 +202,10 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir);
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());
ray($e->getMessage());
throw $e;
} finally {
$command = "docker rm -f backup-of-{$this->backup->uuid}";
instant_remote_process([$command], $this->server);
}
}
private function save_backup_logs(): void
{
$this->backup_log->update([
'status' => $this->backup_status,
'message' => $this->backup_output,
'size' => $this->size,
]);
}
}

View File

@@ -16,65 +16,53 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 500;
public $timeout = 1000;
public ?string $dockerRootFilesystem = null;
public ?int $usageBefore = null;
public function middleware(): array
{
return [
(new WithoutOverlapping("dockerimagejobs"))->shared(),
];
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
}
public function __construct()
public function uniqueId(): string
{
return $this->server->uuid;
}
public function __construct(public Server $server)
{
}
public function handle(): void
{
$queue = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get();
if ($queue->count() > 0) {
$queuedCount = 0;
$this->server->applications()->each(function ($application) use ($queuedCount) {
$count = data_get($application->deployments(), 'count', 0);
$queuedCount += $count;
});
if ($queuedCount > 0) {
ray('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping')->color('orange');
return;
}
try {
// ray()->showQueries()->color('orange');
$servers = Server::all();
foreach ($servers as $server) {
if (
!$server->isFunctional()
) {
continue;
}
if (isDev()) {
$this->dockerRootFilesystem = "/";
if (!$this->server->isFunctional()) {
return;
}
$this->dockerRootFilesystem = "/";
$this->usageBefore = $this->getFilesystemUsage();
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
ray('Cleaning up ' . $this->server->name)->color('orange');
instant_remote_process(['docker image prune -af'], $this->server);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server);
instant_remote_process(['docker builder prune -af'], $this->server);
$usageAfter = $this->getFilesystemUsage();
if ($usageAfter < $this->usageBefore) {
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name)->color('orange');
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
} else {
$this->dockerRootFilesystem = instant_remote_process(
[
"stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')"
],
$server,
false
);
}
if (!$this->dockerRootFilesystem) {
continue;
}
$this->usageBefore = $this->getFilesystemUsage($server);
if ($this->usageBefore >= $server->settings->cleanup_after_percentage) {
ray('Cleaning up ' . $server->name)->color('orange');
instant_remote_process(['docker image prune -af'], $server);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server);
instant_remote_process(['docker builder prune -af'], $server);
$usageAfter = $this->getFilesystemUsage($server);
if ($usageAfter < $this->usageBefore) {
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $server->name)->color('orange');
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $server->name);
} else {
ray('DockerCleanupJob failed to save disk space on ' . $server->name)->color('orange');
}
} else {
ray('No need to clean up ' . $server->name)->color('orange');
ray('DockerCleanupJob failed to save disk space on ' . $this->server->name)->color('orange');
}
} else {
ray('No need to clean up ' . $this->server->name)->color('orange');
}
} catch (\Throwable $e) {
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
@@ -83,8 +71,8 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
}
}
private function getFilesystemUsage(Server $server)
private function getFilesystemUsage()
{
return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $server, false);
return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this->server, false);
}
}

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