mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
1119 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93fb14884e | ||
|
|
26ccc4afb4 | ||
|
|
5fda1bb932 | ||
|
|
409ba8a1bb | ||
|
|
49f5240ff8 | ||
|
|
0c3ed3d393 | ||
|
|
6e3dc474f2 | ||
|
|
d3eb87561e | ||
|
|
8b58c8f856 | ||
|
|
8c60ef5bd6 | ||
|
|
1d59383c78 | ||
|
|
60f590454d | ||
|
|
dcb61a553e | ||
|
|
e06e31642f | ||
|
|
9dfce48380 | ||
|
|
8eed87e2f7 | ||
|
|
d56d4eb8fc | ||
|
|
fd32cd04ab | ||
|
|
1d3b7ffd3b | ||
|
|
0b5baf60a5 | ||
|
|
bc31df6fb2 | ||
|
|
818399bc23 | ||
|
|
e7fdff0f69 | ||
|
|
6312c0ba84 | ||
|
|
44efe0b5e1 | ||
|
|
de7d584648 | ||
|
|
b9f12d2586 | ||
|
|
c76e8bb0de | ||
|
|
3b655f8e3f | ||
|
|
2b9df41444 | ||
|
|
628fec6904 | ||
|
|
f36135cbfc | ||
|
|
75fe005055 | ||
|
|
8ff7aeb78b | ||
|
|
f1a9e28d5a | ||
|
|
843cd90ee5 | ||
|
|
1cbfd03912 | ||
|
|
ce60a39dc5 | ||
|
|
f1e4395a83 | ||
|
|
52fd7ad571 | ||
|
|
5f797ec0ae | ||
|
|
21e77bf0c1 | ||
|
|
0686e48e89 | ||
|
|
1cef233db2 | ||
|
|
907e52572c | ||
|
|
795c8abf64 | ||
|
|
cc641d8cba | ||
|
|
d4668ef44a | ||
|
|
e8b539c3bd | ||
|
|
6555f0b50c | ||
|
|
bb05058dda | ||
|
|
9667cd4a7a | ||
|
|
3ae9501814 | ||
|
|
73d0948734 | ||
|
|
c3e2a741ea | ||
|
|
4792146f1d | ||
|
|
09b9305aa3 | ||
|
|
ff7d0d442d | ||
|
|
9a127bdc80 | ||
|
|
919e88afb4 | ||
|
|
1d1ec20cb7 | ||
|
|
5c29ecdf10 | ||
|
|
9e09c449cf | ||
|
|
09bcd693f5 | ||
|
|
0c15e45419 | ||
|
|
31cbf552a2 | ||
|
|
f7853ee174 | ||
|
|
de3a7b6eca | ||
|
|
b56c7c34cb | ||
|
|
49845f3da7 | ||
|
|
987409bae4 | ||
|
|
07d8461f96 | ||
|
|
f255a71434 | ||
|
|
2a2818ac0d | ||
|
|
fd3cdc2c7d | ||
|
|
84c3f832ae | ||
|
|
70c28fceeb | ||
|
|
f2c4f83f5a | ||
|
|
c8dd6f07ac | ||
|
|
561e424a7d | ||
|
|
c46d38907e | ||
|
|
5c334bbac6 | ||
|
|
9628072b0c | ||
|
|
39ecff9f90 | ||
|
|
d1daec060a | ||
|
|
fb5bea7f91 | ||
|
|
efc3ea6e40 | ||
|
|
ecefb9e1f5 | ||
|
|
a4dea2009a | ||
|
|
829e41f93f | ||
|
|
e7e3adc7fb | ||
|
|
a993fef235 | ||
|
|
81df71416c | ||
|
|
40af7aa025 | ||
|
|
050155328b | ||
|
|
376c081bed | ||
|
|
9f5e1fa9e3 | ||
|
|
7d139fd33b | ||
|
|
b75a2857a0 | ||
|
|
ab6c1ddc20 | ||
|
|
dc0b0980a9 | ||
|
|
788d1711db | ||
|
|
d92dc4c5e6 | ||
|
|
4bf34aea62 | ||
|
|
7e9a54ce67 | ||
|
|
f8c19e1fb3 | ||
|
|
27c1bda09b | ||
|
|
1af7ffcdc4 | ||
|
|
39647367a5 | ||
|
|
ae4b263810 | ||
|
|
8901bb5df8 | ||
|
|
053aa25d2c | ||
|
|
7a7157c155 | ||
|
|
0c5e8600bd | ||
|
|
048e153025 | ||
|
|
e7cafe6850 | ||
|
|
1385a86084 | ||
|
|
348923ae02 | ||
|
|
7d754558b0 | ||
|
|
744609e7e9 | ||
|
|
9bd05b65a3 | ||
|
|
d42934f258 | ||
|
|
ff752e2411 | ||
|
|
4120fba9a8 | ||
|
|
6ecb9c21ce | ||
|
|
01f7b07fa3 | ||
|
|
54d8cb9027 | ||
|
|
238337fecb | ||
|
|
7a51acbf8d | ||
|
|
fb478c79b3 | ||
|
|
abcc004953 | ||
|
|
2edf71a0dd | ||
|
|
cbec39099a | ||
|
|
dba5499182 | ||
|
|
2fdf52929c | ||
|
|
8128dfc061 | ||
|
|
2b394d6fea | ||
|
|
964ded1d0b | ||
|
|
838c3830d6 | ||
|
|
2db93bd9b9 | ||
|
|
2f82dedd4f | ||
|
|
8106602d15 | ||
|
|
3d0bf6b472 | ||
|
|
ba7a7e9695 | ||
|
|
dd0ad04384 | ||
|
|
910a1f43a9 | ||
|
|
e2f959ce4c | ||
|
|
68fe886fb0 | ||
|
|
4631c73809 | ||
|
|
77558b37da | ||
|
|
e060409a76 | ||
|
|
af01bc3e77 | ||
|
|
1e158badfc | ||
|
|
c620bb58ed | ||
|
|
8a91395472 | ||
|
|
c5f3398b73 | ||
|
|
3878527de8 | ||
|
|
4abcb2d5b9 | ||
|
|
a635e51486 | ||
|
|
b6ce2e9122 | ||
|
|
8c60dd5523 | ||
|
|
94e2d951c4 | ||
|
|
381e24bea5 | ||
|
|
2b1e35980f | ||
|
|
a42c8da344 | ||
|
|
7a0e415ecf | ||
|
|
d721f4809a | ||
|
|
22431eee9a | ||
|
|
c058c0a766 | ||
|
|
1724c0d3ff | ||
|
|
0b8f48230f | ||
|
|
e8d84b7067 | ||
|
|
5236bbc757 | ||
|
|
094e1d1bba | ||
|
|
68b25523d6 | ||
|
|
bdc478d5f5 | ||
|
|
002472d7c6 | ||
|
|
0d65bf62b9 | ||
|
|
01c7e76071 | ||
|
|
884ae0efb0 | ||
|
|
8e7040bf7c | ||
|
|
059e6a88eb | ||
|
|
9947158f7e | ||
|
|
61aa9e8766 | ||
|
|
75813a289c | ||
|
|
af11d8cf3d | ||
|
|
48990db699 | ||
|
|
da71353bfa | ||
|
|
0f5559bc61 | ||
|
|
1afb509c33 | ||
|
|
bccca6e874 | ||
|
|
083dc15053 | ||
|
|
1b6d376472 | ||
|
|
891deee05a | ||
|
|
5ffbba908b | ||
|
|
f762959c9f | ||
|
|
90a5a23fd9 | ||
|
|
94e87141ff | ||
|
|
fceaf3e94b | ||
|
|
3be554cb55 | ||
|
|
27b18fbedf | ||
|
|
5e7c6906b3 | ||
|
|
d05ffe32a3 | ||
|
|
f1298d1db4 | ||
|
|
408738e08d | ||
|
|
8d04fbdb74 | ||
|
|
dccb31d17e | ||
|
|
f61210287e | ||
|
|
18ad7220f0 | ||
|
|
79e0df1d43 | ||
|
|
a2f53085e5 | ||
|
|
c5782252ea | ||
|
|
bf3d88facd | ||
|
|
e45b0bf715 | ||
|
|
9db6c12eea | ||
|
|
3a391b69e8 | ||
|
|
cc1fb83c79 | ||
|
|
efa5dd28f1 | ||
|
|
f1eddae379 | ||
|
|
34febe670d | ||
|
|
3137131a1a | ||
|
|
1b6546d26c | ||
|
|
b9f820cef4 | ||
|
|
d8639f58d7 | ||
|
|
cf9be9355f | ||
|
|
e36bb11ba8 | ||
|
|
190beb3d3f | ||
|
|
890a6925d1 | ||
|
|
d03b8420f8 | ||
|
|
cbd3c880c3 | ||
|
|
6b24001876 | ||
|
|
6bb45430c9 | ||
|
|
bc6b4ed850 | ||
|
|
8a63ef5da9 | ||
|
|
e324866a27 | ||
|
|
0e5f733657 | ||
|
|
c5932ed337 | ||
|
|
ef428f844f | ||
|
|
eb8b752a6e | ||
|
|
ce0b38035c | ||
|
|
562a8f1fac | ||
|
|
68f1621757 | ||
|
|
e3087573bb | ||
|
|
7869f223a3 | ||
|
|
0e99f27108 | ||
|
|
f445a8c312 | ||
|
|
845fc191d4 | ||
|
|
95d0d72e0d | ||
|
|
76f695036c | ||
|
|
225bf06736 | ||
|
|
3a287ae974 | ||
|
|
e5c61b9f9f | ||
|
|
32bc876dfc | ||
|
|
c9b3d2a43d | ||
|
|
f343210e7c | ||
|
|
4a42bff0dc | ||
|
|
36931b5b18 | ||
|
|
3b080abada | ||
|
|
7feba4bbaa | ||
|
|
eef8c756df | ||
|
|
9ed30cb0dc | ||
|
|
6bc43bd999 | ||
|
|
e7683ee9a5 | ||
|
|
ee71aeaa36 | ||
|
|
404c664500 | ||
|
|
aa80392b46 | ||
|
|
c9509ef658 | ||
|
|
31e08a24c9 | ||
|
|
14b32e30cd | ||
|
|
5aaad66fe5 | ||
|
|
b6745c691b | ||
|
|
5ee29c6072 | ||
|
|
b69584fe26 | ||
|
|
4c3907c296 | ||
|
|
bf44b4b949 | ||
|
|
bee7a2357b | ||
|
|
98704fc3c2 | ||
|
|
e286e78ddc | ||
|
|
557e1407d0 | ||
|
|
3c99f24b5a | ||
|
|
512197021b | ||
|
|
e233ec05b5 | ||
|
|
d0e3a20a65 | ||
|
|
e2e6813632 | ||
|
|
963c519c38 | ||
|
|
d04513d817 | ||
|
|
64a7f27e37 | ||
|
|
65652142b2 | ||
|
|
7691319c59 | ||
|
|
206bd50d00 | ||
|
|
6159783a73 | ||
|
|
ed5f831c86 | ||
|
|
65be83e75d | ||
|
|
25a471b045 | ||
|
|
60c7a29aa8 | ||
|
|
11ab6669a0 | ||
|
|
53965ab8ed | ||
|
|
ea271ca079 | ||
|
|
f16d0f650f | ||
|
|
cb80341a78 | ||
|
|
83d96c8d11 | ||
|
|
a8ca57d095 | ||
|
|
2d936a4b22 | ||
|
|
0653eb8511 | ||
|
|
cc64132627 | ||
|
|
e0391e5abd | ||
|
|
025135bd2a | ||
|
|
5e5873a08d | ||
|
|
14652c2878 | ||
|
|
9bbe9567c7 | ||
|
|
7913a639b5 | ||
|
|
adecf328fc | ||
|
|
a7ef5c456d | ||
|
|
117f1d4155 | ||
|
|
075f3ce930 | ||
|
|
d5b3e88fc4 | ||
|
|
ba55e0c1bb | ||
|
|
54671354f0 | ||
|
|
a2c7e8d455 | ||
|
|
32dbdf5204 | ||
|
|
019887739c | ||
|
|
5596e41f2b | ||
|
|
6a73a00a1f | ||
|
|
4121c9dd8b | ||
|
|
e4781dc129 | ||
|
|
1c90f46f2a | ||
|
|
e78d851c85 | ||
|
|
52d05005ed | ||
|
|
f03aa57758 | ||
|
|
8c20c833ba | ||
|
|
2fe6766b7f | ||
|
|
d9599da4a8 | ||
|
|
3f453ba7c0 | ||
|
|
bd02c3055a | ||
|
|
7ea7d85d15 | ||
|
|
d38350c282 | ||
|
|
a83c70004c | ||
|
|
49e1404a2c | ||
|
|
76f23e7dbf | ||
|
|
ad8653f54d | ||
|
|
8939d77051 | ||
|
|
37be4a1796 | ||
|
|
e4c923e358 | ||
|
|
62ca3ffaa5 | ||
|
|
9af3ce4be5 | ||
|
|
fe143ef8a5 | ||
|
|
5fb5845e90 | ||
|
|
794cfbd8eb | ||
|
|
29ee9915f3 | ||
|
|
331d485213 | ||
|
|
665e3761c4 | ||
|
|
ac19f0e34f | ||
|
|
d7cfa0578f | ||
|
|
694169bb84 | ||
|
|
ab853cac87 | ||
|
|
51db2f797d | ||
|
|
0c90d3d0a1 | ||
|
|
51efe23690 | ||
|
|
e3ee84105c | ||
|
|
6cbd61ac6c | ||
|
|
638d0c8c99 | ||
|
|
aecc81fe9d | ||
|
|
c9a1437870 | ||
|
|
66b41b3d4c | ||
|
|
c41cfe2a2f | ||
|
|
5f2ad56529 | ||
|
|
cd842bc1b2 | ||
|
|
27b6aad53a | ||
|
|
64b58b7661 | ||
|
|
94960d96a9 | ||
|
|
2549244f97 | ||
|
|
5bfffce33b | ||
|
|
3a4f19f368 | ||
|
|
50e17ed932 | ||
|
|
a8fcd7aee4 | ||
|
|
87036cc49b | ||
|
|
e48842c6ec | ||
|
|
b9e405c497 | ||
|
|
f75effe022 | ||
|
|
a745f568f3 | ||
|
|
e2e3ad0358 | ||
|
|
ba769f5fb7 | ||
|
|
0126286731 | ||
|
|
7952202435 | ||
|
|
798acb8ee5 | ||
|
|
ef595dd4c2 | ||
|
|
70c662daf8 | ||
|
|
8ae385b9f9 | ||
|
|
802a0f7684 | ||
|
|
62c38c9859 | ||
|
|
27c36bec83 | ||
|
|
c6b8eabe10 | ||
|
|
967fca9eca | ||
|
|
40a239ddda | ||
|
|
99d07981cf | ||
|
|
b3ee6b7144 | ||
|
|
468ad7d904 | ||
|
|
f1aa97e374 | ||
|
|
3b6d3343c7 | ||
|
|
ab2f9f073f | ||
|
|
67131152cc | ||
|
|
3bda289428 | ||
|
|
fadfa0ad8e | ||
|
|
11a957c6c9 | ||
|
|
b46de99af9 | ||
|
|
03420076c9 | ||
|
|
549446abdf | ||
|
|
06ab2145ca | ||
|
|
5d088e530e | ||
|
|
123e6eddd7 | ||
|
|
2c17e431ac | ||
|
|
fe6073ba7d | ||
|
|
8a9ad04744 | ||
|
|
75d1ec4f42 | ||
|
|
a0abde8652 | ||
|
|
db13dd9304 | ||
|
|
638bcf9732 | ||
|
|
b06b465ffa | ||
|
|
02c8b9f471 | ||
|
|
1ff1664b6c | ||
|
|
52d84c5e9e | ||
|
|
e0289e2949 | ||
|
|
ff8d8371ad | ||
|
|
69343f974a | ||
|
|
2dc175be63 | ||
|
|
d93bf97919 | ||
|
|
4ea8916d53 | ||
|
|
f7fca69a23 | ||
|
|
f954ee15c3 | ||
|
|
3c54e01d87 | ||
|
|
00d708610d | ||
|
|
f3b04c1ef9 | ||
|
|
6b751f965b | ||
|
|
d3c9894479 | ||
|
|
f68aace445 | ||
|
|
f042c70b3c | ||
|
|
2116d79aad | ||
|
|
4bc63e283c | ||
|
|
d5804f99c2 | ||
|
|
dfc353ce54 | ||
|
|
8f65ddb754 | ||
|
|
29e750f0b2 | ||
|
|
b24661b876 | ||
|
|
bbbd605f32 | ||
|
|
ff13cb4e26 | ||
|
|
5cb572b6a5 | ||
|
|
d8b97e06cf | ||
|
|
becb4df950 | ||
|
|
20dc2b47fe | ||
|
|
67df166c20 | ||
|
|
87c3d0048c | ||
|
|
1c71ac78e2 | ||
|
|
41181cac12 | ||
|
|
41b6df0e6e | ||
|
|
4f3f98be0a | ||
|
|
7a97a4b69c | ||
|
|
6ae87466ca | ||
|
|
5159d47159 | ||
|
|
0138d04080 | ||
|
|
c803768e5f | ||
|
|
60c8e0d625 | ||
|
|
dd99ad0af8 | ||
|
|
24a1f02af5 | ||
|
|
601a1e128e | ||
|
|
ccb9769e67 | ||
|
|
d79da996d3 | ||
|
|
4f800f5331 | ||
|
|
a19a58338c | ||
|
|
8a80dbd5d8 | ||
|
|
ec5cca7b3e | ||
|
|
ce721c1764 | ||
|
|
f4d7c4f942 | ||
|
|
40716550ec | ||
|
|
f0ee26cd86 | ||
|
|
423dfc6280 | ||
|
|
6d9a66ff1b | ||
|
|
17c8872130 | ||
|
|
3ffa2b6b8d | ||
|
|
35134f2327 | ||
|
|
8ed4b540e1 | ||
|
|
47202a7951 | ||
|
|
fe6e76ad0d | ||
|
|
e9920f05f5 | ||
|
|
57a39f12bb | ||
|
|
ba85e3bc8b | ||
|
|
1fd12832ca | ||
|
|
a8807b8d09 | ||
|
|
dbc55233cb | ||
|
|
af6d94c0d8 | ||
|
|
e022492770 | ||
|
|
5e03979f9c | ||
|
|
956416b522 | ||
|
|
2846e049fa | ||
|
|
3ffd3fc819 | ||
|
|
63dff5961e | ||
|
|
4b024017ad | ||
|
|
720bb8c478 | ||
|
|
771dc30b81 | ||
|
|
6f97af096d | ||
|
|
3d28669cad | ||
|
|
efe043ec9d | ||
|
|
6bb79e10bc | ||
|
|
ba2e4c06f1 | ||
|
|
1bfb9637ba | ||
|
|
d7e9821582 | ||
|
|
08585f7e9a | ||
|
|
3eedb43b66 | ||
|
|
26aca6a4c0 | ||
|
|
3cbf8c281d | ||
|
|
d931d57373 | ||
|
|
4e680deb93 | ||
|
|
6a6275d4fa | ||
|
|
efd9087b74 | ||
|
|
6882ce8d0f | ||
|
|
eccd41217f | ||
|
|
538de9bd81 | ||
|
|
a249ee1b1f | ||
|
|
828fec9448 | ||
|
|
205995cabe | ||
|
|
4071e096bc | ||
|
|
14bac0f0d7 | ||
|
|
69c124032c | ||
|
|
b55bd298f2 | ||
|
|
86c2415210 | ||
|
|
82f3d54bc3 | ||
|
|
a6e76dfabc | ||
|
|
c1f6bf41f5 | ||
|
|
f934dfef33 | ||
|
|
19c66c6628 | ||
|
|
d7d948caf6 | ||
|
|
1b34337fe8 | ||
|
|
718603e37e | ||
|
|
9df0a2e545 | ||
|
|
7ffebd71f3 | ||
|
|
2f286a6595 | ||
|
|
13701f6030 | ||
|
|
91f224ddea | ||
|
|
8cffae10b2 | ||
|
|
1158b2f4db | ||
|
|
f542bcf428 | ||
|
|
22178df8ae | ||
|
|
acfe1daf9b | ||
|
|
fb3f71881f | ||
|
|
0f7546a4dc | ||
|
|
6ccafacc87 | ||
|
|
852c2df12e | ||
|
|
4c752951ab | ||
|
|
f32191b889 | ||
|
|
31251ef6cb | ||
|
|
e9365aa09b | ||
|
|
e5c860319f | ||
|
|
ceedd5225f | ||
|
|
61efdfb7c1 | ||
|
|
fcef5d902e | ||
|
|
2073b8949b | ||
|
|
5c59a752e3 | ||
|
|
caf6e3a23e | ||
|
|
8eb1c4da46 | ||
|
|
bab2f391ed | ||
|
|
131c6df82a | ||
|
|
9f44d0c47a | ||
|
|
dff7ed5b7b | ||
|
|
54b6472f3b | ||
|
|
04a36e5c90 | ||
|
|
b2f851272b | ||
|
|
778915599f | ||
|
|
a6f9527157 | ||
|
|
db976709c7 | ||
|
|
7e4947ba07 | ||
|
|
e2578a7dd0 | ||
|
|
7d028b15f5 | ||
|
|
d240bfda8b | ||
|
|
d59ec2548a | ||
|
|
e0cefc787a | ||
|
|
0496198d0f | ||
|
|
cbf7f1fa41 | ||
|
|
66587d864a | ||
|
|
b106705c8d | ||
|
|
5cc22a8271 | ||
|
|
cd22863a8c | ||
|
|
118a02f70d | ||
|
|
862177d61a | ||
|
|
41280aa780 | ||
|
|
3a987c8a6e | ||
|
|
29ca461a9a | ||
|
|
d48b72c160 | ||
|
|
ce49f26c53 | ||
|
|
02989678be | ||
|
|
810b55163c | ||
|
|
ac13ba0957 | ||
|
|
a3e088199f | ||
|
|
42ee4ca032 | ||
|
|
17deff4d86 | ||
|
|
8b6323b906 | ||
|
|
a696b5271a | ||
|
|
31959f26d4 | ||
|
|
45d2f80f69 | ||
|
|
53975fcf61 | ||
|
|
76296c1f19 | ||
|
|
c25baf69e1 | ||
|
|
f952512615 | ||
|
|
c6557eada8 | ||
|
|
2c2d74c0d6 | ||
|
|
028a2eb275 | ||
|
|
ce7fad5bef | ||
|
|
cd7852e4f9 | ||
|
|
7b022a2482 | ||
|
|
12d9b6538b | ||
|
|
335788c2d6 | ||
|
|
2352e4a71d | ||
|
|
dc03179bd1 | ||
|
|
cc72f416e8 | ||
|
|
3b67d0a8de | ||
|
|
0135ba7e89 | ||
|
|
a28a28cd23 | ||
|
|
b52680a2d8 | ||
|
|
0670e6c1d6 | ||
|
|
c3882b75c1 | ||
|
|
64b6f86a36 | ||
|
|
b9efc22253 | ||
|
|
e3d9eb0154 | ||
|
|
66f3967479 | ||
|
|
c54439e84c | ||
|
|
db4a4c74fc | ||
|
|
5c7ef80219 | ||
|
|
243d1c06fc | ||
|
|
ef25f7d800 | ||
|
|
45640ffdb1 | ||
|
|
378291b209 | ||
|
|
3583e552f1 | ||
|
|
d7dfeaf988 | ||
|
|
7fe5eca661 | ||
|
|
0dff57e69f | ||
|
|
f4803ad58b | ||
|
|
2d7bbbe300 | ||
|
|
928b68043b | ||
|
|
b21add0210 | ||
|
|
c41ffd6bfb | ||
|
|
b4874c7df3 | ||
|
|
c505a6ce9c | ||
|
|
706e4b13ee | ||
|
|
4af471ee31 | ||
|
|
87062e4e22 | ||
|
|
500ba0fab8 | ||
|
|
1c72c127d5 | ||
|
|
69bb4ae5ee | ||
|
|
5f8b8bd730 | ||
|
|
44f6d93639 | ||
|
|
e35b8a0f96 | ||
|
|
b26e23e7c3 | ||
|
|
e6f7e32037 | ||
|
|
1c386db41d | ||
|
|
085b655d9f | ||
|
|
2788fcf4e1 | ||
|
|
d058e04213 | ||
|
|
066f171163 | ||
|
|
2001be07d0 | ||
|
|
39552cc42f | ||
|
|
7f5d7e0eb0 | ||
|
|
0eda49b104 | ||
|
|
636995d0e4 | ||
|
|
4c0623f022 | ||
|
|
3e2e1080f5 | ||
|
|
3f866a07d8 | ||
|
|
23571ae104 | ||
|
|
c1710c8f7b | ||
|
|
d4d2cc71a0 | ||
|
|
8d86d53292 | ||
|
|
fae97e4dee | ||
|
|
8d0c3abf2e | ||
|
|
d396f649df | ||
|
|
ec21155c9e | ||
|
|
58111f53b9 | ||
|
|
2cbe1e8489 | ||
|
|
10e5a58b9e | ||
|
|
6f886e8b6f | ||
|
|
f96a91eb31 | ||
|
|
65a1961722 | ||
|
|
c5a932ab88 | ||
|
|
d1e10dacc0 | ||
|
|
96327af838 | ||
|
|
1cb6d594d0 | ||
|
|
16261fc36e | ||
|
|
cff694b0c4 | ||
|
|
97fd56b9e4 | ||
|
|
72cfa3e7b0 | ||
|
|
3cf41e1e23 | ||
|
|
2a7a63a672 | ||
|
|
7fb9e672cf | ||
|
|
9012f6b953 | ||
|
|
407eba8b76 | ||
|
|
68f6ab5796 | ||
|
|
3dd36a2271 | ||
|
|
7f69eb3c2e | ||
|
|
6ccbf911b2 | ||
|
|
5c77cec68f | ||
|
|
25a0489f7f | ||
|
|
5e27b88bef | ||
|
|
ec98afe707 | ||
|
|
ce26127705 | ||
|
|
ef7fc1b260 | ||
|
|
f58e6766e1 | ||
|
|
4a21102983 | ||
|
|
e78b6758d8 | ||
|
|
16eb7f4fb4 | ||
|
|
4974ce6eda | ||
|
|
6cdba17aca | ||
|
|
30f8e8f232 | ||
|
|
608f0b7840 | ||
|
|
d0366c4054 | ||
|
|
f88e3c5b29 | ||
|
|
e33fec0e1a | ||
|
|
912b0a263e | ||
|
|
8f963adbd4 | ||
|
|
8f2c24d7e9 | ||
|
|
9f3dbc3cbb | ||
|
|
8a9ee84925 | ||
|
|
689480003a | ||
|
|
3b20eee909 | ||
|
|
e8cadc176b | ||
|
|
b0c96e64c9 | ||
|
|
9ce3b43e09 | ||
|
|
4c2b3df861 | ||
|
|
467471f54a | ||
|
|
60171093c5 | ||
|
|
38f2a2dac7 | ||
|
|
307ee52ac0 | ||
|
|
b66c9835b7 | ||
|
|
d38d50dca2 | ||
|
|
40023be4ea | ||
|
|
48d7c6e76f | ||
|
|
debacfe2f7 | ||
|
|
d430813230 | ||
|
|
e30c37b041 | ||
|
|
8c73068cc7 | ||
|
|
2c4e69ad50 | ||
|
|
5ae08d009e | ||
|
|
673b944647 | ||
|
|
16281248ac | ||
|
|
8670b41671 | ||
|
|
9c69044da5 | ||
|
|
ebc4ab9af5 | ||
|
|
57738198ad | ||
|
|
b8252b85b0 | ||
|
|
479c2743bd | ||
|
|
81e6482d7a | ||
|
|
88c5d87084 | ||
|
|
ccb972dcb9 | ||
|
|
6c7e091e1b | ||
|
|
91e3d33c0b | ||
|
|
aa00389824 | ||
|
|
b4e54ab3e3 | ||
|
|
8f3c5d4bd3 | ||
|
|
26668c71a1 | ||
|
|
bd7637c696 | ||
|
|
cff54f48a3 | ||
|
|
5c0f239f62 | ||
|
|
d56c28c8d9 | ||
|
|
2b666ff121 | ||
|
|
fb42c43953 | ||
|
|
81437e6822 | ||
|
|
2fe429fe92 | ||
|
|
4f0b214042 | ||
|
|
c866213f34 | ||
|
|
7cec6330cf | ||
|
|
f5de21a343 | ||
|
|
ecbfc4d790 | ||
|
|
55ff00e028 | ||
|
|
a0fc2bbb85 | ||
|
|
51a704b22a | ||
|
|
6d49678842 | ||
|
|
0459b3a115 | ||
|
|
82592c8222 | ||
|
|
25bf8895e2 | ||
|
|
f4f7bdf7d5 | ||
|
|
c008564aa3 | ||
|
|
b825d98b2d | ||
|
|
1f711d9281 | ||
|
|
1de850f640 | ||
|
|
f176247b02 | ||
|
|
3f3a1283df | ||
|
|
087bfcad08 | ||
|
|
efd2899ae3 | ||
|
|
e4b2195932 | ||
|
|
0590ed7b2e | ||
|
|
3a3c9448a4 | ||
|
|
36d65ad5a8 | ||
|
|
8db66952e8 | ||
|
|
45fa88ca4d | ||
|
|
84b74f0b57 | ||
|
|
423cf62d92 | ||
|
|
c4d9deabef | ||
|
|
776b1cb68d | ||
|
|
fc3025398e | ||
|
|
457c16c4dc | ||
|
|
ccf63c67e8 | ||
|
|
945157b30c | ||
|
|
13798392be | ||
|
|
0d05b0a3d6 | ||
|
|
e0d2f88d99 | ||
|
|
e260bfae02 | ||
|
|
5abd4a6d78 | ||
|
|
9dff1e5631 | ||
|
|
02332ade1b | ||
|
|
486de58d5b | ||
|
|
606aeb2b61 | ||
|
|
3fc264560c | ||
|
|
3dd9182281 | ||
|
|
c838ff7198 | ||
|
|
ca6db9c1a9 | ||
|
|
f27e00e80e | ||
|
|
60cf296f31 | ||
|
|
ea64e9d5ad | ||
|
|
55846c5635 | ||
|
|
7763594e6e | ||
|
|
6b5339c1c1 | ||
|
|
f2980738e4 | ||
|
|
f0e3ad0461 | ||
|
|
187050e098 | ||
|
|
9e7823795d | ||
|
|
239459dfa8 | ||
|
|
ce0f560c44 | ||
|
|
95baec99dd | ||
|
|
363e8fc0b5 | ||
|
|
e49caba920 | ||
|
|
30db2b2a09 | ||
|
|
285666e181 | ||
|
|
003934ee1d | ||
|
|
44c7958aa6 | ||
|
|
35b1a81dfe | ||
|
|
e40f397cc7 | ||
|
|
9fd8cd7e6c | ||
|
|
a94b7ee611 | ||
|
|
fc68bf50b5 | ||
|
|
0f99ee787c | ||
|
|
95777e978e | ||
|
|
fb0b9dbfed | ||
|
|
9617000daa | ||
|
|
1818404172 | ||
|
|
d9a966fd98 | ||
|
|
763ce5fc14 | ||
|
|
df021760a7 | ||
|
|
fb2598f2e4 | ||
|
|
7af07b2718 | ||
|
|
23a94c9378 | ||
|
|
ed34fc9645 | ||
|
|
cafd9e0ab2 | ||
|
|
e882477e21 | ||
|
|
db0e3cfcc4 | ||
|
|
b3c4429028 | ||
|
|
87ab4bd71e | ||
|
|
61e1fdede9 | ||
|
|
b189919f97 | ||
|
|
8f5b084931 | ||
|
|
eb96a5ae7b | ||
|
|
f0fb9dbb94 | ||
|
|
cb2d4b4a0a | ||
|
|
3aace2d4f9 | ||
|
|
8c2ed75653 | ||
|
|
8c8aafbc65 | ||
|
|
a2c39fd07e | ||
|
|
9698a051d9 | ||
|
|
51423394ba | ||
|
|
8e5e36dd5b | ||
|
|
c1dd05dcd8 | ||
|
|
dd1ce6ee6c | ||
|
|
fe4c6d396c | ||
|
|
8db54ec069 | ||
|
|
3abc720926 | ||
|
|
64cc0b63f1 | ||
|
|
c78068466b | ||
|
|
88e407756d | ||
|
|
1538116e6e | ||
|
|
aba47d58a4 | ||
|
|
e7f184dd82 | ||
|
|
2ad8d7812b | ||
|
|
8212bb99a1 | ||
|
|
5fc382d09d | ||
|
|
78a80c46da | ||
|
|
c365d132af | ||
|
|
4dc3db3845 | ||
|
|
b9427d2ec1 | ||
|
|
332a0b9e04 | ||
|
|
18e98aaf52 | ||
|
|
a7f9fad627 | ||
|
|
b01f6ac414 | ||
|
|
e1bc2cc406 | ||
|
|
74830b12f3 | ||
|
|
56a977c676 | ||
|
|
a0bb5733e6 | ||
|
|
516e10ddf2 | ||
|
|
2976c72e09 | ||
|
|
7377e9e415 | ||
|
|
d77c55148b | ||
|
|
ad7aa2eed6 | ||
|
|
5cec50efbe | ||
|
|
ef8686d4da | ||
|
|
581cc73cd4 | ||
|
|
358fbf6b3d | ||
|
|
ca0535c285 | ||
|
|
9007a645a6 | ||
|
|
68b1b9774d | ||
|
|
b9b4c23d5b | ||
|
|
149fee2452 | ||
|
|
87af9e46a6 | ||
|
|
d6f87d3fb6 | ||
|
|
493af61233 | ||
|
|
ab03908f1d | ||
|
|
9ef7cf3c12 | ||
|
|
eab7fd44d4 | ||
|
|
0d1d25a945 | ||
|
|
534372c29c | ||
|
|
1ccb239797 | ||
|
|
66287b43d0 | ||
|
|
143e4e0d23 | ||
|
|
73f3a09157 | ||
|
|
5ce449aa08 | ||
|
|
6203804713 | ||
|
|
0858faf628 | ||
|
|
a84f3e0577 | ||
|
|
8d571a5eab | ||
|
|
7a117c61c4 | ||
|
|
8b034f15fc | ||
|
|
b4a6499c83 | ||
|
|
c083acaeef | ||
|
|
9c6d8320d8 | ||
|
|
0e7a304610 | ||
|
|
83993cbbb2 | ||
|
|
6840ddd3e6 | ||
|
|
2c6ece62bb | ||
|
|
3f8514050e | ||
|
|
a4a653603e | ||
|
|
b6d8851c99 | ||
|
|
bcd7697f50 | ||
|
|
f1da735c40 | ||
|
|
01331c287b | ||
|
|
3320de787a | ||
|
|
2bddb09384 | ||
|
|
6f673d7a07 | ||
|
|
0a5a101ef4 | ||
|
|
88590fbf0f | ||
|
|
90291b2edf | ||
|
|
070573f0df | ||
|
|
e583beb753 | ||
|
|
d31683df61 | ||
|
|
0a83ed82fa | ||
|
|
4031e477ee | ||
|
|
0c1991d1de | ||
|
|
05b697b18c | ||
|
|
061aeba605 | ||
|
|
f446e784cc | ||
|
|
72fe24d98e | ||
|
|
0cd3a3d848 | ||
|
|
126b2dc65b | ||
|
|
a0031efce0 | ||
|
|
3bffe3f010 | ||
|
|
b9a37233a2 | ||
|
|
8ae18f49dc | ||
|
|
4feb99cbe0 | ||
|
|
aab122d97e | ||
|
|
658d608f55 | ||
|
|
0838343841 | ||
|
|
b557ea1e1d | ||
|
|
4520070df3 | ||
|
|
be8ea78b1b | ||
|
|
1175d68ab5 | ||
|
|
f56d373ed2 | ||
|
|
c6253658ca | ||
|
|
4249aec936 | ||
|
|
25f80aba5f | ||
|
|
4550983761 | ||
|
|
b0238372a2 | ||
|
|
a021b71496 | ||
|
|
e3958d9626 | ||
|
|
55891d7001 | ||
|
|
b12ac8bb29 | ||
|
|
728a9f88eb | ||
|
|
57267c3ee0 | ||
|
|
d3d133ed1f | ||
|
|
f5240abbe5 | ||
|
|
abf5840f97 | ||
|
|
dc6d5af4aa | ||
|
|
0b88cd69f2 | ||
|
|
ce165719d6 | ||
|
|
4f543ce20f | ||
|
|
55f957df21 | ||
|
|
38f59b9410 | ||
|
|
ebe6655349 | ||
|
|
038ea08ca7 | ||
|
|
ba424efd39 | ||
|
|
75aef0e60b | ||
|
|
eda8b34297 | ||
|
|
d8151ddb2e | ||
|
|
7925228f97 | ||
|
|
a7dc62aaa0 | ||
|
|
632dbd155b | ||
|
|
fe092bb7a5 | ||
|
|
928345c8ea | ||
|
|
52d6fb51d5 | ||
|
|
06d7c69487 | ||
|
|
756c7f81ca | ||
|
|
d7af57a95e | ||
|
|
722ff15fbd | ||
|
|
f9c469497e | ||
|
|
7ecbedb48a | ||
|
|
76878f66b9 | ||
|
|
b9afef50c4 | ||
|
|
83ebd1e649 | ||
|
|
76431c3fd5 | ||
|
|
96a4d0bbb0 | ||
|
|
4cfc739730 | ||
|
|
fcd0d8d359 | ||
|
|
3fcac0ac35 | ||
|
|
fcc8a7f0ed | ||
|
|
6950ead041 | ||
|
|
f78c49fc82 | ||
|
|
5f2581020b | ||
|
|
2fb674ae85 | ||
|
|
a95bd906bc | ||
|
|
21795cf788 | ||
|
|
6e98fd9403 | ||
|
|
ead1edc2b9 | ||
|
|
db822cb876 | ||
|
|
65bfce43c0 | ||
|
|
50fc05ab52 | ||
|
|
c9cf5c486f | ||
|
|
379f4b9dff | ||
|
|
aa02b8d433 | ||
|
|
70ecb92e82 | ||
|
|
d5cc2a2eed | ||
|
|
2b91bd24c5 | ||
|
|
5e8ac1b48e | ||
|
|
dc86170ef5 | ||
|
|
0232cf5b4c | ||
|
|
6e73f7f2e4 | ||
|
|
61c43804e3 | ||
|
|
72421d692b | ||
|
|
f801bb98cd | ||
|
|
b2d111e49a | ||
|
|
c82e02218f | ||
|
|
29f64076de | ||
|
|
393c334b12 | ||
|
|
678b264688 | ||
|
|
2620bfbf08 | ||
|
|
18c32decad | ||
|
|
a6f9e5f0af | ||
|
|
f187040b7e | ||
|
|
5510321776 | ||
|
|
69691b2ca7 | ||
|
|
8bfc1a7c06 | ||
|
|
554222abc7 | ||
|
|
b1a1aeeb75 | ||
|
|
91acd4cb6a | ||
|
|
6c5a1c317a | ||
|
|
b09a9f871e | ||
|
|
b5506f006b | ||
|
|
a6c3594448 | ||
|
|
5dd3952230 | ||
|
|
22ec0f8826 | ||
|
|
da6e04bb1a | ||
|
|
aaeacad781 | ||
|
|
b539f40fa5 | ||
|
|
fae340afcb | ||
|
|
69ebff1a7a | ||
|
|
5d9cfc393e | ||
|
|
e2a256b31c | ||
|
|
4855af7e57 | ||
|
|
a664174c02 | ||
|
|
c19c13b4e2 | ||
|
|
266b99bc25 | ||
|
|
51ef24e1fb | ||
|
|
33d38ccf40 | ||
|
|
f470ebbbe0 | ||
|
|
11bd46b200 | ||
|
|
53f5674771 | ||
|
|
c53d88902c | ||
|
|
e342c4fd65 | ||
|
|
aab7bd5e28 | ||
|
|
3adefb9e49 | ||
|
|
1bfce6716c | ||
|
|
c904441787 | ||
|
|
b7f79ae034 | ||
|
|
2d63fcdc7f | ||
|
|
c1d0cabcfb | ||
|
|
166419b13a | ||
|
|
cfc4d3acc7 | ||
|
|
13a0c2cf43 | ||
|
|
6ef6975432 | ||
|
|
2c40e93d3b | ||
|
|
a30ae4fb38 | ||
|
|
5b8785d1a9 | ||
|
|
f6f3364269 | ||
|
|
2f93f4450f | ||
|
|
2ad7c2b1ce | ||
|
|
6c848199ed | ||
|
|
76aab722b8 | ||
|
|
12290304c4 | ||
|
|
3a27d13c3e | ||
|
|
4f588ced96 | ||
|
|
e266c7cdec | ||
|
|
eedc3faba3 | ||
|
|
2e2c932f07 | ||
|
|
e4aed185a2 | ||
|
|
dddbe40bbe | ||
|
|
59d6818f70 | ||
|
|
7678cd47df | ||
|
|
b101fbacd4 | ||
|
|
a61a86dc3b | ||
|
|
0b3cde44c3 | ||
|
|
618d5d837c | ||
|
|
d234e8969d | ||
|
|
1be77b3fea | ||
|
|
6b302ab786 | ||
|
|
5831dd6196 | ||
|
|
3c623f13e2 | ||
|
|
da54c24e8d | ||
|
|
423d31f227 | ||
|
|
fbb063030d | ||
|
|
1968726cfe |
@@ -4,3 +4,7 @@ APP_KEY=
|
|||||||
|
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
PUSHER_APP_ID=
|
||||||
|
PUSHER_APP_KEY=
|
||||||
|
PUSHER_APP_SECRET=
|
||||||
|
|||||||
12
.env.windows-docker-desktop.example
Normal file
12
.env.windows-docker-desktop.example
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
IS_WINDOWS_DOCKER_DESKTOP=true
|
||||||
|
|
||||||
|
APP_ID=coolify-windows-docker-desktop
|
||||||
|
APP_NAME=Coolify
|
||||||
|
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
|
||||||
|
|
||||||
|
DB_PASSWORD=coolify
|
||||||
|
REDIS_PASSWORD=coolify
|
||||||
|
|
||||||
|
PUSHER_APP_ID=coolify
|
||||||
|
PUSHER_APP_KEY=coolify
|
||||||
|
PUSHER_APP_SECRET=coolify
|
||||||
84
.github/workflows/coolify-testing-host.yml
vendored
Normal file
84
.github/workflows/coolify-testing-host.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
name: Coolify Testing Host (v4-non-prod)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main", "next" ]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/coolify-testing-host.yml
|
||||||
|
- docker/testing-host/Dockerfile
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify-testing-host"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/testing-host/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
aarch64:
|
||||||
|
runs-on: [ self-hosted, arm64 ]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/testing-host/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [ amd64, aarch64 ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
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:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
102
.github/workflows/development-build.yml
vendored
102
.github/workflows/development-build.yml
vendored
@@ -2,7 +2,7 @@ name: Development Build (v4)
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["next"]
|
branches-ignore: ["main", "v3"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- .github/workflows/coolify-helper.yml
|
- .github/workflows/coolify-helper.yml
|
||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
@@ -13,67 +13,67 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: [self-hosted, x64]
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod-ssu/Dockerfile
|
file: docker/prod-ssu/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [self-hosted, arm64]
|
runs-on: [self-hosted, arm64]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod-ssu/Dockerfile
|
file: docker/prod-ssu/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
needs: [amd64, aarch64]
|
needs: [amd64, aarch64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
|
|||||||
24
.github/workflows/production-build.yml
vendored
24
.github/workflows/production-build.yml
vendored
@@ -10,11 +10,11 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: [self-hosted, x64]
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod-ssu/Dockerfile
|
file: docker/prod-ssu/Dockerfile
|
||||||
@@ -34,9 +34,9 @@ jobs:
|
|||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [self-hosted, arm64]
|
runs-on: [self-hosted, arm64]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod-ssu/Dockerfile
|
file: docker/prod-ssu/Dockerfile
|
||||||
@@ -61,13 +61,13 @@ jobs:
|
|||||||
needs: [amd64, aarch64]
|
needs: [amd64, aarch64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
22
.tinkerwell/snippets/DeleteUser.php
Normal file
22
.tinkerwell/snippets/DeleteUser.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
$email = 'test@example.com';
|
||||||
|
$user = User::whereEmail($email)->first();
|
||||||
|
$teams = $user->teams;
|
||||||
|
foreach ($teams as $team) {
|
||||||
|
$servers = $team->servers;
|
||||||
|
if ($servers->count() > 0) {
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
dump($server);
|
||||||
|
$server->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dump($team);
|
||||||
|
$team->delete();
|
||||||
|
}
|
||||||
|
if ($user) {
|
||||||
|
dump($user);
|
||||||
|
$user->delete();
|
||||||
|
}
|
||||||
28
.tinkerwell/snippets/SendEmail.php
Normal file
28
.tinkerwell/snippets/SendEmail.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @label Send Email
|
||||||
|
* @description Send email to all users
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
|
||||||
|
set_transanctional_email_settings();
|
||||||
|
|
||||||
|
$users = User::whereEmail('test@example.com');
|
||||||
|
foreach ($users as $user) {
|
||||||
|
Mail::send([], [], function ($message) use ($user) {
|
||||||
|
$message
|
||||||
|
->to($user->email)
|
||||||
|
->subject("Testing")
|
||||||
|
->text(
|
||||||
|
<<<EOF
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
Welcome to Coolify Cloud.
|
||||||
|
Here is your user id: $user->id
|
||||||
|
|
||||||
|
EOF
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,13 +6,14 @@
|
|||||||
You can ask for guidance anytime on our
|
You can ask for guidance anytime on our
|
||||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
||||||
|
|
||||||
|
## Code Contribution
|
||||||
|
|
||||||
## 1) Setup your development environment
|
### 1) Setup your development environment
|
||||||
|
|
||||||
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
- 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/).
|
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
||||||
|
|
||||||
## 2) Set your environment variables
|
### 2) Set your environment variables
|
||||||
|
|
||||||
- Copy [.env.development.example](./.env.development.example) to .env.
|
- Copy [.env.development.example](./.env.development.example) to .env.
|
||||||
|
|
||||||
@@ -21,9 +22,13 @@ You can ask for guidance anytime on our
|
|||||||
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
|
- 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.
|
- 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
|
||||||
|
|
||||||
## 4) Start development
|
|
||||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
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.
|
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
||||||
|
|
||||||
|
Mails are caught by Mailpit: `localhost:8025`
|
||||||
|
|
||||||
|
## New Service Contribution
|
||||||
|
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).
|
||||||
|
|
||||||
|
|||||||
115
README.md
115
README.md
@@ -4,65 +4,38 @@ Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Verc
|
|||||||
|
|
||||||
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
|
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
|
||||||
|
|
||||||
Image if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
||||||
|
|
||||||
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
||||||
|
|
||||||
For more information, take a look at our landing page [here](https://coolify.io).
|
For more information, take a look at our landing page [here](https://coolify.io).
|
||||||
|
|
||||||
> If you are looking for previous (v3) version, it is [here](https://github.com/coollabsio/coolify/tree/v3).
|
# Donations
|
||||||
|
To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
|
||||||
|
|
||||||
# Cloud
|
https://coolify.io/sponsorships
|
||||||
|
|
||||||
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
Thank you so much!
|
||||||
|
|
||||||
You can easily attach your own servers, get all the automations, free email notifications, etc.
|
|
||||||
|
|
||||||
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
|
||||||
|
|
||||||
# Beta
|
|
||||||
|
|
||||||
The latest version (v4) is still in beta. That does not mean it is unstable. All the features that are available are stable enough be usable in real-life.
|
|
||||||
|
|
||||||
There are hundreds of people using it for managing their client's applications, freelancers, hobbyists, businesses.
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
You can find the installation script [here](./scripts/install.sh).
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
Contact us [here](https://coolify.io/docs/contact).
|
|
||||||
|
|
||||||
## Recognitions
|
|
||||||
|
|
||||||
<a href="https://news.ycombinator.com/item?id=26624341">
|
|
||||||
<img
|
|
||||||
style="width: 250px; height: 54px;" width="250" height="54"
|
|
||||||
alt="Featured on Hacker News"
|
|
||||||
src="https://hackernews-badge.vercel.app/api?id=26624341"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
|
||||||
|
|
||||||
## 💰 Financial Contributors
|
|
||||||
|
|
||||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
|
||||||
|
|
||||||
### Organizations
|
|
||||||
|
|
||||||
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
|
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
|
||||||
|
|
||||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="appwrite logo" width="200"/></a>
|
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
|
||||||
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
|
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
|
||||||
|
|
||||||
Support this project with your organization. Your logo will show up here with a link to your website.
|
## Github Sponsors ($15+)
|
||||||
|
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
|
||||||
|
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a>
|
||||||
|
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
|
||||||
|
<a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a>
|
||||||
|
<a href="https://github.com/pixelinfinito"><img src="https://github.com/pixelinfinito.png" width="60px" alt="Pixel Infinito" /></a>
|
||||||
|
<a href="https://github.com/whitesidest"><img src="https://avatars.githubusercontent.com/u/12365916?s=52&v=4" width="60px" alt="Tyler Whitesides" /></a>
|
||||||
|
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
|
||||||
|
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
|
||||||
|
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
|
||||||
|
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
||||||
|
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
||||||
|
|
||||||
|
## Organizations
|
||||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
|
||||||
@@ -74,10 +47,56 @@ Support this project with your organization. Your logo will show up here with a
|
|||||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||||
|
|
||||||
### Individuals
|
## Individuals
|
||||||
|
|
||||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||||
|
|
||||||
## Star History
|
# Cloud
|
||||||
|
|
||||||
|
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
||||||
|
|
||||||
|
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
||||||
|
|
||||||
|
## Why should I use the Cloud version?
|
||||||
|
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
|
||||||
|
|
||||||
|
By subscribing to the cloud version, you get the Coolify server for the same price, but with:
|
||||||
|
- High-availability
|
||||||
|
- Free email notifications
|
||||||
|
- Better support
|
||||||
|
- Less maintenance for you
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
You can find the installation script source [here](./scripts/install.sh).
|
||||||
|
|
||||||
|
# Support
|
||||||
|
|
||||||
|
Contact us [here](https://coolify.io/docs/contact).
|
||||||
|
|
||||||
|
# Recognitions
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://news.ycombinator.com/item?id=26624341">
|
||||||
|
<img
|
||||||
|
style="width: 250px; height: 54px;" width="250" height="54"
|
||||||
|
alt="Featured on Hacker News"
|
||||||
|
src="https://hackernews-badge.vercel.app/api?id=26624341"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
|
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
|
# Repo Activity
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Star History
|
||||||
|
|
||||||
[](https://star-history.com/#coollabsio/coolify&Date)
|
[](https://star-history.com/#coollabsio/coolify&Date)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Application;
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopApplication
|
class StopApplication
|
||||||
@@ -12,19 +11,37 @@ class StopApplication
|
|||||||
public function handle(Application $application)
|
public function handle(Application $application)
|
||||||
{
|
{
|
||||||
$server = $application->destination->server;
|
$server = $application->destination->server;
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id);
|
if (!$server->isFunctional()) {
|
||||||
if ($containers->count() > 0) {
|
return 'Server is not functional';
|
||||||
foreach ($containers as $container) {
|
}
|
||||||
$containerName = data_get($container, 'Names');
|
if ($server->isSwarm()) {
|
||||||
if ($containerName) {
|
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
||||||
instant_remote_process(
|
} else {
|
||||||
["docker rm -f {$containerName}"],
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
$server
|
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));
|
||||||
|
}
|
||||||
|
// Delete Preview Deployments
|
||||||
|
$previewDeployments = $application->previews;
|
||||||
|
foreach ($previewDeployments as $previewDeployment) {
|
||||||
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id);
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$name = str_replace('/', '', $container['Names']);
|
||||||
|
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: make notification for application
|
|
||||||
// $application->environment->project->team->notify(new StatusChanged($application));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class PrepareCoolifyTask
|
|||||||
|
|
||||||
public function __invoke(): Activity
|
public function __invoke(): Activity
|
||||||
{
|
{
|
||||||
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors);
|
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish);
|
||||||
dispatch($job);
|
dispatch($job);
|
||||||
$this->activity->refresh();
|
$this->activity->refresh();
|
||||||
return $this->activity;
|
return $this->activity;
|
||||||
|
|||||||
@@ -17,24 +17,24 @@ class RunRemoteProcess
|
|||||||
|
|
||||||
public bool $hide_from_output;
|
public bool $hide_from_output;
|
||||||
|
|
||||||
public bool $is_finished;
|
|
||||||
|
|
||||||
public bool $ignore_errors;
|
public bool $ignore_errors;
|
||||||
|
|
||||||
|
public $call_event_on_finish = null;
|
||||||
|
|
||||||
protected $time_start;
|
protected $time_start;
|
||||||
|
|
||||||
protected $current_time;
|
protected $current_time;
|
||||||
|
|
||||||
protected $last_write_at = 0;
|
protected $last_write_at = 0;
|
||||||
|
|
||||||
protected $throttle_interval_ms = 500;
|
protected $throttle_interval_ms = 200;
|
||||||
|
|
||||||
protected int $counter = 1;
|
protected int $counter = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(Activity $activity, bool $hide_from_output = false, bool $is_finished = false, bool $ignore_errors = false)
|
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
|
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
|
||||||
@@ -43,8 +43,8 @@ class RunRemoteProcess
|
|||||||
|
|
||||||
$this->activity = $activity;
|
$this->activity = $activity;
|
||||||
$this->hide_from_output = $hide_from_output;
|
$this->hide_from_output = $hide_from_output;
|
||||||
$this->is_finished = $is_finished;
|
|
||||||
$this->ignore_errors = $ignore_errors;
|
$this->ignore_errors = $ignore_errors;
|
||||||
|
$this->call_event_on_finish = $call_event_on_finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function decodeOutput(?Activity $activity = null): string
|
public static function decodeOutput(?Activity $activity = null): string
|
||||||
@@ -74,17 +74,29 @@ class RunRemoteProcess
|
|||||||
$this->time_start = hrtime(true);
|
$this->time_start = hrtime(true);
|
||||||
|
|
||||||
$status = ProcessStatus::IN_PROGRESS;
|
$status = ProcessStatus::IN_PROGRESS;
|
||||||
$processResult = Process::forever()->run($this->getCommand(), $this->handleOutput(...));
|
$timeout = config('constants.ssh.command_timeout');
|
||||||
|
$process = Process::timeout($timeout)->start($this->getCommand(), $this->handleOutput(...));
|
||||||
|
$this->activity->properties = $this->activity->properties->merge([
|
||||||
|
'process_id' => $process->id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$processResult = $process->wait();
|
||||||
|
// $processResult = Process::timeout($timeout)->run($this->getCommand(), $this->handleOutput(...));
|
||||||
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
|
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
|
||||||
$status = ProcessStatus::ERROR;
|
$status = ProcessStatus::ERROR;
|
||||||
} else {
|
} else {
|
||||||
if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
|
if ($processResult->exitCode() == 0) {
|
||||||
$status = ProcessStatus::FINISHED;
|
$status = ProcessStatus::FINISHED;
|
||||||
}
|
}
|
||||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
||||||
$status = ProcessStatus::ERROR;
|
$status = ProcessStatus::ERROR;
|
||||||
}
|
}
|
||||||
|
// if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
|
||||||
|
// $status = ProcessStatus::FINISHED;
|
||||||
|
// }
|
||||||
|
// if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
||||||
|
// $status = ProcessStatus::ERROR;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->activity->properties = $this->activity->properties->merge([
|
$this->activity->properties = $this->activity->properties->merge([
|
||||||
@@ -97,7 +109,15 @@ class RunRemoteProcess
|
|||||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
||||||
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
|
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
|
||||||
}
|
}
|
||||||
|
if ($this->call_event_on_finish) {
|
||||||
|
try {
|
||||||
|
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||||
|
'userId' => $this->activity->causer_id,
|
||||||
|
]));
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
return $processResult;
|
return $processResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +137,6 @@ class RunRemoteProcess
|
|||||||
}
|
}
|
||||||
$this->current_time = $this->elapsedTime();
|
$this->current_time = $this->elapsedTime();
|
||||||
$this->activity->description = $this->encodeOutput($type, $output);
|
$this->activity->description = $this->encodeOutput($type, $output);
|
||||||
|
|
||||||
if ($this->isAfterLastThrottle()) {
|
if ($this->isAfterLastThrottle()) {
|
||||||
// Let's write to database.
|
// Let's write to database.
|
||||||
DB::transaction(function () {
|
DB::transaction(function () {
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -11,15 +15,53 @@ class StartDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||||
{
|
{
|
||||||
$internalPort = null;
|
$internalPort = null;
|
||||||
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
|
$type = $database->getMorphClass();
|
||||||
$internalPort = 6379;
|
$network = data_get($database, 'destination.network');
|
||||||
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
|
$server = data_get($database, 'destination.server');
|
||||||
$internalPort = 5432;
|
$containerName = data_get($database, 'uuid');
|
||||||
|
$proxyContainerName = "{$database->uuid}-proxy";
|
||||||
|
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$databaseType = $database->databaseType();
|
||||||
|
$network = data_get($database, 'service.destination.network');
|
||||||
|
$server = data_get($database, 'service.destination.server');
|
||||||
|
$proxyContainerName = "{$database->service->uuid}-proxy";
|
||||||
|
switch ($databaseType) {
|
||||||
|
case 'standalone-mariadb':
|
||||||
|
$type = 'App\Models\StandaloneMariadb';
|
||||||
|
$containerName = "mariadb-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-mongodb':
|
||||||
|
$type = 'App\Models\StandaloneMongodb';
|
||||||
|
$containerName = "mongodb-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-mysql':
|
||||||
|
$type = 'App\Models\StandaloneMysql';
|
||||||
|
$containerName = "mysql-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-postgresql':
|
||||||
|
$type = 'App\Models\StandalonePostgresql';
|
||||||
|
$containerName = "postgresql-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-redis':
|
||||||
|
$type = 'App\Models\StandaloneRedis';
|
||||||
|
$containerName = "redis-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($type === 'App\Models\StandaloneRedis') {
|
||||||
|
$internalPort = 6379;
|
||||||
|
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||||
|
$internalPort = 5432;
|
||||||
|
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||||
|
$internalPort = 27017;
|
||||||
|
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||||
|
$internalPort = 3306;
|
||||||
|
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||||
|
$internalPort = 3306;
|
||||||
}
|
}
|
||||||
$containerName = "{$database->uuid}-proxy";
|
|
||||||
$configuration_dir = database_proxy_dir($database->uuid);
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
$nginxconf = <<<EOF
|
$nginxconf = <<<EOF
|
||||||
user nginx;
|
user nginx;
|
||||||
@@ -33,7 +75,7 @@ class StartDatabaseProxy
|
|||||||
stream {
|
stream {
|
||||||
server {
|
server {
|
||||||
listen $database->public_port;
|
listen $database->public_port;
|
||||||
proxy_pass $database->uuid:$internalPort;
|
proxy_pass $containerName:$internalPort;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF;
|
EOF;
|
||||||
@@ -45,19 +87,19 @@ class StartDatabaseProxy
|
|||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
'version' => '3.8',
|
||||||
'services' => [
|
'services' => [
|
||||||
$containerName => [
|
$proxyContainerName => [
|
||||||
'build' => [
|
'build' => [
|
||||||
'context' => $configuration_dir,
|
'context' => $configuration_dir,
|
||||||
'dockerfile' => 'Dockerfile',
|
'dockerfile' => 'Dockerfile',
|
||||||
],
|
],
|
||||||
'image' => "nginx:stable-alpine",
|
'image' => "nginx:stable-alpine",
|
||||||
'container_name' => $containerName,
|
'container_name' => $proxyContainerName,
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'ports' => [
|
'ports' => [
|
||||||
"$database->public_port:$database->public_port",
|
"$database->public_port:$database->public_port",
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$database->destination->network,
|
$network,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
@@ -72,9 +114,9 @@ class StartDatabaseProxy
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$database->destination->network => [
|
$network => [
|
||||||
'external' => true,
|
'external' => true,
|
||||||
'name' => $database->destination->network,
|
'name' => $network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -87,7 +129,8 @@ class StartDatabaseProxy
|
|||||||
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
||||||
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||||
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||||
|
"docker compose --project-directory {$configuration_dir} pull",
|
||||||
"docker compose --project-directory {$configuration_dir} up --build -d",
|
"docker compose --project-directory {$configuration_dir} up --build -d",
|
||||||
], $database->destination->server);
|
], $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
172
app/Actions/Database/StartMariadb.php
Normal file
172
app/Actions/Database/StartMariadb.php
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartMariadb
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneMariadb $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneMariadb $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$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_mysql();
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"],
|
||||||
|
'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' => (float) $this->database->limits_cpus,
|
||||||
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $this->database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
|
}
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "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->mariadb_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$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[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
|
}
|
||||||
|
|
||||||
|
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->real_value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
||||||
|
}
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
|
}
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_mysql()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->mariadb_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'custom-config.cnf';
|
||||||
|
$content = $this->database->mariadb_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
|
}
|
||||||
192
app/Actions/Database/StartMongodb.php
Normal file
192
app/Actions/Database/StartMongodb.php
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartMongodb
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneMongodb $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneMongodb $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$startCommand = "mongod";
|
||||||
|
|
||||||
|
$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_mongo_conf();
|
||||||
|
|
||||||
|
$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',
|
||||||
|
'mongosh --eval "printjson(db.runCommand(\"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' => (float) $this->database->limits_cpus,
|
||||||
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $this->database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
|
}
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "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->mongo_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/mongod.conf',
|
||||||
|
'target' => '/etc/mongo/mongod.conf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
|
||||||
|
}
|
||||||
|
$this->add_default_database();
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
|
||||||
|
'target' => '/docker-entrypoint-initdb.d',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
$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[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
|
}
|
||||||
|
|
||||||
|
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->real_value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||||
|
}
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_mongo_conf()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->mongo_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'mongod.conf';
|
||||||
|
$content = $this->database->mongo_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
|
private function add_default_database()
|
||||||
|
{
|
||||||
|
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js";
|
||||||
|
}
|
||||||
|
}
|
||||||
172
app/Actions/Database/StartMysql.php
Normal file
172
app/Actions/Database/StartMysql.php
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartMysql
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneMysql $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneMysql $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$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_mysql();
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"],
|
||||||
|
'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' => (float) $this->database->limits_cpus,
|
||||||
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $this->database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
|
}
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "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->mysql_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$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[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
|
}
|
||||||
|
|
||||||
|
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->real_value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
||||||
|
}
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
|
}
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_mysql()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->mysql_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'custom-config.cnf';
|
||||||
|
$content = $this->database->mysql_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@@ -17,14 +16,14 @@ class StartPostgresql
|
|||||||
public array $init_scripts = [];
|
public array $init_scripts = [];
|
||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
public function handle(Server $server, StandalonePostgresql $database)
|
public function handle(StandalonePostgresql $database)
|
||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo '####### Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
||||||
];
|
];
|
||||||
@@ -33,6 +32,8 @@ class StartPostgresql
|
|||||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->generate_init_scripts();
|
$this->generate_init_scripts();
|
||||||
|
$this->add_custom_conf();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
'version' => '3.8',
|
||||||
'services' => [
|
'services' => [
|
||||||
@@ -49,12 +50,8 @@ class StartPostgresql
|
|||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
'CMD-SHELL',
|
"CMD-SHELL",
|
||||||
'pg_isready',
|
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1"
|
||||||
'-d',
|
|
||||||
$this->database->postgres_db,
|
|
||||||
'-U',
|
|
||||||
$this->database->postgres_user,
|
|
||||||
],
|
],
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
'timeout' => '5s',
|
'timeout' => '5s',
|
||||||
@@ -65,8 +62,7 @@ class StartPostgresql
|
|||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => $this->database->limits_cpus,
|
'cpus' => (float) $this->database->limits_cpus,
|
||||||
'cpuset' => $this->database->limits_cpuset,
|
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
@@ -78,6 +74,20 @@ class StartPostgresql
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
|
}
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
ray('Log Drain Enabled');
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "true",
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
}
|
}
|
||||||
@@ -97,14 +107,29 @@ class StartPostgresql
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!is_null($this->database->postgres_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/custom-postgres.conf',
|
||||||
|
'target' => '/etc/postgresql/postgresql.conf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
$docker_compose['services'][$container_name]['command'] = [
|
||||||
|
'postgres',
|
||||||
|
'-c',
|
||||||
|
'config_file=/etc/postgresql/postgresql.conf',
|
||||||
|
];
|
||||||
|
}
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
return remote_process($this->commands, $server);
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
private function generate_local_persistent_volumes()
|
||||||
@@ -139,12 +164,15 @@ class StartPostgresql
|
|||||||
ray('Generate Environment Variables')->green();
|
ray('Generate Environment Variables')->green();
|
||||||
ray($this->database->runtime_environment_variables)->green();
|
ray($this->database->runtime_environment_variables)->green();
|
||||||
foreach ($this->database->runtime_environment_variables as $env) {
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
$environment_variables->push("$env->key=$env->value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||||
}
|
}
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
||||||
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||||
@@ -169,4 +197,14 @@ class StartPostgresql
|
|||||||
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function add_custom_conf()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->postgres_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'custom-postgres.conf';
|
||||||
|
$content = $this->database->postgres_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -17,7 +17,7 @@ class StartRedis
|
|||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
|
||||||
public function handle(Server $server, StandaloneRedis $database)
|
public function handle(StandaloneRedis $database)
|
||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class StartRedis
|
|||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo '####### Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -66,8 +66,7 @@ class StartRedis
|
|||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => $this->database->limits_cpus,
|
'cpus' => (float) $this->database->limits_cpus,
|
||||||
'cpuset' => $this->database->limits_cpuset,
|
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
@@ -79,6 +78,19 @@ class StartRedis
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
|
}
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "true",
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
}
|
}
|
||||||
@@ -95,16 +107,18 @@ class StartRedis
|
|||||||
'target' => '/usr/local/etc/redis/redis.conf',
|
'target' => '/usr/local/etc/redis/redis.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
|
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
}
|
}
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
return remote_process($this->commands, $server);
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
private function generate_local_persistent_volumes()
|
||||||
@@ -137,7 +151,7 @@ class StartRedis
|
|||||||
{
|
{
|
||||||
$environment_variables = collect();
|
$environment_variables = collect();
|
||||||
foreach ($this->database->runtime_environment_variables as $env) {
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
$environment_variables->push("$env->key=$env->value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
@@ -152,9 +166,9 @@ class StartRedis
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'redis.conf';
|
$filename = 'redis.conf';
|
||||||
$content = $this->database->redis_conf;
|
Storage::disk('local')->put("tmp/redis.conf_{$this->database->uuid}", $this->database->redis_conf);
|
||||||
$content_base64 = base64_encode($content);
|
$path = Storage::path("tmp/redis.conf_{$this->database->uuid}");
|
||||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
instant_scp($path, "{$this->configuration_dir}/{$filename}", $this->database->destination->server);
|
||||||
|
Storage::disk('local')->delete("tmp/redis.conf_{$this->database->uuid}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Events\DatabaseStatusChanged;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopDatabase
|
class StopDatabase
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||||
{
|
{
|
||||||
$server = $database->destination->server;
|
$server = $database->destination->server;
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
instant_remote_process(
|
instant_remote_process(
|
||||||
["docker rm -f {$database->uuid}"],
|
["docker rm -f {$database->uuid}"],
|
||||||
$server
|
$server
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -10,9 +14,13 @@ class StopDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||||
{
|
{
|
||||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
$server = data_get($database, 'destination.server');
|
||||||
|
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$server = data_get($database, 'service.server');
|
||||||
|
}
|
||||||
|
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $server);
|
||||||
$database->is_public = false;
|
$database->is_public = false;
|
||||||
$database->save();
|
$database->save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,26 @@ namespace App\Actions\License;
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
|
||||||
class CheckResaleLicense
|
class CheckResaleLicense
|
||||||
{
|
{
|
||||||
public function __invoke()
|
use AsAction;
|
||||||
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
$settings->update([
|
|
||||||
'is_resale_license_active' => false,
|
|
||||||
]);
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
$settings->update([
|
||||||
|
'is_resale_license_active' => true,
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!$settings->resale_license) {
|
// if (!$settings->resale_license) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
$base_url = config('coolify.license_url');
|
$base_url = config('coolify.license_url');
|
||||||
if (isDev()) {
|
|
||||||
$base_url = 'http://host.docker.internal:8787';
|
|
||||||
}
|
|
||||||
$instance_id = config('app.id');
|
$instance_id = config('app.id');
|
||||||
|
|
||||||
ray("Checking license key against $base_url/lemon/validate");
|
ray("Checking license key against $base_url/lemon/validate");
|
||||||
|
|||||||
@@ -17,35 +17,45 @@ class CheckProxy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$status = getContainerStatus($server, 'coolify-proxy');
|
if ($server->isSwarm()) {
|
||||||
if ($status === 'running') {
|
$status = getContainerStatus($server, 'coolify-proxy_traefik');
|
||||||
$server->proxy->set('status', 'running');
|
$server->proxy->set('status', $status);
|
||||||
$server->save();
|
$server->save();
|
||||||
return false;
|
if ($status === 'running') {
|
||||||
}
|
return false;
|
||||||
$ip = $server->ip;
|
}
|
||||||
if ($server->id === 0) {
|
return true;
|
||||||
$ip = 'host.docker.internal';
|
} else {
|
||||||
}
|
$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');
|
$connection80 = @fsockopen($ip, '80');
|
||||||
$connection443 = @fsockopen($ip, '443');
|
$connection443 = @fsockopen($ip, '443');
|
||||||
$port80 = is_resource($connection80) && fclose($connection80);
|
$port80 = is_resource($connection80) && fclose($connection80);
|
||||||
$port443 = is_resource($connection443) && fclose($connection443);
|
$port443 = is_resource($connection443) && fclose($connection443);
|
||||||
if ($port80) {
|
if ($port80) {
|
||||||
if ($fromUI) {
|
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>");
|
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 {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if ($port443) {
|
||||||
if ($port443) {
|
if ($fromUI) {
|
||||||
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>");
|
||||||
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 {
|
||||||
} else {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
|
use App\Events\ProxyStatusChanged;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -24,18 +25,29 @@ class StartProxy
|
|||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
$commands = $commands->merge([
|
if ($server->isSwarm()) {
|
||||||
"mkdir -p $proxy_path && cd $proxy_path",
|
$commands = $commands->merge([
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
'docker compose pull',
|
"echo 'Starting coolify-proxy.'",
|
||||||
"echo 'Stopping existing coolify-proxy.'",
|
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
||||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
"echo 'Proxy started successfully.'"
|
||||||
"echo 'Starting coolify-proxy.'",
|
]);
|
||||||
'docker compose up -d --remove-orphans',
|
} else {
|
||||||
"echo 'Proxy started successfully.'"
|
$commands = $commands->merge([
|
||||||
]);
|
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
"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) {
|
if ($async) {
|
||||||
$activity = remote_process($commands, $server);
|
$activity = remote_process($commands, $server);
|
||||||
return $activity;
|
return $activity;
|
||||||
@@ -46,11 +58,9 @@ class StartProxy
|
|||||||
$server->save();
|
$server->save();
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
} catch(\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
app/Actions/Server/CleanupDocker.php
Normal file
23
app/Actions/Server/CleanupDocker.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use App\Models\Server;
|
||||||
|
|
||||||
|
class CleanupDocker
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Server $server, bool $force = true)
|
||||||
|
{
|
||||||
|
if ($force) {
|
||||||
|
instant_remote_process(['docker image prune -af'], $server, false);
|
||||||
|
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||||
|
instant_remote_process(['docker builder prune -af'], $server, false);
|
||||||
|
} else {
|
||||||
|
instant_remote_process(['docker image prune -f'], $server, false);
|
||||||
|
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||||
|
instant_remote_process(['docker builder prune -f'], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,11 @@ class InstallDocker
|
|||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
|
$supported_os_type = $server->validateOS();
|
||||||
|
if (!$supported_os_type) {
|
||||||
|
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/servers#install-docker-engine-manually">documentation</a>.');
|
||||||
|
}
|
||||||
|
ray('Installing Docker on server: ' . $server->name . ' (' . $server->ip . ')' . ' with OS type: ' . $supported_os_type);
|
||||||
$dockerVersion = '24.0';
|
$dockerVersion = '24.0';
|
||||||
$config = base64_encode('{
|
$config = base64_encode('{
|
||||||
"log-driver": "json-file",
|
"log-driver": "json-file",
|
||||||
@@ -27,36 +32,65 @@ class InstallDocker
|
|||||||
'server_id' => $server->id,
|
'server_id' => $server->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
$command = collect([]);
|
||||||
if (isDev() && $server->id === 0) {
|
if (isDev() && $server->id === 0) {
|
||||||
$command = [
|
$command = $command->merge([
|
||||||
"echo '####### Installing Prerequisites...'",
|
"echo 'Installing Prerequisites...'",
|
||||||
"sleep 1",
|
"sleep 1",
|
||||||
"echo '####### Installing/updating Docker Engine...'",
|
"echo 'Installing Docker Engine...'",
|
||||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||||
"sleep 4",
|
"sleep 4",
|
||||||
"echo '####### Restarting Docker Engine...'",
|
"echo 'Restarting Docker Engine...'",
|
||||||
"ls -l /tmp"
|
"ls -l /tmp"
|
||||||
];
|
]);
|
||||||
|
return remote_process($command, $server);
|
||||||
} else {
|
} else {
|
||||||
$command = [
|
if ($supported_os_type->contains('debian')) {
|
||||||
"echo '####### Installing Prerequisites...'",
|
$command = $command->merge([
|
||||||
"command -v jq >/dev/null || apt-get update",
|
"echo 'Installing Prerequisites...'",
|
||||||
"command -v jq >/dev/null || apt install -y jq",
|
"command -v jq >/dev/null || apt-get update -y",
|
||||||
"echo '####### Installing/updating Docker Engine...'",
|
"command -v jq >/dev/null || apt install -y curl wget git jq",
|
||||||
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh",
|
|
||||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
]);
|
||||||
|
} else if ($supported_os_type->contains('rhel')) {
|
||||||
|
$command = $command->merge([
|
||||||
|
"echo 'Installing Prerequisites...'",
|
||||||
|
"command -v jq >/dev/null || dnf install -y curl wget git jq",
|
||||||
|
]);
|
||||||
|
} else if ($supported_os_type->contains('sles')) {
|
||||||
|
$command = $command->merge([
|
||||||
|
"echo 'Installing Prerequisites...'",
|
||||||
|
"command -v jq >/dev/null || zypper update -y",
|
||||||
|
"command -v jq >/dev/null || zypper install -y curl wget git jq",
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unsupported OS');
|
||||||
|
}
|
||||||
|
$command = $command->merge([
|
||||||
|
"echo 'Installing Docker Engine...'",
|
||||||
|
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
|
||||||
|
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||||
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
||||||
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
||||||
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
|
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
|
||||||
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
|
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
|
||||||
"echo '####### Restarting Docker Engine...'",
|
"echo 'Restarting Docker Engine...'",
|
||||||
|
"systemctl enable docker >/dev/null 2>&1 || true",
|
||||||
"systemctl restart docker",
|
"systemctl restart docker",
|
||||||
"echo '####### Creating default Docker network (coolify)...'",
|
]);
|
||||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
if ($server->isSwarm()) {
|
||||||
"echo '####### Done!'"
|
$command = $command->merge([
|
||||||
];
|
"docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true",
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$command = $command->merge([
|
||||||
|
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||||
|
]);
|
||||||
|
$command = $command->merge([
|
||||||
|
"echo 'Done!'",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return remote_process($command, $server);
|
||||||
}
|
}
|
||||||
return remote_process($command, $server);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
211
app/Actions/Server/InstallLogDrain.php
Normal file
211
app/Actions/Server/InstallLogDrain.php
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use App\Models\Server;
|
||||||
|
|
||||||
|
class InstallLogDrain
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
if ($server->settings->is_logdrain_newrelic_enabled) {
|
||||||
|
$type = 'newrelic';
|
||||||
|
} else if ($server->settings->is_logdrain_highlight_enabled) {
|
||||||
|
$type = 'highlight';
|
||||||
|
} else if ($server->settings->is_logdrain_axiom_enabled) {
|
||||||
|
$type = 'axiom';
|
||||||
|
} else if ($server->settings->is_logdrain_custom_enabled) {
|
||||||
|
$type = 'custom';
|
||||||
|
} else {
|
||||||
|
$type = 'none';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if ($type === 'none') {
|
||||||
|
$command = [
|
||||||
|
"echo 'Stopping old Fluent Bit'",
|
||||||
|
"docker rm -f coolify-log-drain || true",
|
||||||
|
];
|
||||||
|
return instant_remote_process($command, $server);
|
||||||
|
} else if ($type === 'newrelic') {
|
||||||
|
if (!$server->settings->is_logdrain_newrelic_enabled) {
|
||||||
|
throw new \Exception('New Relic log drain is not enabled.');
|
||||||
|
}
|
||||||
|
$config = base64_encode("
|
||||||
|
[SERVICE]
|
||||||
|
Flush 5
|
||||||
|
Daemon off
|
||||||
|
Tag container_logs
|
||||||
|
Log_Level debug
|
||||||
|
Parsers_File parsers.conf
|
||||||
|
[INPUT]
|
||||||
|
Name forward
|
||||||
|
Buffer_Chunk_Size 1M
|
||||||
|
Buffer_Max_Size 6M
|
||||||
|
[FILTER]
|
||||||
|
Name grep
|
||||||
|
Match *
|
||||||
|
Exclude log 127.0.0.1
|
||||||
|
[FILTER]
|
||||||
|
Name modify
|
||||||
|
Match *
|
||||||
|
Set server_name {$server->name}
|
||||||
|
[OUTPUT]
|
||||||
|
Name nrlogs
|
||||||
|
Match *
|
||||||
|
license_key \${LICENSE_KEY}
|
||||||
|
# https://log-api.eu.newrelic.com/log/v1 - EU
|
||||||
|
# https://log-api.newrelic.com/log/v1 - US
|
||||||
|
base_uri \${BASE_URI}
|
||||||
|
");
|
||||||
|
} else if ($type === 'highlight') {
|
||||||
|
if (!$server->settings->is_logdrain_highlight_enabled) {
|
||||||
|
throw new \Exception('Highlight log drain is not enabled.');
|
||||||
|
}
|
||||||
|
$config = base64_encode("
|
||||||
|
[SERVICE]
|
||||||
|
Flush 5
|
||||||
|
Daemon off
|
||||||
|
Log_Level debug
|
||||||
|
Parsers_File parsers.conf
|
||||||
|
[INPUT]
|
||||||
|
Name forward
|
||||||
|
tag \${HIGHLIGHT_PROJECT_ID}
|
||||||
|
Buffer_Chunk_Size 1M
|
||||||
|
Buffer_Max_Size 6M
|
||||||
|
[OUTPUT]
|
||||||
|
Name forward
|
||||||
|
Match *
|
||||||
|
Host otel.highlight.io
|
||||||
|
Port 24224
|
||||||
|
");
|
||||||
|
} else if ($type === 'axiom') {
|
||||||
|
if (!$server->settings->is_logdrain_axiom_enabled) {
|
||||||
|
throw new \Exception('Axiom log drain is not enabled.');
|
||||||
|
}
|
||||||
|
$config = base64_encode("
|
||||||
|
[SERVICE]
|
||||||
|
Flush 5
|
||||||
|
Daemon off
|
||||||
|
Log_Level debug
|
||||||
|
Parsers_File parsers.conf
|
||||||
|
[INPUT]
|
||||||
|
Name forward
|
||||||
|
Buffer_Chunk_Size 1M
|
||||||
|
Buffer_Max_Size 6M
|
||||||
|
[FILTER]
|
||||||
|
Name grep
|
||||||
|
Match *
|
||||||
|
Exclude log 127.0.0.1
|
||||||
|
[FILTER]
|
||||||
|
Name modify
|
||||||
|
Match *
|
||||||
|
Set server_name {$server->name}
|
||||||
|
[OUTPUT]
|
||||||
|
Name http
|
||||||
|
Match *
|
||||||
|
Host api.axiom.co
|
||||||
|
Port 443
|
||||||
|
URI /v1/datasets/\${AXIOM_DATASET_NAME}/ingest
|
||||||
|
# Authorization Bearer should be an API token
|
||||||
|
Header Authorization Bearer \${AXIOM_API_KEY}
|
||||||
|
compress gzip
|
||||||
|
format json
|
||||||
|
json_date_key _time
|
||||||
|
json_date_format iso8601
|
||||||
|
tls On
|
||||||
|
");
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
if (!$server->settings->is_logdrain_custom_enabled) {
|
||||||
|
throw new \Exception('Custom log drain is not enabled.');
|
||||||
|
}
|
||||||
|
$config = base64_encode($server->settings->logdrain_custom_config);
|
||||||
|
$parsers = base64_encode($server->settings->logdrain_custom_config_parser);
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unknown log drain type.');
|
||||||
|
}
|
||||||
|
if ($type !== 'custom') {
|
||||||
|
$parsers = base64_encode("
|
||||||
|
[PARSER]
|
||||||
|
Name empty_line_skipper
|
||||||
|
Format regex
|
||||||
|
Regex /^(?!\s*$).+/
|
||||||
|
");
|
||||||
|
}
|
||||||
|
$compose = base64_encode("
|
||||||
|
services:
|
||||||
|
coolify-log-drain:
|
||||||
|
image: cr.fluentbit.io/fluent/fluent-bit:2.0
|
||||||
|
container_name: coolify-log-drain
|
||||||
|
command: -c /fluent-bit.conf
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- ./fluent-bit.conf:/fluent-bit.conf
|
||||||
|
- ./parsers.conf:/parsers.conf
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:24224:24224
|
||||||
|
restart: unless-stopped
|
||||||
|
");
|
||||||
|
$readme = base64_encode('# New Relic Log Drain
|
||||||
|
This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder.
|
||||||
|
|
||||||
|
Files:
|
||||||
|
- `fluent-bit.conf` - configuration file for Fluent Bit
|
||||||
|
- `docker-compose.yml` - docker-compose file to run Fluent Bit
|
||||||
|
- `.env` - environment variables for Fluent Bit
|
||||||
|
');
|
||||||
|
$license_key = $server->settings->logdrain_newrelic_license_key;
|
||||||
|
$base_uri = $server->settings->logdrain_newrelic_base_uri;
|
||||||
|
$base_path = config('coolify.base_config_path');
|
||||||
|
|
||||||
|
$config_path = $base_path . '/log-drains';
|
||||||
|
$fluent_bit_config = $config_path . '/fluent-bit.conf';
|
||||||
|
$parsers_config = $config_path . '/parsers.conf';
|
||||||
|
$compose_path = $config_path . '/docker-compose.yml';
|
||||||
|
$readme_path = $config_path . '/README.md';
|
||||||
|
$command = [
|
||||||
|
"echo 'Saving configuration'",
|
||||||
|
"mkdir -p $config_path",
|
||||||
|
"echo '{$parsers}' | base64 -d > $parsers_config",
|
||||||
|
"echo '{$config}' | base64 -d > $fluent_bit_config",
|
||||||
|
"echo '{$compose}' | base64 -d > $compose_path",
|
||||||
|
"echo '{$readme}' | base64 -d > $readme_path",
|
||||||
|
"test -f $config_path/.env && rm $config_path/.env",
|
||||||
|
|
||||||
|
];
|
||||||
|
if ($type === 'newrelic') {
|
||||||
|
$add_envs_command = [
|
||||||
|
"echo LICENSE_KEY=$license_key >> $config_path/.env",
|
||||||
|
"echo BASE_URI=$base_uri >> $config_path/.env",
|
||||||
|
];
|
||||||
|
} else if ($type === 'highlight') {
|
||||||
|
$add_envs_command = [
|
||||||
|
"echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env",
|
||||||
|
];
|
||||||
|
} else if ($type === 'axiom') {
|
||||||
|
$add_envs_command = [
|
||||||
|
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
|
||||||
|
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
|
||||||
|
];
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
$add_envs_command = [
|
||||||
|
"touch $config_path/.env"
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unknown log drain type.');
|
||||||
|
}
|
||||||
|
$restart_command = [
|
||||||
|
"echo 'Stopping old Fluent Bit'",
|
||||||
|
"cd $config_path && docker rm -f coolify-log-drain || true",
|
||||||
|
"echo 'Starting Fluent Bit'",
|
||||||
|
"cd $config_path && docker compose up -d --remove-orphans",
|
||||||
|
];
|
||||||
|
$command = array_merge($command, $add_envs_command, $restart_command);
|
||||||
|
return instant_remote_process($command, $server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,10 +18,11 @@ class UpdateCoolify
|
|||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
ray('Running InstanceAutoUpdateJob');
|
ray('Running InstanceAutoUpdateJob');
|
||||||
$this->server = Server::find(0)->first();
|
$this->server = Server::find(0);
|
||||||
if (!$this->server) {
|
if (!$this->server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
CleanupDocker::run($this->server, false);
|
||||||
$this->latestVersion = get_latest_version_of_coolify();
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
$this->currentVersion = config('version');
|
$this->currentVersion = config('version');
|
||||||
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
|
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
|
||||||
|
|||||||
51
app/Actions/Service/DeleteService.php
Normal file
51
app/Actions/Service/DeleteService.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use App\Models\Service;
|
||||||
|
|
||||||
|
class DeleteService
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Service $service)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$server = data_get($service, 'server');
|
||||||
|
if ($server->isFunctional()) {
|
||||||
|
$storagesToDelete = collect([]);
|
||||||
|
|
||||||
|
$service->environment_variables()->delete();
|
||||||
|
$commands = [];
|
||||||
|
foreach ($service->applications()->get() as $application) {
|
||||||
|
$storages = $application->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
$storagesToDelete->push($storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($service->databases()->get() as $database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
$storagesToDelete->push($storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($storagesToDelete as $storage) {
|
||||||
|
$commands[] = "docker volume rm -f $storage->name";
|
||||||
|
}
|
||||||
|
$commands[] = "docker rm -f $service->uuid";
|
||||||
|
|
||||||
|
instant_remote_process($commands, $server, false);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception($e->getMessage());
|
||||||
|
} finally {
|
||||||
|
foreach ($service->applications()->get() as $application) {
|
||||||
|
$application->forceDelete();
|
||||||
|
}
|
||||||
|
foreach ($service->databases()->get() as $database) {
|
||||||
|
$database->forceDelete();
|
||||||
|
}
|
||||||
|
$service->tags()->detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,24 +11,27 @@ class StartService
|
|||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
$network = $service->destination->network;
|
ray('Starting service: ' . $service->name);
|
||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = "cd " . $service->workdir();
|
$commands[] = "cd " . $service->workdir();
|
||||||
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
$commands[] = "echo '####### Creating Docker network.'";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "docker network create --attachable {$service->uuid} >/dev/null 2>/dev/null || true";
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid >/dev/null 2>&1 || true";
|
||||||
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
|
$commands[] = "echo 'Starting service $service->name on {$service->server->name}.'";
|
||||||
$commands[] = "echo '####### Pulling images.'";
|
$commands[] = "echo 'Pulling images.'";
|
||||||
$commands[] = "docker compose pull";
|
$commands[] = "docker compose pull";
|
||||||
$commands[] = "echo '####### Starting containers.'";
|
$commands[] = "echo 'Starting containers.'";
|
||||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate";
|
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
|
||||||
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
|
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||||
$compose = data_get($service,'docker_compose',[]);
|
if (data_get($service, 'connect_to_docker_network')) {
|
||||||
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
|
$compose = data_get($service, 'docker_compose', []);
|
||||||
foreach($serviceNames as $serviceName => $serviceConfig){
|
$network = $service->destination->network;
|
||||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} 2>/dev/null || true";
|
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||||
|
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
||||||
|
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$activity = remote_process($commands, $service->server);
|
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
||||||
return $activity;
|
return $activity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ namespace App\Actions\Service;
|
|||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
|
|
||||||
class StopService
|
class StopService
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
|
$server = $service->destination->server;
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
ray('Stopping service: ' . $service->name);
|
||||||
$applications = $service->applications()->get();
|
$applications = $service->applications()->get();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||||
|
|||||||
25
app/Actions/Shared/PullImage.php
Normal file
25
app/Actions/Shared/PullImage.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Shared;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class PullImage
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Service $resource)
|
||||||
|
{
|
||||||
|
$resource->saveComposeConfigs();
|
||||||
|
|
||||||
|
$commands[] = "cd " . $resource->workdir();
|
||||||
|
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
|
||||||
|
$commands[] = "docker compose pull";
|
||||||
|
|
||||||
|
$server = data_get($resource, 'server');
|
||||||
|
|
||||||
|
if (!$server) return;
|
||||||
|
|
||||||
|
instant_remote_process($commands, $resource->server);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/Console/Commands/CleanupQueue.php
Normal file
23
app/Console/Commands/CleanupQueue.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
class CleanupQueue extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:queue';
|
||||||
|
protected $description = 'Cleanup Queue';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Running queue cleanup...\n";
|
||||||
|
$prefix = config('database.redis.options.prefix');
|
||||||
|
$keys = Redis::connection()->keys('*:laravel*');
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
||||||
|
Redis::connection()->del($keyWithoutPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
295
app/Console/Commands/CleanupStuckedResources.php
Normal file
295
app/Console/Commands/CleanupStuckedResources.php
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanupStuckedResources extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:stucked-resources';
|
||||||
|
protected $description = 'Cleanup Stucked Resources';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Running cleanup stucked...\n";
|
||||||
|
$this->cleanup_stucked_resources();
|
||||||
|
}
|
||||||
|
private function cleanup_stucked_resources()
|
||||||
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
echo "Deleting stuck application: {$application->name}\n";
|
||||||
|
$application->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
echo "Deleting stuck postgresql: {$postgresql->name}\n";
|
||||||
|
$postgresql->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($redis as $redis) {
|
||||||
|
echo "Deleting stuck redis: {$redis->name}\n";
|
||||||
|
$redis->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($mongodbs as $mongodb) {
|
||||||
|
echo "Deleting stuck mongodb: {$mongodb->name}\n";
|
||||||
|
$mongodb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($mysqls as $mysql) {
|
||||||
|
echo "Deleting stuck mysql: {$mysql->name}\n";
|
||||||
|
$mysql->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($mariadbs as $mariadb) {
|
||||||
|
echo "Deleting stuck mariadb: {$mariadb->name}\n";
|
||||||
|
$mariadb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($services as $service) {
|
||||||
|
echo "Deleting stuck service: {$service->name}\n";
|
||||||
|
$service->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($serviceApps as $serviceApp) {
|
||||||
|
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
|
||||||
|
$serviceApp->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($serviceDbs as $serviceDb) {
|
||||||
|
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
|
||||||
|
$serviceDb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup any resources that are not attached to any environment or destination or server
|
||||||
|
try {
|
||||||
|
$applications = Application::all();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
if (!data_get($application, 'environment')) {
|
||||||
|
echo 'Application without environment: ' . $application->name . ' soft deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$application->destination()) {
|
||||||
|
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($application, 'destination.server')) {
|
||||||
|
echo 'Application without server: ' . $application->name . ' soft deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in application: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
if (!data_get($postgresql, 'environment')) {
|
||||||
|
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$postgresql->destination()) {
|
||||||
|
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($postgresql, 'destination.server')) {
|
||||||
|
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in postgresql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$redis = StandaloneRedis::all();
|
||||||
|
foreach ($redis as $redis) {
|
||||||
|
if (!data_get($redis, 'environment')) {
|
||||||
|
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$redis->destination()) {
|
||||||
|
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($redis, 'destination.server')) {
|
||||||
|
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in redis: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mongodbs = StandaloneMongodb::all();
|
||||||
|
foreach ($mongodbs as $mongodb) {
|
||||||
|
if (!data_get($mongodb, 'environment')) {
|
||||||
|
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$mongodb->destination()) {
|
||||||
|
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($mongodb, 'destination.server')) {
|
||||||
|
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mongodb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mysqls = StandaloneMysql::all();
|
||||||
|
foreach ($mysqls as $mysql) {
|
||||||
|
if (!data_get($mysql, 'environment')) {
|
||||||
|
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$mysql->destination()) {
|
||||||
|
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($mysql, 'destination.server')) {
|
||||||
|
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mysql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mariadbs = StandaloneMariadb::all();
|
||||||
|
foreach ($mariadbs as $mariadb) {
|
||||||
|
if (!data_get($mariadb, 'environment')) {
|
||||||
|
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$mariadb->destination()) {
|
||||||
|
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($mariadb, 'destination.server')) {
|
||||||
|
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mariadb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$services = Service::all();
|
||||||
|
foreach ($services as $service) {
|
||||||
|
if (!data_get($service, 'environment')) {
|
||||||
|
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$service->destination()) {
|
||||||
|
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($service, 'server')) {
|
||||||
|
echo 'Service without server: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in service: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceApplications = ServiceApplication::all();
|
||||||
|
foreach ($serviceApplications as $service) {
|
||||||
|
if (!data_get($service, 'service')) {
|
||||||
|
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceDatabases = ServiceDatabase::all();
|
||||||
|
foreach ($serviceDatabases as $service) {
|
||||||
|
if (!data_get($service, 'service')) {
|
||||||
|
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanupUnreachableServers extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:unreachable-servers';
|
||||||
|
protected $description = 'Cleanup Unreachable Servers (3 days)';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Running unreachable server cleanup...\n";
|
||||||
|
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get();
|
||||||
|
if ($servers->count() > 0) {
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||||
|
$server->update([
|
||||||
|
'ip' => '1.2.3.4'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/Console/Commands/Dev.php
Normal file
33
app/Console/Commands/Dev.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
|
||||||
|
class Dev extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'dev:init';
|
||||||
|
protected $description = 'Init the app in dev mode';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
// Generate APP_KEY if not exists
|
||||||
|
if (empty(env('APP_KEY'))) {
|
||||||
|
echo "Generating APP_KEY.\n";
|
||||||
|
Artisan::call('key:generate');
|
||||||
|
}
|
||||||
|
// Seed database if it's empty
|
||||||
|
$settings = InstanceSettings::find(0);
|
||||||
|
if (!$settings) {
|
||||||
|
echo "Initializing instance, seeding database.\n";
|
||||||
|
Artisan::call('migrate --seed');
|
||||||
|
} else {
|
||||||
|
echo "Instance already initialized.\n";
|
||||||
|
}
|
||||||
|
// Set permissions
|
||||||
|
Process::run(['chmod', '-R', 'o+rwx', '.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,6 @@ use App\Models\ScheduledDatabaseBackup;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Models\TeamInvitation;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Waitlist;
|
use App\Models\Waitlist;
|
||||||
use App\Notifications\Application\DeploymentFailed;
|
use App\Notifications\Application\DeploymentFailed;
|
||||||
use App\Notifications\Application\DeploymentSuccess;
|
use App\Notifications\Application\DeploymentSuccess;
|
||||||
@@ -18,13 +16,11 @@ use App\Notifications\Application\StatusChanged;
|
|||||||
use App\Notifications\Database\BackupFailed;
|
use App\Notifications\Database\BackupFailed;
|
||||||
use App\Notifications\Database\BackupSuccess;
|
use App\Notifications\Database\BackupSuccess;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use App\Notifications\TransactionalEmails\InvitationLink;
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Mail;
|
use Mail;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
use function Laravel\Prompts\confirm;
|
use function Laravel\Prompts\confirm;
|
||||||
use function Laravel\Prompts\select;
|
use function Laravel\Prompts\select;
|
||||||
|
|||||||
@@ -3,25 +3,124 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init';
|
protected $signature = 'app:init {--cleanup}';
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->alive();
|
||||||
}
|
$cleanup = $this->option('cleanup');
|
||||||
|
if ($cleanup) {
|
||||||
|
echo "Running cleanups...\n";
|
||||||
|
$this->call('cleanup:stucked-resources');
|
||||||
|
// Required for falsely deleted coolify db
|
||||||
|
$this->restore_coolify_db_backup();
|
||||||
|
|
||||||
|
// $this->cleanup_ssh();
|
||||||
|
}
|
||||||
|
$this->cleanup_in_progress_application_deployments();
|
||||||
|
$this->cleanup_stucked_helper_containers();
|
||||||
|
|
||||||
|
try {
|
||||||
|
setup_dynamic_configuration();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if (!is_null(env('AUTOUPDATE', null))) {
|
||||||
|
if (env('AUTOUPDATE') == true) {
|
||||||
|
$settings->update(['is_auto_update_enabled' => true]);
|
||||||
|
} else {
|
||||||
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->call('cleanup:queue');
|
||||||
|
}
|
||||||
|
private function restore_coolify_db_backup()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||||
|
if ($database && $database->trashed()) {
|
||||||
|
echo "Restoring coolify db backup\n";
|
||||||
|
$database->restore();
|
||||||
|
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
||||||
|
if (!$scheduledBackup) {
|
||||||
|
ScheduledDatabaseBackup::create([
|
||||||
|
'id' => 0,
|
||||||
|
'enabled' => true,
|
||||||
|
'save_s3' => false,
|
||||||
|
'frequency' => '0 0 * * *',
|
||||||
|
'database_id' => $database->id,
|
||||||
|
'database_type' => 'App\Models\StandalonePostgresql',
|
||||||
|
'team_id' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function cleanup_stucked_helper_containers()
|
||||||
|
{
|
||||||
|
$servers = Server::all();
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
if ($server->isFunctional()) {
|
||||||
|
CleanupHelperContainersJob::dispatch($server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function alive()
|
||||||
|
{
|
||||||
|
$id = config('app.id');
|
||||||
|
$version = config('version');
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$do_not_track = data_get($settings, 'do_not_track');
|
||||||
|
if ($do_not_track == true) {
|
||||||
|
echo "Skipping alive as do_not_track is enabled\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
||||||
|
echo "I am alive!\n";
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in alive: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// private function cleanup_ssh()
|
||||||
|
// {
|
||||||
|
|
||||||
|
// TODO: it will cleanup id.root@host.docker.internal
|
||||||
|
// try {
|
||||||
|
// $files = Storage::allFiles('ssh/keys');
|
||||||
|
// foreach ($files as $file) {
|
||||||
|
// Storage::delete($file);
|
||||||
|
// }
|
||||||
|
// $files = Storage::allFiles('ssh/mux');
|
||||||
|
// foreach ($files as $file) {
|
||||||
|
// Storage::delete($file);
|
||||||
|
// }
|
||||||
|
// } catch (\Throwable $e) {
|
||||||
|
// echo "Error in cleaning ssh: {$e->getMessage()}\n";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanup_in_progress_application_deployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get();
|
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', '==', ApplicationDeploymentStatus::QUEUED)->get();
|
||||||
foreach ($halted_deployments as $deployment) {
|
foreach ($halted_deployments as $deployment) {
|
||||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||||
$deployment->save();
|
$deployment->save();
|
||||||
@@ -30,4 +129,5 @@ class Init extends Command
|
|||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
41
app/Console/Commands/RootChangeEmail.php
Normal file
41
app/Console/Commands/RootChangeEmail.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class RootChangeEmail extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'root:change-email';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Change Root Email';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
$this->info('You are about to change the root user\'s email.');
|
||||||
|
$email = $this->ask('Give me a new email for root user');
|
||||||
|
$this->info('Updating root email...');
|
||||||
|
try {
|
||||||
|
User::find(0)->update(['email' => $email]);
|
||||||
|
$this->info('Root user\'s email updated successfully.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error('Failed to update root user\'s email.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,14 +8,14 @@ use Illuminate\Support\Facades\Hash;
|
|||||||
|
|
||||||
use function Laravel\Prompts\password;
|
use function Laravel\Prompts\password;
|
||||||
|
|
||||||
class UsersResetRoot extends Command
|
class RootResetPassword extends Command
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'users:reset-root';
|
protected $signature = 'root:reset-password';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Jobs\DeleteResourceJob;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
@@ -12,21 +13,21 @@ use function Laravel\Prompts\confirm;
|
|||||||
use function Laravel\Prompts\multiselect;
|
use function Laravel\Prompts\multiselect;
|
||||||
use function Laravel\Prompts\select;
|
use function Laravel\Prompts\select;
|
||||||
|
|
||||||
class ResourcesDelete extends Command
|
class ServicesDelete extends Command
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'resources:delete';
|
protected $signature = 'services:delete';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Delete a resource from the database';
|
protected $description = 'Delete a service from the database';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
@@ -34,7 +35,7 @@ class ResourcesDelete extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$resource = select(
|
$resource = select(
|
||||||
'What resource do you want to delete?',
|
'What service do you want to delete?',
|
||||||
['Application', 'Database', 'Service', 'Server'],
|
['Application', 'Database', 'Service', 'Server'],
|
||||||
);
|
);
|
||||||
if ($resource === 'Application') {
|
if ($resource === 'Application') {
|
||||||
@@ -61,12 +62,14 @@ class ResourcesDelete extends Command
|
|||||||
|
|
||||||
foreach ($serversToDelete as $server) {
|
foreach ($serversToDelete as $server) {
|
||||||
$toDelete = $servers->where('id', $server)->first();
|
$toDelete = $servers->where('id', $server)->first();
|
||||||
$this->info($toDelete);
|
if ($toDelete) {
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
$this->info($toDelete);
|
||||||
if (!$confirmed) {
|
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||||
break;
|
if (!$confirmed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$toDelete->delete();
|
||||||
}
|
}
|
||||||
$toDelete->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function deleteApplication()
|
private function deleteApplication()
|
||||||
@@ -82,14 +85,15 @@ class ResourcesDelete extends Command
|
|||||||
);
|
);
|
||||||
|
|
||||||
foreach ($applicationsToDelete as $application) {
|
foreach ($applicationsToDelete as $application) {
|
||||||
ray($application);
|
|
||||||
$toDelete = $applications->where('id', $application)->first();
|
$toDelete = $applications->where('id', $application)->first();
|
||||||
$this->info($toDelete);
|
if ($toDelete) {
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
$this->info($toDelete);
|
||||||
if (!$confirmed) {
|
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
||||||
break;
|
if (!$confirmed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DeleteResourceJob::dispatch($toDelete);
|
||||||
}
|
}
|
||||||
$toDelete->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function deleteDatabase()
|
private function deleteDatabase()
|
||||||
@@ -106,12 +110,14 @@ class ResourcesDelete extends Command
|
|||||||
|
|
||||||
foreach ($databasesToDelete as $database) {
|
foreach ($databasesToDelete as $database) {
|
||||||
$toDelete = $databases->where('id', $database)->first();
|
$toDelete = $databases->where('id', $database)->first();
|
||||||
$this->info($toDelete);
|
if ($toDelete) {
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
$this->info($toDelete);
|
||||||
if (!$confirmed) {
|
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||||
return;
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DeleteResourceJob::dispatch($toDelete);
|
||||||
}
|
}
|
||||||
$toDelete->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function deleteService()
|
private function deleteService()
|
||||||
@@ -128,12 +134,14 @@ class ResourcesDelete extends Command
|
|||||||
|
|
||||||
foreach ($servicesToDelete as $service) {
|
foreach ($servicesToDelete as $service) {
|
||||||
$toDelete = $services->where('id', $service)->first();
|
$toDelete = $services->where('id', $service)->first();
|
||||||
$this->info($toDelete);
|
if ($toDelete) {
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
$this->info($toDelete);
|
||||||
if (!$confirmed) {
|
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||||
return;
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DeleteResourceJob::dispatch($toDelete);
|
||||||
}
|
}
|
||||||
$toDelete->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
107
app/Console/Commands/ServicesGenerate.php
Normal file
107
app/Console/Commands/ServicesGenerate.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class ServicesGenerate extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'services:generate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
// ray()->clearAll();
|
||||||
|
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
|
||||||
|
$files = array_filter($files, function ($file) {
|
||||||
|
return strpos($file, '.yaml') !== false;
|
||||||
|
});
|
||||||
|
$serviceTemplatesJson = [];
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$parsed = $this->process_file($file);
|
||||||
|
if ($parsed) {
|
||||||
|
$name = data_get($parsed, 'name');
|
||||||
|
$parsed = data_forget($parsed, 'name');
|
||||||
|
$serviceTemplatesJson[$name] = $parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT);
|
||||||
|
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function process_file($file)
|
||||||
|
{
|
||||||
|
$serviceName = str($file)->before('.yaml')->value();
|
||||||
|
$content = file_get_contents(base_path("templates/compose/$file"));
|
||||||
|
// $this->info($content);
|
||||||
|
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
|
||||||
|
if ($ignore->count() > 0) {
|
||||||
|
$ignore = (bool)str($ignore[0])->after('# ignore:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$ignore = false;
|
||||||
|
}
|
||||||
|
if ($ignore) {
|
||||||
|
$this->info("Ignoring $file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->info("Processing $file");
|
||||||
|
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
|
||||||
|
if ($documentation->count() > 0) {
|
||||||
|
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$documentation = 'https://coolify.io/docs';
|
||||||
|
}
|
||||||
|
|
||||||
|
$slogan = collect(preg_grep('/^# slogan:/', explode("\n", $content)))->values();
|
||||||
|
if ($slogan->count() > 0) {
|
||||||
|
$slogan = str($slogan[0])->after('# slogan:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$slogan = str($file)->headline()->value();
|
||||||
|
}
|
||||||
|
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
|
||||||
|
if ($env_file->count() > 0) {
|
||||||
|
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$env_file = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tags = collect(preg_grep('/^# tags:/', explode("\n", $content)))->values();
|
||||||
|
if ($tags->count() > 0) {
|
||||||
|
$tags = str($tags[0])->after('# tags:')->trim()->explode(',')->map(function ($tag) {
|
||||||
|
return str($tag)->trim()->lower()->value();
|
||||||
|
})->values();
|
||||||
|
} else {
|
||||||
|
$tags = null;
|
||||||
|
}
|
||||||
|
$json = Yaml::parse($content);
|
||||||
|
$yaml = base64_encode(Yaml::dump($json, 10, 2));
|
||||||
|
$payload = [
|
||||||
|
'name' => $serviceName,
|
||||||
|
'documentation' => $documentation,
|
||||||
|
'slogan' => $slogan,
|
||||||
|
'compose' => $yaml,
|
||||||
|
'tags' => $tags,
|
||||||
|
];
|
||||||
|
if ($env_file) {
|
||||||
|
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
|
||||||
|
$env_file_base64 = base64_encode($env_file_content);
|
||||||
|
$payload['envs'] = $env_file_base64;
|
||||||
|
}
|
||||||
|
return $payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ class SyncBunny extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'sync:bunny {--only-template} {--only-version}';
|
protected $signature = 'sync:bunny {--templates} {--release}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -31,8 +31,8 @@ class SyncBunny extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$that = $this;
|
$that = $this;
|
||||||
$only_template = $this->option('only-template');
|
$only_template = $this->option('templates');
|
||||||
$only_version = $this->option('only-version');
|
$only_version = $this->option('release');
|
||||||
$bunny_cdn = "https://cdn.coollabs.io";
|
$bunny_cdn = "https://cdn.coollabs.io";
|
||||||
$bunny_cdn_path = "coolify";
|
$bunny_cdn_path = "coolify";
|
||||||
$bunny_cdn_storage_name = "coolcdn";
|
$bunny_cdn_storage_name = "coolcdn";
|
||||||
@@ -48,7 +48,7 @@ class SyncBunny extends Command
|
|||||||
|
|
||||||
$versions = "versions.json";
|
$versions = "versions.json";
|
||||||
|
|
||||||
PendingRequest::macro('storage', function ($fileName) use($that) {
|
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
||||||
$headers = [
|
$headers = [
|
||||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
@@ -71,19 +71,31 @@ class SyncBunny extends Command
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
$confirmed = confirm('Are you sure you want to sync?');
|
if (!$only_template && !$only_version) {
|
||||||
if (!$confirmed) {
|
$this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if ($only_template) {
|
if ($only_template) {
|
||||||
|
$this->info('About to sync service-templates.json to BunnyCDN.');
|
||||||
|
$confirmed = confirm("Are you sure you want to sync?");
|
||||||
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
|
$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"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
||||||
]);
|
]);
|
||||||
$this->info('Service template uploaded & purged...');
|
$this->info('Service template uploaded & purged...');
|
||||||
return;
|
return;
|
||||||
}
|
} else if ($only_version) {
|
||||||
if ($only_version) {
|
$this->info('About to sync versions.json to BunnyCDN.');
|
||||||
|
$file = file_get_contents("$parent_dir/$versions");
|
||||||
|
$json = json_decode($file, true);
|
||||||
|
$actual_version = data_get($json, 'coolify.v4.version');
|
||||||
|
|
||||||
|
$confirmed = confirm("Are you sure you want to sync to {$actual_version}?");
|
||||||
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||||
@@ -92,6 +104,7 @@ class SyncBunny extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||||
|
|||||||
@@ -2,15 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Jobs\CheckResaleLicenseJob;
|
use App\Jobs\CheckLogDrainContainerJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
use App\Jobs\DockerCleanupJob;
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\InstanceAutoUpdateJob;
|
use App\Jobs\InstanceAutoUpdateJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
|
use App\Jobs\PullHelperImageJob;
|
||||||
|
use App\Jobs\ServerStatusJob;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
|
use App\Models\ScheduledTask;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\Team;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
@@ -19,42 +23,58 @@ class Kernel extends ConsoleKernel
|
|||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
|
// Instance Jobs
|
||||||
// $schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
// Server Jobs
|
||||||
// $this->instance_auto_update($schedule);
|
|
||||||
// $this->check_scheduled_backups($schedule);
|
|
||||||
$this->check_resources($schedule);
|
|
||||||
$this->cleanup_servers($schedule);
|
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
|
$this->check_resources($schedule);
|
||||||
|
$this->check_scheduled_backups($schedule);
|
||||||
|
$this->pull_helper_image($schedule);
|
||||||
|
$this->check_scheduled_tasks($schedule);
|
||||||
} else {
|
} else {
|
||||||
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
|
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||||
|
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
|
|
||||||
|
// Server Jobs
|
||||||
$this->instance_auto_update($schedule);
|
$this->instance_auto_update($schedule);
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->cleanup_servers($schedule);
|
$this->pull_helper_image($schedule);
|
||||||
|
$this->check_scheduled_tasks($schedule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function cleanup_servers($schedule)
|
private function pull_helper_image($schedule)
|
||||||
{
|
{
|
||||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
|
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
|
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function check_resources($schedule)
|
private function check_resources($schedule)
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
|
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||||
|
$own = Team::find(0)->servers;
|
||||||
|
$servers = $servers->merge($own);
|
||||||
|
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
||||||
} else {
|
} else {
|
||||||
$servers = Server::all();
|
$servers = Server::all()->where('ip', '!=', '1.2.3.4');
|
||||||
|
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
||||||
|
}
|
||||||
|
foreach ($containerServers as $server) {
|
||||||
|
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||||
|
if ($server->isLogDrainEnabled()) {
|
||||||
|
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function instance_auto_update($schedule)
|
private function instance_auto_update($schedule)
|
||||||
@@ -69,7 +89,6 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
private function check_scheduled_backups($schedule)
|
private function check_scheduled_backups($schedule)
|
||||||
{
|
{
|
||||||
ray('check_scheduled_backups');
|
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||||
if ($scheduled_backups->isEmpty()) {
|
if ($scheduled_backups->isEmpty()) {
|
||||||
ray('no scheduled backups');
|
ray('no scheduled backups');
|
||||||
@@ -94,6 +113,32 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function check_scheduled_tasks($schedule)
|
||||||
|
{
|
||||||
|
$scheduled_tasks = ScheduledTask::all();
|
||||||
|
if ($scheduled_tasks->isEmpty()) {
|
||||||
|
ray('no scheduled tasks');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($scheduled_tasks as $scheduled_task) {
|
||||||
|
$service = $scheduled_task->service()->get();
|
||||||
|
$application = $scheduled_task->application()->get();
|
||||||
|
|
||||||
|
if (!$application && !$service) {
|
||||||
|
ray('application/service attached to scheduled task does not exist');
|
||||||
|
$scheduled_task->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||||
|
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||||
|
}
|
||||||
|
$schedule->job(new ScheduledTaskJob(
|
||||||
|
task: $scheduled_task
|
||||||
|
))->cron($scheduled_task->frequency)->onOneServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected function commands(): void
|
protected function commands(): void
|
||||||
{
|
{
|
||||||
$this->load(__DIR__ . '/Commands');
|
$this->load(__DIR__ . '/Commands');
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ class CoolifyTaskArgs extends Data
|
|||||||
public string $command,
|
public string $command,
|
||||||
public string $type,
|
public string $type,
|
||||||
public ?string $type_uuid = null,
|
public ?string $type_uuid = null,
|
||||||
|
public ?int $process_id = null,
|
||||||
public ?Model $model = null,
|
public ?Model $model = null,
|
||||||
public ?string $status = null ,
|
public ?string $status = null ,
|
||||||
public bool $ignore_errors = false,
|
public bool $ignore_errors = false,
|
||||||
|
public $call_event_on_finish = null,
|
||||||
) {
|
) {
|
||||||
if(is_null($status)){
|
if(is_null($status)){
|
||||||
$this->status = ProcessStatus::QUEUED->value;
|
$this->status = ProcessStatus::QUEUED->value;
|
||||||
|
|||||||
@@ -8,5 +8,7 @@ enum ProcessStatus: string
|
|||||||
case IN_PROGRESS = 'in_progress';
|
case IN_PROGRESS = 'in_progress';
|
||||||
case FINISHED = 'finished';
|
case FINISHED = 'finished';
|
||||||
case ERROR = 'error';
|
case ERROR = 'error';
|
||||||
|
case KILLED = 'killed';
|
||||||
case CANCELLED = 'cancelled';
|
case CANCELLED = 'cancelled';
|
||||||
|
case CLOSED = 'closed';
|
||||||
}
|
}
|
||||||
|
|||||||
34
app/Events/ApplicationStatusChanged.php
Normal file
34
app/Events/ApplicationStatusChanged.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ApplicationStatusChanged implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
public $teamId;
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
$teamId = auth()->user()->currentTeam()->id ?? null;
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception("Team id is null");
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/Events/BackupCreated.php
Normal file
34
app/Events/BackupCreated.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class BackupCreated implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
public $teamId;
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
$teamId = auth()->user()->currentTeam()->id ?? null;
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception("Team id is null");
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/Events/DatabaseStatusChanged.php
Normal file
34
app/Events/DatabaseStatusChanged.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class DatabaseStatusChanged implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
public $userId;
|
||||||
|
public function __construct($userId = null)
|
||||||
|
{
|
||||||
|
if (is_null($userId)) {
|
||||||
|
$userId = auth()->user()->id ?? null;
|
||||||
|
}
|
||||||
|
if (is_null($userId)) {
|
||||||
|
throw new \Exception("User id is null");
|
||||||
|
}
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/Events/ProxyStatusChanged.php
Normal file
34
app/Events/ProxyStatusChanged.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ProxyStatusChanged implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
public $teamId;
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
$teamId = auth()->user()->currentTeam()->id ?? null;
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception("Team id is null");
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/Events/ServiceStatusChanged.php
Normal file
34
app/Events/ServiceStatusChanged.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ServiceStatusChanged implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
public $userId;
|
||||||
|
public function __construct($userId = null)
|
||||||
|
{
|
||||||
|
if (is_null($userId)) {
|
||||||
|
$userId = auth()->user()->id ?? null;
|
||||||
|
}
|
||||||
|
if (is_null($userId)) {
|
||||||
|
throw new \Exception("User id is null");
|
||||||
|
}
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Events/TestEvent.php
Normal file
28
app/Events/TestEvent.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class TestEvent implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
public $teamId;
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->teamId = auth()->user()->currentTeam()->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ namespace App\Exceptions;
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\AuthenticationException;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
|
use RuntimeException;
|
||||||
use Sentry\Laravel\Integration;
|
use Sentry\Laravel\Integration;
|
||||||
use Sentry\State\Scope;
|
use Sentry\State\Scope;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
@@ -40,6 +42,13 @@ class Handler extends ExceptionHandler
|
|||||||
];
|
];
|
||||||
private InstanceSettings $settings;
|
private InstanceSettings $settings;
|
||||||
|
|
||||||
|
protected function unauthenticated($request, AuthenticationException $exception)
|
||||||
|
{
|
||||||
|
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
|
||||||
|
return response()->json(['message' => $exception->getMessage()], 401);
|
||||||
|
}
|
||||||
|
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Register the exception handling callbacks for the application.
|
* Register the exception handling callbacks for the application.
|
||||||
*/
|
*/
|
||||||
@@ -47,6 +56,9 @@ class Handler extends ExceptionHandler
|
|||||||
{
|
{
|
||||||
$this->reportable(function (Throwable $e) {
|
$this->reportable(function (Throwable $e) {
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
if ($e instanceof RuntimeException) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = InstanceSettings::get();
|
||||||
@@ -65,6 +77,7 @@ class Handler extends ExceptionHandler
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
ray('reporting to sentry');
|
||||||
Integration::captureUnhandledException($e);
|
Integration::captureUnhandledException($e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
159
app/Http/Controllers/Api/Deploy.php
Normal file
159
app/Http/Controllers/Api/Deploy.php
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartMariadb;
|
||||||
|
use App\Actions\Database\StartMongodb;
|
||||||
|
use App\Actions\Database\StartMysql;
|
||||||
|
use App\Actions\Database\StartPostgresql;
|
||||||
|
use App\Actions\Database\StartRedis;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class Deploy extends Controller
|
||||||
|
{
|
||||||
|
public function deploy(Request $request)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$teamId = data_get($token, 'team_id');
|
||||||
|
$uuids = $request->query->get('uuid');
|
||||||
|
$tags = $request->query->get('tag');
|
||||||
|
$force = $request->query->get('force') ?? false;
|
||||||
|
|
||||||
|
if ($uuids && $tags) {
|
||||||
|
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||||
|
}
|
||||||
|
if ($tags) {
|
||||||
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
|
} else if ($uuids) {
|
||||||
|
return $this->by_uuids($uuids, $teamId, $force);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||||
|
{
|
||||||
|
$uuids = explode(',', $uuid);
|
||||||
|
$uuids = collect(array_filter($uuids));
|
||||||
|
|
||||||
|
if (count($uuids) === 0) {
|
||||||
|
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
foreach ($uuids as $uuid) {
|
||||||
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
|
if ($resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
return response()->json(['message' => $message->toArray()], 200);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
}
|
||||||
|
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||||
|
{
|
||||||
|
$tags = explode(',', $tags);
|
||||||
|
$tags = collect(array_filter($tags));
|
||||||
|
|
||||||
|
if (count($tags) === 0) {
|
||||||
|
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||||
|
if (!$found_tag) {
|
||||||
|
$message->push("Tag {$tag} not found.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$applications = $found_tag->applications();
|
||||||
|
$services = $found_tag->services();
|
||||||
|
if ($applications->count() === 0 && $services->count() === 0) {
|
||||||
|
$message->push("No resources found for tag {$tag}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($applications as $resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
foreach ($services as $resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
return response()->json(['message' => $message->toArray()], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
}
|
||||||
|
public function deploy_resource($resource, bool $force = false): Collection
|
||||||
|
{
|
||||||
|
$message = collect([]);
|
||||||
|
$type = $resource->getMorphClass();
|
||||||
|
if ($type === 'App\Models\Application') {
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $resource,
|
||||||
|
deployment_uuid: new Cuid2(7),
|
||||||
|
force_rebuild: $force,
|
||||||
|
);
|
||||||
|
$message->push("Application {$resource->name} deployment queued.");
|
||||||
|
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartPostgresql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneRedis') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartRedis::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMongodb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMysql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMariadb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\Service') {
|
||||||
|
StartService::run($resource);
|
||||||
|
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
||||||
|
}
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
|
||||||
|
|
||||||
class ApplicationController extends Controller
|
|
||||||
{
|
|
||||||
use AuthorizesRequests, ValidatesRequests;
|
|
||||||
|
|
||||||
public function configuration()
|
|
||||||
{
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
|
||||||
if (!$environment) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
|
||||||
if (!$application) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
return view('project.application.configuration', ['application' => $application]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deployments()
|
|
||||||
{
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
|
||||||
if (!$environment) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
|
||||||
if (!$application) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 8);
|
|
||||||
return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deployment()
|
|
||||||
{
|
|
||||||
$deploymentUuid = request()->route('deployment_uuid');
|
|
||||||
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
|
||||||
if (!$environment) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
|
||||||
if (!$application) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
// $activity = Activity::where('properties->type_uuid', '=', $deploymentUuid)->first();
|
|
||||||
// if (!$activity) {
|
|
||||||
// return redirect()->route('project.application.deployments', [
|
|
||||||
// 'project_uuid' => $project->uuid,
|
|
||||||
// 'environment_name' => $environment->name,
|
|
||||||
// 'application_uuid' => $application->uuid,
|
|
||||||
// ]);
|
|
||||||
// }
|
|
||||||
$application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first();
|
|
||||||
if (!$application_deployment_queue) {
|
|
||||||
return redirect()->route('project.application.deployments', [
|
|
||||||
'project_uuid' => $project->uuid,
|
|
||||||
'environment_name' => $environment->name,
|
|
||||||
'application_uuid' => $application->uuid,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return view('project.application.deployment', [
|
|
||||||
'application' => $application,
|
|
||||||
// 'activity' => $activity,
|
|
||||||
'application_deployment_queue' => $application_deployment_queue,
|
|
||||||
'deployment_uuid' => $deploymentUuid,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,23 +2,68 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Events\TestEvent;
|
||||||
use App\Models\S3Storage;
|
|
||||||
use App\Models\StandalonePostgresql;
|
|
||||||
use App\Models\TeamInvitation;
|
use App\Models\TeamInvitation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Laravel\Fortify\Fortify;
|
||||||
|
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse;
|
||||||
|
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
|
||||||
class Controller extends BaseController
|
class Controller extends BaseController
|
||||||
{
|
{
|
||||||
use AuthorizesRequests, ValidatesRequests;
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
|
|
||||||
|
public function realtime_test() {
|
||||||
|
if (auth()->user()?->currentTeam()->id !== 0) {
|
||||||
|
return redirect(RouteServiceProvider::HOME);
|
||||||
|
}
|
||||||
|
TestEvent::dispatch();
|
||||||
|
return 'Look at your other tab.';
|
||||||
|
}
|
||||||
|
public function verify() {
|
||||||
|
return view('auth.verify-email');
|
||||||
|
}
|
||||||
|
public function email_verify(EmailVerificationRequest $request) {
|
||||||
|
$request->fulfill();
|
||||||
|
$name = request()->user()?->name;
|
||||||
|
send_internal_notification("User {$name} verified their email address.");
|
||||||
|
return redirect(RouteServiceProvider::HOME);
|
||||||
|
}
|
||||||
|
public function forgot_password(Request $request) {
|
||||||
|
if (is_transactional_emails_active()) {
|
||||||
|
$arrayOfRequest = $request->only(Fortify::email());
|
||||||
|
$request->merge([
|
||||||
|
'email' => Str::lower($arrayOfRequest['email']),
|
||||||
|
]);
|
||||||
|
$type = set_transanctional_email_settings();
|
||||||
|
if (!$type) {
|
||||||
|
return response()->json(['message' => 'Transactional emails are not active'], 400);
|
||||||
|
}
|
||||||
|
$request->validate([Fortify::email() => 'required|email']);
|
||||||
|
$status = Password::broker(config('fortify.passwords'))->sendResetLink(
|
||||||
|
$request->only(Fortify::email())
|
||||||
|
);
|
||||||
|
if ($status == Password::RESET_LINK_SENT) {
|
||||||
|
return app(SuccessfulPasswordResetLinkRequestResponse::class, ['status' => $status]);
|
||||||
|
}
|
||||||
|
if ($status == Password::RESET_THROTTLED) {
|
||||||
|
return response('Already requested a password reset in the past minutes.', 400);
|
||||||
|
}
|
||||||
|
return app(FailedPasswordResetLinkRequestResponse::class, ['status' => $status]);
|
||||||
|
}
|
||||||
|
return response()->json(['message' => 'Transactional emails are not active'], 400);
|
||||||
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
$token = request()->get('token');
|
$token = request()->get('token');
|
||||||
@@ -39,6 +84,10 @@ class Controller extends BaseController
|
|||||||
} else {
|
} else {
|
||||||
$team = $user->teams()->first();
|
$team = $user->teams()->first();
|
||||||
}
|
}
|
||||||
|
if (is_null(data_get($user, 'email_verified_at'))) {
|
||||||
|
$user->email_verified_at = now();
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
session(['currentTeam' => $team]);
|
session(['currentTeam' => $team]);
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
@@ -47,98 +96,31 @@ class Controller extends BaseController
|
|||||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function license()
|
public function accept_invitation()
|
||||||
{
|
|
||||||
if (!isCloud()) {
|
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
return view('settings.license', [
|
|
||||||
'settings' => InstanceSettings::get(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function force_passoword_reset()
|
|
||||||
{
|
|
||||||
return view('auth.force-password-reset');
|
|
||||||
}
|
|
||||||
public function boarding()
|
|
||||||
{
|
|
||||||
if (currentTeam()->boarding || isDev()) {
|
|
||||||
return view('boarding');
|
|
||||||
} else {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function settings()
|
|
||||||
{
|
|
||||||
if (isInstanceAdmin()) {
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
$database = StandalonePostgresql::whereName('coolify-db')->first();
|
|
||||||
if ($database) {
|
|
||||||
$s3s = S3Storage::whereTeamId(0)->get();
|
|
||||||
}
|
|
||||||
return view('settings.configuration', [
|
|
||||||
'settings' => $settings,
|
|
||||||
'database' => $database,
|
|
||||||
's3s' => $s3s ?? [],
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function team()
|
|
||||||
{
|
|
||||||
$invitations = [];
|
|
||||||
if (auth()->user()->isAdminFromSession()) {
|
|
||||||
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
|
|
||||||
}
|
|
||||||
return view('team.index', [
|
|
||||||
'invitations' => $invitations,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function storages()
|
|
||||||
{
|
|
||||||
$s3 = S3Storage::ownedByCurrentTeam()->get();
|
|
||||||
return view('team.storages.all', [
|
|
||||||
's3' => $s3,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function storages_show()
|
|
||||||
{
|
|
||||||
$storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->firstOrFail();
|
|
||||||
return view('team.storages.show', [
|
|
||||||
'storage' => $storage,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function members()
|
|
||||||
{
|
|
||||||
$invitations = [];
|
|
||||||
if (auth()->user()->isAdminFromSession()) {
|
|
||||||
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
|
|
||||||
}
|
|
||||||
return view('team.members', [
|
|
||||||
'invitations' => $invitations,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function acceptInvitation()
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
$resetPassword = request()->query('reset-password');
|
||||||
|
$invitationUuid = request()->route('uuid');
|
||||||
|
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
|
||||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||||
if (auth()->user()->id !== $user->id) {
|
|
||||||
abort(401);
|
|
||||||
}
|
|
||||||
$invitationValid = $invitation->isValid();
|
$invitationValid = $invitation->isValid();
|
||||||
if ($invitationValid) {
|
if ($invitationValid) {
|
||||||
|
if ($resetPassword) {
|
||||||
|
$user->update([
|
||||||
|
'password' => Hash::make($invitationUuid),
|
||||||
|
'force_password_reset' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
|
||||||
|
$invitation->delete();
|
||||||
|
return redirect()->route('team.index');
|
||||||
|
}
|
||||||
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
||||||
refreshSession($invitation->team);
|
|
||||||
$invitation->delete();
|
$invitation->delete();
|
||||||
|
if (auth()->user()?->id !== $user->id) {
|
||||||
|
return redirect()->route('login');
|
||||||
|
}
|
||||||
|
refreshSession($invitation->team);
|
||||||
return redirect()->route('team.index');
|
return redirect()->route('team.index');
|
||||||
} else {
|
} else {
|
||||||
abort(401);
|
abort(401);
|
||||||
@@ -149,7 +131,7 @@ class Controller extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revokeInvitation()
|
public function revoke_invitation()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
|
||||||
|
|
||||||
class DatabaseController extends Controller
|
|
||||||
{
|
|
||||||
use AuthorizesRequests, ValidatesRequests;
|
|
||||||
|
|
||||||
public function configuration()
|
|
||||||
{
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
|
||||||
if (!$environment) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
|
||||||
if (!$database) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
return view('project.database.configuration', ['database' => $database]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function executions()
|
|
||||||
{
|
|
||||||
$backup_uuid = request()->route('backup_uuid');
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
|
||||||
if (!$environment) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
|
||||||
if (!$database) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$backup = $database->scheduledBackups->where('uuid', $backup_uuid)->first();
|
|
||||||
if (!$backup) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$executions = collect($backup->executions)->sortByDesc('created_at');
|
|
||||||
return view('project.database.backups.executions', [
|
|
||||||
'database' => $database,
|
|
||||||
'backup' => $backup,
|
|
||||||
'executions' => $executions,
|
|
||||||
's3s' => currentTeam()->s3s,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function backups()
|
|
||||||
{
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
|
||||||
if (!$environment) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
|
||||||
if (!$database) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
// No backups for redis
|
|
||||||
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
|
|
||||||
return redirect()->route('project.database.configuration', [
|
|
||||||
'project_uuid' => $project->uuid,
|
|
||||||
'environment_name' => $environment->name,
|
|
||||||
'database_uuid' => $database->uuid,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return view('project.database.backups.all', [
|
|
||||||
'database' => $database,
|
|
||||||
's3s' => currentTeam()->s3s,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,8 +32,14 @@ class MagicController extends Controller
|
|||||||
|
|
||||||
public function environments()
|
public function environments()
|
||||||
{
|
{
|
||||||
|
$project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first();
|
||||||
|
if (!$project) {
|
||||||
|
return response()->json([
|
||||||
|
'environments' => []
|
||||||
|
]);
|
||||||
|
}
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments
|
'environments' => $project->environments
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
public function all()
|
|
||||||
{
|
|
||||||
return view('projects', [
|
|
||||||
'projects' => Project::ownedByCurrentTeam()->get(),
|
|
||||||
'servers' => Server::ownedByCurrentTeam()->count(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function edit()
|
|
||||||
{
|
|
||||||
$projectUuid = request()->route('project_uuid');
|
|
||||||
$teamId = currentTeam()->id;
|
|
||||||
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
return view('project.edit', ['project' => $project]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function show()
|
|
||||||
{
|
|
||||||
$projectUuid = request()->route('project_uuid');
|
|
||||||
$teamId = currentTeam()->id;
|
|
||||||
|
|
||||||
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$project->load(['environments']);
|
|
||||||
return view('project.show', ['project' => $project]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
|
||||||
{
|
|
||||||
$services = getServiceTemplates();
|
|
||||||
$type = Str::of(request()->query('type'));
|
|
||||||
$destination_uuid = request()->query('destination');
|
|
||||||
$server_id = request()->query('server_id');
|
|
||||||
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
|
|
||||||
if (!$environment) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
if (in_array($type, DATABASE_TYPES)) {
|
|
||||||
if ($type->value() === "postgresql") {
|
|
||||||
$database = create_standalone_postgresql($environment->id, $destination_uuid);
|
|
||||||
} else if ($type->value() === 'redis') {
|
|
||||||
$database = create_standalone_redis($environment->id, $destination_uuid);
|
|
||||||
}
|
|
||||||
return redirect()->route('project.database.configuration', [
|
|
||||||
'project_uuid' => $project->uuid,
|
|
||||||
'environment_name' => $environment->name,
|
|
||||||
'database_uuid' => $database->uuid,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
|
|
||||||
$oneClickServiceName = $type->after('one-click-service-')->value();
|
|
||||||
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
|
||||||
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
|
||||||
if ($oneClickDotEnvs) {
|
|
||||||
$oneClickDotEnvs = Str::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->value()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resources()
|
|
||||||
{
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
|
|
||||||
if (!$environment) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
return view('project.resources', [
|
|
||||||
'project' => $project,
|
|
||||||
'environment' => $environment
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire;
|
|
||||||
|
|
||||||
use App\Enums\ProcessStatus;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Spatie\Activitylog\Models\Activity;
|
|
||||||
|
|
||||||
class ActivityMonitor extends Component
|
|
||||||
{
|
|
||||||
public string|null $header = null;
|
|
||||||
public $activityId;
|
|
||||||
public $isPollingActive = false;
|
|
||||||
|
|
||||||
protected $activity;
|
|
||||||
protected $listeners = ['newMonitorActivity'];
|
|
||||||
|
|
||||||
public function newMonitorActivity($activityId)
|
|
||||||
{
|
|
||||||
$this->activityId = $activityId;
|
|
||||||
|
|
||||||
$this->hydrateActivity();
|
|
||||||
|
|
||||||
$this->isPollingActive = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hydrateActivity()
|
|
||||||
{
|
|
||||||
$this->activity = Activity::query()
|
|
||||||
->find($this->activityId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function polling()
|
|
||||||
{
|
|
||||||
$this->hydrateActivity();
|
|
||||||
$this->setStatus(ProcessStatus::IN_PROGRESS);
|
|
||||||
$exit_code = data_get($this->activity, 'properties.exitCode');
|
|
||||||
if ($exit_code !== null) {
|
|
||||||
if ($exit_code === 0) {
|
|
||||||
$this->setStatus(ProcessStatus::FINISHED);
|
|
||||||
} else {
|
|
||||||
$this->setStatus(ProcessStatus::ERROR);
|
|
||||||
}
|
|
||||||
$this->isPollingActive = false;
|
|
||||||
$this->emit('activityFinished');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function setStatus($status)
|
|
||||||
{
|
|
||||||
$this->activity->properties = $this->activity->properties->merge([
|
|
||||||
'status' => $status,
|
|
||||||
]);
|
|
||||||
$this->activity->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Application;
|
|
||||||
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class DeploymentLogs extends Component
|
|
||||||
{
|
|
||||||
public ApplicationDeploymentQueue $application_deployment_queue;
|
|
||||||
public $isKeepAliveOn = true;
|
|
||||||
protected $listeners = ['refreshQueue'];
|
|
||||||
|
|
||||||
public function refreshQueue()
|
|
||||||
{
|
|
||||||
$this->application_deployment_queue->refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function polling()
|
|
||||||
{
|
|
||||||
$this->emit('deploymentFinished');
|
|
||||||
$this->application_deployment_queue->refresh();
|
|
||||||
if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') {
|
|
||||||
$this->isKeepAliveOn = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Application;
|
|
||||||
|
|
||||||
use App\Models\Application;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Deployments extends Component
|
|
||||||
{
|
|
||||||
public Application $application;
|
|
||||||
public $deployments = [];
|
|
||||||
public int $deployments_count = 0;
|
|
||||||
public string $current_url;
|
|
||||||
public int $skip = 0;
|
|
||||||
public int $default_take = 8;
|
|
||||||
public bool $show_next = false;
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->current_url = url()->current();
|
|
||||||
$this->show_more();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function show_more()
|
|
||||||
{
|
|
||||||
if (count($this->deployments) !== 0) {
|
|
||||||
$this->show_next = true;
|
|
||||||
if (count($this->deployments) < $this->default_take) {
|
|
||||||
$this->show_next = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reload_deployments()
|
|
||||||
{
|
|
||||||
$this->load_deployments();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function load_deployments(int|null $take = null)
|
|
||||||
{
|
|
||||||
if ($take) {
|
|
||||||
$this->skip = $this->skip + $take;
|
|
||||||
}
|
|
||||||
$take = $this->default_take;
|
|
||||||
|
|
||||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
|
|
||||||
$this->deployments = $deployments;
|
|
||||||
$this->deployments_count = $count;
|
|
||||||
$this->show_more();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
public string $applicationId;
|
|
||||||
|
|
||||||
public Application $application;
|
|
||||||
public Collection $services;
|
|
||||||
public string $name;
|
|
||||||
public ?string $fqdn = null;
|
|
||||||
public string $git_repository;
|
|
||||||
public string $git_branch;
|
|
||||||
public ?string $git_commit_sha = null;
|
|
||||||
public string $build_pack;
|
|
||||||
|
|
||||||
public bool $is_static;
|
|
||||||
public bool $is_git_submodules_enabled;
|
|
||||||
public bool $is_git_lfs_enabled;
|
|
||||||
public bool $is_debug_enabled;
|
|
||||||
public bool $is_preview_deployments_enabled;
|
|
||||||
public bool $is_auto_deploy_enabled;
|
|
||||||
public bool $is_force_https_enabled;
|
|
||||||
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'application.name' => 'required',
|
|
||||||
'application.description' => 'nullable',
|
|
||||||
'application.fqdn' => 'nullable',
|
|
||||||
'application.git_repository' => 'required',
|
|
||||||
'application.git_branch' => 'required',
|
|
||||||
'application.git_commit_sha' => 'nullable',
|
|
||||||
'application.install_command' => 'nullable',
|
|
||||||
'application.build_command' => 'nullable',
|
|
||||||
'application.start_command' => 'nullable',
|
|
||||||
'application.build_pack' => 'required',
|
|
||||||
'application.static_image' => 'required',
|
|
||||||
'application.base_directory' => 'required',
|
|
||||||
'application.publish_directory' => 'nullable',
|
|
||||||
'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',
|
|
||||||
];
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'application.name' => 'name',
|
|
||||||
'application.description' => 'description',
|
|
||||||
'application.fqdn' => 'FQDN',
|
|
||||||
'application.git_repository' => 'Git repository',
|
|
||||||
'application.git_branch' => 'Git branch',
|
|
||||||
'application.git_commit_sha' => 'Git commit SHA',
|
|
||||||
'application.install_command' => 'Install command',
|
|
||||||
'application.build_command' => 'Build command',
|
|
||||||
'application.start_command' => 'Start command',
|
|
||||||
'application.build_pack' => 'Build pack',
|
|
||||||
'application.static_image' => 'Static image',
|
|
||||||
'application.base_directory' => 'Base directory',
|
|
||||||
'application.publish_directory' => 'Publish directory',
|
|
||||||
'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',
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
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 instantSave()
|
|
||||||
{
|
|
||||||
// @TODO: find another way - if possible
|
|
||||||
$this->application->settings->is_static = $this->is_static;
|
|
||||||
if ($this->is_static) {
|
|
||||||
$this->application->ports_exposes = 80;
|
|
||||||
} else {
|
|
||||||
$this->application->ports_exposes = 3000;
|
|
||||||
}
|
|
||||||
$this->application->settings->is_git_submodules_enabled = $this->is_git_submodules_enabled;
|
|
||||||
$this->application->settings->is_git_lfs_enabled = $this->is_git_lfs_enabled;
|
|
||||||
$this->application->settings->is_debug_enabled = $this->is_debug_enabled;
|
|
||||||
$this->application->settings->is_preview_deployments_enabled = $this->is_preview_deployments_enabled;
|
|
||||||
$this->application->settings->is_auto_deploy_enabled = $this->is_auto_deploy_enabled;
|
|
||||||
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
|
|
||||||
$this->application->settings->save();
|
|
||||||
$this->application->save();
|
|
||||||
$this->application->refresh();
|
|
||||||
$this->emit('success', 'Application settings updated!');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getWildcardDomain() {
|
|
||||||
$server = data_get($this->application, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
$fqdn = generateFqdn($server, $this->application->uuid);
|
|
||||||
ray($fqdn);
|
|
||||||
$this->application->fqdn = $fqdn;
|
|
||||||
$this->application->save();
|
|
||||||
$this->emit('success', 'Application settings updated!');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
$this->application->fqdn = $domains->implode(',');
|
|
||||||
}
|
|
||||||
if (data_get($this->application, 'dockerfile')) {
|
|
||||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
|
||||||
if ($port && !$this->application->ports_exposes) {
|
|
||||||
$this->application->ports_exposes = $port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->application->base_directory && $this->application->base_directory !== '/') {
|
|
||||||
$this->application->base_directory = rtrim($this->application->base_directory, '/');
|
|
||||||
}
|
|
||||||
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
|
|
||||||
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
|
|
||||||
}
|
|
||||||
$this->application->save();
|
|
||||||
$this->emit('success', 'Application settings updated!');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Application;
|
|
||||||
|
|
||||||
use App\Actions\Application\StopApplication;
|
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Models\Application;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Heading extends Component
|
|
||||||
{
|
|
||||||
public Application $application;
|
|
||||||
public array $parameters;
|
|
||||||
|
|
||||||
protected string $deploymentUuid;
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function check_status()
|
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
$this->deploy(force_rebuild: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deploy(bool $force_rebuild = false)
|
|
||||||
{
|
|
||||||
$this->setDeploymentUuid();
|
|
||||||
queue_application_deployment(
|
|
||||||
application_id: $this->application->id,
|
|
||||||
deployment_uuid: $this->deploymentUuid,
|
|
||||||
force_rebuild: $force_rebuild,
|
|
||||||
);
|
|
||||||
return redirect()->route('project.application.deployment', [
|
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
|
||||||
'deployment_uuid' => $this->deploymentUuid,
|
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function setDeploymentUuid()
|
|
||||||
{
|
|
||||||
$this->deploymentUuid = new Cuid2(7);
|
|
||||||
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function stop()
|
|
||||||
{
|
|
||||||
StopApplication::run($this->application);
|
|
||||||
$this->application->status = 'exited';
|
|
||||||
$this->application->save();
|
|
||||||
$this->application->refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Application;
|
|
||||||
|
|
||||||
use App\Models\Application;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Rollback extends Component
|
|
||||||
{
|
|
||||||
public Application $application;
|
|
||||||
public $images = [];
|
|
||||||
public string|null $current;
|
|
||||||
public array $parameters;
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rollbackImage($commit)
|
|
||||||
{
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
|
||||||
|
|
||||||
queue_application_deployment(
|
|
||||||
application_id: $this->application->id,
|
|
||||||
deployment_uuid: $deployment_uuid,
|
|
||||||
commit: $commit,
|
|
||||||
force_rebuild: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
return redirect()->route('project.application.deployment', [
|
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
|
||||||
'deployment_uuid' => $deployment_uuid,
|
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadImages()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$image = $this->application->uuid;
|
|
||||||
$output = instant_remote_process([
|
|
||||||
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
|
|
||||||
], $this->application->destination->server, throwError: false);
|
|
||||||
$current_tag = Str::of($output)->trim()->explode(":");
|
|
||||||
$this->current = data_get($current_tag, 1);
|
|
||||||
|
|
||||||
$output = instant_remote_process([
|
|
||||||
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
|
|
||||||
], $this->application->destination->server);
|
|
||||||
$this->images = Str::of($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
|
|
||||||
return Str::of($item)->contains($image);
|
|
||||||
})->map(function ($item) {
|
|
||||||
$item = Str::of($item)->explode('#');
|
|
||||||
if ($item[1] === $this->current) {
|
|
||||||
// $is_current = true;
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
'tag' => $item[1],
|
|
||||||
'created_at' => $item[2],
|
|
||||||
'is_current' => $is_current ?? null,
|
|
||||||
];
|
|
||||||
})->toArray();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database;
|
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class BackupExecution extends Component
|
|
||||||
{
|
|
||||||
public ScheduledDatabaseBackupExecution $execution;
|
|
||||||
|
|
||||||
public function download()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(): void
|
|
||||||
{
|
|
||||||
delete_backup_locally($this->execution->filename, $this->execution->scheduledDatabaseBackup->database->destination->server);
|
|
||||||
$this->execution->delete();
|
|
||||||
$this->emit('success', 'Backup deleted successfully.');
|
|
||||||
$this->emit('refreshBackupExecutions');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database;
|
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class BackupExecutions extends Component
|
|
||||||
{
|
|
||||||
public $backup;
|
|
||||||
public $executions;
|
|
||||||
protected $listeners = ['refreshBackupExecutions'];
|
|
||||||
|
|
||||||
public function refreshBackupExecutions(): void
|
|
||||||
{
|
|
||||||
$this->executions = $this->backup->executions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
class Heading extends Component
|
|
||||||
{
|
|
||||||
public $database;
|
|
||||||
public array $parameters;
|
|
||||||
|
|
||||||
protected $listeners = ['activityFinished'];
|
|
||||||
|
|
||||||
public function activityFinished()
|
|
||||||
{
|
|
||||||
$this->database->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$this->emit('refresh');
|
|
||||||
$this->check_status();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function check_status()
|
|
||||||
{
|
|
||||||
dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
|
||||||
$this->database->refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function stop()
|
|
||||||
{
|
|
||||||
StopDatabase::run($this->database);
|
|
||||||
$this->database->status = 'exited';
|
|
||||||
$this->database->save();
|
|
||||||
$this->check_status();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function start()
|
|
||||||
{
|
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database;
|
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class ScheduledBackups extends Component
|
|
||||||
{
|
|
||||||
public $database;
|
|
||||||
public $parameters;
|
|
||||||
protected $listeners = ['refreshScheduledBackups'];
|
|
||||||
|
|
||||||
public function mount(): void
|
|
||||||
{
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete($scheduled_backup_id): void
|
|
||||||
{
|
|
||||||
$this->database->scheduledBackups->find($scheduled_backup_id)->delete();
|
|
||||||
$this->emit('success', 'Scheduled backup deleted successfully.');
|
|
||||||
$this->refreshScheduledBackups();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function refreshScheduledBackups(): void
|
|
||||||
{
|
|
||||||
ray('refreshScheduledBackups');
|
|
||||||
$this->database->refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project;
|
|
||||||
|
|
||||||
use App\Models\Project;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Edit extends Component
|
|
||||||
{
|
|
||||||
public Project $project;
|
|
||||||
protected $rules = [
|
|
||||||
'project.name' => 'required|min:3|max:255',
|
|
||||||
'project.description' => 'nullable|string|max:255',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
$this->validate();
|
|
||||||
try {
|
|
||||||
$this->project->save();
|
|
||||||
$this->emit('saved');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
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 $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->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()
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
$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->parse(isNew: true);
|
|
||||||
|
|
||||||
return redirect()->route('project.service', [
|
|
||||||
'service_uuid' => $service->uuid,
|
|
||||||
'environment_name' => $environment->name,
|
|
||||||
'project_uuid' => $project->uuid,
|
|
||||||
]);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Service;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
$this->emit('generateDockerCompose');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Service;
|
|
||||||
|
|
||||||
use App\Actions\Service\StartService;
|
|
||||||
use App\Actions\Service\StopService;
|
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Models\Service;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Navbar extends Component
|
|
||||||
{
|
|
||||||
public Service $service;
|
|
||||||
public array $parameters;
|
|
||||||
public array $query;
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.project.service.navbar');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkStatus()
|
|
||||||
{
|
|
||||||
$this->emit('checkStatus');
|
|
||||||
}
|
|
||||||
public function deploy()
|
|
||||||
{
|
|
||||||
$this->service->parse();
|
|
||||||
$activity = StartService::run($this->service);
|
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
|
||||||
}
|
|
||||||
public function stop()
|
|
||||||
{
|
|
||||||
StopService::run($this->service);
|
|
||||||
$this->service->refresh();
|
|
||||||
$this->emit('success', 'Service stopped successfully.');
|
|
||||||
$this->checkStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Shared;
|
|
||||||
|
|
||||||
use App\Jobs\StopResourceJob;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Danger extends Component
|
|
||||||
{
|
|
||||||
public $resource;
|
|
||||||
public array $parameters;
|
|
||||||
public ?string $modalId = null;
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->modalId = new Cuid2(7);
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
StopResourceJob::dispatchSync($this->resource);
|
|
||||||
return redirect()->route('project.resources', [
|
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
|
||||||
'environment_name' => $this->parameters['environment_name']
|
|
||||||
]);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Shared;
|
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Destination extends Component
|
|
||||||
{
|
|
||||||
public $destination;
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
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' => 'nullable',
|
|
||||||
'env.is_build_time' => 'required|boolean',
|
|
||||||
];
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'key' => 'key',
|
|
||||||
'value' => 'value',
|
|
||||||
'is_build_time' => 'build',
|
|
||||||
];
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
$this->submit();
|
|
||||||
}
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
$this->validate();
|
|
||||||
$this->env->save();
|
|
||||||
$this->emit('success', 'Environment variable updated successfully.');
|
|
||||||
$this->emit('refreshEnvs');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete()
|
|
||||||
{
|
|
||||||
$this->env->delete();
|
|
||||||
$this->emit('refreshEnvs');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<?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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Shared\Storages;
|
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Add extends Component
|
|
||||||
{
|
|
||||||
public $uuid;
|
|
||||||
public $parameters;
|
|
||||||
public string $name;
|
|
||||||
public string $mount_path;
|
|
||||||
public string|null $host_path = null;
|
|
||||||
|
|
||||||
protected $listeners = ['clearAddStorage' => 'clear'];
|
|
||||||
protected $rules = [
|
|
||||||
'name' => 'required|string',
|
|
||||||
'mount_path' => 'required|string',
|
|
||||||
'host_path' => 'string|nullable',
|
|
||||||
];
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'name' => 'name',
|
|
||||||
'mount_path' => 'mount',
|
|
||||||
'host_path' => 'host',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
$this->validate();
|
|
||||||
$name = $this->uuid . '-' . $this->name;
|
|
||||||
$this->emit('addNewVolume', [
|
|
||||||
'name' => $name,
|
|
||||||
'mount_path' => $this->mount_path,
|
|
||||||
'host_path' => $this->host_path,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->name = '';
|
|
||||||
$this->mount_path = '';
|
|
||||||
$this->host_path = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Server;
|
|
||||||
|
|
||||||
use App\Actions\Server\InstallDocker;
|
|
||||||
use App\Models\Server;
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Form extends Component
|
|
||||||
{
|
|
||||||
use AuthorizesRequests;
|
|
||||||
public Server $server;
|
|
||||||
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',
|
|
||||||
'server.description' => 'nullable',
|
|
||||||
'server.ip' => 'required',
|
|
||||||
'server.user' => 'required',
|
|
||||||
'server.port' => 'required',
|
|
||||||
'server.settings.is_cloudflare_tunnel' => 'required',
|
|
||||||
'server.settings.is_reachable' => 'required',
|
|
||||||
'server.settings.is_part_of_swarm' => 'required',
|
|
||||||
'wildcard_domain' => 'nullable|url',
|
|
||||||
];
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'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'
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
|
||||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
|
||||||
}
|
|
||||||
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 = InstallDocker::run($this->server);
|
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
|
||||||
}
|
|
||||||
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 = $this->server->validateConnection();
|
|
||||||
if ($uptime) {
|
|
||||||
$install && $this->emit('success', 'Server is reachable.');
|
|
||||||
} else {
|
|
||||||
$install && $this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$dockerInstalled = $this->server->validateDockerEngine();
|
|
||||||
if ($dockerInstalled) {
|
|
||||||
$install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
|
|
||||||
} else {
|
|
||||||
$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);
|
|
||||||
} finally {
|
|
||||||
$this->emit('proxyStatusUpdated');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->authorize('delete', $this->server);
|
|
||||||
if (!$this->server->isEmpty()) {
|
|
||||||
$this->emit('error', 'Server has defined resources. Please delete them first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->server->delete();
|
|
||||||
return redirect()->route('server.all');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
if (in_array($this->server->ip, $uniqueIPs)) {
|
|
||||||
$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();
|
|
||||||
$this->server->save();
|
|
||||||
$this->emit('success', 'Server updated successfully.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Server\Proxy;
|
|
||||||
|
|
||||||
use App\Actions\Proxy\CheckProxy;
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Deploy extends Component
|
|
||||||
{
|
|
||||||
public Server $server;
|
|
||||||
public bool $traefikDashboardAvailable = false;
|
|
||||||
public ?string $currentRoute = null;
|
|
||||||
public ?string $serverIp = null;
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
$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 {
|
|
||||||
$activity = StartProxy::run($this->server);
|
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function stop()
|
|
||||||
{
|
|
||||||
instant_remote_process([
|
|
||||||
"docker rm -f coolify-proxy",
|
|
||||||
], $this->server);
|
|
||||||
$this->server->proxy->status = 'exited';
|
|
||||||
$this->server->save();
|
|
||||||
$this->emit('proxyStatusUpdated');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Settings;
|
|
||||||
|
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Models\InstanceSettings as ModelsInstanceSettings;
|
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Spatie\Url\Url;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
|
|
||||||
class Configuration extends Component
|
|
||||||
{
|
|
||||||
public ModelsInstanceSettings $settings;
|
|
||||||
public bool $do_not_track;
|
|
||||||
public bool $is_auto_update_enabled;
|
|
||||||
public bool $is_registration_enabled;
|
|
||||||
public bool $next_channel;
|
|
||||||
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
|
|
||||||
protected Server $server;
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'settings.fqdn' => 'nullable',
|
|
||||||
'settings.resale_license' => 'nullable',
|
|
||||||
'settings.public_port_min' => 'required',
|
|
||||||
'settings.public_port_max' => 'required',
|
|
||||||
];
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'settings.fqdn' => 'FQDN',
|
|
||||||
'settings.resale_license' => 'Resale License',
|
|
||||||
'settings.public_port_min' => 'Public port min',
|
|
||||||
'settings.public_port_max' => 'Public port max',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->do_not_track = $this->settings->do_not_track;
|
|
||||||
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
|
|
||||||
$this->is_registration_enabled = $this->settings->is_registration_enabled;
|
|
||||||
$this->next_channel = $this->settings->next_channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
$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;
|
|
||||||
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!');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
$this->resetErrorBag();
|
|
||||||
if ($this->settings->public_port_min > $this->settings->public_port_max) {
|
|
||||||
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->validate();
|
|
||||||
$this->settings->save();
|
|
||||||
$this->server = Server::findOrFail(0);
|
|
||||||
$this->setup_instance_fqdn();
|
|
||||||
$this->emit('success', 'Instance settings updated successfully!');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setup_instance_fqdn()
|
|
||||||
{
|
|
||||||
$file = "$this->dynamic_config_path/coolify.yaml";
|
|
||||||
if (empty($this->settings->fqdn)) {
|
|
||||||
instant_remote_process([
|
|
||||||
"rm -f $file",
|
|
||||||
], $this->server);
|
|
||||||
} else {
|
|
||||||
$url = Url::fromString($this->settings->fqdn);
|
|
||||||
$host = $url->getHost();
|
|
||||||
$schema = $url->getScheme();
|
|
||||||
$traefik_dynamic_conf = [
|
|
||||||
'http' =>
|
|
||||||
[
|
|
||||||
'routers' =>
|
|
||||||
[
|
|
||||||
'coolify-http' =>
|
|
||||||
[
|
|
||||||
'entryPoints' => [
|
|
||||||
0 => 'http',
|
|
||||||
],
|
|
||||||
'service' => 'coolify',
|
|
||||||
'rule' => "Host(`{$host}`)",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'services' =>
|
|
||||||
[
|
|
||||||
'coolify' =>
|
|
||||||
[
|
|
||||||
'loadBalancer' =>
|
|
||||||
[
|
|
||||||
'servers' =>
|
|
||||||
[
|
|
||||||
0 =>
|
|
||||||
[
|
|
||||||
'url' => 'http://coolify:80',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($schema === 'https') {
|
|
||||||
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
|
|
||||||
0 => 'redirect-to-https@docker',
|
|
||||||
];
|
|
||||||
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
|
|
||||||
'entryPoints' => [
|
|
||||||
0 => 'https',
|
|
||||||
],
|
|
||||||
'service' => 'coolify',
|
|
||||||
'rule' => "Host(`{$host}`)",
|
|
||||||
'tls' => [
|
|
||||||
'certresolver' => 'letsencrypt',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$this->save_configuration_to_disk($traefik_dynamic_conf, $file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function save_configuration_to_disk(array $traefik_dynamic_conf, string $file)
|
|
||||||
{
|
|
||||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
|
||||||
$yaml =
|
|
||||||
"# This file is automatically generated by Coolify.\n" .
|
|
||||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
|
||||||
$yaml;
|
|
||||||
|
|
||||||
$base64 = base64_encode($yaml);
|
|
||||||
instant_remote_process([
|
|
||||||
"mkdir -p $this->dynamic_config_path",
|
|
||||||
"echo '$base64' | base64 -d > $file",
|
|
||||||
], $this->server);
|
|
||||||
|
|
||||||
if (config('app.env') == 'local') {
|
|
||||||
ray($yaml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Source\Github;
|
|
||||||
|
|
||||||
use App\Models\GithubApp;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Change extends Component
|
|
||||||
{
|
|
||||||
public string $webhook_endpoint;
|
|
||||||
public string|null $ipv4;
|
|
||||||
public string|null $ipv6;
|
|
||||||
public string|null $fqdn;
|
|
||||||
|
|
||||||
public bool|null $default_permissions = true;
|
|
||||||
public bool|null $preview_deployment_permissions = true;
|
|
||||||
|
|
||||||
public $parameters;
|
|
||||||
public GithubApp $github_app;
|
|
||||||
public string $name;
|
|
||||||
public bool $is_system_wide;
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'github_app.name' => 'required|string',
|
|
||||||
'github_app.organization' => 'nullable|string',
|
|
||||||
'github_app.api_url' => 'required|string',
|
|
||||||
'github_app.html_url' => 'required|string',
|
|
||||||
'github_app.custom_user' => 'required|string',
|
|
||||||
'github_app.custom_port' => 'required|int',
|
|
||||||
'github_app.app_id' => 'required|int',
|
|
||||||
'github_app.installation_id' => 'nullable',
|
|
||||||
'github_app.client_id' => 'nullable',
|
|
||||||
'github_app.client_secret' => 'nullable',
|
|
||||||
'github_app.webhook_secret' => 'nullable',
|
|
||||||
'github_app.is_system_wide' => 'required|bool',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
if (isCloud() && !isDev()) {
|
|
||||||
$this->webhook_endpoint = config('app.url');
|
|
||||||
} else {
|
|
||||||
$this->webhook_endpoint = $this->ipv4;
|
|
||||||
$this->is_system_wide = $this->github_app->is_system_wide;
|
|
||||||
}
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->validate();
|
|
||||||
$this->github_app->save();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->github_app->delete();
|
|
||||||
redirect()->route('source.all');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Team;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Delete extends Component
|
|
||||||
{
|
|
||||||
public function delete()
|
|
||||||
{
|
|
||||||
$currentTeam = currentTeam();
|
|
||||||
$currentTeam->delete();
|
|
||||||
|
|
||||||
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
|
||||||
if ($user->id === auth()->user()->id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$user->teams()->detach($currentTeam);
|
|
||||||
$session = DB::table('sessions')->where('user_id', $user->id)->first();
|
|
||||||
if ($session) {
|
|
||||||
DB::table('sessions')->where('id', $session->id)->delete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
refreshSession();
|
|
||||||
return redirect()->route('team.index');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Team;
|
|
||||||
|
|
||||||
use App\Models\Team;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Form extends Component
|
|
||||||
{
|
|
||||||
public Team $team;
|
|
||||||
protected $rules = [
|
|
||||||
'team.name' => 'required|min:3|max:255',
|
|
||||||
'team.description' => 'nullable|min:3|max:255',
|
|
||||||
];
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'team.name' => 'name',
|
|
||||||
'team.description' => 'description',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->team = currentTeam();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
$this->validate();
|
|
||||||
try {
|
|
||||||
$this->team->save();
|
|
||||||
refreshSession();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,7 @@ class CheckForcePasswordReset
|
|||||||
}
|
}
|
||||||
$force_password_reset = auth()->user()->force_password_reset;
|
$force_password_reset = auth()->user()->force_password_reset;
|
||||||
if ($force_password_reset) {
|
if ($force_password_reset) {
|
||||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
|
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
return redirect()->route('auth.force-password-reset');
|
return redirect()->route('auth.force-password-reset');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@@ -11,9 +12,12 @@ class DecideWhatToDoWithUser
|
|||||||
{
|
{
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
|
if(auth()?->user()?->currentTeam()){
|
||||||
|
refreshSession(auth()->user()->currentTeam());
|
||||||
|
}
|
||||||
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||||
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||||
return redirect('boarding');
|
return redirect()->route('boarding');
|
||||||
}
|
}
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
@@ -21,27 +25,27 @@ class DecideWhatToDoWithUser
|
|||||||
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
|
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
return redirect('/verify');
|
return redirect()->route('verify.email');
|
||||||
}
|
}
|
||||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||||
if (Str::startsWith($request->path(), 'invitations')) {
|
if (Str::startsWith($request->path(), 'invitations')) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
return redirect('subscription');
|
return redirect()->route('subscription.index');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||||
if (Str::startsWith($request->path(), 'invitations')) {
|
if (Str::startsWith($request->path(), 'invitations')) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
return redirect('boarding');
|
return redirect()->route('boarding');
|
||||||
}
|
}
|
||||||
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
|
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
|
||||||
return redirect('/');
|
return redirect(RouteServiceProvider::HOME);
|
||||||
}
|
}
|
||||||
if (isSubscriptionActive() && $request->path() === 'subscription') {
|
if (isSubscriptionActive() && $request->path() === 'subscription') {
|
||||||
return redirect('/');
|
return redirect(RouteServiceProvider::HOME);
|
||||||
}
|
}
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class IsBoardingFlow
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handle an incoming request.
|
|
||||||
*
|
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
|
||||||
*/
|
|
||||||
public function handle(Request $request, Closure $next): Response
|
|
||||||
{
|
|
||||||
// ray()->showQueries()->color('orange');
|
|
||||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
|
||||||
if (Str::startsWith($request->path(), 'invitations')) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
return redirect('boarding');
|
|
||||||
}
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class IsSubscriptionValid
|
|
||||||
{
|
|
||||||
public function handle(Request $request, Closure $next): Response
|
|
||||||
{
|
|
||||||
if (isInstanceAdmin()) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
if (!auth()->user() || !isCloud()) {
|
|
||||||
if ($request->path() === 'subscription') {
|
|
||||||
return redirect('/');
|
|
||||||
} else {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isSubscriptionActive() && $request->path() === 'subscription') {
|
|
||||||
// ray('active subscription Middleware');
|
|
||||||
return redirect('/');
|
|
||||||
}
|
|
||||||
if (isSubscriptionOnGracePeriod()) {
|
|
||||||
// ray('is_subscription_in_grace_period Middleware');
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
|
||||||
// ray('SubscriptionValid Middleware');
|
|
||||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
|
||||||
if (Str::startsWith($request->path(), 'invitations')) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
return redirect('subscription');
|
|
||||||
} else {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -17,38 +17,34 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public string $build_logs_url;
|
public string $build_logs_url;
|
||||||
public Application $application;
|
|
||||||
public ApplicationPreview $preview;
|
|
||||||
public string $body;
|
public string $body;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $application_id,
|
public Application $application,
|
||||||
public int $pull_request_id,
|
public ApplicationPreview $preview,
|
||||||
public string $deployment_uuid,
|
public ProcessStatus $status,
|
||||||
public string $status
|
public ?string $deployment_uuid = null
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->application = Application::findOrFail($this->application_id);
|
if ($this->status === ProcessStatus::CLOSED) {
|
||||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
$this->delete_comment();
|
||||||
|
return;
|
||||||
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
} else if ($this->status === ProcessStatus::IN_PROGRESS) {
|
||||||
|
|
||||||
if ($this->status === ProcessStatus::IN_PROGRESS->value) {
|
|
||||||
$this->body = "The preview deployment is in progress. 🟡\n\n";
|
$this->body = "The preview deployment is in progress. 🟡\n\n";
|
||||||
}
|
} else if ($this->status === ProcessStatus::FINISHED) {
|
||||||
if ($this->status === ProcessStatus::FINISHED->value) {
|
|
||||||
$this->body = "The preview deployment is ready. 🟢\n\n";
|
$this->body = "The preview deployment is ready. 🟢\n\n";
|
||||||
if ($this->preview->fqdn) {
|
if ($this->preview->fqdn) {
|
||||||
$this->body .= "[Open Preview]({$this->preview->fqdn}) | ";
|
$this->body .= "[Open Preview]({$this->preview->fqdn}) | ";
|
||||||
}
|
}
|
||||||
}
|
} else if ($this->status === ProcessStatus::ERROR) {
|
||||||
if ($this->status === ProcessStatus::ERROR->value) {
|
|
||||||
$this->body = "The preview deployment failed. 🔴\n\n";
|
$this->body = "The preview deployment failed. 🔴\n\n";
|
||||||
}
|
}
|
||||||
|
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||||
|
|
||||||
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n";
|
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n";
|
||||||
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET";
|
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET";
|
||||||
|
|
||||||
@@ -77,10 +73,14 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function create_comment()
|
private function create_comment()
|
||||||
{
|
{
|
||||||
['data' => $data] = githubApi(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->preview->pull_request_id}/comments", method: 'post', data: [
|
||||||
'body' => $this->body,
|
'body' => $this->body,
|
||||||
]);
|
]);
|
||||||
$this->preview->pull_request_issue_comment_id = $data['id'];
|
$this->preview->pull_request_issue_comment_id = $data['id'];
|
||||||
$this->preview->save();
|
$this->preview->save();
|
||||||
}
|
}
|
||||||
|
private function delete_comment()
|
||||||
|
{
|
||||||
|
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
app/Jobs/ApplicationRestartJob.php
Normal file
28
app/Jobs/ApplicationRestartJob.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Traits\ExecuteRemoteCommand;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationRestartJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
|
||||||
|
|
||||||
|
public $timeout = 3600;
|
||||||
|
public $tries = 1;
|
||||||
|
public string $applicationDeploymentQueueId;
|
||||||
|
public function __construct(string $applicationDeploymentQueueId)
|
||||||
|
{
|
||||||
|
$this->applicationDeploymentQueueId = $applicationDeploymentQueueId;
|
||||||
|
}
|
||||||
|
public function handle() {
|
||||||
|
ray('Restarting application');
|
||||||
|
}
|
||||||
|
}
|
||||||
88
app/Jobs/CheckLogDrainContainerJob.php
Normal file
88
app/Jobs/CheckLogDrainContainerJob.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Server\InstallLogDrain;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
|
use App\Notifications\Container\ContainerStopped;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Sleep;
|
||||||
|
|
||||||
|
class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uniqueId(): int
|
||||||
|
{
|
||||||
|
return $this->server->id;
|
||||||
|
}
|
||||||
|
public function healthcheck()
|
||||||
|
{
|
||||||
|
$status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false);
|
||||||
|
if (str($status)->contains('running')) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// ray("checking log drain statuses for {$this->server->id}");
|
||||||
|
try {
|
||||||
|
if (!$this->server->isFunctional()) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||||
|
if (!$containers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
|
||||||
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
|
||||||
|
$foundLogDrainContainer = $containers->filter(function ($value, $key) {
|
||||||
|
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||||
|
})->first();
|
||||||
|
if (!$foundLogDrainContainer || !$this->healthcheck()) {
|
||||||
|
ray('Log drain container not found or unhealthy. Restarting...');
|
||||||
|
InstallLogDrain::run($this->server);
|
||||||
|
Sleep::for(10)->seconds();
|
||||||
|
if ($this->healthcheck()) {
|
||||||
|
if ($this->server->log_drain_notification_sent) {
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||||
|
$this->server->update(['log_drain_notification_sent' => false]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$this->server->log_drain_notification_sent) {
|
||||||
|
ray('Log drain container still unhealthy. Sending notification...');
|
||||||
|
$this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
|
||||||
|
$this->server->update(['log_drain_notification_sent' => true]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($this->server->log_drain_notification_sent) {
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||||
|
$this->server->update(['log_drain_notification_sent' => false]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
resolve(CheckResaleLicense::class)();
|
CheckResaleLicense::run();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('CheckResaleLicenseJob failed with: ' . $e->getMessage());
|
send_internal_notification('CheckResaleLicenseJob failed with: ' . $e->getMessage());
|
||||||
ray($e);
|
ray($e);
|
||||||
|
|||||||
40
app/Jobs/CleanupHelperContainersJob.php
Normal file
40
app/Jobs/CleanupHelperContainersJob.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CleanupHelperContainersJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ray('Cleaning up helper containers on ' . $this->server->name);
|
||||||
|
$containers = instant_remote_process(['docker container ps --filter "ancestor=ghcr.io/coollabsio/coolify-helper:next" --filter "ancestor=ghcr.io/coollabsio/coolify-helper:latest" --format \'{{json .}}\''], $this->server, false);
|
||||||
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
if ($containers->count() > 0) {
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$containerId = data_get($container,'ID');
|
||||||
|
ray('Removing container ' . $containerId);
|
||||||
|
instant_remote_process(['docker container rm -f ' . $containerId], $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification('CleanupHelperContainersJob failed with error: ' . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,156 +2,114 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
use App\Actions\Proxy\CheckProxy;
|
use App\Actions\Proxy\CheckProxy;
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Container\ContainerRestarted;
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
use App\Notifications\Container\ContainerStopped;
|
use App\Notifications\Container\ContainerStopped;
|
||||||
use App\Notifications\Server\Revived;
|
|
||||||
use App\Notifications\Server\Unreachable;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 4;
|
||||||
public $timeout = 120;
|
public function backoff(): int
|
||||||
|
{
|
||||||
|
return isDev() ? 1 : 3;
|
||||||
|
}
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function uniqueId(): string
|
public function uniqueId(): int
|
||||||
{
|
{
|
||||||
return $this->server->uuid;
|
return $this->server->uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
|
||||||
{
|
|
||||||
if (isDev()) {
|
|
||||||
$this->handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
if (!$this->server->isFunctional()) {
|
||||||
|
return 'Server is not ready.';
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
// ray("checking server status for {$this->server->id}");
|
if ($this->server->isSwarm()) {
|
||||||
// ray()->clearAll();
|
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
$serverUptimeCheckNumber = $this->server->unreachable_count;
|
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||||
$serverUptimeCheckNumberMax = 3;
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
$this->server->update(['unreachable_email_sent' => true]);
|
|
||||||
}
|
|
||||||
$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 {
|
} else {
|
||||||
$serverUptimeCheckNumber++;
|
// Precheck for containers
|
||||||
$this->server->settings()->update([
|
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||||
'is_reachable' => false,
|
if (!$containers) {
|
||||||
]);
|
return;
|
||||||
$this->server->update([
|
}
|
||||||
'unreachable_count' => $serverUptimeCheckNumber,
|
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
]);
|
$containerReplicates = null;
|
||||||
|
}
|
||||||
|
if (is_null($containers)) {
|
||||||
return;
|
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;
|
|
||||||
}
|
|
||||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
|
|
||||||
$containers = format_docker_command_output_to_json($containers);
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
if ($containerReplicates) {
|
||||||
|
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||||
|
foreach ($containerReplicates as $containerReplica) {
|
||||||
|
$name = data_get($containerReplica, 'Name');
|
||||||
|
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
|
if (data_get($container, 'Spec.Name') === $name) {
|
||||||
|
$replicas = data_get($containerReplica, 'Replicas');
|
||||||
|
$running = str($replicas)->explode('/')[0];
|
||||||
|
$total = str($replicas)->explode('/')[1];
|
||||||
|
if ($running === $total) {
|
||||||
|
data_set($container, 'State.Status', 'running');
|
||||||
|
data_set($container, 'State.Health.Status', 'healthy');
|
||||||
|
} else {
|
||||||
|
data_set($container, 'State.Status', 'starting');
|
||||||
|
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $container;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
$applications = $this->server->applications();
|
$applications = $this->server->applications();
|
||||||
$databases = $this->server->databases();
|
$databases = $this->server->databases();
|
||||||
$services = $this->server->services()->get();
|
$services = $this->server->services()->get();
|
||||||
$previews = $this->server->previews();
|
$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) {
|
|
||||||
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();
|
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
|
||||||
}
|
|
||||||
$foundApplications = [];
|
$foundApplications = [];
|
||||||
$foundApplicationPreviews = [];
|
$foundApplicationPreviews = [];
|
||||||
$foundDatabases = [];
|
$foundDatabases = [];
|
||||||
$foundServices = [];
|
$foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$labels = data_get($container, 'Spec.Labels');
|
||||||
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
|
} else {
|
||||||
|
$labels = data_get($container, 'Config.Labels');
|
||||||
|
}
|
||||||
$containerStatus = data_get($container, 'State.Status');
|
$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)";
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
$labels = data_get($container, 'Config.Labels');
|
|
||||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
$labelId = data_get($labels, 'coolify.applicationId');
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
if ($labelId) {
|
if ($applicationId) {
|
||||||
if (str_contains($labelId, '-pr-')) {
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
if ($pullRequestId) {
|
||||||
$applicationId = (int) Str::before($labelId, '-pr-');
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
if ($preview) {
|
if ($preview) {
|
||||||
$foundApplicationPreviews[] = $preview->id;
|
$foundApplicationPreviews[] = $preview->id;
|
||||||
@@ -163,7 +121,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
//Notify user that this container should not be there.
|
//Notify user that this container should not be there.
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$application = $applications->where('id', $labelId)->first();
|
$application = $applications->where('id', $applicationId)->first();
|
||||||
if ($application) {
|
if ($application) {
|
||||||
$foundApplications[] = $application->id;
|
$foundApplications[] = $application->id;
|
||||||
$statusFromDb = $application->status;
|
$statusFromDb = $application->status;
|
||||||
@@ -179,15 +137,33 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($uuid) {
|
if ($uuid) {
|
||||||
$database = $databases->where('uuid', $uuid)->first();
|
$database = $databases->where('uuid', $uuid)->first();
|
||||||
if ($database) {
|
if ($database) {
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
$foundDatabases[] = $database->id;
|
$foundDatabases[] = $database->id;
|
||||||
$statusFromDb = $database->status;
|
$statusFromDb = $database->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$database->update(['status' => $containerStatus]);
|
$database->update(['status' => $containerStatus]);
|
||||||
}
|
}
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Notify user that this container should not be there.
|
// Notify user that this container should not be there.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (data_get($container,'Name') === '/coolify-db') {
|
||||||
|
$foundDatabases[] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
if ($serviceLabelId) {
|
if ($serviceLabelId) {
|
||||||
@@ -238,12 +214,17 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
$name = data_get($exitedService, 'name');
|
$name = data_get($exitedService, 'name');
|
||||||
$fqdn = data_get($exitedService, 'fqdn');
|
$fqdn = data_get($exitedService, 'fqdn');
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
||||||
$project = data_get($service, 'environment.project');
|
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
$environment = data_get($service, 'environment');
|
$serviceUuid = data_get($service, 'uuid');
|
||||||
|
$environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/service/" . $service->uuid;
|
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
$exitedService->update(['status' => 'exited']);
|
$exitedService->update(['status' => 'exited']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,12 +241,17 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$project = data_get($application, 'environment.project');
|
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
$environment = data_get($application, 'environment');
|
$applicationUuid = data_get($application, 'uuid');
|
||||||
|
$environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $application->uuid;
|
if ($projectUuid && $applicationUuid && $environment) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
}
|
||||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
@@ -280,11 +266,17 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$project = data_get($preview, 'application.environment.project');
|
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
$environment = data_get($preview, 'application.environment');
|
$environmentName = data_get($preview, 'application.environment.name');
|
||||||
|
$applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $preview->application->uuid;
|
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
}
|
||||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
foreach ($notRunningDatabases as $database) {
|
foreach ($notRunningDatabases as $database) {
|
||||||
@@ -299,14 +291,45 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
$containerName = $name;
|
$containerName = $name;
|
||||||
|
|
||||||
$project = data_get($database, 'environment.project');
|
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
$environment = data_get($database, 'environment');
|
$environmentName = data_get($database, 'environment.name');
|
||||||
|
$databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/database/" . $database->uuid;
|
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if proxy is running
|
||||||
|
$this->server->proxyType();
|
||||||
|
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
|
send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
public Activity $activity,
|
public Activity $activity,
|
||||||
public bool $ignore_errors = false,
|
public bool $ignore_errors = false,
|
||||||
|
public $call_event_on_finish = null
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$remote_process = resolve(RunRemoteProcess::class, [
|
$remote_process = resolve(RunRemoteProcess::class, [
|
||||||
'activity' => $this->activity,
|
'activity' => $this->activity,
|
||||||
'ignore_errors' => $this->ignore_errors,
|
'ignore_errors' => $this->ignore_errors,
|
||||||
|
'call_event_on_finish' => $this->call_event_on_finish
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$remote_process();
|
$remote_process();
|
||||||
|
|||||||
@@ -2,10 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Database\StopDatabase;
|
||||||
|
use App\Events\BackupCreated;
|
||||||
use App\Models\S3Storage;
|
use App\Models\S3Storage;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
use App\Models\ScheduledDatabaseBackupExecution;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Database\BackupFailed;
|
use App\Notifications\Database\BackupFailed;
|
||||||
@@ -19,6 +25,7 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
@@ -27,9 +34,10 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public ?Team $team = null;
|
public ?Team $team = null;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public ScheduledDatabaseBackup $backup;
|
public ScheduledDatabaseBackup $backup;
|
||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
|
||||||
|
|
||||||
public ?string $container_name = null;
|
public ?string $container_name = null;
|
||||||
|
public ?string $directory_name = null;
|
||||||
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
||||||
public string $backup_status = 'failed';
|
public string $backup_status = 'failed';
|
||||||
public ?string $backup_location = null;
|
public ?string $backup_location = null;
|
||||||
@@ -43,9 +51,15 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
$this->backup = $backup;
|
$this->backup = $backup;
|
||||||
$this->team = Team::find($backup->team_id);
|
$this->team = Team::find($backup->team_id);
|
||||||
$this->database = data_get($this->backup, 'database');
|
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||||
$this->server = $this->database->destination->server;
|
$this->database = data_get($this->backup, 'database');
|
||||||
$this->s3 = $this->backup->s3;
|
$this->server = $this->database->service->server;
|
||||||
|
$this->s3 = $this->backup->s3;
|
||||||
|
} else {
|
||||||
|
$this->database = data_get($this->backup, 'database');
|
||||||
|
$this->server = $this->database->destination->server;
|
||||||
|
$this->s3 = $this->backup->s3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
@@ -61,30 +75,156 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
BackupCreated::dispatch($this->team->id);
|
||||||
|
// Check if team is exists
|
||||||
|
if (is_null($this->team)) {
|
||||||
|
$this->backup->update(['status' => 'failed']);
|
||||||
|
StopDatabase::run($this->database);
|
||||||
|
$this->database->delete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
$status = Str::of(data_get($this->database, 'status'));
|
$status = Str::of(data_get($this->database, 'status'));
|
||||||
if (!$status->startsWith('running') && $this->database->id !== 0) {
|
if (!$status->startsWith('running') && $this->database->id !== 0) {
|
||||||
ray('database not running');
|
ray('database not running');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$databaseType = $this->database->type();
|
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||||
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
$databaseType = $this->database->databaseType();
|
||||||
|
$serviceUuid = $this->database->service->uuid;
|
||||||
|
$serviceName = str($this->database->service->name)->slug();
|
||||||
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
|
$this->directory_name = $serviceName . '-' . $this->container_name;
|
||||||
|
$commands[] = "docker exec $this->container_name env | grep POSTGRES_";
|
||||||
|
$envs = instant_remote_process($commands, $this->server);
|
||||||
|
$envs = str($envs)->explode("\n");
|
||||||
|
|
||||||
|
$user = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('POSTGRES_USER=');
|
||||||
|
})->first();
|
||||||
|
if ($user) {
|
||||||
|
$this->database->postgres_user = str($user)->after('POSTGRES_USER=')->value();
|
||||||
|
} else {
|
||||||
|
$this->database->postgres_user = 'postgres';
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('POSTGRES_DB=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('POSTGRES_DB=')->value();
|
||||||
|
} else {
|
||||||
|
$databasesToBackup = $this->database->postgres_user;
|
||||||
|
}
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
|
$this->directory_name = $serviceName . '-' . $this->container_name;
|
||||||
|
$commands[] = "docker exec $this->container_name env | grep MYSQL_";
|
||||||
|
$envs = instant_remote_process($commands, $this->server);
|
||||||
|
$envs = str($envs)->explode("\n");
|
||||||
|
|
||||||
|
$rootPassword = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_ROOT_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($rootPassword) {
|
||||||
|
$this->database->mysql_root_password = str($rootPassword)->after('MYSQL_ROOT_PASSWORD=')->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_DATABASE=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('MYSQL_DATABASE=')->value();
|
||||||
|
} else {
|
||||||
|
throw new \Exception('MYSQL_DATABASE not found');
|
||||||
|
}
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
|
$this->directory_name = $serviceName . '-' . $this->container_name;
|
||||||
|
$commands[] = "docker exec $this->container_name env";
|
||||||
|
$envs = instant_remote_process($commands, $this->server);
|
||||||
|
$envs = str($envs)->explode("\n");
|
||||||
|
$rootPassword = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MARIADB_ROOT_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($rootPassword) {
|
||||||
|
$this->database->mariadb_root_password = str($rootPassword)->after('MARIADB_ROOT_PASSWORD=')->value();
|
||||||
|
} else {
|
||||||
|
$rootPassword = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_ROOT_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($rootPassword) {
|
||||||
|
$this->database->mariadb_root_password = str($rootPassword)->after('MYSQL_ROOT_PASSWORD=')->value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MARIADB_DATABASE=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('MARIADB_DATABASE=')->value();
|
||||||
|
} else {
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_DATABASE=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('MYSQL_DATABASE=')->value();
|
||||||
|
} else {
|
||||||
|
throw new \Exception('MARIADB_DATABASE or MYSQL_DATABASE not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$databaseName = str($this->database->name)->slug()->value();
|
||||||
|
$this->container_name = $this->database->uuid;
|
||||||
|
$this->directory_name = $databaseName . '-' . $this->container_name;
|
||||||
|
$databaseType = $this->database->type();
|
||||||
|
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
||||||
|
}
|
||||||
|
|
||||||
if (is_null($databasesToBackup)) {
|
if (is_null($databasesToBackup)) {
|
||||||
if ($databaseType === 'standalone-postgresql') {
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
$databasesToBackup = [$this->database->postgres_db];
|
$databasesToBackup = [$this->database->postgres_db];
|
||||||
|
} else if ($databaseType === 'standalone-mongodb') {
|
||||||
|
$databasesToBackup = ['*'];
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
$databasesToBackup = [$this->database->mysql_database];
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
$databasesToBackup = [$this->database->mariadb_database];
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$databasesToBackup = explode(',', $databasesToBackup);
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
$databasesToBackup = array_map('trim', $databasesToBackup);
|
// Format: db1,db2,db3
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
} else if ($databaseType === 'standalone-mongodb') {
|
||||||
|
// Format: db1:collection1,collection2|db2:collection3,collection4
|
||||||
|
$databasesToBackup = explode('|', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
ray($databasesToBackup);
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
// Format: db1,db2,db3
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
// Format: db1,db2,db3
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->container_name = $this->database->uuid;
|
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->directory_name;
|
||||||
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->container_name;
|
|
||||||
|
|
||||||
if ($this->database->name === 'coolify-db') {
|
if ($this->database->name === 'coolify-db') {
|
||||||
$databasesToBackup = ['coolify'];
|
$databasesToBackup = ['coolify'];
|
||||||
$this->container_name = "coolify-db";
|
$this->directory_name = $this->container_name = "coolify-db";
|
||||||
$ip = Str::slug($this->server->ip);
|
$ip = Str::slug($this->server->ip);
|
||||||
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
||||||
}
|
}
|
||||||
@@ -92,49 +232,124 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$size = 0;
|
$size = 0;
|
||||||
ray('Backing up ' . $database);
|
ray('Backing up ' . $database);
|
||||||
try {
|
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') {
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
|
$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,
|
||||||
|
]);
|
||||||
$this->backup_standalone_postgresql($database);
|
$this->backup_standalone_postgresql($database);
|
||||||
|
} else if ($databaseType === 'standalone-mongodb') {
|
||||||
|
if ($database === '*') {
|
||||||
|
$database = 'all';
|
||||||
|
$databaseName = 'all';
|
||||||
|
} else {
|
||||||
|
if (str($database)->contains(':')) {
|
||||||
|
$databaseName = str($database)->before(':');
|
||||||
|
} else {
|
||||||
|
$databaseName = $database;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz";
|
||||||
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
|
'database_name' => $databaseName,
|
||||||
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
|
$this->backup_standalone_mongodb($database);
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
$this->backup_file = "/mysql-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,
|
||||||
|
]);
|
||||||
|
$this->backup_standalone_mysql($database);
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
$this->backup_file = "/mariadb-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,
|
||||||
|
]);
|
||||||
|
$this->backup_standalone_mariadb($database);
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unsupported database type');
|
||||||
}
|
}
|
||||||
$size = $this->calculate_size();
|
$size = $this->calculate_size();
|
||||||
$this->remove_old_backups();
|
$this->remove_old_backups();
|
||||||
if ($this->backup->save_s3) {
|
if ($this->backup->save_s3) {
|
||||||
$this->upload_to_s3();
|
$this->upload_to_s3();
|
||||||
}
|
}
|
||||||
$this->team->notify(new BackupSuccess($this->backup, $this->database));
|
$this->team?->notify(new BackupSuccess($this->backup, $this->database));
|
||||||
$this->backup_log->update([
|
$this->backup_log->update([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => $this->backup_output,
|
'message' => $this->backup_output,
|
||||||
'size' => $size,
|
'size' => $size,
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->backup_log->update([
|
if ($this->backup_log) {
|
||||||
'status' => 'failed',
|
$this->backup_log->update([
|
||||||
'message' => $this->backup_output,
|
'status' => 'failed',
|
||||||
'size' => $size,
|
'message' => $this->backup_output,
|
||||||
'filename' => null
|
'size' => $size,
|
||||||
]);
|
'filename' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
BackupCreated::dispatch($this->team->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$url = $this->database->getDbUrl(useInternal: true);
|
||||||
|
if ($databaseWithCollections === 'all') {
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
||||||
|
} else {
|
||||||
|
if (str($databaseWithCollections)->contains(':')) {
|
||||||
|
$databaseName = str($databaseWithCollections)->before(':');
|
||||||
|
$collectionsToExclude = str($databaseWithCollections)->after(':')->explode(',');
|
||||||
|
} else {
|
||||||
|
$databaseName = $databaseWithCollections;
|
||||||
|
$collectionsToExclude = collect();
|
||||||
|
}
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
if ($collectionsToExclude->count() === 0) {
|
||||||
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
|
||||||
|
} else {
|
||||||
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
|
$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);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$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());
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function backup_standalone_postgresql(string $database): void
|
private function backup_standalone_postgresql(string $database): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ray($this->backup_dir);
|
|
||||||
$commands[] = "mkdir -p " . $this->backup_dir;
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $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 = instant_remote_process($commands, $this->server);
|
||||||
@@ -149,7 +364,42 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function backup_standalone_mysql(string $database): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
|
||||||
|
ray($commands);
|
||||||
|
$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);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$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());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function backup_standalone_mariadb(string $database): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
|
||||||
|
ray($commands);
|
||||||
|
$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);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$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());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
private function add_to_backup_output($output): void
|
private function add_to_backup_output($output): void
|
||||||
{
|
{
|
||||||
if ($this->backup_output) {
|
if ($this->backup_output) {
|
||||||
@@ -169,7 +419,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($this->backup->number_of_backups_locally === 0) {
|
if ($this->backup->number_of_backups_locally === 0) {
|
||||||
$deletable = $this->backup->executions()->where('status', 'success');
|
$deletable = $this->backup->executions()->where('status', 'success');
|
||||||
} else {
|
} else {
|
||||||
$deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally);
|
$deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally - 1);
|
||||||
}
|
}
|
||||||
foreach ($deletable->get() as $execution) {
|
foreach ($deletable->get() as $execution) {
|
||||||
delete_backup_locally($execution->filename, $this->server);
|
delete_backup_locally($execution->filename, $this->server);
|
||||||
@@ -188,13 +438,13 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// $region = $this->s3->region;
|
// $region = $this->s3->region;
|
||||||
$bucket = $this->s3->bucket;
|
$bucket = $this->s3->bucket;
|
||||||
$endpoint = $this->s3->endpoint;
|
$endpoint = $this->s3->endpoint;
|
||||||
$this->s3->testConnection();
|
$this->s3->testConnection(shouldSave: true);
|
||||||
if (isDev()) {
|
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||||
$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";
|
$network = $this->database->service->destination->network;
|
||||||
} else {
|
} 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";
|
$network = $this->database->destination->network;
|
||||||
}
|
}
|
||||||
|
$commands[] = "docker run --pull=always -d --network {$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 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}/";
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
||||||
instant_remote_process($commands, $this->server);
|
instant_remote_process($commands, $this->server);
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ namespace App\Jobs;
|
|||||||
|
|
||||||
use App\Actions\Application\StopApplication;
|
use App\Actions\Application\StopApplication;
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
|
use App\Actions\Service\DeleteService;
|
||||||
use App\Actions\Service\StopService;
|
use App\Actions\Service\StopService;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
@@ -16,40 +20,37 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis $resource)
|
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = $this->resource->destination->server;
|
$this->resource->forceDelete();
|
||||||
if (!$server->isFunctional()) {
|
|
||||||
return 'Server is not functional';
|
|
||||||
}
|
|
||||||
switch ($this->resource->type()) {
|
switch ($this->resource->type()) {
|
||||||
case 'application':
|
case 'application':
|
||||||
StopApplication::run($this->resource);
|
StopApplication::run($this->resource);
|
||||||
break;
|
break;
|
||||||
case 'standalone-postgresql':
|
case 'standalone-postgresql':
|
||||||
StopDatabase::run($this->resource);
|
|
||||||
break;
|
|
||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
|
case 'standalone-mongodb':
|
||||||
|
case 'standalone-mysql':
|
||||||
|
case 'standalone-mariadb':
|
||||||
StopDatabase::run($this->resource);
|
StopDatabase::run($this->resource);
|
||||||
break;
|
break;
|
||||||
case 'service':
|
case 'service':
|
||||||
StopService::run($this->resource);
|
StopService::run($this->resource);
|
||||||
|
DeleteService::run($this->resource);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
|
||||||
$this->resource->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user