mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
3916 Commits
v3.2.3
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
1e39c3d5ab | ||
|
|
6071412986 | ||
|
|
ba7148206a | ||
|
|
59c5b22e6c | ||
|
|
be7f2ad9c4 | ||
|
|
62295ef573 | ||
|
|
ceb9fcf3b6 | ||
|
|
60282f7b6c | ||
|
|
f14b0a3411 | ||
|
|
30af317bd9 | ||
|
|
95faa1c3ad | ||
|
|
423d31f227 | ||
|
|
fbb063030d | ||
|
|
1968726cfe | ||
|
|
fb280afe41 | ||
|
|
fd488a561a | ||
|
|
ab57a5d8ef | ||
|
|
f5ae222a6e | ||
|
|
5d95d8b79a | ||
|
|
fbb5f2ca2e | ||
|
|
16cbca36c1 | ||
|
|
24a578bedb | ||
|
|
36dc479772 | ||
|
|
83d6e488e4 | ||
|
|
a4d358d512 | ||
|
|
c0c197101d | ||
|
|
62e39ccc7f | ||
|
|
a88a016137 | ||
|
|
f4c8986ab3 | ||
|
|
e286eae53b | ||
|
|
bc3e59e4ef | ||
|
|
17ebc650c9 | ||
|
|
0ef386b4a8 | ||
|
|
26fce85bb0 | ||
|
|
2a079e3365 | ||
|
|
5fb5ed75c4 | ||
|
|
a2008fe9d1 | ||
|
|
3c96485e3d | ||
|
|
1c97d47ea0 | ||
|
|
8f8f5878dd | ||
|
|
6e6f39dc1f | ||
|
|
d2d1f984e1 | ||
|
|
d635e5dbae | ||
|
|
49c56524e1 | ||
|
|
6ced607f2a | ||
|
|
aaa2febef4 | ||
|
|
10e6eddcfe | ||
|
|
2639bf92ad | ||
|
|
59eae3a44e | ||
|
|
5aa8ccfcf4 | ||
|
|
aea7cc9638 | ||
|
|
c970907c73 | ||
|
|
38c6c1ee40 | ||
|
|
a6118f5daf | ||
|
|
b196c138d9 | ||
|
|
8f9949160c | ||
|
|
beae0b545f | ||
|
|
b8dd7704b3 | ||
|
|
d913be66e6 | ||
|
|
63de538879 | ||
|
|
1d733b2282 | ||
|
|
1d0ad51fdf | ||
|
|
83d00bbe3c | ||
|
|
c422b4dbcf | ||
|
|
767fd334dd | ||
|
|
972223f01b | ||
|
|
9318cac189 | ||
|
|
7aa991fd7c | ||
|
|
5c27f43b3d | ||
|
|
a2f4d4ed6d | ||
|
|
6aca2740fb | ||
|
|
cd13b5b83e | ||
|
|
758dbafbf1 | ||
|
|
f6663661df | ||
|
|
9666099408 | ||
|
|
d382af6860 | ||
|
|
4905454269 | ||
|
|
ed8bd37230 | ||
|
|
ec1a7aa893 | ||
|
|
62adf2c5dc | ||
|
|
3e4538de98 | ||
|
|
5a7b16ea5f | ||
|
|
aa7bc40f85 | ||
|
|
4fd83dc727 | ||
|
|
0e451f87a9 | ||
|
|
0b0ae55f0b | ||
|
|
40ec3d9753 | ||
|
|
9535c8df29 | ||
|
|
6ca1d36d5d | ||
|
|
f5d16c46cb | ||
|
|
725c3fd547 | ||
|
|
dcfcee1db6 | ||
|
|
9f8c44d96b | ||
|
|
5b74fd34f5 | ||
|
|
5a4c9422b2 | ||
|
|
81b916724e | ||
|
|
9540f60fa2 | ||
|
|
8eb1686125 | ||
|
|
daf3710a5e | ||
|
|
1067f37e4d | ||
|
|
3b3c0b94e5 | ||
|
|
8b0a0d67da | ||
|
|
5541c135df | ||
|
|
2552cb2208 | ||
|
|
f001e9bc34 | ||
|
|
f943fdc5be | ||
|
|
a4f1fcba58 | ||
|
|
68091b44fc | ||
|
|
9f8caac91c | ||
|
|
8082dc1a01 | ||
|
|
a71cf5bc66 | ||
|
|
0775074509 | ||
|
|
242d2fb283 | ||
|
|
5646818965 | ||
|
|
ffc5320940 | ||
|
|
7c10c55b1c | ||
|
|
7c96b6207a | ||
|
|
0be8ffbdc9 | ||
|
|
24fa56762e | ||
|
|
84d8e35411 | ||
|
|
3d3ccc435c | ||
|
|
be3b01472e | ||
|
|
de6f5b1105 | ||
|
|
14d9c06dcd | ||
|
|
8abfaa1967 | ||
|
|
46f7ae9588 | ||
|
|
f2c32b9aeb | ||
|
|
3dab1eb92e | ||
|
|
9c22e01716 | ||
|
|
d1c47a4062 | ||
|
|
6c3f97d9ae | ||
|
|
ebd8e2ce40 | ||
|
|
b650f3f754 | ||
|
|
f33ba40478 | ||
|
|
5cea9c4603 | ||
|
|
d32832fabc | ||
|
|
165f0a3d4a | ||
|
|
f14995200b | ||
|
|
12bb2ecc4a | ||
|
|
933ec5741d | ||
|
|
8004a40139 | ||
|
|
a6209fbe5c | ||
|
|
b47c327b55 | ||
|
|
25434a7acd | ||
|
|
0de042dbac | ||
|
|
eb9e2203b0 | ||
|
|
dcaa7a6ad7 | ||
|
|
5b584a6c6d | ||
|
|
c40ea6f1da | ||
|
|
61a7b9ac94 | ||
|
|
9e81416fef | ||
|
|
c58706e3e4 | ||
|
|
45bca8649b | ||
|
|
b095b88281 | ||
|
|
cb41584137 | ||
|
|
6659153804 | ||
|
|
44c429a224 | ||
|
|
fe9c501c1d | ||
|
|
4e94b4a0c1 | ||
|
|
2f4d7c0e43 | ||
|
|
e443fc394a | ||
|
|
5b56c50f03 | ||
|
|
3adeb2f73f | ||
|
|
9eaa13a08a | ||
|
|
df5a4a9667 | ||
|
|
26048339d6 | ||
|
|
d85af3fefc | ||
|
|
575338609b | ||
|
|
d32e43ef37 | ||
|
|
a96ef1bfab | ||
|
|
1bfedf69f2 | ||
|
|
208fe7d87b | ||
|
|
a6d58b5d72 | ||
|
|
277b4276e6 | ||
|
|
f6adc9285a | ||
|
|
f03bbe0e95 | ||
|
|
535375193c | ||
|
|
d79c063fd6 | ||
|
|
35f45492e3 | ||
|
|
ab8a7893d9 | ||
|
|
76b8d048d4 | ||
|
|
0e583334e7 | ||
|
|
0ad8ca224f | ||
|
|
050e56f69a | ||
|
|
6099ac11d9 | ||
|
|
adac728a60 | ||
|
|
e2e64e36a0 | ||
|
|
762af66cbf | ||
|
|
b08f525bd4 | ||
|
|
5ae16b195c | ||
|
|
91db1953ff | ||
|
|
1c8f92d3b7 | ||
|
|
4075572dbc | ||
|
|
2971e360d7 | ||
|
|
32bb2780f2 | ||
|
|
af69575b29 | ||
|
|
d4a7d0d25f | ||
|
|
45f9def0f6 | ||
|
|
5a90eed7ef | ||
|
|
38e1f17edf | ||
|
|
1651845e20 | ||
|
|
38e96548b5 | ||
|
|
47e4126dca | ||
|
|
e0b175ab07 | ||
|
|
93ec785f4f | ||
|
|
e849addab8 | ||
|
|
a5e6975dac | ||
|
|
4ac8e1cc67 | ||
|
|
1a5e3a7836 | ||
|
|
4498d1ed4b | ||
|
|
c8b974820b | ||
|
|
527373e297 | ||
|
|
a84be8dc33 | ||
|
|
bd856f7f67 | ||
|
|
8ff216e5fb | ||
|
|
5255311a2e | ||
|
|
774a245e84 | ||
|
|
194675c838 | ||
|
|
75862ca8de | ||
|
|
5580a4e704 | ||
|
|
51e601a303 | ||
|
|
734e9fd68d | ||
|
|
09fc950ae8 | ||
|
|
cf6caa279d | ||
|
|
68c976ab70 | ||
|
|
1560ab2a50 | ||
|
|
e3a6458506 | ||
|
|
1768b9374f | ||
|
|
51d0a30a6c | ||
|
|
9701c65297 | ||
|
|
58e3bb2571 | ||
|
|
31cbd1602d | ||
|
|
dd5723d596 | ||
|
|
620f26a6f1 | ||
|
|
540717e809 | ||
|
|
d446cd4103 | ||
|
|
7d1a76570c | ||
|
|
e18766ec21 | ||
|
|
3d0354cf7e | ||
|
|
af5b9fced1 | ||
|
|
ab5202515e | ||
|
|
f863db7ea5 | ||
|
|
3adc0bdd6e | ||
|
|
97027875bf | ||
|
|
fd9c13009f | ||
|
|
46a72fac47 | ||
|
|
5d6ee04991 | ||
|
|
d523becb29 | ||
|
|
41672f75d0 | ||
|
|
acd8541e68 | ||
|
|
ed6af777a4 | ||
|
|
05f162f4e8 | ||
|
|
5e0adc3777 | ||
|
|
7d06fc4403 | ||
|
|
0e1bcceb8e | ||
|
|
a922f2fedf | ||
|
|
1e0226c8ed | ||
|
|
5c45908087 | ||
|
|
aefdc76805 | ||
|
|
b3c8c881b7 | ||
|
|
9ab5a1f7bd | ||
|
|
23968e7886 | ||
|
|
390d24b6d7 | ||
|
|
e4296345b3 | ||
|
|
bcffbe418b | ||
|
|
bfbee4e78f | ||
|
|
2bdb44dac3 | ||
|
|
4daa1b8c16 | ||
|
|
febc399568 | ||
|
|
9b9d4f9941 | ||
|
|
f49b87870c | ||
|
|
ed49d4e3a0 | ||
|
|
7811c75139 | ||
|
|
068a1b4bc4 | ||
|
|
c618d912db | ||
|
|
3d43f2127a | ||
|
|
79fde593a9 | ||
|
|
64ce41df0f | ||
|
|
23e205b6cd | ||
|
|
4161ea7eb6 | ||
|
|
77037f8933 | ||
|
|
bdcc0c8de5 | ||
|
|
ac133875fa | ||
|
|
f4819a849b | ||
|
|
2cd1785a92 | ||
|
|
797352a5db | ||
|
|
03a3405524 | ||
|
|
925326b885 | ||
|
|
ffdf51a490 | ||
|
|
272d5547c8 | ||
|
|
fbaec769f0 | ||
|
|
d93640a8d9 | ||
|
|
3749970831 | ||
|
|
7d45bb1335 | ||
|
|
da04e0c027 | ||
|
|
c637d2d90d | ||
|
|
1bb8860f37 | ||
|
|
38a22dcf4d | ||
|
|
91e1eb7664 | ||
|
|
c7946e7551 | ||
|
|
958645e37f | ||
|
|
acdfa89ec1 | ||
|
|
62ec85ee2e | ||
|
|
8f24a66456 | ||
|
|
0dd0888775 | ||
|
|
5040ddea28 | ||
|
|
1a04a57c01 | ||
|
|
9b6c162224 | ||
|
|
e22c5d22f5 | ||
|
|
84e5b39830 | ||
|
|
cdb6964b0b | ||
|
|
df4ecd47a7 | ||
|
|
4a84c7238a | ||
|
|
2b3057e1b4 | ||
|
|
ae65172946 | ||
|
|
3a2d17bc05 | ||
|
|
4c1067cf36 | ||
|
|
b046a3e9f7 | ||
|
|
199881c596 | ||
|
|
99c8607ff4 | ||
|
|
e61fcc77f9 | ||
|
|
20dca179fb | ||
|
|
0f542c65ae | ||
|
|
d609fcaee1 | ||
|
|
fe8a7fc54f | ||
|
|
398f122593 | ||
|
|
f0abdcc2da | ||
|
|
c9a278b750 | ||
|
|
6990c593a4 | ||
|
|
8f54b51ecd | ||
|
|
3eb628b773 | ||
|
|
fabb97330a | ||
|
|
03c9793d11 | ||
|
|
a4320b7cee | ||
|
|
cbf9bc99ea | ||
|
|
0fbc382467 | ||
|
|
0f8ccac775 | ||
|
|
ee20c3339e | ||
|
|
0b11093d18 | ||
|
|
de8118b59d | ||
|
|
58522b59b7 | ||
|
|
356394c03d | ||
|
|
3e4db2f5b2 | ||
|
|
872981b8b4 | ||
|
|
51c468ae0b | ||
|
|
80a797aec8 | ||
|
|
cfdab13d77 | ||
|
|
4fc8988ff4 | ||
|
|
ab5619292e | ||
|
|
b02e5d3f27 | ||
|
|
5f0c9c3a31 | ||
|
|
ea6ec07a45 | ||
|
|
36be325d0d | ||
|
|
6138ddcac6 | ||
|
|
0509da6730 | ||
|
|
5b877b84c2 | ||
|
|
0c35726a8d | ||
|
|
e74899611b | ||
|
|
e9149e534d | ||
|
|
7cded7a36d | ||
|
|
ba74d55b4c | ||
|
|
a1d13fc14e | ||
|
|
cdb2a3a8e5 | ||
|
|
074c56edaa | ||
|
|
66bc03b4cd | ||
|
|
2dcb7fec05 | ||
|
|
249bccb49e | ||
|
|
a55eaa10ac | ||
|
|
ca2c75ce19 | ||
|
|
250d7cbc53 | ||
|
|
92a53a151e | ||
|
|
db3148c080 | ||
|
|
c46eeac4b5 | ||
|
|
19111ba059 | ||
|
|
9bc61a0a17 | ||
|
|
78b9166bdb | ||
|
|
1752448050 | ||
|
|
3fc78bc760 | ||
|
|
0c30b6222d | ||
|
|
3f74609c7f | ||
|
|
31b0ccba99 | ||
|
|
3fc544e0b9 | ||
|
|
67078fdc71 | ||
|
|
c91f426af3 | ||
|
|
9c2fea4b2e | ||
|
|
53d1fa0331 | ||
|
|
4ae7e46e81 | ||
|
|
ebfc0bd1e1 | ||
|
|
e1a1490911 | ||
|
|
6b75ff7de4 | ||
|
|
301469de6b | ||
|
|
b4d69a22df | ||
|
|
a86e971020 | ||
|
|
145af41c82 | ||
|
|
69c0b7240a | ||
|
|
6543132bcb | ||
|
|
9134437218 | ||
|
|
b7acf99cde | ||
|
|
21d52d7846 | ||
|
|
ab5929cc69 | ||
|
|
1452cdf5ad | ||
|
|
3eb1a1f48c | ||
|
|
5f7a97c31f | ||
|
|
9cba0a6df3 | ||
|
|
af57b2aa73 | ||
|
|
b9b9582601 | ||
|
|
c8ba98b93d | ||
|
|
a50e1e2f0c | ||
|
|
1093294f06 | ||
|
|
c023be2348 | ||
|
|
deece51e83 | ||
|
|
e2ab569244 | ||
|
|
93b202bde4 | ||
|
|
6bb6de188c | ||
|
|
61f58fa30f | ||
|
|
026f3fd72d | ||
|
|
9a706d55b4 | ||
|
|
9646969107 | ||
|
|
df433efe62 | ||
|
|
efa7cab3e2 | ||
|
|
a386a1bde5 | ||
|
|
cc1bf023be | ||
|
|
edb06fa233 | ||
|
|
abdb8074b1 | ||
|
|
157da798dd | ||
|
|
9c5501326e | ||
|
|
3ea462efc9 | ||
|
|
f77df5b732 | ||
|
|
b77074fe4e | ||
|
|
a7b7d3fa32 | ||
|
|
f4f3034389 | ||
|
|
4b2ffb456f | ||
|
|
e17ff99c5b | ||
|
|
1cf036bbc6 | ||
|
|
da4c2ee60f | ||
|
|
fcf7c5ddd5 | ||
|
|
27926322e1 | ||
|
|
c735ff545e | ||
|
|
b4f048b028 | ||
|
|
54a57d217f | ||
|
|
cf28490acc | ||
|
|
019670d5d1 | ||
|
|
b07cc500e7 | ||
|
|
82c235d5af | ||
|
|
307e4a6990 | ||
|
|
ddb7af63a6 | ||
|
|
7a429ee5bb | ||
|
|
bb591604ac | ||
|
|
81f7a65dd5 | ||
|
|
4b313bb1c6 | ||
|
|
aba0b2f13c | ||
|
|
9a284e47da | ||
|
|
9f2fbc661a | ||
|
|
949407368e | ||
|
|
93c65f6a79 | ||
|
|
d9fe16a3ee | ||
|
|
adaca4d4e3 | ||
|
|
a6d5f3038c | ||
|
|
4d49132821 | ||
|
|
c287276d0e | ||
|
|
e89868c692 | ||
|
|
f93317cd2e | ||
|
|
8412802f4d | ||
|
|
53c20e1e99 | ||
|
|
3c8c8e20b1 | ||
|
|
49f8abcd79 | ||
|
|
4a4d73b87b | ||
|
|
046eab3776 | ||
|
|
17c0e91a0d | ||
|
|
fe4a0ae166 | ||
|
|
52c84f8d22 | ||
|
|
b22fecb615 | ||
|
|
23b7fc3c54 | ||
|
|
1efb1235b4 | ||
|
|
0924070a13 | ||
|
|
12fc5a8f91 | ||
|
|
9eba058cf7 | ||
|
|
fa4f5fea8c | ||
|
|
898563fe7c | ||
|
|
c418a17161 | ||
|
|
cd0da04ea2 | ||
|
|
01e942c6a0 | ||
|
|
bb9abafa82 | ||
|
|
d0cd926517 | ||
|
|
9baf0161c7 | ||
|
|
8ba18b2ce1 | ||
|
|
ab021ee535 | ||
|
|
c76a1b1ba5 | ||
|
|
6266a5e500 | ||
|
|
5d27e89bfa | ||
|
|
6da4e78374 | ||
|
|
be30651172 | ||
|
|
95764c2b76 | ||
|
|
92a75685b5 | ||
|
|
a1592373aa | ||
|
|
5747a87f66 | ||
|
|
9cda671aef | ||
|
|
2c9983046c | ||
|
|
11d33f328e | ||
|
|
f79c741d95 | ||
|
|
8a39a4469a | ||
|
|
42daae10c6 | ||
|
|
64a65e2018 | ||
|
|
16c71f3647 | ||
|
|
363f525ad1 | ||
|
|
f4fb519d55 | ||
|
|
b7786504b8 | ||
|
|
f0adf10e6a | ||
|
|
cc8c6c5d16 | ||
|
|
4782446f42 | ||
|
|
230155312f | ||
|
|
3ab4365fca | ||
|
|
7349068b95 | ||
|
|
da6cc151d1 | ||
|
|
50527cf0a3 | ||
|
|
26f490bb00 | ||
|
|
dc4f412227 | ||
|
|
ec4234e243 | ||
|
|
f47fcb01ce | ||
|
|
02f6673345 | ||
|
|
e6cd8702b5 | ||
|
|
fda4ea8cca | ||
|
|
655d004ce7 | ||
|
|
56981d134c | ||
|
|
b9d49d2951 | ||
|
|
0c1e7c499e | ||
|
|
32fead5753 | ||
|
|
e5e9faba35 | ||
|
|
2852630d6c | ||
|
|
a4cc406114 | ||
|
|
53b15a5762 | ||
|
|
929a4e6474 | ||
|
|
45b597bbab | ||
|
|
0d1a2aa5d1 | ||
|
|
b82353d5e2 | ||
|
|
b17c09f7a7 | ||
|
|
f6c3fe7888 | ||
|
|
2e855e030f | ||
|
|
49f86621f4 | ||
|
|
03d9f93397 | ||
|
|
c472042a94 | ||
|
|
9f4356f67d | ||
|
|
a50317cc76 | ||
|
|
8afa98a1ca | ||
|
|
f6737f21dd | ||
|
|
e4a51cc116 | ||
|
|
acd78ae196 | ||
|
|
953bcfb5bb | ||
|
|
dacfab8b29 | ||
|
|
48b3e99939 | ||
|
|
41ad67c7c9 | ||
|
|
b49725cb1c | ||
|
|
75e674a966 | ||
|
|
9d826d9fb4 | ||
|
|
0af221f9fc | ||
|
|
9066c9bf90 | ||
|
|
77e3208f00 | ||
|
|
db5ecf07bd | ||
|
|
7f28aa6985 | ||
|
|
9d53e04ce9 | ||
|
|
3db9a1dd6e | ||
|
|
38f9761b67 | ||
|
|
7117ecf634 | ||
|
|
522e20f10a | ||
|
|
ebbce2396c | ||
|
|
f9a2ff6d90 | ||
|
|
df1b9e7319 | ||
|
|
e7c0c26b32 | ||
|
|
0dbb8b4420 | ||
|
|
a4b44bacc1 | ||
|
|
1338e68b8c | ||
|
|
f6f4cdde24 | ||
|
|
3daadf13c6 | ||
|
|
cbd5fab2e7 | ||
|
|
48ccb508f9 | ||
|
|
67edce0612 | ||
|
|
e8a41d7e6e | ||
|
|
be1dad03bd | ||
|
|
31db1db636 | ||
|
|
dca332a688 | ||
|
|
35f19ed53f | ||
|
|
a5c45ffe90 | ||
|
|
b6c3a65d2d | ||
|
|
220c8211fd | ||
|
|
ffbd04df29 | ||
|
|
73c59be865 | ||
|
|
3966abaf80 | ||
|
|
759517316a | ||
|
|
3e3024d47e | ||
|
|
517cb77637 | ||
|
|
14bd89a991 | ||
|
|
304de29924 | ||
|
|
ab3055150f | ||
|
|
6b9c7aa9c5 | ||
|
|
040f47b59c | ||
|
|
eac7834083 | ||
|
|
135a298080 | ||
|
|
0065a86371 | ||
|
|
2a842a2f50 | ||
|
|
231c02e00e | ||
|
|
4de4587ea6 | ||
|
|
8675e1d13f | ||
|
|
6ceacc68cc | ||
|
|
4aacf134b7 | ||
|
|
0605772715 | ||
|
|
3fa53556f4 | ||
|
|
76510b8971 | ||
|
|
66162966b9 | ||
|
|
71e1571c39 | ||
|
|
806b761e74 | ||
|
|
0176b38958 | ||
|
|
7a180c7310 | ||
|
|
3e1120182c | ||
|
|
8e86ce671c | ||
|
|
f75a324030 | ||
|
|
3fabff93f6 | ||
|
|
e74efc4e76 | ||
|
|
472ed0753d | ||
|
|
67538ff60c | ||
|
|
ae8bd69106 | ||
|
|
2538890b52 | ||
|
|
87dd819ae4 | ||
|
|
7ec560d4a2 | ||
|
|
6f9cd6a16b | ||
|
|
923af88336 | ||
|
|
5b6667c461 | ||
|
|
6f00740f67 | ||
|
|
248863cf16 | ||
|
|
97d48823dd | ||
|
|
5eb41e1a15 | ||
|
|
4a4837d9f5 | ||
|
|
4ad72fab7b | ||
|
|
fe68e45609 | ||
|
|
291b9a84ef | ||
|
|
2f9b7b188a | ||
|
|
d04d41bc23 | ||
|
|
6cb3d7167f | ||
|
|
90b1659a18 | ||
|
|
1aaf44f9b0 | ||
|
|
d7cfb84351 | ||
|
|
d28cf0b76d | ||
|
|
b4a3236284 | ||
|
|
556168892d | ||
|
|
77667be570 | ||
|
|
f48a912287 | ||
|
|
af30d0831d | ||
|
|
5989eb8f6e | ||
|
|
2bb778834b | ||
|
|
a5ce191e4d | ||
|
|
7617756576 | ||
|
|
0dfd3a5b0e | ||
|
|
61a54f48c5 | ||
|
|
5ca0237e34 | ||
|
|
ab1207e461 | ||
|
|
75fea4f7c0 | ||
|
|
fb34eb5394 | ||
|
|
41101217c6 | ||
|
|
19b1f5004a | ||
|
|
75fcd88f73 | ||
|
|
c21ce45d70 | ||
|
|
9f10cb2899 | ||
|
|
aa0c621223 | ||
|
|
f1dfb9051c | ||
|
|
932e58531d | ||
|
|
35a75e1066 | ||
|
|
522713473d | ||
|
|
3d6e268d15 | ||
|
|
cf8129dcbb | ||
|
|
3985cca8cc | ||
|
|
86ab65ef66 | ||
|
|
9db9616a43 | ||
|
|
39fd6f054b | ||
|
|
9b5f6ceca8 | ||
|
|
746da1a76e | ||
|
|
0a2d0da36f | ||
|
|
0076455e6e | ||
|
|
b674a0ed88 | ||
|
|
90dba34ecc | ||
|
|
01b40b26f5 | ||
|
|
c0805a285e | ||
|
|
ac76870d67 | ||
|
|
7160f50322 | ||
|
|
ba39f2595c | ||
|
|
38688b7065 | ||
|
|
9ef3218bb5 | ||
|
|
6f14e127a3 | ||
|
|
39890b319a | ||
|
|
2d8f166e4a | ||
|
|
d62af76097 | ||
|
|
b39ca51d41 | ||
|
|
2414ddd360 | ||
|
|
bed959f1cd | ||
|
|
a3f3470137 | ||
|
|
b7ec1d7d65 | ||
|
|
7e37068fc0 | ||
|
|
d049acad70 | ||
|
|
07044680d4 | ||
|
|
847b3fe54f | ||
|
|
7e9f0cc07a | ||
|
|
392c1650db | ||
|
|
0432c9bef3 | ||
|
|
e5e10ade72 | ||
|
|
ee14d5caf5 | ||
|
|
5a3457c180 | ||
|
|
c26002426f | ||
|
|
d48af9cea4 | ||
|
|
b34ab8a128 | ||
|
|
fd74e07fc8 | ||
|
|
3ab38e69fc | ||
|
|
d15e1bcc7d | ||
|
|
701df4b1ad | ||
|
|
7712a9afac | ||
|
|
eb8f760dca | ||
|
|
9940aa68e7 | ||
|
|
878db64878 | ||
|
|
56161e8e0d | ||
|
|
9c33689c11 | ||
|
|
6eff24369b | ||
|
|
81f7dd9a1d | ||
|
|
3d432d025a | ||
|
|
61facbb871 | ||
|
|
aba86dffd7 | ||
|
|
316f855280 | ||
|
|
77dad42f97 | ||
|
|
c35c2fe1ee | ||
|
|
f8d7c5209e | ||
|
|
88b3005589 | ||
|
|
952d335789 | ||
|
|
f8506f9d20 | ||
|
|
e4279bf257 | ||
|
|
b941f35812 | ||
|
|
619d395331 | ||
|
|
82a01b4483 | ||
|
|
c762195c8a | ||
|
|
054bebb081 | ||
|
|
8a93f1fc0c | ||
|
|
833e45155d | ||
|
|
e60ec6c47e | ||
|
|
61864970c1 | ||
|
|
b7c9810461 | ||
|
|
f097b80c35 | ||
|
|
662b191dfd | ||
|
|
c26407fa7e | ||
|
|
d973b87f5f | ||
|
|
5675ec8c02 | ||
|
|
1cafa02ba5 | ||
|
|
c66f250cd9 | ||
|
|
fcb2e10097 | ||
|
|
e17f1935d2 | ||
|
|
d2a4dbf283 | ||
|
|
04622a9e3b | ||
|
|
a3353aac0c | ||
|
|
a378b5108e | ||
|
|
d18de24cf9 | ||
|
|
46909dca85 | ||
|
|
b4b1c671bd | ||
|
|
bd9a1dbaf3 | ||
|
|
f2228cec7b | ||
|
|
a8ee779b31 | ||
|
|
971d7f703d | ||
|
|
bfc20ef219 | ||
|
|
20e1cd6d6b | ||
|
|
a020bc872d | ||
|
|
0a040a0531 | ||
|
|
305bf18819 | ||
|
|
f6e888ecf9 | ||
|
|
2a7e7e978b | ||
|
|
ef7a41f896 | ||
|
|
b57b4f9bd3 | ||
|
|
469e404725 | ||
|
|
35d9e98e83 | ||
|
|
5b6406d09d | ||
|
|
6aa6f4c8a2 | ||
|
|
0d56d3a555 | ||
|
|
331b85c57e | ||
|
|
7f9afa9c46 | ||
|
|
f3a25939db | ||
|
|
adc56d5877 | ||
|
|
766fa85c4c | ||
|
|
4a20abcbfa | ||
|
|
7e4b1a8f8f | ||
|
|
e9bd1f88c0 | ||
|
|
9c393ff6bc | ||
|
|
d0a85713d2 | ||
|
|
c7fc00095c | ||
|
|
97e2a5d30b | ||
|
|
e7d019028a | ||
|
|
8d8dcb1a3e | ||
|
|
802ef03013 | ||
|
|
98d057a2ac | ||
|
|
9321cc3de8 | ||
|
|
fd89735521 | ||
|
|
8deeb59d5c | ||
|
|
5c5e1c8b3c | ||
|
|
ef065d480a | ||
|
|
6cd29ad7e4 | ||
|
|
341f64839b | ||
|
|
54df58d44b | ||
|
|
1de2c222b4 | ||
|
|
e2121177e2 | ||
|
|
e4849cc0ac | ||
|
|
8604364846 | ||
|
|
dbd09edb51 | ||
|
|
ae6cc21f10 | ||
|
|
62247f4601 | ||
|
|
7b8657367c | ||
|
|
329ab73e1b | ||
|
|
ce749dad30 | ||
|
|
87e0f79d02 | ||
|
|
8b128c1bbe | ||
|
|
2f613aed26 | ||
|
|
e9ba295a1e | ||
|
|
48ad40dea2 | ||
|
|
9f4342bd19 | ||
|
|
a96fb46296 | ||
|
|
c6b6e841b6 | ||
|
|
cbefbb7927 | ||
|
|
cac59e4873 | ||
|
|
e714e87ad6 | ||
|
|
f3f8a62a18 | ||
|
|
5bda8a426c | ||
|
|
d93fa7f752 | ||
|
|
a0b2868e95 | ||
|
|
3cc1731c12 | ||
|
|
4c88944286 | ||
|
|
6c955424cd | ||
|
|
5570cea6b1 | ||
|
|
7bfeb6c177 | ||
|
|
41e8683307 | ||
|
|
18d26904e2 | ||
|
|
712151aa80 | ||
|
|
f38a9f36a1 | ||
|
|
a6d9f2d636 | ||
|
|
4b6a1c1c5e | ||
|
|
112256ca01 | ||
|
|
49733c239b | ||
|
|
81fee02e8f | ||
|
|
ef0393bed6 | ||
|
|
9ab0a83eb3 | ||
|
|
a9e3b2f625 | ||
|
|
37d645c00e | ||
|
|
4f98cef7cb | ||
|
|
1342634907 | ||
|
|
d757134ced | ||
|
|
4fc3ada42e | ||
|
|
67eaf8c70d | ||
|
|
cf0dbef3b5 | ||
|
|
e4d3ecb997 | ||
|
|
48586d953b | ||
|
|
0487b2fcb3 | ||
|
|
c97efda49b | ||
|
|
c1a0fbc66f | ||
|
|
c2a8da4551 | ||
|
|
9d8251f1b9 | ||
|
|
63a275c6e5 | ||
|
|
cb0939acf3 | ||
|
|
a44b1544b4 | ||
|
|
4fc8821cba | ||
|
|
bfe55c12a7 | ||
|
|
9c5dce925e | ||
|
|
b10ce8ea2d | ||
|
|
55d5b1e8da | ||
|
|
b370826624 | ||
|
|
4daa10d38b | ||
|
|
fed35d9c13 | ||
|
|
80af200c9f | ||
|
|
ba18c589f0 | ||
|
|
47d37c6047 | ||
|
|
63accc552a | ||
|
|
f7ed98d6b7 | ||
|
|
019a1fd448 | ||
|
|
3293979381 | ||
|
|
0bade9add3 | ||
|
|
f973744ff6 | ||
|
|
a830e0f59c | ||
|
|
2165528640 | ||
|
|
c43b403820 | ||
|
|
78e7841479 | ||
|
|
9c97690edd | ||
|
|
2262b99fb4 | ||
|
|
bc89483d47 | ||
|
|
1dc3c77bc9 | ||
|
|
d74ba03cd9 | ||
|
|
41832dcb4f | ||
|
|
bf3d22a4c8 | ||
|
|
1f445965e7 | ||
|
|
f6d27d3ba7 | ||
|
|
3da73b9d0d | ||
|
|
563d9e221f | ||
|
|
3b8fb073f4 | ||
|
|
97231bb477 | ||
|
|
c44319251a | ||
|
|
13a08ebbe1 | ||
|
|
697bbe29bc | ||
|
|
3d8024b0d9 | ||
|
|
87fb4f9dd2 | ||
|
|
59ff3a794d | ||
|
|
6701e07335 | ||
|
|
756dc08387 | ||
|
|
1304631aad | ||
|
|
37646bcfa0 | ||
|
|
4488f98900 | ||
|
|
7ce1dc0d48 | ||
|
|
daec4ac9b6 | ||
|
|
ee211aba51 | ||
|
|
6aa7cbade0 | ||
|
|
d8adea734f | ||
|
|
fa2f34ba3c | ||
|
|
ad04bdc99c | ||
|
|
448fa200a4 | ||
|
|
3af354b343 | ||
|
|
33cae2c222 | ||
|
|
d5d671bac1 | ||
|
|
cfe9aed42a | ||
|
|
0d5c4b83bb | ||
|
|
6dad4ce38b | ||
|
|
9ab7af44cb | ||
|
|
d10a46f8dd | ||
|
|
7032fd782e | ||
|
|
12cd01783b | ||
|
|
6951aaaeb9 | ||
|
|
ce7ffc8c43 | ||
|
|
065e6c55b1 | ||
|
|
6aad65f55c | ||
|
|
7a954a288d | ||
|
|
c1c2afd47d | ||
|
|
1763e0341d | ||
|
|
d0af38fb14 | ||
|
|
cfc2405596 | ||
|
|
4a1378debd | ||
|
|
d4976c6eb6 | ||
|
|
43583d7777 | ||
|
|
5c797667bb | ||
|
|
1a9af245c1 | ||
|
|
3bdea4c931 | ||
|
|
9f0ca1cc2e | ||
|
|
f648ab49f7 | ||
|
|
de759290e5 | ||
|
|
8910d5a65d | ||
|
|
bb3a1d2f16 | ||
|
|
6d2b88c31f | ||
|
|
f7dd110a49 | ||
|
|
631968ee5b | ||
|
|
ace5866024 | ||
|
|
ad2287f9eb | ||
|
|
f6db5968eb | ||
|
|
6946ae9397 | ||
|
|
7299c6b81d | ||
|
|
42504f0936 | ||
|
|
b94a50e720 | ||
|
|
448182497f | ||
|
|
7bcbfc13b0 | ||
|
|
716c9aa860 | ||
|
|
c407306db5 | ||
|
|
897525657f | ||
|
|
5bab5bb8c1 | ||
|
|
66f612e6b4 | ||
|
|
9d6557bacb | ||
|
|
a4c5105d78 | ||
|
|
3d8e6f2215 | ||
|
|
9ea8eb29df | ||
|
|
8f3ef2b1c5 | ||
|
|
ab1ea30dec | ||
|
|
3f28d19e06 | ||
|
|
b948457115 | ||
|
|
66af4d98ce | ||
|
|
6067e5118f | ||
|
|
587072c135 | ||
|
|
c69f95dea1 | ||
|
|
45c8b331de | ||
|
|
a0e229a489 | ||
|
|
0123ae97a1 | ||
|
|
793cae1dfa | ||
|
|
3589b92ec9 | ||
|
|
7456fc1ac7 | ||
|
|
8daad8f8b8 | ||
|
|
c4c2e81701 | ||
|
|
2c3ddfd363 | ||
|
|
2b8467e6a1 | ||
|
|
66dc9f2560 | ||
|
|
05c9126184 | ||
|
|
ca9ac9f92b | ||
|
|
3b502005e9 | ||
|
|
f828cd813b | ||
|
|
c9c56c915c | ||
|
|
b68aabb2c9 | ||
|
|
c91a3f1efc | ||
|
|
36c30c3016 | ||
|
|
0fc8aa9698 | ||
|
|
58236e0840 | ||
|
|
719d3114da | ||
|
|
be27f7d403 | ||
|
|
42d12e36e5 | ||
|
|
0d45d9980c | ||
|
|
f46493e885 | ||
|
|
c580375af2 | ||
|
|
5211523af4 | ||
|
|
cbfc490d95 | ||
|
|
890f0819ad | ||
|
|
8dcb790d12 | ||
|
|
7b386119ee | ||
|
|
e91fcfb244 | ||
|
|
cdf6e600bd | ||
|
|
766a3d490a | ||
|
|
a489d95ea1 | ||
|
|
fc039361b6 | ||
|
|
01808d3e71 | ||
|
|
ea362131b7 | ||
|
|
624323151e | ||
|
|
1540493b29 | ||
|
|
38cb3fddd9 | ||
|
|
8b6598ea6b | ||
|
|
fe51f8fbf7 | ||
|
|
1f3a7758c4 | ||
|
|
aa3dbdfaa2 | ||
|
|
f79b3841c7 | ||
|
|
384ab2dd18 | ||
|
|
8719fbaff7 | ||
|
|
bcdab8da1c | ||
|
|
2c9c9a86f4 | ||
|
|
4698ab9722 | ||
|
|
8f0cd69cd3 | ||
|
|
767824f2f7 | ||
|
|
f43ca07f33 | ||
|
|
70fa57d8c2 | ||
|
|
4df928a660 | ||
|
|
7ce31c77c5 | ||
|
|
35b9ce4ff0 | ||
|
|
1584a48b11 | ||
|
|
1bbf79fe20 | ||
|
|
3f3036961e | ||
|
|
0d05623bd0 | ||
|
|
eca9f60d7d | ||
|
|
8dbe3cfe0c | ||
|
|
cafe9019c1 | ||
|
|
5252a085c3 | ||
|
|
dfe183d4fc | ||
|
|
ff4eed5342 | ||
|
|
eaf31eb946 | ||
|
|
87547d5622 | ||
|
|
ecd16de53f | ||
|
|
9e66e7a3e1 | ||
|
|
76bd4ae8ef | ||
|
|
c701fbad32 | ||
|
|
e4704fb7e6 | ||
|
|
a4177ca0ec | ||
|
|
0f7512a394 | ||
|
|
309ea0e4d2 | ||
|
|
bc79e142e5 | ||
|
|
6626795a99 | ||
|
|
169dc3c2d1 | ||
|
|
0496a32e1d | ||
|
|
07ada6867e | ||
|
|
f5e0d0e032 | ||
|
|
063945d9c5 | ||
|
|
1477e9c35c | ||
|
|
6077a1c448 | ||
|
|
118e4fd089 | ||
|
|
18fde01ed5 | ||
|
|
3e18f0f238 | ||
|
|
0c4a9c6fad | ||
|
|
ef920088b1 | ||
|
|
bae8098974 | ||
|
|
2ae67b8ca9 | ||
|
|
3da58ed2b8 | ||
|
|
d96cae2fff | ||
|
|
7ef4d8dccb | ||
|
|
fa1fca7dbd | ||
|
|
3476d6087d | ||
|
|
d80b0f77c5 | ||
|
|
19a83bb2c0 | ||
|
|
a97d22b81b | ||
|
|
b097842d01 | ||
|
|
127d42d873 | ||
|
|
9767ca47ff | ||
|
|
0add4155ab | ||
|
|
b3cdd7df3c | ||
|
|
63d268c77e | ||
|
|
d650f61845 | ||
|
|
f749b11751 | ||
|
|
815fa538da | ||
|
|
d01486ce42 | ||
|
|
b2c3c9069f | ||
|
|
88d1028950 | ||
|
|
e37260f809 | ||
|
|
d029029043 | ||
|
|
eb9c65d765 | ||
|
|
5286aa2b1d | ||
|
|
91fb7b09bf | ||
|
|
1d9b006db7 | ||
|
|
774b8d987b | ||
|
|
5abe308a97 | ||
|
|
d67d72987a | ||
|
|
1fa077fc8a | ||
|
|
881bae0a15 | ||
|
|
0f18fbc24c | ||
|
|
6e084db3d9 | ||
|
|
9c055f2149 | ||
|
|
e73d86255b | ||
|
|
bbcabc8e71 | ||
|
|
50bac2c056 | ||
|
|
2c3682cc26 | ||
|
|
5d864f5888 | ||
|
|
05ee63cc83 | ||
|
|
0fb8d74077 | ||
|
|
c2a5b6d7f6 | ||
|
|
e2a5b5706e | ||
|
|
82c786517e | ||
|
|
ca7219f3cd | ||
|
|
17d72089ce | ||
|
|
a22ed4132a | ||
|
|
f8a39803e6 | ||
|
|
d9e37b37bb | ||
|
|
5f5dde6565 | ||
|
|
b46410d705 | ||
|
|
f11a89aec7 | ||
|
|
2014183e88 | ||
|
|
63a2e69cc6 | ||
|
|
fe189da30f | ||
|
|
6a599c53d7 | ||
|
|
6b82a9ef11 | ||
|
|
b82648ef5b | ||
|
|
e5aad4d170 | ||
|
|
0f28acac00 | ||
|
|
77321172a2 | ||
|
|
39d37010a3 | ||
|
|
ea5c1b9e12 | ||
|
|
4a96b4b622 | ||
|
|
8946a8bde2 | ||
|
|
6e094eaf42 | ||
|
|
0aa816b4f2 | ||
|
|
c8f70a4e3b | ||
|
|
7138eb4b69 | ||
|
|
2d9db6d9cb | ||
|
|
cd625ba1e1 | ||
|
|
7341e2586d | ||
|
|
eae167f8bd | ||
|
|
4c40eedffb | ||
|
|
8991de2610 | ||
|
|
25870dadd8 | ||
|
|
5b3a3edcd0 | ||
|
|
327b7769f3 | ||
|
|
59f631e1d4 | ||
|
|
c953482ba9 | ||
|
|
232d2ccf79 | ||
|
|
40bab90946 | ||
|
|
3d4e985aa6 | ||
|
|
ca777384eb | ||
|
|
726d46c3c1 | ||
|
|
0f50d1accd | ||
|
|
6e72889294 | ||
|
|
108c790cf4 | ||
|
|
bdb19dae89 | ||
|
|
d103a01c53 | ||
|
|
c9ed94bcc5 | ||
|
|
84a5099dd6 | ||
|
|
820ae61c20 | ||
|
|
2f8ebecdb2 | ||
|
|
abdaeb10c5 | ||
|
|
950c23aa5b | ||
|
|
4ddc65d0c5 | ||
|
|
820e432f92 | ||
|
|
97b336bff4 | ||
|
|
b85a0c4d09 | ||
|
|
1d2c4fc553 | ||
|
|
0bd7d99dd9 | ||
|
|
e0227e7d1c | ||
|
|
e59cad8265 | ||
|
|
ac7a00bf3f | ||
|
|
86e59c8c41 | ||
|
|
d4f642ed2b | ||
|
|
948ec522b0 | ||
|
|
19cefd9718 | ||
|
|
aac5177495 | ||
|
|
6b2ff63255 | ||
|
|
e517afc306 | ||
|
|
2b5bf56153 | ||
|
|
0e26731b75 | ||
|
|
6e3cc9d1f9 | ||
|
|
bb0554c727 | ||
|
|
15af5e5b37 | ||
|
|
d066b2086b | ||
|
|
6192bd2cc4 | ||
|
|
89614fc047 | ||
|
|
8bf1b9e707 | ||
|
|
c5a40926e3 | ||
|
|
6f50f857c6 | ||
|
|
1a101bfda4 | ||
|
|
4eb89414bb | ||
|
|
afaab39d88 | ||
|
|
c81ec556db | ||
|
|
fde7790680 | ||
|
|
81de9ff39e | ||
|
|
c90171fd5c | ||
|
|
4e7ef94543 | ||
|
|
c97656d45e | ||
|
|
ac424ce529 | ||
|
|
e872dc8edb | ||
|
|
604f6832da | ||
|
|
b7dffc8594 | ||
|
|
d97edbcac3 | ||
|
|
abafef3ad8 | ||
|
|
ca4bd779f2 | ||
|
|
a672d4dbdd | ||
|
|
570fb8f4c3 | ||
|
|
236b35c93e | ||
|
|
a415c10394 | ||
|
|
4044f28167 | ||
|
|
353465cba7 | ||
|
|
aeefc34ae4 | ||
|
|
9e465f6a0d | ||
|
|
a4c5752a0c | ||
|
|
cf0cf13a68 | ||
|
|
80c63e5701 | ||
|
|
3ba9f399b8 | ||
|
|
18c69d731c | ||
|
|
af9240240d | ||
|
|
ec867335d5 | ||
|
|
f23a760aac | ||
|
|
45ea53b9ba | ||
|
|
e9a2310f62 | ||
|
|
62a214920d | ||
|
|
b9057d05a1 | ||
|
|
f93b643e43 | ||
|
|
f5da79b690 | ||
|
|
56abe0e5a5 | ||
|
|
2445c0e464 | ||
|
|
3d76be07b2 | ||
|
|
d2b0823cd0 | ||
|
|
ea2a13dff2 | ||
|
|
bbd6b68eae | ||
|
|
89ba10ab4e | ||
|
|
2834d7f342 | ||
|
|
acaacec82d | ||
|
|
971132c2ba | ||
|
|
af15b561ab | ||
|
|
302ebb0082 | ||
|
|
41d366d27e | ||
|
|
f766600fd8 | ||
|
|
5a1a33242c | ||
|
|
58c93d99a9 | ||
|
|
64101b3b06 | ||
|
|
167379b0e0 | ||
|
|
a0306f3951 | ||
|
|
a36ab6486d | ||
|
|
b07f2c486c | ||
|
|
838d6f1981 | ||
|
|
6e7ee0ca48 | ||
|
|
8677b1d85d | ||
|
|
aea6bced69 | ||
|
|
8e819485e4 | ||
|
|
c526e383b4 | ||
|
|
c89ea2a1f0 | ||
|
|
ee515ff940 | ||
|
|
c023fdae8b | ||
|
|
a044354294 | ||
|
|
1dbd1065f9 | ||
|
|
c1bafccac1 | ||
|
|
f5bf07a7c3 | ||
|
|
e00ccf65cf | ||
|
|
8f7fd4295e | ||
|
|
ecf0b30513 | ||
|
|
828ecc6803 | ||
|
|
13fda50aac | ||
|
|
c4397adfce | ||
|
|
5932ef09e3 | ||
|
|
cd5655bd3f | ||
|
|
203c19c01f | ||
|
|
d4c35dc6c8 | ||
|
|
b58a3bf209 | ||
|
|
f2e91f97ed | ||
|
|
c350018716 | ||
|
|
5f104c58f2 | ||
|
|
6c71d168c4 | ||
|
|
4e7bfc12aa | ||
|
|
14d7e9e7f9 | ||
|
|
8e5adb47a0 | ||
|
|
3b7456a561 | ||
|
|
70466bb9b5 | ||
|
|
62da6173e2 | ||
|
|
6f1f7bf0f3 | ||
|
|
5b59fb38a4 | ||
|
|
03d224b708 | ||
|
|
3b636ef022 | ||
|
|
5c069d3c3c | ||
|
|
ae144588cc | ||
|
|
3565dd9c56 | ||
|
|
339d94e300 | ||
|
|
4156ba8908 | ||
|
|
ab6d361e64 | ||
|
|
b14612aba5 | ||
|
|
38a45d9816 | ||
|
|
8651d02651 | ||
|
|
28cf89627d | ||
|
|
846e74b8b5 | ||
|
|
99be766e90 | ||
|
|
ec233162e9 | ||
|
|
9bbf25b9f4 | ||
|
|
dedcc80501 | ||
|
|
ce265e1f91 | ||
|
|
0072343e0f | ||
|
|
a275ac5f41 | ||
|
|
752c86d8f7 | ||
|
|
57c64d0b86 | ||
|
|
d93dcd2a6c | ||
|
|
c44d7de143 | ||
|
|
9cff545c99 | ||
|
|
e54369334b | ||
|
|
ea62d25fa3 | ||
|
|
5fce4a2e2a | ||
|
|
7fd224b44a | ||
|
|
e635dc4316 | ||
|
|
3575972c43 | ||
|
|
114647810b | ||
|
|
6910f9c5c7 | ||
|
|
43a4b1c9cb | ||
|
|
bdd4a24567 | ||
|
|
c47b0ddcc1 | ||
|
|
6671f11a91 | ||
|
|
ec3ae7f6de | ||
|
|
845a8e5076 | ||
|
|
86afa200cd | ||
|
|
35f8911b1b | ||
|
|
3ba4a5cfb5 | ||
|
|
25815988d9 | ||
|
|
ee084b0bd4 | ||
|
|
931664c0c0 | ||
|
|
d6dc540236 | ||
|
|
0731b1fe6e | ||
|
|
9404071c95 | ||
|
|
a1483a4111 | ||
|
|
19e1f60d69 | ||
|
|
5778152e95 | ||
|
|
4a5ee9342e | ||
|
|
7b9d1284aa | ||
|
|
4b8cdc03a9 | ||
|
|
2191e4cdcc | ||
|
|
ea7d147d79 | ||
|
|
70d032ff23 | ||
|
|
8e1c6d2bd2 | ||
|
|
3a448a6ffc | ||
|
|
9275b3edd7 | ||
|
|
27e867e6ad | ||
|
|
946758e219 | ||
|
|
fe6ecd465e | ||
|
|
775ee5d27a | ||
|
|
c41bc8dac2 | ||
|
|
c659683d92 | ||
|
|
e122ee827c | ||
|
|
b5b36a8eb1 | ||
|
|
d488df0827 | ||
|
|
d64ec4b45d | ||
|
|
a9617bba3d | ||
|
|
be6c415a4e | ||
|
|
7721bc6dce | ||
|
|
3e69b47d76 | ||
|
|
f63036454e | ||
|
|
48e2ce2ca4 | ||
|
|
3b43b7aa65 | ||
|
|
815657f0c6 | ||
|
|
c931226f23 | ||
|
|
112082b643 | ||
|
|
e69040c799 | ||
|
|
2ae57ae1f1 | ||
|
|
69e5e3f461 | ||
|
|
6a10b8c127 | ||
|
|
76bf601e1b | ||
|
|
19ad184cd6 | ||
|
|
db92dc3636 | ||
|
|
bba8b25b48 | ||
|
|
ec3fe284b6 | ||
|
|
d3bf1137d7 | ||
|
|
d77d32853f | ||
|
|
dfecf2cc60 | ||
|
|
a37f748639 | ||
|
|
f421bcb2c9 | ||
|
|
e6726b0690 | ||
|
|
5ed91c05bf | ||
|
|
982f5beaf5 | ||
|
|
117ba360ac | ||
|
|
631b61e11c | ||
|
|
80e915c015 | ||
|
|
26da4eadb0 | ||
|
|
a7ff6e157d | ||
|
|
cefb9e825d | ||
|
|
96b9f8213c | ||
|
|
dcc1c72882 | ||
|
|
7fcf75829a | ||
|
|
539f82eb08 | ||
|
|
23f58b8c13 | ||
|
|
1e98cddaa6 | ||
|
|
c4a4801414 | ||
|
|
d5b332fc59 | ||
|
|
ec0e560bac | ||
|
|
69811e26cd | ||
|
|
32b7a1ffcd | ||
|
|
b8b0d2243f | ||
|
|
135f9ab048 | ||
|
|
ac976d0f3a | ||
|
|
3156d97969 | ||
|
|
a0a03f386a | ||
|
|
34b162e5d3 | ||
|
|
186402168f | ||
|
|
c3b1c493a1 | ||
|
|
4a97b762be | ||
|
|
58596632b3 | ||
|
|
f2a70a0e86 | ||
|
|
5d5d856155 | ||
|
|
d5c1e1d3f4 | ||
|
|
0aff193e17 | ||
|
|
35d6881336 | ||
|
|
48df84fb19 | ||
|
|
abf778ce86 | ||
|
|
aeacd7cf34 | ||
|
|
612460ca16 | ||
|
|
906b4ce158 | ||
|
|
59d69587dd | ||
|
|
c420f89d48 | ||
|
|
34a935f757 | ||
|
|
4eb1c0412b | ||
|
|
c295a3e90b | ||
|
|
cc51b1d394 | ||
|
|
7c13a92d9a | ||
|
|
499101ae9c | ||
|
|
7fe50a9a1f | ||
|
|
b69410697d | ||
|
|
e174e2f68f | ||
|
|
1ad7d885b3 | ||
|
|
8bfdcdbf44 | ||
|
|
97572f158c | ||
|
|
581930d8c6 | ||
|
|
ad671cfcf1 | ||
|
|
1f5345dfdb | ||
|
|
3e475d87eb | ||
|
|
476abd30f4 | ||
|
|
48762724eb | ||
|
|
124e5d94ed | ||
|
|
6f3f31b872 | ||
|
|
c3cb5c7356 | ||
|
|
218e5ecb35 | ||
|
|
a3517d28fa | ||
|
|
a6fd3f102d | ||
|
|
abce04805f | ||
|
|
e20cdd83f8 | ||
|
|
fd02219c83 | ||
|
|
4403147485 | ||
|
|
22028e23e3 | ||
|
|
ac0c88f486 | ||
|
|
f6c7343b70 | ||
|
|
05bd6cb180 | ||
|
|
f50e850774 | ||
|
|
83392dd8dc | ||
|
|
aab29f9077 | ||
|
|
7c884797d1 | ||
|
|
79a850f3b9 | ||
|
|
ce9fb38055 | ||
|
|
85af1b4ffb | ||
|
|
00a8c847e9 | ||
|
|
e7763f3b73 | ||
|
|
f723b504a1 | ||
|
|
910e8279a0 | ||
|
|
58d51dd128 | ||
|
|
7fd93ab556 | ||
|
|
23cea9a130 | ||
|
|
53e2960ffd | ||
|
|
4706414e26 | ||
|
|
8868765ea5 | ||
|
|
a11dc20d71 | ||
|
|
1a9f360132 | ||
|
|
77c86400c0 | ||
|
|
4ebae8dcf6 | ||
|
|
7377f80bdd | ||
|
|
4c999c3ff2 | ||
|
|
906d77e0cc | ||
|
|
1d2b5d5b15 | ||
|
|
7ea73c3db2 | ||
|
|
c579dcfb53 | ||
|
|
758a24be5b | ||
|
|
4519070995 | ||
|
|
8df0be52bf | ||
|
|
45889b8b9f | ||
|
|
9f19e9161f | ||
|
|
19137ad705 | ||
|
|
3673117580 | ||
|
|
321087cb6d | ||
|
|
2fb8d928e5 | ||
|
|
ebba7f7ecb | ||
|
|
b733865cb5 | ||
|
|
bc6feed17f | ||
|
|
a7c4e06bc3 | ||
|
|
befc51beec | ||
|
|
8ab185369e | ||
|
|
0730a88e63 | ||
|
|
257af96b97 | ||
|
|
a623b18947 | ||
|
|
803b8f5292 | ||
|
|
c98d756e6b | ||
|
|
28601437e4 | ||
|
|
98983bb604 | ||
|
|
4435563f1e | ||
|
|
d51167d770 | ||
|
|
f5c1533309 | ||
|
|
d1b266a361 | ||
|
|
91ba7a5704 | ||
|
|
227eebc5f0 | ||
|
|
9e06058b59 | ||
|
|
8032dd08bc | ||
|
|
540edea866 | ||
|
|
f83688f804 | ||
|
|
c0c9dacac6 | ||
|
|
4d69094a1f | ||
|
|
9fd62b4437 | ||
|
|
908d3fbf98 | ||
|
|
f12ad10fce | ||
|
|
4ff73893cb | ||
|
|
fd7a01b923 | ||
|
|
30f597c563 | ||
|
|
c44a9afdeb | ||
|
|
2487dde69e | ||
|
|
2c68eed072 | ||
|
|
9f32730714 | ||
|
|
f4210e39f2 | ||
|
|
cc1c08786f | ||
|
|
3e9e1e94d6 | ||
|
|
9051214278 | ||
|
|
6135c139da | ||
|
|
dd51b002b8 | ||
|
|
18a2d0bd90 | ||
|
|
50316c9cf6 | ||
|
|
07c952d75b | ||
|
|
85a311b3ae | ||
|
|
71a264ba96 | ||
|
|
1e187560cd | ||
|
|
f3f9aa96ca | ||
|
|
706088b9b3 | ||
|
|
de533cbf16 | ||
|
|
f832f11589 | ||
|
|
caa005a6ed | ||
|
|
9719682199 | ||
|
|
570461a45f | ||
|
|
9d6c93768d | ||
|
|
97f0f34209 | ||
|
|
ffa8f40864 | ||
|
|
ab588d37c0 | ||
|
|
2e8b1134b9 | ||
|
|
f61a67279a | ||
|
|
d947175e4b | ||
|
|
c3f13b54c1 | ||
|
|
ec87b99121 | ||
|
|
d6c725ea83 | ||
|
|
dea324b512 | ||
|
|
46a543441f | ||
|
|
14919980c2 | ||
|
|
1ba57ef6c3 | ||
|
|
ff5ff7f310 | ||
|
|
06e00ffccb | ||
|
|
14be2c1ebf | ||
|
|
4ecf1edb25 | ||
|
|
4c3c546c2f | ||
|
|
9b26a2a859 | ||
|
|
568fec3708 | ||
|
|
310cac8233 | ||
|
|
bb694cbffd | ||
|
|
c8434acd0d | ||
|
|
09ab64105d | ||
|
|
2f91b42a7f | ||
|
|
f5741a2a89 | ||
|
|
1905ed0dc6 | ||
|
|
408236b6b1 | ||
|
|
38315492a2 | ||
|
|
6fd54561ca | ||
|
|
3327e6b584 | ||
|
|
9b59e88214 | ||
|
|
f79583a394 | ||
|
|
928ca44f7d | ||
|
|
514976e9e5 | ||
|
|
94b6c0c49e | ||
|
|
7954b892bc | ||
|
|
f784cdfc68 | ||
|
|
02edfaf54b | ||
|
|
01ec1ed563 | ||
|
|
174fc76fb8 | ||
|
|
040e5a5198 | ||
|
|
135eb7a11e | ||
|
|
b3657dfe2b | ||
|
|
e6f0059e5e | ||
|
|
dea1164b1d | ||
|
|
2d17c15b71 | ||
|
|
302f224bc0 | ||
|
|
91e4280f6b | ||
|
|
2a8d603f98 | ||
|
|
33572bd13b | ||
|
|
b68600582c | ||
|
|
ecfbdc4870 | ||
|
|
259f41bb8a | ||
|
|
a30c763e09 | ||
|
|
fdcd3c7515 | ||
|
|
27172740e9 | ||
|
|
13fbdc2340 | ||
|
|
940cb3c000 | ||
|
|
829a45f410 | ||
|
|
026f953961 | ||
|
|
a642cc2470 | ||
|
|
9f4a7384b2 | ||
|
|
cd31ec8db1 | ||
|
|
304087a24d | ||
|
|
8c94bcb27b | ||
|
|
f246d06629 | ||
|
|
c29115146d | ||
|
|
2d422326bd | ||
|
|
ff3d0b29e3 | ||
|
|
5d89df554a | ||
|
|
e129e8b347 | ||
|
|
2d25d3848c | ||
|
|
1da3f871fa | ||
|
|
bb435b082e | ||
|
|
e052047b04 | ||
|
|
47d3ac3ef5 | ||
|
|
00ae88b6b6 | ||
|
|
e63b15cb67 | ||
|
|
8cc5276c9d | ||
|
|
ae0bb7727b | ||
|
|
04626eb1ee | ||
|
|
a852a15dab | ||
|
|
da2deb85c4 | ||
|
|
00d9983655 | ||
|
|
12ef88b90f | ||
|
|
19ec042a0a | ||
|
|
8019dc1267 | ||
|
|
da2f657342 | ||
|
|
f05ab87c08 | ||
|
|
f8c9915800 | ||
|
|
7f3e8bdad9 | ||
|
|
eb6dc9615c | ||
|
|
b9ffe3b204 | ||
|
|
3b191fa73e | ||
|
|
e2f527fc7b | ||
|
|
90424d9847 | ||
|
|
6205598b45 | ||
|
|
037861321d | ||
|
|
55e45a668f | ||
|
|
e822b85541 | ||
|
|
31b653f235 | ||
|
|
e11f8837a4 | ||
|
|
d70f9f9a46 | ||
|
|
b0f062ff0c | ||
|
|
0aa91b977d | ||
|
|
8a12813e1a | ||
|
|
3b95e840db | ||
|
|
845ca5c6b3 | ||
|
|
7c9506c96d | ||
|
|
7e47fdf52f | ||
|
|
1c259fe12e | ||
|
|
78c4344583 | ||
|
|
9019d1484e | ||
|
|
8e0c1027bb | ||
|
|
593f1acf10 | ||
|
|
4df66ebf00 | ||
|
|
0b248e9be4 | ||
|
|
01301c99df | ||
|
|
54441ddfde | ||
|
|
46c2d311e9 | ||
|
|
f4daffaa89 | ||
|
|
f7dd65d1e1 | ||
|
|
8d530d5f6f | ||
|
|
79f3b7c4b8 | ||
|
|
434ca04e00 | ||
|
|
e7592949f7 | ||
|
|
50bc6b0a48 | ||
|
|
7eeaf37873 | ||
|
|
653efb6983 | ||
|
|
1c87146a50 | ||
|
|
ca0a3974e4 | ||
|
|
e604abf47a | ||
|
|
f57684b024 | ||
|
|
9e326d15b9 | ||
|
|
789b699556 | ||
|
|
f7c615c958 | ||
|
|
26cfcd31f8 | ||
|
|
e47d493776 | ||
|
|
436d22e385 | ||
|
|
c1866f84e4 | ||
|
|
0d3b493a76 | ||
|
|
29fb40bd16 | ||
|
|
34c9265aa9 | ||
|
|
cf121062a1 | ||
|
|
83a7ef21f3 | ||
|
|
d0d33da493 | ||
|
|
be351232d3 | ||
|
|
e74c464857 | ||
|
|
1bb3117b05 | ||
|
|
3c4e5a080b | ||
|
|
6524fade3e | ||
|
|
97cd5ed537 | ||
|
|
d7c7494007 | ||
|
|
9a7a992495 | ||
|
|
8de996a368 | ||
|
|
232e907363 | ||
|
|
2d6af39ed0 | ||
|
|
bfdae4339c | ||
|
|
a613e9f113 | ||
|
|
75326f6626 | ||
|
|
a0da981ec7 | ||
|
|
315d35ca10 | ||
|
|
5c22dd35cf | ||
|
|
fed15ba072 | ||
|
|
0b44e7d6c7 | ||
|
|
722d0cbadc | ||
|
|
edd1719431 | ||
|
|
e8088e2a70 | ||
|
|
c4d39aced2 | ||
|
|
b40a5adeb0 | ||
|
|
558a900620 | ||
|
|
6b5e5a504d | ||
|
|
e44dca2464 | ||
|
|
e1f84b277a | ||
|
|
2518f46b08 | ||
|
|
01e18a9496 | ||
|
|
564ca709d3 | ||
|
|
a54a36ae18 | ||
|
|
43603b0961 | ||
|
|
96cd99f904 | ||
|
|
3438d10e25 | ||
|
|
022ccb42a1 | ||
|
|
e6d72e9f87 | ||
|
|
06e8a6af23 | ||
|
|
ac188d137a | ||
|
|
cae466745a | ||
|
|
d61f16dab0 | ||
|
|
02ba277a86 | ||
|
|
470ff49a02 | ||
|
|
04d741581d | ||
|
|
038f210148 | ||
|
|
2adad3a7bd | ||
|
|
05fb26a49b | ||
|
|
1c237affb4 | ||
|
|
3e81d7e9cb | ||
|
|
edb66620c1 | ||
|
|
04f7e8e777 | ||
|
|
eee201013c | ||
|
|
1190cb4ea1 | ||
|
|
507100ea0b | ||
|
|
9b13912b6d | ||
|
|
ee65deebfd | ||
|
|
ba9fa442d1 | ||
|
|
87da27f9bf | ||
|
|
b5bc5fe2c6 | ||
|
|
d2329360d0 | ||
|
|
7ece0ae10a | ||
|
|
f931b47eb8 | ||
|
|
7f7eb12ded | ||
|
|
c0940f7a19 | ||
|
|
9dfde11e35 | ||
|
|
6f15cc2dbc | ||
|
|
120308638f | ||
|
|
1d04ef99bb | ||
|
|
9b00d177ef | ||
|
|
884524c448 | ||
|
|
3ae1e7e87d | ||
|
|
81f885311d | ||
|
|
d9362f09d8 | ||
|
|
906d181d1b | ||
|
|
44b8812a7b | ||
|
|
3308c45e88 | ||
|
|
e530ecf9f9 | ||
|
|
51b5edb04f | ||
|
|
f0d89f850e | ||
|
|
b777e08542 | ||
|
|
2e485df530 | ||
|
|
3c37d22a6e | ||
|
|
08ab7a504a | ||
|
|
06563ef921 | ||
|
|
34f6210bc0 | ||
|
|
0bbde0c605 | ||
|
|
a8f24fd1b7 | ||
|
|
c3e0237696 | ||
|
|
bb6925920f | ||
|
|
63ec2a33ae | ||
|
|
c89a959fe8 | ||
|
|
150b50e0ba | ||
|
|
4ef824f665 | ||
|
|
5a56cca0aa | ||
|
|
b9189d7647 | ||
|
|
20226c914b | ||
|
|
434e7f8a09 | ||
|
|
a29d733a02 | ||
|
|
9abe4b967b | ||
|
|
3b6a4ece0f | ||
|
|
28d2471b4d | ||
|
|
d122af9fed | ||
|
|
77271f3856 | ||
|
|
ededfb68a6 | ||
|
|
4a3affdd24 | ||
|
|
8f8ea120d3 | ||
|
|
0fa88009f8 | ||
|
|
4375a807df | ||
|
|
b2d97c5908 | ||
|
|
ec89dd606d | ||
|
|
198508a7c3 | ||
|
|
4845e986bb | ||
|
|
1da8a307fc | ||
|
|
b4886e604e | ||
|
|
e84544136e | ||
|
|
ce70252a69 | ||
|
|
5c56962ea1 | ||
|
|
d2ed53b946 | ||
|
|
a4da80b498 | ||
|
|
9bd01492b1 | ||
|
|
da032941b4 | ||
|
|
c138fcc2e2 | ||
|
|
cbb69b0350 | ||
|
|
a8aed3354d | ||
|
|
e8790a4d4c | ||
|
|
df6ef3aaa0 | ||
|
|
2820d99f7b | ||
|
|
077aa4445a | ||
|
|
23bfc119d9 | ||
|
|
ab712ac637 | ||
|
|
b056826e94 | ||
|
|
6311627899 | ||
|
|
37cea5fb61 | ||
|
|
655a8cd60d | ||
|
|
4c8babc96a | ||
|
|
612bacebed | ||
|
|
ade7c8566d | ||
|
|
19553ce5c8 | ||
|
|
18ed2527e8 | ||
|
|
b0652bc884 | ||
|
|
15c9ad23fe | ||
|
|
578bb12562 | ||
|
|
f82cfda07f | ||
|
|
9e52b2788d | ||
|
|
2e56a113d9 | ||
|
|
4722d777e6 | ||
|
|
2141d54ae0 | ||
|
|
e346225136 | ||
|
|
012d4dae56 | ||
|
|
b4d9fe70af | ||
|
|
85e83b5441 | ||
|
|
6b2a453b8f | ||
|
|
27021538d8 | ||
|
|
8b57a2b055 | ||
|
|
75dd894685 | ||
|
|
9101ef8774 | ||
|
|
5932540630 | ||
|
|
ec376b2e47 | ||
|
|
a176562ad0 | ||
|
|
becf37b676 | ||
|
|
9b5efab8f8 | ||
|
|
e98a8ba599 | ||
|
|
7ddac50008 | ||
|
|
9837ae359f | ||
|
|
710a829dcb | ||
|
|
ccd84fa454 | ||
|
|
335b36d3a9 | ||
|
|
2be30fae00 | ||
|
|
db5cd21884 | ||
|
|
bfd3020031 | ||
|
|
344c36997a | ||
|
|
dfd9272b70 | ||
|
|
359f4520f5 | ||
|
|
aecf014f4e | ||
|
|
d2a89ddf84 | ||
|
|
c01fe153ae | ||
|
|
4f4a838799 | ||
|
|
ac6f2567eb | ||
|
|
05a5816ac6 | ||
|
|
9c8f6e9195 | ||
|
|
2fd001f6d2 | ||
|
|
d641d32413 | ||
|
|
18064ef6a2 | ||
|
|
5cb9216add | ||
|
|
91c36dc810 | ||
|
|
6efb02fa32 | ||
|
|
97313e4180 | ||
|
|
568ab24fd9 | ||
|
|
5a745efcd3 | ||
|
|
c651570e62 | ||
|
|
8980598085 | ||
|
|
c07c742feb | ||
|
|
1053abb9a9 | ||
|
|
2c9e57cbb1 | ||
|
|
c6eaa2c8a6 | ||
|
|
5ab5e913ee | ||
|
|
cea53ca476 | ||
|
|
58af09114b | ||
|
|
c4c0417e2d | ||
|
|
74f90e6947 | ||
|
|
ad5c339780 | ||
|
|
305823db00 | ||
|
|
baf58b298f | ||
|
|
c37367d018 | ||
|
|
1c98796e64 | ||
|
|
e686d9a6ea | ||
|
|
a1936b9d59 | ||
|
|
834f9c9337 | ||
|
|
615f8cfd3b | ||
|
|
8ed134105f | ||
|
|
5d6169b270 | ||
|
|
e83de8b938 | ||
|
|
ee55e039b2 | ||
|
|
086dd89144 | ||
|
|
68e5d4dd2c | ||
|
|
55a35c6bec | ||
|
|
d09b4885fe | ||
|
|
a46773e6d8 | ||
|
|
a422d0220c | ||
|
|
e5eba8430a | ||
|
|
3d235dc316 | ||
|
|
80d3b4be8c | ||
|
|
fe8b7480df | ||
|
|
cebfc3aaa0 | ||
|
|
f778b5a12d | ||
|
|
2244050160 | ||
|
|
9284e42b62 | ||
|
|
ee40120496 | ||
|
|
30cd2149ea | ||
|
|
395df36d57 | ||
|
|
79597ea0e5 | ||
|
|
283f39270a | ||
|
|
7d892bb19d | ||
|
|
a025f124f3 | ||
|
|
84f7287bf8 | ||
|
|
a58544b502 | ||
|
|
4d26175ebe | ||
|
|
78f0e6ff6b | ||
|
|
3af97af634 | ||
|
|
2c2663c8a4 | ||
|
|
1122b8a2f7 | ||
|
|
5b9f38948b | ||
|
|
507eb3b424 | ||
|
|
56fbc0ed6c | ||
|
|
7aaad314e3 | ||
|
|
356949dd54 | ||
|
|
9878baca53 | ||
|
|
9cbc7c2939 | ||
|
|
4680b63911 | ||
|
|
ce4a2d95f2 | ||
|
|
b2e048de8d | ||
|
|
d25a9d7515 | ||
|
|
dc130d3705 | ||
|
|
2391850218 | ||
|
|
c8f7ca920e | ||
|
|
e3e39af6fb | ||
|
|
f38114f5a5 | ||
|
|
1ee9d041df | ||
|
|
9c6f412f04 | ||
|
|
4fa0f2d04a | ||
|
|
e566a66ea4 | ||
|
|
58a42abc67 | ||
|
|
5676bd9d0d | ||
|
|
9691010e7b | ||
|
|
d19be3ad52 | ||
|
|
ec3cbf788b | ||
|
|
1282fd0b76 | ||
|
|
93430e5607 | ||
|
|
14201f4052 | ||
|
|
47979bf16d | ||
|
|
29530f3b17 | ||
|
|
af548e6ef8 | ||
|
|
ed24a9c990 | ||
|
|
cb1d86d08b | ||
|
|
88f3f628ef | ||
|
|
295bea37bc | ||
|
|
bd7d756254 | ||
|
|
4261147fe8 | ||
|
|
a70adc5eb3 | ||
|
|
0d51b04d79 | ||
|
|
06d40b8a81 | ||
|
|
2358510cba | ||
|
|
e6d13cb7d7 | ||
|
|
39e21c3f36 | ||
|
|
8da900ee72 | ||
|
|
9f4e81a1a3 | ||
|
|
0b918c2f51 | ||
|
|
085cd2a314 | ||
|
|
98d2399568 | ||
|
|
515d9a0008 | ||
|
|
aece1fa7d3 | ||
|
|
abc614ecfd | ||
|
|
1180d3fdde | ||
|
|
1639d1725a | ||
|
|
5df1deecbc | ||
|
|
fe3c0cf76e | ||
|
|
cc0df0182c | ||
|
|
eb354f639f | ||
|
|
02c530dcbe | ||
|
|
379b1de64f | ||
|
|
f3ff324925 | ||
|
|
0f2160222f | ||
|
|
ce3750c51c | ||
|
|
72a7ea6e91 | ||
|
|
4ad7e1f8e6 | ||
|
|
2007ba0c3b | ||
|
|
2009dc11db | ||
|
|
62f2196a0c | ||
|
|
e63c65da4f | ||
|
|
570a082227 | ||
|
|
9b1ede3a59 | ||
|
|
c445fc0f8a | ||
|
|
699493cf24 | ||
|
|
6c89686f31 | ||
|
|
f55b861849 | ||
|
|
adf82c04ad | ||
|
|
1b80956fe8 | ||
|
|
de9da8caf9 | ||
|
|
967f42dd89 | ||
|
|
95e8b29fa2 | ||
|
|
2e3c815e53 | ||
|
|
132707caa7 | ||
|
|
0dad616c38 | ||
|
|
c0882dffde | ||
|
|
5e082c647c | ||
|
|
285c3c2f5d | ||
|
|
dcb29a80fe | ||
|
|
b45ad19732 | ||
|
|
f12d453b5f | ||
|
|
8a00b711be | ||
|
|
56204efc7a | ||
|
|
da638c270f | ||
|
|
ad4b974274 | ||
|
|
943a05edcc | ||
|
|
1a28e65e50 | ||
|
|
cd3af7fa39 | ||
|
|
8ccb0c88db | ||
|
|
127880cf8d | ||
|
|
2e56086661 | ||
|
|
a129be0dbd | ||
|
|
12c0760cb3 | ||
|
|
9d3ed85ffd | ||
|
|
850d57d0d2 | ||
|
|
7981bec1ed | ||
|
|
76373a8597 | ||
|
|
9913e7b70b | ||
|
|
a08bb25bfa | ||
|
|
28ec164bc2 | ||
|
|
3d5ea8629c | ||
|
|
4aaf59d034 | ||
|
|
14850476c7 | ||
|
|
bf5b6170fa | ||
|
|
6f91591448 | ||
|
|
3c723bcba2 | ||
|
|
e7dd13cffa | ||
|
|
ad91630faa | ||
|
|
57f746b584 | ||
|
|
a55720091c | ||
|
|
b461635834 | ||
|
|
1375580651 | ||
|
|
3d20433ad1 | ||
|
|
58447c6456 | ||
|
|
c6273e9177 | ||
|
|
ffdc158d44 | ||
|
|
876c81fad8 | ||
|
|
028ee6d7b1 | ||
|
|
ec00548f1b | ||
|
|
c4dc03e4a8 | ||
|
|
3a510a77ec | ||
|
|
98a785fced | ||
|
|
c48654160d | ||
|
|
55b80132c4 | ||
|
|
1f0c168936 | ||
|
|
6715bc750f | ||
|
|
04a48a626b | ||
|
|
2f9f0da7c6 | ||
|
|
513c4f9e29 | ||
|
|
3f078517a0 | ||
|
|
37036f0fca | ||
|
|
5789aadb5c | ||
|
|
a768ed718a | ||
|
|
9c6092f31f | ||
|
|
40d294a247 | ||
|
|
72844e4edc | ||
|
|
db0a71125a | ||
|
|
da244af39d | ||
|
|
067f502d3c | ||
|
|
fffc6b1e4e | ||
|
|
9121c6a078 | ||
|
|
9c4e581d8b | ||
|
|
dfadd31f46 | ||
|
|
0cfa6fff43 | ||
|
|
d61671c1a0 | ||
|
|
d4f10a9af3 | ||
|
|
03861af893 | ||
|
|
ae531c445d | ||
|
|
4b26aeef9a | ||
|
|
1e47b79b50 | ||
|
|
0c223dcec4 | ||
|
|
0f4536c3d3 | ||
|
|
f43c584463 | ||
|
|
91c558ec83 | ||
|
|
9d45ab3246 | ||
|
|
34ff6eb567 | ||
|
|
8793c00438 | ||
|
|
d7981d5c3e | ||
|
|
bcaae3b67b | ||
|
|
046d9f9597 | ||
|
|
81bd0301d2 | ||
|
|
530e7e494f | ||
|
|
d402fd5690 | ||
|
|
eebec3b92f | ||
|
|
211c6585fa | ||
|
|
e1b5c40ca0 | ||
|
|
747a9b521b | ||
|
|
c2d72ad309 | ||
|
|
596181b622 | ||
|
|
77c5270e1e | ||
|
|
a663c14df8 | ||
|
|
3bd9f00268 | ||
|
|
1aadda735d | ||
|
|
12035208e2 | ||
|
|
df8a9f673c | ||
|
|
aa5c8a2c56 | ||
|
|
a84540e6bb | ||
|
|
fb91b64063 | ||
|
|
94cc77ebca | ||
|
|
aac6981304 | ||
|
|
ca05828b68 | ||
|
|
8ec6b4c59c | ||
|
|
f1be5f5341 | ||
|
|
714c264002 | ||
|
|
eca58097ef | ||
|
|
281146e22b | ||
|
|
f3a19a5d02 | ||
|
|
9b9b6937f4 | ||
|
|
f54c0b7dff | ||
|
|
36c58ad286 | ||
|
|
a67f633259 | ||
|
|
f39a607c1a | ||
|
|
0cc67ed2e5 | ||
|
|
5f8402c645 | ||
|
|
3ab87cd11e | ||
|
|
d5620d305d | ||
|
|
35ebc5e842 | ||
|
|
66276be1d2 | ||
|
|
47c0d522db | ||
|
|
b654883d1a | ||
|
|
b4f9d29129 | ||
|
|
bec6b961f3 | ||
|
|
2ce8f34306 | ||
|
|
30d1ae59ec | ||
|
|
ac7d4e3645 | ||
|
|
868c4001f6 | ||
|
|
e99c44d967 | ||
|
|
48a877f160 | ||
|
|
cea894a8bd | ||
|
|
087e7b9311 | ||
|
|
39ba498293 | ||
|
|
fe7390bd4d | ||
|
|
75af551435 | ||
|
|
ae2d3ebb48 | ||
|
|
5ff6c53715 | ||
|
|
3c94723b23 | ||
|
|
c6a2e3e328 | ||
|
|
2dc5e10878 | ||
|
|
4086dfcf56 | ||
|
|
7937c2bab0 | ||
|
|
5ffa8e9936 | ||
|
|
c431cee517 | ||
|
|
375f17e728 | ||
|
|
d3f658c874 | ||
|
|
5e340a4cdd | ||
|
|
409a5b9f99 | ||
|
|
fba305020b | ||
|
|
bd4ce3ac45 | ||
|
|
733de60f7c | ||
|
|
c365a44e01 | ||
|
|
e94f450bf0 | ||
|
|
d5efc9ddde | ||
|
|
68895ba4a5 | ||
|
|
139aa7a0fc | ||
|
|
4955157e13 | ||
|
|
f2dd5cc75e | ||
|
|
2ad634dbc6 | ||
|
|
de13f65a24 | ||
|
|
8994dde8f0 | ||
|
|
b7303a0828 | ||
|
|
5bc330162a | ||
|
|
0ebc0217f3 | ||
|
|
95c810b80a | ||
|
|
82d7fb883d | ||
|
|
b96e710543 | ||
|
|
24e5e85225 | ||
|
|
7b8f81f1b2 | ||
|
|
62e60fc7ab | ||
|
|
ccd3d4aded | ||
|
|
f0cf155b5c | ||
|
|
b66f67d889 | ||
|
|
e5dc07bde1 | ||
|
|
a955eb0fec | ||
|
|
c070af9681 | ||
|
|
c15e060ef2 | ||
|
|
6d6f2454a7 | ||
|
|
9ff44ed46b | ||
|
|
adf3ef61b8 | ||
|
|
832107e0b8 | ||
|
|
0ea1e71808 | ||
|
|
3b9b3f8ffa | ||
|
|
fda8823050 | ||
|
|
b5756cb14f | ||
|
|
617d3dbe52 | ||
|
|
c18beb1c7c | ||
|
|
a6957b919c | ||
|
|
816a362534 | ||
|
|
7ce3ebde4e | ||
|
|
cc2f83c4d9 | ||
|
|
6ce492049e | ||
|
|
a7999de4b0 | ||
|
|
d4bdfabf19 | ||
|
|
85030ab804 | ||
|
|
2a9bd00a50 | ||
|
|
1c2d76e651 | ||
|
|
a97f7d225a | ||
|
|
2781848aac | ||
|
|
d179da2bee | ||
|
|
60e7922734 | ||
|
|
1ece37ec3c | ||
|
|
8dad865146 | ||
|
|
80d15e782b | ||
|
|
d24e4c6518 | ||
|
|
6def46544c | ||
|
|
d66bae32d3 | ||
|
|
1b753a4020 | ||
|
|
25e6a74a0a | ||
|
|
afde00a4be | ||
|
|
0022d380bb | ||
|
|
d80d2ab934 | ||
|
|
13c1734753 | ||
|
|
bf33d6c34e | ||
|
|
1b02f9bd5d | ||
|
|
9f63c645ff | ||
|
|
abf271fb68 | ||
|
|
363755c3bf | ||
|
|
ba2db666aa | ||
|
|
9f3677b694 | ||
|
|
e6024c997f | ||
|
|
3cb83e2286 | ||
|
|
780d03e5e1 | ||
|
|
214114e6ce | ||
|
|
274d3fe679 | ||
|
|
0ecf86d8a3 | ||
|
|
c2d4390a72 | ||
|
|
7627d59d43 | ||
|
|
71ce9a6b37 | ||
|
|
232018c925 | ||
|
|
9dfbbe58ff | ||
|
|
fa9738a2e0 | ||
|
|
94ecbc5921 | ||
|
|
3c68d317d7 | ||
|
|
56d4edfb9d | ||
|
|
c6c037ff17 | ||
|
|
44feba4d89 | ||
|
|
962f2c7380 | ||
|
|
22007426aa | ||
|
|
008e9a92d3 | ||
|
|
41139ee2ab | ||
|
|
845c40d23c | ||
|
|
a22f26c4c8 | ||
|
|
99ff020f56 | ||
|
|
f863b42b71 | ||
|
|
2e713b459e | ||
|
|
923241ce1e | ||
|
|
3a8929b9d7 | ||
|
|
eb92d39d40 | ||
|
|
bdc62a007e | ||
|
|
4b35db6291 | ||
|
|
c8282b215d | ||
|
|
c123669828 | ||
|
|
781fd0a1cd | ||
|
|
9bd99605fb | ||
|
|
dc626bd4f0 | ||
|
|
aa27aeafa1 | ||
|
|
cdb25cd0e9 | ||
|
|
dc2d15fd9c | ||
|
|
55cb788380 | ||
|
|
0f3b7fe643 | ||
|
|
4b812350a8 | ||
|
|
aec37164de | ||
|
|
dec02bd8db | ||
|
|
1bd6a8ed9e | ||
|
|
2030f714fa | ||
|
|
4416646954 | ||
|
|
52ba9dc02a | ||
|
|
dad3d42d14 | ||
|
|
0d12f3043b | ||
|
|
1225786fc0 | ||
|
|
71496d5229 | ||
|
|
eb0aa20fe1 | ||
|
|
c34de3d0a3 | ||
|
|
54e0a9fc28 | ||
|
|
4bcd034b3d | ||
|
|
111bd29cc8 | ||
|
|
e038865693 | ||
|
|
dfd29dc37a | ||
|
|
b0fcd23ca6 | ||
|
|
f80b1d31f5 | ||
|
|
811ea5b92a | ||
|
|
f9dfbd5800 | ||
|
|
88f1c36929 | ||
|
|
8bbe771f5b | ||
|
|
c578fa63e5 | ||
|
|
4448b86b93 | ||
|
|
17badf95dc | ||
|
|
a267ee40d2 | ||
|
|
8ef645b3c2 | ||
|
|
35625b22f5 | ||
|
|
221dcefd6c | ||
|
|
9c74a9c1db | ||
|
|
55fc3920fc | ||
|
|
5d60b5eb8b | ||
|
|
049d5166e8 | ||
|
|
f4019db3d1 | ||
|
|
9f3732d35b | ||
|
|
b4f17ac3c6 | ||
|
|
978e35d335 | ||
|
|
22cbbec960 | ||
|
|
21f3a70788 | ||
|
|
b4c6f80e1c | ||
|
|
e1198c42eb | ||
|
|
e09fdbcef0 | ||
|
|
b708e79929 | ||
|
|
cbaecff3b7 | ||
|
|
4f7d2630af | ||
|
|
92d3860240 | ||
|
|
3757d5da9f | ||
|
|
1d38a885bb | ||
|
|
dbd767e8f1 | ||
|
|
8b83c38127 | ||
|
|
f1ea01e709 | ||
|
|
12a1aeb0f8 | ||
|
|
413150012f | ||
|
|
8ef5604ce8 | ||
|
|
42e50c800b | ||
|
|
8fbd08003c | ||
|
|
877577efdb | ||
|
|
a6f457749b | ||
|
|
9afb713df1 | ||
|
|
8f660c0276 | ||
|
|
a7e86d9afd | ||
|
|
462eea90c0 | ||
|
|
79c30dfc91 | ||
|
|
410a78b366 | ||
|
|
065807a0bc | ||
|
|
1d93658e56 | ||
|
|
2b7865e6ea | ||
|
|
0cdba8c329 | ||
|
|
11b317b788 | ||
|
|
fb955e15f4 | ||
|
|
ae2d141f0d | ||
|
|
68c983923e | ||
|
|
bf252f7f20 | ||
|
|
324038486f | ||
|
|
bef5da49cf | ||
|
|
24e7f547fa | ||
|
|
3ee3ab0ad1 | ||
|
|
f734154da8 | ||
|
|
7a053ce697 | ||
|
|
25f250310e | ||
|
|
4eca05bbba | ||
|
|
45b0f791bb | ||
|
|
42415a81c1 | ||
|
|
6882e83d1e | ||
|
|
d4b7318413 | ||
|
|
a2b4d400af | ||
|
|
f07868d24e | ||
|
|
d46ee049f4 | ||
|
|
c62eda5627 | ||
|
|
7683164ed2 | ||
|
|
7090c16575 | ||
|
|
9e634fed13 | ||
|
|
9bb125cebd | ||
|
|
0c4850b91d | ||
|
|
0eb7688c4d | ||
|
|
f47cdb68d9 | ||
|
|
d3c3cded37 | ||
|
|
e91ea4ecbe | ||
|
|
680b20d199 | ||
|
|
ec97e04fd4 | ||
|
|
b0b2657fe0 | ||
|
|
d27426fd8f | ||
|
|
d8206c0e3e | ||
|
|
3f1841a188 | ||
|
|
cb478e0dc8 | ||
|
|
02c42a7e3a | ||
|
|
ef40f7349e | ||
|
|
86eebb35cb | ||
|
|
a901388887 | ||
|
|
6cd1c5de38 | ||
|
|
7489f172a1 | ||
|
|
702798c275 | ||
|
|
430d51866c | ||
|
|
9d08421f01 | ||
|
|
f4051874b2 | ||
|
|
bbe0690056 | ||
|
|
772c0d1e41 | ||
|
|
8eb9ca0260 | ||
|
|
bd27afe0da | ||
|
|
a3af21275a | ||
|
|
61eb155d13 | ||
|
|
7932c1c4a9 | ||
|
|
f776fb83e7 | ||
|
|
a97521aba2 | ||
|
|
d1c0fe503e | ||
|
|
ed02c1ae36 | ||
|
|
9a67cf7355 | ||
|
|
755eeda364 | ||
|
|
136dee7747 | ||
|
|
e4e8428855 | ||
|
|
de8dc021f9 | ||
|
|
991587f252 | ||
|
|
8dbcf257c4 | ||
|
|
0b067364a9 | ||
|
|
5367bd6134 | ||
|
|
92228c4379 | ||
|
|
fb2c7896b3 | ||
|
|
23265d9091 | ||
|
|
2c9bb0e767 | ||
|
|
f9e8400d83 | ||
|
|
927a13cd76 | ||
|
|
51b3293e69 | ||
|
|
3f76cadea9 | ||
|
|
6dbf53b558 | ||
|
|
22e937c798 | ||
|
|
ac5cc8b299 | ||
|
|
c588ab723b | ||
|
|
4b2dfc051d | ||
|
|
5238c83f3f | ||
|
|
90bb580e50 | ||
|
|
f40e142704 | ||
|
|
a67618675d | ||
|
|
4fe436e4d1 | ||
|
|
683b8c966f | ||
|
|
28377a156d | ||
|
|
3dcc4faabb | ||
|
|
60a033f93a | ||
|
|
436bd73786 | ||
|
|
5c69ff3339 | ||
|
|
2105b1e7c4 | ||
|
|
523004e5b2 | ||
|
|
5e02c386ec | ||
|
|
b4501fe52d | ||
|
|
3c29eaa1b1 | ||
|
|
ee67e163b1 | ||
|
|
9662bc29fb | ||
|
|
96f2660b98 | ||
|
|
20f594c66c | ||
|
|
2b8d59dca3 | ||
|
|
d44047d109 | ||
|
|
57c4d33bd3 | ||
|
|
7a5377efe0 | ||
|
|
91e7cffccc | ||
|
|
df31e47313 | ||
|
|
cb9586270c | ||
|
|
21dfa5227c | ||
|
|
9d15d2be77 | ||
|
|
929c02d31f | ||
|
|
846185dd42 | ||
|
|
7bc2299a8e | ||
|
|
d40e131bd8 | ||
|
|
552c7297bf | ||
|
|
3f5fd23955 | ||
|
|
8b8566251e | ||
|
|
6db47def8e | ||
|
|
1d0edc7b25 | ||
|
|
f9a417638a | ||
|
|
984fe01551 | ||
|
|
d0cb350687 | ||
|
|
5f51011ce1 | ||
|
|
9ca125ac55 | ||
|
|
360f4f8c27 | ||
|
|
6501f71bd6 | ||
|
|
bf6b799dba | ||
|
|
5f57279283 | ||
|
|
5ed3565520 | ||
|
|
513fa90b8a | ||
|
|
a4d9b9689b | ||
|
|
1c05c0dcbb | ||
|
|
a1b49a3a6b | ||
|
|
6f57298cbb | ||
|
|
d8ce673088 | ||
|
|
4cd7af7a74 | ||
|
|
49c61b5992 | ||
|
|
e44d0550d2 | ||
|
|
17f82109b6 | ||
|
|
2d8888ae9b | ||
|
|
4abe9c6fb2 | ||
|
|
f9d94fa660 | ||
|
|
eaa13f4990 | ||
|
|
01fd5901fe | ||
|
|
3d6adeffc4 | ||
|
|
9066952759 | ||
|
|
6dd7f6274a | ||
|
|
7a8fe6d152 | ||
|
|
be507be3a9 | ||
|
|
657b97f190 | ||
|
|
9d7745cd9b | ||
|
|
3668f83693 | ||
|
|
a2d5d99c1f | ||
|
|
f379ef6a3b | ||
|
|
510a748749 | ||
|
|
550150d685 | ||
|
|
011ea9659e | ||
|
|
6eca7d948e | ||
|
|
90e639f119 | ||
|
|
86ac6461d1 | ||
|
|
18a95bf9ab | ||
|
|
7949bbe66d | ||
|
|
4b603c452a | ||
|
|
837f0634b6 | ||
|
|
78076f7854 | ||
|
|
719350cee1 | ||
|
|
4f6be3e6f5 | ||
|
|
8e61e9fecb | ||
|
|
2083285d78 | ||
|
|
034e86e2cb | ||
|
|
f4a2d5c652 | ||
|
|
534ccd6bf6 | ||
|
|
c17064f853 | ||
|
|
1e1566082f | ||
|
|
449548654d | ||
|
|
6fc99524f0 | ||
|
|
051629fad3 | ||
|
|
f957008c1c | ||
|
|
98e1deec88 | ||
|
|
99127652af | ||
|
|
e9b9e9e82c | ||
|
|
2ed5c3746e | ||
|
|
8902056fdb | ||
|
|
defa6ff6e8 | ||
|
|
eed44e81be | ||
|
|
1951aec5ec | ||
|
|
9c4e0b4107 | ||
|
|
c8deac660d | ||
|
|
4cc5ec9bd0 | ||
|
|
c41bef2e81 | ||
|
|
5b735cf960 | ||
|
|
604e960aa9 | ||
|
|
6c465aa1f2 | ||
|
|
c266832fdc | ||
|
|
906d8d0413 | ||
|
|
cb05fd4a3c | ||
|
|
2eda24799b | ||
|
|
41e221f0cb | ||
|
|
f75af035bb | ||
|
|
e9e6449edf | ||
|
|
f09d76da35 | ||
|
|
40dfe0919b | ||
|
|
85990dd074 | ||
|
|
38acc16e1c | ||
|
|
b7cc4c1e92 | ||
|
|
1f232d96d8 | ||
|
|
83508f165d | ||
|
|
7cc58e7e84 | ||
|
|
31d9740aac | ||
|
|
69891a64a0 | ||
|
|
0940309600 | ||
|
|
a762b1ed60 | ||
|
|
1b9d9d3a8b | ||
|
|
d9908b3d61 | ||
|
|
c40b80436a | ||
|
|
8f1e352bcc | ||
|
|
18e769b5e5 | ||
|
|
27af6459b3 | ||
|
|
2c4bfab01a | ||
|
|
e689be552b | ||
|
|
ad80e7f48b | ||
|
|
d81b75b084 | ||
|
|
90f1431047 | ||
|
|
61ea7dabae | ||
|
|
5d9f5f4a7d | ||
|
|
f956f612d3 | ||
|
|
3f5108268d | ||
|
|
4c0dfc3f30 | ||
|
|
1670fe9b1c | ||
|
|
300b28c0f2 | ||
|
|
e7038961ef | ||
|
|
24e77a5211 | ||
|
|
9df039fbc2 | ||
|
|
143cd46a81 | ||
|
|
680e9871ed | ||
|
|
d5ece58f71 | ||
|
|
d7bbb5c4b7 | ||
|
|
cf9c991c79 | ||
|
|
0f0d96195d | ||
|
|
3a562bb714 | ||
|
|
6381ba8478 | ||
|
|
9e3c14841a | ||
|
|
1917091338 | ||
|
|
b1bb508554 | ||
|
|
0a68a48fc5 | ||
|
|
d3af6792d0 | ||
|
|
44dc3b743e | ||
|
|
b469d2832d | ||
|
|
d844026c29 | ||
|
|
21b4990652 | ||
|
|
39e24bdc97 | ||
|
|
bc66b98176 | ||
|
|
d6d3fb46cc | ||
|
|
4040b334f5 | ||
|
|
d7e72519ef | ||
|
|
c7752f0be9 | ||
|
|
0ffe28a733 | ||
|
|
56f24fe317 | ||
|
|
341cde2781 | ||
|
|
33bb8d434d | ||
|
|
9f813b7385 | ||
|
|
02a336a25d | ||
|
|
88ed1446f4 | ||
|
|
c69312f128 | ||
|
|
c5bcff0e10 | ||
|
|
871d1e2440 | ||
|
|
1619afb938 | ||
|
|
25528913f1 | ||
|
|
7df532fa72 | ||
|
|
ef91441c76 | ||
|
|
aa6c56b63d | ||
|
|
18e899d15e | ||
|
|
63fa8924ae | ||
|
|
0e13e3bd81 | ||
|
|
372c0ed457 | ||
|
|
071077200b | ||
|
|
65579a2861 | ||
|
|
bb7603ae2a | ||
|
|
cce67d274e | ||
|
|
794329dcad | ||
|
|
e36fda3ff1 | ||
|
|
3832d33259 | ||
|
|
1f40c2ccf8 | ||
|
|
7350524456 | ||
|
|
a1a973a873 | ||
|
|
f2a915700c | ||
|
|
e184f99617 | ||
|
|
ab07adb14f | ||
|
|
6535c68276 | ||
|
|
dde2772e52 | ||
|
|
4a8fd309c5 | ||
|
|
b416849d9c | ||
|
|
bc321d8ced | ||
|
|
ac72c19d22 | ||
|
|
67fc2fd3c0 | ||
|
|
4acc59204c | ||
|
|
07cadb59e0 | ||
|
|
6fa4741c81 | ||
|
|
f4bac2382c | ||
|
|
67b72220c0 | ||
|
|
feeb14ea47 | ||
|
|
bdfb6f5f46 | ||
|
|
53491e9eaa | ||
|
|
f720de65fa | ||
|
|
3d70162a8d | ||
|
|
d3a1bbc3d0 | ||
|
|
0078574ee6 | ||
|
|
7cfd313531 | ||
|
|
e7919e9a1b | ||
|
|
98073202e9 | ||
|
|
8dee345f85 | ||
|
|
9161882f33 | ||
|
|
eef313665b | ||
|
|
53e70fbfcb | ||
|
|
05a1721499 | ||
|
|
2f772080b8 | ||
|
|
a5548c080c | ||
|
|
7e0a1ecc80 | ||
|
|
3f2dcccc07 | ||
|
|
adc5965b32 | ||
|
|
45919fc0cf | ||
|
|
dd6f4c4844 | ||
|
|
bb47db033f | ||
|
|
111ea78693 | ||
|
|
c17253589a | ||
|
|
7e6156f5dd | ||
|
|
d5cfb63f52 | ||
|
|
cab15055e7 | ||
|
|
9185910171 | ||
|
|
b4892e0caf | ||
|
|
83e0cafef9 | ||
|
|
7cb75506c3 | ||
|
|
ac6970ad40 | ||
|
|
5a95cc236c | ||
|
|
95c942f477 | ||
|
|
6088f2e573 | ||
|
|
fc705746c0 | ||
|
|
8182359fe4 | ||
|
|
e7ae15162c | ||
|
|
12ca20432d | ||
|
|
8b7406e168 | ||
|
|
9d6317f782 | ||
|
|
d8bdb73140 | ||
|
|
476db15431 | ||
|
|
20ce356296 | ||
|
|
ea594dcbc6 | ||
|
|
021b9746a8 | ||
|
|
c4615ae557 | ||
|
|
95a5089bdc | ||
|
|
cef1fba281 | ||
|
|
5c7859a258 | ||
|
|
986cdae5b0 | ||
|
|
3b11e28d6c | ||
|
|
eba63e8e76 | ||
|
|
2fc65e3b42 | ||
|
|
7d504ab2bf | ||
|
|
216c7efd42 | ||
|
|
8c4149db16 | ||
|
|
20ac8f69ea | ||
|
|
1db3d7a6fb | ||
|
|
b1c1138cf8 | ||
|
|
00b1a4f174 | ||
|
|
86cc665b58 | ||
|
|
e26dd578ef | ||
|
|
f1f3217052 | ||
|
|
8f14fd89ef | ||
|
|
26e4d52a61 | ||
|
|
319c647147 | ||
|
|
f4cd93bd36 | ||
|
|
5a80bb1d2a | ||
|
|
1126dcacf5 | ||
|
|
bdf9a73d19 | ||
|
|
1f73b83a79 | ||
|
|
73bd62c51e | ||
|
|
9acd5c94e8 | ||
|
|
6e85eac14b | ||
|
|
936baf676e | ||
|
|
867f06d813 | ||
|
|
7f9f440789 | ||
|
|
5a15e64471 | ||
|
|
c9aecd51f3 | ||
|
|
6ca1d978d4 | ||
|
|
a18c73bd7c | ||
|
|
26d86cbcb5 | ||
|
|
b24a5d9aca | ||
|
|
5305bc1ceb | ||
|
|
a672f0f56c | ||
|
|
9ab5e13e8f | ||
|
|
a49171f8cc | ||
|
|
65d8dc412a | ||
|
|
8600400632 | ||
|
|
11d10bee12 | ||
|
|
dbd16e8285 | ||
|
|
eb26787079 | ||
|
|
b0a7b1eb3d | ||
|
|
f994092d7f | ||
|
|
946d8e5be5 | ||
|
|
6d7c2ae74a | ||
|
|
1ba71b0b1b | ||
|
|
47c3af6a0e | ||
|
|
e5527a5aa5 | ||
|
|
9bb0dcd73f | ||
|
|
4159804052 | ||
|
|
adb27cf143 | ||
|
|
a4879d854f | ||
|
|
8b92dfb889 | ||
|
|
eb4868cb6e | ||
|
|
e06e6e05ae | ||
|
|
4fce4f81c7 | ||
|
|
ae11283574 | ||
|
|
15fc9aa483 | ||
|
|
2ebfb8e6a9 | ||
|
|
d098ea675f | ||
|
|
8ad152e5fc | ||
|
|
14077fcf51 | ||
|
|
b427573e19 | ||
|
|
46268f0dcf | ||
|
|
006c178eb1 | ||
|
|
d63b20dabb | ||
|
|
fcf0a391ed | ||
|
|
263b9c4b3e | ||
|
|
1dc7355952 | ||
|
|
67e4a72a28 | ||
|
|
e6ea07f9b7 | ||
|
|
44a691ae29 | ||
|
|
290dbc43cb | ||
|
|
219f1f9f3f | ||
|
|
582170f26e | ||
|
|
4e2dad7720 | ||
|
|
d002ec72ad | ||
|
|
f6bb14f7c4 | ||
|
|
e1697848a5 | ||
|
|
4d48bba350 | ||
|
|
a690cc5564 | ||
|
|
be16f76034 | ||
|
|
ae4cf44728 | ||
|
|
4ac0df71b1 | ||
|
|
dbd948867c | ||
|
|
a9b5cd6c31 | ||
|
|
92f513d514 | ||
|
|
b239d21961 | ||
|
|
40e8dd4a8d | ||
|
|
a667435ef2 | ||
|
|
042b4e7587 | ||
|
|
c46a1b4a59 | ||
|
|
e6035d5479 | ||
|
|
008d090093 | ||
|
|
6ff080c36b | ||
|
|
086ca30323 | ||
|
|
17f3ecbbcb | ||
|
|
1f2c8c4ad2 | ||
|
|
bc03331c66 | ||
|
|
ffd1711d4f | ||
|
|
1cdbda1b6b | ||
|
|
fd15e5182d | ||
|
|
b4cc0fb0f3 | ||
|
|
fb2d03f2e1 | ||
|
|
ab261aba49 | ||
|
|
615bc8c5b9 | ||
|
|
3fc98c8c1b | ||
|
|
a5cc14e885 | ||
|
|
fe4c0a4f28 | ||
|
|
a21678b5b8 | ||
|
|
2272ea1139 | ||
|
|
e9affabf39 | ||
|
|
5a412000e1 | ||
|
|
30312de4dd | ||
|
|
d9a775de16 | ||
|
|
3d7cd78d0e | ||
|
|
ccd550bbc4 | ||
|
|
a6ffb5c61c | ||
|
|
cb7659bb86 | ||
|
|
391f21f57e | ||
|
|
fdce70937f | ||
|
|
2060619e5b | ||
|
|
d7fd1fc65b | ||
|
|
6760d7e776 | ||
|
|
d61187c836 | ||
|
|
f9932f9fee | ||
|
|
c003e56c03 | ||
|
|
4d94106bff | ||
|
|
3bd2d4f868 | ||
|
|
75b37ea0dc | ||
|
|
c356d0455d | ||
|
|
278f75e70c | ||
|
|
2c7c5a3dc3 | ||
|
|
806ffacedd | ||
|
|
93246f80c4 | ||
|
|
e9723d3f22 | ||
|
|
6baec7277f | ||
|
|
d43554d290 | ||
|
|
1922e5e014 | ||
|
|
6c065a64fa | ||
|
|
3b6e5853d8 | ||
|
|
463fee429b | ||
|
|
570b286ef9 | ||
|
|
4c5e71f33c | ||
|
|
3884483bca | ||
|
|
fc1a89cc63 | ||
|
|
c471eed808 | ||
|
|
35450dfc8f | ||
|
|
5360c60f3d | ||
|
|
f60c640dc6 | ||
|
|
91bb323e84 | ||
|
|
cf9e122bd2 | ||
|
|
7528ca18d8 | ||
|
|
05ee35b6bc | ||
|
|
0e8b069781 | ||
|
|
3b95d7278d | ||
|
|
7691706295 | ||
|
|
2af65ee946 | ||
|
|
279e1fd9c5 | ||
|
|
0f8f33e9fe | ||
|
|
12e91f1c6b | ||
|
|
f4f605867b | ||
|
|
ac40abba2e | ||
|
|
224604f2e7 | ||
|
|
ee4360de3a | ||
|
|
0af59b9602 | ||
|
|
ff8fe68f14 | ||
|
|
71c15e0ff5 | ||
|
|
6be1fbacde | ||
|
|
be594dd49e | ||
|
|
6b65d435fb | ||
|
|
0dc5212066 | ||
|
|
7c8ffd510e | ||
|
|
8e6423e873 | ||
|
|
c99f74b351 | ||
|
|
57b462223c | ||
|
|
055db97273 | ||
|
|
c80ebf73ee | ||
|
|
9b613294ae | ||
|
|
8cb73e1680 | ||
|
|
27dfa24cfb | ||
|
|
c53f0dbb30 | ||
|
|
db16a357e8 | ||
|
|
01e71958b2 | ||
|
|
f379519d40 | ||
|
|
54a09958d5 | ||
|
|
3421de06d5 | ||
|
|
e44eb01396 | ||
|
|
313143586b | ||
|
|
c8ae72893a | ||
|
|
cbc3735ca0 | ||
|
|
31fdbdf8c9 | ||
|
|
2741d0ab2a | ||
|
|
79b0187b58 | ||
|
|
bf5659d0e2 | ||
|
|
f6314cab69 | ||
|
|
4f5fe3d383 | ||
|
|
1c720d587c | ||
|
|
c46dc99224 | ||
|
|
5e43d4f20d | ||
|
|
359434bfd3 | ||
|
|
e755a2d4ec | ||
|
|
0b416cd03e | ||
|
|
857e0f251b | ||
|
|
f040c7c742 | ||
|
|
2e82c9d312 | ||
|
|
126923c33e | ||
|
|
26528d8bec | ||
|
|
f99da111f7 | ||
|
|
8c30472472 | ||
|
|
11131ebe06 | ||
|
|
8dd80589d6 | ||
|
|
51e27146f3 | ||
|
|
70717dcbe5 | ||
|
|
51cba32d8d | ||
|
|
b9076714cf | ||
|
|
b76caabd32 | ||
|
|
0922fd66a4 | ||
|
|
4e7e9b2cfc | ||
|
|
0c24134ac2 | ||
|
|
f96e418dd6 | ||
|
|
1627415cca | ||
|
|
d047c91399 | ||
|
|
8bec5550cf | ||
|
|
7a61ade4a0 | ||
|
|
0239be69e6 | ||
|
|
883bdc2879 | ||
|
|
c3457a4c8a | ||
|
|
d93506a18c | ||
|
|
0bb77a671b | ||
|
|
028f883499 | ||
|
|
727133e28b | ||
|
|
1bd08cb2db | ||
|
|
2962aa6166 | ||
|
|
bac55cd90d | ||
|
|
9b51936131 | ||
|
|
692665d0da | ||
|
|
14cdf33473 | ||
|
|
17f4f83ad6 | ||
|
|
c981606da7 | ||
|
|
e21d1bffe8 | ||
|
|
32706092f3 | ||
|
|
a7fe446550 | ||
|
|
bb01cfb330 | ||
|
|
2590efba4b | ||
|
|
186c5897a0 | ||
|
|
6150a008d5 | ||
|
|
fb2a86ba3f | ||
|
|
91fa762985 | ||
|
|
d80f760c92 | ||
|
|
ce2c887469 | ||
|
|
8b5c7c94cd | ||
|
|
3dd2a059bf | ||
|
|
2f724ffba2 | ||
|
|
0586ec3f42 | ||
|
|
4908463722 | ||
|
|
26d0ef9ac9 | ||
|
|
c2c9d992e7 | ||
|
|
ce9aa636c4 | ||
|
|
68d06dcd19 | ||
|
|
03bf93eb12 | ||
|
|
163eabb76c | ||
|
|
bed1bb2429 | ||
|
|
bc802b6f19 | ||
|
|
9b67a253f1 | ||
|
|
29dc5a8bb4 | ||
|
|
b63e516274 | ||
|
|
db9d5a0fb0 | ||
|
|
3f5e6faac5 | ||
|
|
eef6b95e24 | ||
|
|
96b76aea6b | ||
|
|
0745a12e7d | ||
|
|
645d5e19db | ||
|
|
45e2c7bd03 | ||
|
|
9001f34fab | ||
|
|
4d3dd2052f | ||
|
|
ff8849a907 | ||
|
|
adecdc8788 | ||
|
|
36cbf7f0fb | ||
|
|
1262f6b11b | ||
|
|
edbc34e7e5 | ||
|
|
e2b4ec47b0 | ||
|
|
e9565c3602 | ||
|
|
8d3ca92fe9 | ||
|
|
7ed1ced521 | ||
|
|
801b9c1483 | ||
|
|
3990bebca3 | ||
|
|
8a2de1001f |
8
.coolify-logo
Normal file
8
.coolify-logo
Normal file
@@ -0,0 +1,8 @@
|
||||
_____ _ _ __
|
||||
/ ____| | (_)/ _|
|
||||
| | ___ ___ | |_| |_ _ _
|
||||
| | / _ \ / _ \| | | _| | | |
|
||||
| |___| (_) | (_) | | | | | |_| |
|
||||
\_____\___/ \___/|_|_|_| \__, |
|
||||
__/ |
|
||||
|___/
|
||||
@@ -1,16 +0,0 @@
|
||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile
|
||||
|
||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||
ARG VARIANT="16-bullseye"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment if you want to install an additional version of node using nvm
|
||||
# ARG EXTRA_NODE_VERSION=10
|
||||
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
||||
|
||||
# [Optional] Uncomment if you want to install more global node modules
|
||||
RUN su node -c "npm install -g pnpm"
|
||||
@@ -1,35 +0,0 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node
|
||||
{
|
||||
"name": "Node.js",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
|
||||
// Append -bullseye or -buster to pin to an OS version.
|
||||
// Use -bullseye variants on local arm64/Apple Silicon.
|
||||
"args": {
|
||||
"VARIANT": "16-bullseye"
|
||||
}
|
||||
},
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"ms-azuretools.vscode-docker",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"svelte.svelte-vscode",
|
||||
"ardenivanov.svelte-intellisense",
|
||||
"Prisma.prisma",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [3000],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "cp .env.template .env && pnpm install && pnpm db:push && pnpm db:seed",
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node",
|
||||
"features": {
|
||||
"docker-in-docker": "20.10",
|
||||
"github-cli": "latest"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,24 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
build
|
||||
.svelte-kit
|
||||
package
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
local-serve
|
||||
.env.backup
|
||||
.env.secrets
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
||||
/.npm
|
||||
/.bash_history
|
||||
/_data
|
||||
.rnd
|
||||
/.ssh
|
||||
|
||||
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
||||
16
.env.development.example
Normal file
16
.env.development.example
Normal file
@@ -0,0 +1,16 @@
|
||||
APP_NAME=Coolify-localhost
|
||||
APP_ID=development
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
APP_PORT=8000
|
||||
MUX_ENABLED=false
|
||||
|
||||
DUSK_DRIVER_URL=http://selenium:4444
|
||||
|
||||
## For Andras only
|
||||
# To purge cache
|
||||
BUNNY_API_KEY=
|
||||
# To upload assets
|
||||
BUNNY_STORAGE_API_KEY=
|
||||
10
.env.production
Normal file
10
.env.production
Normal file
@@ -0,0 +1,10 @@
|
||||
APP_ID=
|
||||
APP_NAME=Coolify
|
||||
APP_KEY=
|
||||
|
||||
DB_PASSWORD=
|
||||
REDIS_PASSWORD=
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
2
.github/FUNDING.yml → .github/FUNDING.yaml
vendored
2
.github/FUNDING.yml → .github/FUNDING.yaml
vendored
@@ -1,2 +1,2 @@
|
||||
open_collective: coollabsio
|
||||
github: coollabsio
|
||||
github: coollabsio
|
||||
47
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
47
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
@@ -1,47 +0,0 @@
|
||||
name: 🐞 Bug report
|
||||
description: Create a bug report to help us improve coolify
|
||||
title: "[Bug]: "
|
||||
labels: [Bug]
|
||||
assignees:
|
||||
- andrasbacsai
|
||||
- vasani-arpit
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! Please fill the form in English
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A concise description of what you're experiencing and what you expect.
|
||||
placeholder: |
|
||||
When I do <X>, <Y> happens and I see the error message attached below:
|
||||
```...```
|
||||
What I expect is <Z>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Add steps to reproduce this behaviour, include console / network logs & videos
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: "The version of your coolify Instance"
|
||||
placeholder: "2.5.2"
|
||||
validations:
|
||||
required: true
|
||||
31
.github/ISSUE_TEMPLATE/--feature-request.yaml
vendored
31
.github/ISSUE_TEMPLATE/--feature-request.yaml
vendored
@@ -1,31 +0,0 @@
|
||||
name: 🛠️ Feature request
|
||||
description: Suggest an idea to improve coolify
|
||||
title: '[Feature]: '
|
||||
labels: [Enhancement]
|
||||
assignees:
|
||||
- andrasbacsai
|
||||
- vasani-arpit
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to request a feature for coolify! Please also add your request here to get feedback from the community: https://feedback.coolify.io/!
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue related to this feature request already exists.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: One paragraph description of the feature.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Why should this be worked on?
|
||||
description: A concise description of the problems or use cases for this feature request.
|
||||
validations:
|
||||
required: true
|
||||
20
.github/ISSUE_TEMPLATE/--task.yaml
vendored
20
.github/ISSUE_TEMPLATE/--task.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
name: 📝 Task
|
||||
description: Create a task for the team to work on
|
||||
title: "[Task]: "
|
||||
labels: [Task]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue related to this already exists.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: SubTasks
|
||||
placeholder: |
|
||||
- Sub Task 1
|
||||
- Sub Task 2
|
||||
validations:
|
||||
required: false
|
||||
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Bug report
|
||||
description: Create a new bug report
|
||||
title: '[Bug]: '
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Minimal Reproduction (if possible, example repository)
|
||||
description: Please provide a step by step guide to reproduce the issue
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Exception or Error
|
||||
description: Please provide error logs if possible.
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: Coolify's version (see bottom left corner).
|
||||
validations:
|
||||
required: true
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🤔 Questions and Help
|
||||
url: https://discord.com/invite/6rDM4fkymF
|
||||
about: Reach out to us on discord or our github discussions page.
|
||||
- name: 🙋♂️ Service request
|
||||
url: https://feedback.coolify.io/
|
||||
about: Want to request a new service or build pack? For example Wordpress, Hasura, Appwrite, Angular etc...
|
||||
- name: 🤔 Community Support (Chat)
|
||||
url: https://coollabs.io/discord
|
||||
about: Reach out to us on Discord.
|
||||
- name: 🙋♂️ Feature Requests
|
||||
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
|
||||
about: All feature requests will be discussed here.
|
||||
|
||||
84
.github/workflows/coolify-helper-next.yml
vendored
Normal file
84
.github/workflows/coolify-helper-next.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: Coolify Helper Image Development (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "next" ]
|
||||
paths:
|
||||
- .github/workflows/coolify-helper-next.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify-helper"
|
||||
|
||||
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/coolify-helper/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
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/coolify-helper/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-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 }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
84
.github/workflows/coolify-helper.yml
vendored
Normal file
84
.github/workflows/coolify-helper.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: Coolify Helper Image (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify-helper"
|
||||
|
||||
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/coolify-helper/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/coolify-helper/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_PROD_RELEASE_CHANNEL }}
|
||||
79
.github/workflows/development-build.yml
vendored
Normal file
79
.github/workflows/development-build.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: Development Build (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore: ["main", "v3"]
|
||||
paths-ignore:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
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@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
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:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-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 }}:${{ github.ref_name }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
44
.github/workflows/docker-image.yml
vendored
Normal file
44
.github/workflows/docker-image.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches: [ "main" ]
|
||||
# pull_request:
|
||||
# branches: [ "*" ]
|
||||
push:
|
||||
branches: ["this-does-not-exist"]
|
||||
pull_request:
|
||||
branches: ["this-does-not-exist"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
/usr/local/share/ca-certificates
|
||||
/var/cache/apt/archives
|
||||
/var/lib/apt/lists
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-docker-
|
||||
- name: Build the Docker image
|
||||
run: |
|
||||
cp .env.example .env
|
||||
docker run --rm -u "$(id -u):$(id -g)" \
|
||||
-v "$(pwd):/app" \
|
||||
-w /app composer:2 \
|
||||
composer install --ignore-platform-reqs
|
||||
./vendor/bin/spin build
|
||||
- name: Start the stack
|
||||
run: |
|
||||
./vendor/bin/spin up -d
|
||||
./vendor/bin/spin exec coolify php artisan key:generate
|
||||
./vendor/bin/spin exec coolify php artisan migrate:fresh --seed
|
||||
- name: Test (missing E2E tests)
|
||||
run: |
|
||||
./vendor/bin/spin exec coolify php artisan test
|
||||
85
.github/workflows/production-build.yml
vendored
Normal file
85
.github/workflows/production-build.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Production Build (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
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: Get Version
|
||||
id: version
|
||||
run: |
|
||||
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
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
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: Get Version
|
||||
id: version
|
||||
run: |
|
||||
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
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-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: Get Version
|
||||
id: version
|
||||
run: |
|
||||
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
|
||||
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 }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||
37
.github/workflows/production-release.yml
vendored
37
.github/workflows/production-release.yml
vendored
@@ -1,37 +0,0 @@
|
||||
name: production-release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
making-something-cool:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Get current package version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
id: package-version
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: coollabsio/coolify:latest,coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache,mode=max
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_CHANNEL }}
|
||||
35
.github/workflows/staging-release.yml
vendored
35
.github/workflows/staging-release.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: staging-release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
|
||||
jobs:
|
||||
staging-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/coolify:next
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next,mode=max
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_CHANNEL }}
|
||||
45
.gitignore
vendored
45
.gitignore
vendored
@@ -1,13 +1,34 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
build
|
||||
.svelte-kit
|
||||
package
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
local-serve
|
||||
apps/api/db/migration.db-journal
|
||||
.env.backup
|
||||
.env.secrets
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
||||
/.npm
|
||||
/.bash_history
|
||||
/_data
|
||||
_testing_hosts/
|
||||
_volumes/
|
||||
.lesshst
|
||||
psysh_history
|
||||
.psql_history
|
||||
_ide_helper.php
|
||||
.gitignore
|
||||
.phpstorm.meta.php
|
||||
_ide_helper_models.php
|
||||
.rnd
|
||||
/.ssh
|
||||
scripts/load-test/*
|
||||
|
||||
68
.gitpod.yml
68
.gitpod.yml
@@ -1,13 +1,65 @@
|
||||
# This configuration file was automatically generated by Gitpod.
|
||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||
# and commit this file to your remote git repository to share the goodness with others.
|
||||
image: gitpod/workspace-node:2022-06-20-19-54-55
|
||||
tasks:
|
||||
- init: pnpm install && pnpm db:push && pnpm db:seed
|
||||
command: pnpm dev
|
||||
- name: Setup Spin environment and Composer dependencies
|
||||
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614
|
||||
before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
|
||||
init: |
|
||||
cp .env.example .env &&
|
||||
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
|
||||
sed -i "s#USERID=#USERID=33333#g" .env
|
||||
sed -i "s#GROUPID=#GROUPID=33333#g" .env
|
||||
composer install --ignore-platform-reqs
|
||||
./vendor/bin/spin up -d
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan key:generate
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan storage:link
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed
|
||||
cat .coolify-logo
|
||||
gp sync-done spin-is-ready
|
||||
|
||||
- name: Install Node dependencies and run Vite
|
||||
command: |
|
||||
echo "Waiting for Sail environment to boot up."
|
||||
gp sync-await spin-is-ready
|
||||
./vendor/bin/spin exec vite npm install
|
||||
./vendor/bin/spin exec vite npm run dev
|
||||
|
||||
- name: Laravel Queue Worker, listening to code changes
|
||||
command: |
|
||||
echo "Waiting for Sail environment to boot up."
|
||||
gp sync-await spin-is-ready
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan queue:listen
|
||||
|
||||
ports:
|
||||
- port: 3001
|
||||
- port: 5432
|
||||
onOpen: ignore
|
||||
name: PostgreSQL
|
||||
visibility: public
|
||||
- port: 3000
|
||||
- port: 5173
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
name: Node Server for Vite
|
||||
- port: 8000
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
name: Coolify
|
||||
|
||||
# Configure vscode
|
||||
vscode:
|
||||
extensions:
|
||||
- bmewburn.vscode-intelephense-client
|
||||
- ikappas.composer
|
||||
- ms-azuretools.vscode-docker
|
||||
- ecmel.vscode-html-css
|
||||
- MehediDracula.php-namespace-resolver
|
||||
- wmaurer.change-case
|
||||
- Equinusocio.vsc-community-material-theme
|
||||
- EditorConfig.EditorConfig
|
||||
- streetsidesoftware.code-spell-checker
|
||||
- rangav.vscode-thunder-client
|
||||
- PKief.material-icon-theme
|
||||
- cierra.livewire-vscode
|
||||
- lennardv.livewire-goto-updated
|
||||
- bradlc.vscode-tailwindcss
|
||||
- heybourn.headwind
|
||||
- adrianwilczynski.alpine-js-intellisense
|
||||
- amiralizadeh9480.laravel-extra-intellisense
|
||||
- shufo.vscode-blade-formatter
|
||||
|
||||
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
|
||||
);
|
||||
});
|
||||
}
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"i18n-ally.localesPaths": ["src/lib/locales"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.extract.ignoredByFiles": {
|
||||
"src\\routes\\__layout.svelte": ["Coolify", "coolLabs logo"]
|
||||
},
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.enabledFrameworks": ["svelte"],
|
||||
"i18n-ally.enabledParsers": ["js", "ts", "json"],
|
||||
"i18n-ally.extract.autoDetect": true
|
||||
}
|
||||
94
CODE_OF_CONDUCT.md
Normal file
94
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Citizen Code of Conduct
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
A primary goal of Coolify is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
|
||||
|
||||
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
|
||||
|
||||
We invite all those who participate in Coolify to help us create safe and positive experiences for everyone.
|
||||
|
||||
## 2. Open [Source/Culture/Tech] Citizenship
|
||||
|
||||
A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
|
||||
|
||||
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
|
||||
|
||||
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
|
||||
|
||||
## 3. Expected Behavior
|
||||
|
||||
The following behaviors are expected and requested of all community members:
|
||||
|
||||
* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
|
||||
* Exercise consideration and respect in your speech and actions.
|
||||
* Attempt collaboration before conflict.
|
||||
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
|
||||
* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
|
||||
* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
|
||||
|
||||
## 4. Unacceptable Behavior
|
||||
|
||||
The following behaviors are considered harassment and are unacceptable within our community:
|
||||
|
||||
* Violence, threats of violence or violent language directed against another person.
|
||||
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
|
||||
* Posting or displaying sexually explicit or violent material.
|
||||
* Posting or threatening to post other people's personally identifying information ("doxing").
|
||||
* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
|
||||
* Inappropriate photography or recording.
|
||||
* Inappropriate physical contact. You should have someone's consent before touching them.
|
||||
* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
|
||||
* Deliberate intimidation, stalking or following (online or in person).
|
||||
* Advocating for, or encouraging, any of the above behavior.
|
||||
* Sustained disruption of community events, including talks and presentations.
|
||||
|
||||
## 5. Weapons Policy
|
||||
|
||||
No weapons will be allowed at Coolify events, community spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in possession of one of these items will be asked to leave immediately, and will only be allowed to return without the weapon. Community members are further expected to comply with all state and local laws on this matter.
|
||||
|
||||
## 6. Consequences of Unacceptable Behavior
|
||||
|
||||
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
|
||||
|
||||
Anyone asked to stop unacceptable behavior is expected to comply immediately.
|
||||
|
||||
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
|
||||
|
||||
## 7. Reporting Guidelines
|
||||
|
||||
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. hi@coollabs.io.
|
||||
|
||||
|
||||
|
||||
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
|
||||
|
||||
## 8. Addressing Grievances
|
||||
|
||||
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify coollabsio with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
|
||||
|
||||
|
||||
|
||||
## 9. Scope
|
||||
|
||||
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to community business.
|
||||
|
||||
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
|
||||
|
||||
## 10. Contact info
|
||||
|
||||
hi@coollabs.io
|
||||
|
||||
## 11. License and attribution
|
||||
|
||||
The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
|
||||
|
||||
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
|
||||
|
||||
_Revision 2.3. Posted 6 March 2017._
|
||||
|
||||
_Revision 2.2. Posted 4 February 2016._
|
||||
|
||||
_Revision 2.1. Posted 23 June 2014._
|
||||
|
||||
_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._
|
||||
281
CONTRIBUTING.md
281
CONTRIBUTING.md
@@ -1,281 +0,0 @@
|
||||
# 👋 Welcome
|
||||
|
||||
First of all, thank you for considering contributing to my project! It means a lot 💜.
|
||||
|
||||
Contribution guide is for v2, not applicable for v3
|
||||
|
||||
## 🙋 Want to help?
|
||||
|
||||
If you begin in GitHub contribution, you can find the [first contribution](https://github.com/firstcontributions/first-contributions) and follow this guide.
|
||||
|
||||
Follow the [introduction](#introduction) to get started then start contributing!
|
||||
|
||||
This is a little list of what you can do to help the project:
|
||||
|
||||
- [🧑💻 Develop your own ideas](#developer-contribution)
|
||||
- [🌐 Translate the project](#translation)
|
||||
|
||||
## 👋 Introduction
|
||||
|
||||
### Setup with github codespaces
|
||||
|
||||
If you have github codespaces enabled then you can just create a codespace and run `pnpm dev` to run your the dev environment. All the required dependencies and packages has been configured for you already.
|
||||
|
||||
### Setup locally in your machine
|
||||
|
||||
> 🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS. 💡 Although windows users can use github codespaces for development
|
||||
|
||||
#### Recommended Pull Request Guideline
|
||||
|
||||
- Fork the project
|
||||
- Clone your fork repo to local
|
||||
- Create a new branch
|
||||
- Push to your fork repo
|
||||
- Create a pull request: https://github.com/coollabsio/compare
|
||||
- Write a proper description
|
||||
- Open the pull request to review against `next` branch
|
||||
|
||||
---
|
||||
|
||||
# How to start after you set up your local fork?
|
||||
|
||||
Due to the lock file, this repository is best with [pnpm](https://pnpm.io). I recommend you try and use `pnpm` because it is cool and efficient!
|
||||
|
||||
You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
|
||||
|
||||
#### Steps for local setup
|
||||
|
||||
1. Copy `.env.template` to `.env` and set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||
2. Install dependencies with `pnpm install`.
|
||||
3. Need to create a local SQlite database with `pnpm db:push`.
|
||||
|
||||
This will apply all migrations at `db/dev.db`.
|
||||
|
||||
4. Seed the database with base entities with `pnpm db:seed`
|
||||
5. You can start coding after starting `pnpm dev`.
|
||||
|
||||
## 🧑💻 Developer contribution
|
||||
|
||||
### Technical skills required
|
||||
|
||||
- **Languages**: Node.js / Javascript / Typescript
|
||||
- **Framework JS/TS**: Svelte / SvelteKit
|
||||
- **Database ORM**: Prisma.io
|
||||
- **Docker Engine**
|
||||
|
||||
### Database migrations
|
||||
|
||||
During development, if you change the database layout, you need to run `pnpm db:push` to migrate the database and create types for Prisma. You also need to restart the development process.
|
||||
|
||||
If the schema is finalized, you need to create a migration file with `pnpm db:migrate <nameOfMigration>` where `nameOfMigration` is given by you. Make it sense. :)
|
||||
|
||||
### Tricky parts
|
||||
|
||||
- BullMQ, the queue system Coolify uses, cannot be hot reloaded. So if you change anything in the files related to it, you need to restart the development process. I'm actively looking for a different queue/scheduler library. I'm open to discussion!
|
||||
|
||||
---
|
||||
|
||||
# How to add new services
|
||||
|
||||
You can add any open-source and self-hostable software (service/application) to Coolify if the following statements are true:
|
||||
|
||||
- Self-hostable (obviously)
|
||||
- Open-source
|
||||
- Maintained (I do not want to add software full of bugs)
|
||||
|
||||
## Backend
|
||||
|
||||
There are 5 steps you should make on the backend side.
|
||||
|
||||
1. Create Prisma / database schema for the new service.
|
||||
2. Add supported versions of the service.
|
||||
3. Update global functions.
|
||||
4. Create API endpoints.
|
||||
5. Define automatically generated variables.
|
||||
|
||||
> I will use [Umami](https://umami.is/) as an example service.
|
||||
|
||||
### Create Prisma / database schema for the new service.
|
||||
|
||||
You only need to do this if you store passwords or any persistent configuration. Mostly it is required by all services, but there are some exceptions, like NocoDB.
|
||||
|
||||
Update Prisma schema in [prisma/schema.prisma](prisma/schema.prisma).
|
||||
|
||||
- Add new model with the new service name.
|
||||
- Make a relationshup with `Service` model.
|
||||
- In the `Service` model, the name of the new field should be with low-capital.
|
||||
- If the service needs a database, define a `publicPort` field to be able to make it's database public, example field name in case of PostgreSQL: `postgresqlPublicPort`. It should be a optional field.
|
||||
|
||||
If you are finished with the Prisma schema, you should update the database schema with `pnpm db:push` command.
|
||||
|
||||
> You must restart the running development environment to be able to use the new model
|
||||
|
||||
> If you use VSCode, you probably need to restart the `Typescript Language Server` to get the new types loaded in the running VSCode.
|
||||
|
||||
### Add supported versions
|
||||
|
||||
Supported versions are hardcoded into Coolify (for now).
|
||||
|
||||
You need to update `supportedServiceTypesAndVersions` function at [src/lib/components/common.ts](src/lib/components/common.ts). Example JSON:
|
||||
|
||||
```js
|
||||
{
|
||||
// Name used to identify the service internally
|
||||
name: 'umami',
|
||||
// Fancier name to show to the user
|
||||
fancyName: 'Umami',
|
||||
// Docker base image for the service
|
||||
baseImage: 'ghcr.io/mikecao/umami',
|
||||
// Optional: If there is any dependent image, you should list it here
|
||||
images: [],
|
||||
// Usable tags
|
||||
versions: ['postgresql-latest'],
|
||||
// Which tag is the recommended
|
||||
recommendedVersion: 'postgresql-latest',
|
||||
// Application's default port, Umami listens on 3000
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Update global functions
|
||||
|
||||
1. Add the new service to the `include` variable in [src/lib/database/services.ts](src/lib/database/services.ts), so it will be included in all places in the database queries where it is required.
|
||||
|
||||
```js
|
||||
const include: Prisma.ServiceInclude = {
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
serviceSecret: true,
|
||||
minio: true,
|
||||
plausibleAnalytics: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
meiliSearch: true,
|
||||
umami: true // This line!
|
||||
};
|
||||
```
|
||||
|
||||
2. Update the database update query with the new service type to `configureServiceType` function in [src/lib/database/services.ts](src/lib/database/services.ts). This function defines the automatically generated variables (passwords, users, etc.) and it's encryption process (if applicable).
|
||||
|
||||
```js
|
||||
[...]
|
||||
else if (type === 'umami') {
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword());
|
||||
const postgresqlDatabase = 'umami';
|
||||
const hashSalt = encrypt(generatePassword(64));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
umami: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlPassword,
|
||||
postgresqlUser,
|
||||
hashSalt,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
3. Add decryption process for configurations and passwords to `getService` function in [src/lib/database/services.ts](src/lib/database/services.ts)
|
||||
|
||||
```js
|
||||
if (body.umami?.postgresqlPassword)
|
||||
body.umami.postgresqlPassword = decrypt(body.umami.postgresqlPassword);
|
||||
|
||||
if (body.umami?.hashSalt) body.umami.hashSalt = decrypt(body.umami.hashSalt);
|
||||
```
|
||||
|
||||
4. Add service deletion query to `removeService` function in [src/lib/database/services.ts](src/lib/database/services.ts)
|
||||
|
||||
### Create API endpoints.
|
||||
|
||||
You need to add a new folder under [src/routes/services/[id]](src/routes/services/[id]) with the low-capital name of the service. You need 3 default files in that folder.
|
||||
|
||||
#### `index.json.ts`:
|
||||
|
||||
It has a POST endpoint that updates the service details in Coolify's database, such as name, url, other configurations, like passwords. It should look something like this:
|
||||
|
||||
```js
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
|
||||
let { name, fqdn } = await event.request.json();
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
|
||||
try {
|
||||
await db.updateService({ id, fqdn, name });
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If it's necessary, you can create your own database update function, specifically for the new service.
|
||||
|
||||
#### `start.json.ts`
|
||||
|
||||
It has a POST endpoint that sets all the required secrets, persistent volumes, `docker-compose.yaml` file and sends a request to the specified docker engine.
|
||||
|
||||
You could also define an `HTTP` or `TCP` proxy for every other port that should be proxied to your server. (See `startHttpProxy` and `startTcpProxy` functions in [src/lib/haproxy/index.ts](src/lib/haproxy/index.ts))
|
||||
|
||||
#### `stop.json.ts`
|
||||
|
||||
It has a POST endpoint that stops the service and all dependent (TCP/HTTP proxies) containers. If publicPort is specified it also needs to cleanup it from the database.
|
||||
|
||||
## Frontend
|
||||
|
||||
1. You need to add a custom logo at [src/lib/components/svg/services/](src/lib/components/svg/services/) as a svelte component.
|
||||
|
||||
SVG is recommended, but you can use PNG as well. It should have the `isAbsolute` variable with the suitable CSS classes, primarily for sizing and positioning.
|
||||
|
||||
2. You need to include it the logo at
|
||||
|
||||
- [src/routes/services/index.svelte](src/routes/services/index.svelte) with `isAbsolute` in two places,
|
||||
- [src/lib/components/ServiceLinks.svelte](src/lib/components/ServiceLinks.svelte) with `isAbsolute` and a link to the docs/main site of the service
|
||||
- [src/routes/services/[id]/configuration/type.svelte](src/routes/services/[id]/configuration/type.svelte) with `isAbsolute`.
|
||||
|
||||
3. By default the URL and the name frontend forms are included in [src/routes/services/[id]/\_Services/\_Services.svelte](src/routes/services/[id]/_Services/_Services.svelte).
|
||||
|
||||
If you need to show more details on the frontend, such as users/passwords, you need to add Svelte component to [src/routes/services/[id]/\_Services](src/routes/services/[id]/_Services) with an underscore. For example, see other files in that folder.
|
||||
|
||||
You also need to add the new inputs to the `index.json.ts` file of the specific service, like for MinIO here: [src/routes/services/[id]/minio/index.json.ts](src/routes/services/[id]/minio/index.json.ts)
|
||||
|
||||
## 🌐 Translate the project
|
||||
|
||||
The project use [sveltekit-i18n](https://github.com/sveltekit-i18n/lib) to translate the project.
|
||||
It follows the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to name languages.
|
||||
|
||||
### Installation
|
||||
|
||||
You must have gone throw all the [intro](#introduction) steps before you can start translating.
|
||||
|
||||
It's only an advice, but I recommend you to use:
|
||||
|
||||
- Visual Studio Code
|
||||
- [i18n Ally for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally): ideal to see the progress of the translation.
|
||||
- [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode): to get the syntax color for the project
|
||||
|
||||
### Adding a language
|
||||
|
||||
If your language doesn't appear in the [locales folder list](src/lib/locales/), follow the step below:
|
||||
|
||||
1. In `src/lib/locales/`, Copy paste `en.json` and rename it with your language (eg: `cz.json`).
|
||||
2. In the [lang.json](src/lib/lang.json) file, add a line after the first bracket (`{`) with `"ISO of your language": "Language",` (eg: `"cz": "Czech",`).
|
||||
3. Have fun translating!
|
||||
34
CONTRIBUTION.md
Normal file
34
CONTRIBUTION.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Contributing
|
||||
|
||||
> "First, thanks for considering to contribute to my project.
|
||||
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
|
||||
|
||||
You can ask for guidance anytime on our
|
||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
||||
|
||||
## Code Contribution
|
||||
|
||||
### 1) Setup your development environment
|
||||
|
||||
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
||||
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
||||
|
||||
### 2) Set your environment variables
|
||||
|
||||
- Copy [.env.development.example](./.env.development.example) to .env.
|
||||
|
||||
## 3) Start & setup Coolify
|
||||
|
||||
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
|
||||
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
|
||||
|
||||
### 4) Start development
|
||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
||||
|
||||
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
||||
|
||||
Mails are caught by Mailpit: `localhost:8025`
|
||||
|
||||
## New Service Contribution
|
||||
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).
|
||||
|
||||
47
Dockerfile
47
Dockerfile
@@ -1,47 +0,0 @@
|
||||
FROM node:18-alpine3.16 as build
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache curl
|
||||
RUN curl -sL https://unpkg.com/@pnpm/self-installer | node
|
||||
|
||||
COPY . .
|
||||
RUN pnpm install
|
||||
RUN pnpm build
|
||||
|
||||
# Production build
|
||||
FROM node:18-alpine3.16
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV production
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
ENV PRISMA_QUERY_ENGINE_BINARY=/app/prisma-engines/query-engine \
|
||||
PRISMA_MIGRATION_ENGINE_BINARY=/app/prisma-engines/migration-engine \
|
||||
PRISMA_INTROSPECTION_ENGINE_BINARY=/app/prisma-engines/introspection-engine \
|
||||
PRISMA_FMT_BINARY=/app/prisma-engines/prisma-fmt \
|
||||
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
|
||||
PRISMA_CLIENT_ENGINE_TYPE=binary
|
||||
|
||||
COPY --from=coollabsio/prisma-engine:3.15 /prisma-engines/query-engine /prisma-engines/migration-engine /prisma-engines/introspection-engine /prisma-engines/prisma-fmt /app/prisma-engines/
|
||||
|
||||
RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite openssl psmisc
|
||||
RUN curl -sL https://unpkg.com/@pnpm/self-installer | node
|
||||
|
||||
RUN mkdir -p ~/.docker/cli-plugins/
|
||||
# https://download.docker.com/linux/static/stable/
|
||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
|
||||
# https://github.com/docker/compose/releases
|
||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.7.0 -o ~/.docker/cli-plugins/docker-compose
|
||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
||||
|
||||
RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack)
|
||||
|
||||
COPY --from=build /app/apps/api/build/ .
|
||||
COPY --from=build /app/apps/ui/build/ ./public
|
||||
COPY --from=build /app/apps/api/prisma/ ./prisma
|
||||
COPY --from=build /app/apps/api/package.json .
|
||||
COPY --from=build /app/docker-compose.yaml .
|
||||
|
||||
RUN pnpm install -p
|
||||
|
||||
EXPOSE 3000
|
||||
CMD pnpm start
|
||||
174
README.md
174
README.md
@@ -1,20 +1,41 @@
|
||||
# Coolify
|
||||
# About the Project
|
||||
|
||||
An open-source & self-hostable Heroku / Netlify alternative
|
||||
(ARM support is in beta).
|
||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||
|
||||
## Financial Contributors
|
||||
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.
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
||||
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
||||
|
||||
### Individuals
|
||||
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. 🪄️
|
||||
|
||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||
For more information, take a look at our landing page [here](https://coolify.io).
|
||||
|
||||
### Organizations
|
||||
# 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.
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website.
|
||||
https://coolify.io/sponsorships
|
||||
|
||||
Thank you so much!
|
||||
|
||||
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="cccareers logo" width="200"/></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
|
||||
|
||||
## 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/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>
|
||||
@@ -26,125 +47,52 @@ 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/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||
|
||||
---
|
||||
## Individuals
|
||||
|
||||
## Live Demo
|
||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||
|
||||
https://demo.coolify.io/
|
||||
# Cloud
|
||||
|
||||
(If it is unresponsive, that means someone overloaded the server. 😄)
|
||||
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
||||
|
||||
## Feedback
|
||||
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
||||
|
||||
If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
||||
## 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
|
||||
|
||||
## How to install
|
||||
|
||||
Installation is automated with the following command:
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
```
|
||||
You can find the installation script source [here](./scripts/install.sh).
|
||||
|
||||
If you would like no questions during installation:
|
||||
# Support
|
||||
|
||||
```bash
|
||||
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -f
|
||||
```
|
||||
Contact us [here](https://coolify.io/docs/contact).
|
||||
|
||||
For more details goto the [docs](https://docs.coollabs.io/coolify/installation).
|
||||
# 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>
|
||||
|
||||
## Features
|
||||
<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>
|
||||
|
||||
### Git Sources
|
||||
<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>
|
||||
|
||||
You can use the following Git Sources to be auto-deployed to your Coolify instance! (Self-hosted versions are also supported.)
|
||||
# Star History
|
||||
|
||||
<a href="https://github.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/github.com"></a>
|
||||
<a href="https://gitlab.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/gitlab.com"></a>
|
||||
|
||||
### Destinations
|
||||
|
||||
You can deploy your applications to the following destinations:
|
||||
|
||||
- Local Docker Engine
|
||||
- Remote Docker Engine
|
||||
|
||||
### Applications
|
||||
|
||||
Predefined build packs to cover the basic needs to deploy applications.
|
||||
|
||||
If you have an advanced use case, you can use the Docker build pack that allows you to deploy your application based on your custom Dockerfile.
|
||||
|
||||
<a href="https://html5.org/">
|
||||
<svg style="width:40px;height:40px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" ><g clip-path="url(#HTML5_Clip0_4)" ><path d="M30.216 0L27.6454 28.7967L16.0907 32L4.56783 28.8012L2 0H30.216Z" fill="#E44D26" /><path d="M16.108 29.5515L25.4447 26.963L27.6415 2.35497H16.108V29.5515Z" fill="#F16529" /><path d="M11.1109 9.4197H16.108V5.88731H7.25053L7.33509 6.83499L8.20327 16.5692H16.108V13.0369H11.4338L11.1109 9.4197Z" fill="#EBEBEB" /><path d="M11.907 18.3354H8.36111L8.856 23.8818L16.0917 25.8904L16.108 25.8859V22.2108L16.0925 22.2149L12.1585 21.1527L11.907 18.3354Z" fill="#EBEBEB" /><path d="M16.0958 16.5692H20.4455L20.0354 21.1504L16.0958 22.2138V25.8887L23.3373 23.8817L23.3904 23.285L24.2205 13.9855L24.3067 13.0369H16.0958V16.5692Z" fill="white" /><path d="M16.0958 9.41105V9.41969H24.6281L24.6989 8.62572L24.8599 6.83499L24.9444 5.88731H16.0958V9.41105Z" fill="white" /></g><defs><clipPath id="HTML5_Clip0_4"><rect width="32" height="32" fill="white" /></clipPath></defs></svg></a>
|
||||
<a href="https://nodejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nodejs.org"></a>
|
||||
<a href="https://vuejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/vuejs.org"></a>
|
||||
<a href="https://nuxtjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nuxtjs.org"></a>
|
||||
<a href="https://nextjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nextjs.org"></a>
|
||||
<a href="https://reactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/reactjs.org"></a>
|
||||
<a href="https://preactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/preactjs.org"></a>
|
||||
<a href="https://gatsbyjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/gatsbyjs.org"></a>
|
||||
<a href="https://svelte.dev"><img style="width:40px;height:40px" src="https://icon.horse/icon/svelte.dev"></a>
|
||||
<a href="https://php.net"><img style="width:40px;height:40px" src="https://icon.horse/icon/php.net"></a>
|
||||
<a href="https://laravel.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/laravel.com"></a>
|
||||
<a href="https://python.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/python.org"></a>
|
||||
<a href="https://deno.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/deno.com"></a>
|
||||
<a href="https://docker.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/docker.com"></a>
|
||||
|
||||
If you have a new build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
||||
|
||||
### Databases
|
||||
|
||||
One-click database is ready to be used internally or shared over the internet:
|
||||
|
||||
<a href="https://mongodb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mongodb.org"></a>
|
||||
<a href="https://mariadb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mariadb.org"></a>
|
||||
<a href="https://mysql.com"><svg style="width:40px;height:40px" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 25.6 25.6" ><path d="M179.076 94.886c-3.568-.1-6.336.268-8.656 1.25-.668.27-1.74.27-1.828 1.116.357.355.4.936.713 1.428.535.893 1.473 2.096 2.32 2.72l2.855 2.053c1.74 1.07 3.703 1.695 5.398 2.766.982.625 1.963 1.428 2.945 2.098.5.357.803.938 1.428 1.16v-.135c-.312-.4-.402-.98-.713-1.428l-1.34-1.293c-1.293-1.74-2.9-3.258-4.64-4.506-1.428-.982-4.55-2.32-5.13-3.97l-.088-.1c.98-.1 2.14-.447 3.078-.715 1.518-.4 2.9-.312 4.46-.713l2.143-.625v-.4c-.803-.803-1.383-1.874-2.23-2.632-2.275-1.963-4.775-3.882-7.363-5.488-1.383-.892-3.168-1.473-4.64-2.23-.537-.268-1.428-.402-1.74-.848-.805-.98-1.25-2.275-1.83-3.436l-3.658-7.763c-.803-1.74-1.295-3.48-2.275-5.086-4.596-7.585-9.594-12.18-17.268-16.687-1.65-.937-3.613-1.34-5.7-1.83l-3.346-.18c-.715-.312-1.428-1.16-2.053-1.562-2.543-1.606-9.102-5.086-10.977-.5-1.205 2.9 1.785 5.755 2.8 7.228.76 1.026 1.74 2.186 2.277 3.346.3.758.4 1.562.713 2.365.713 1.963 1.383 4.15 2.32 5.98.5.937 1.025 1.92 1.65 2.767.357.5.982.714 1.115 1.517-.625.893-.668 2.23-1.025 3.347-1.607 5.042-.982 11.288 1.293 15 .715 1.115 2.4 3.57 4.686 2.632 2.008-.803 1.56-3.346 2.14-5.577.135-.535.045-.892.312-1.25v.1l1.83 3.703c1.383 2.186 3.793 4.462 5.8 5.98 1.07.803 1.918 2.187 3.256 2.677v-.135h-.088c-.268-.4-.67-.58-1.027-.892-.803-.803-1.695-1.785-2.32-2.677-1.873-2.498-3.523-5.265-4.996-8.12-.715-1.383-1.34-2.9-1.918-4.283-.27-.536-.27-1.34-.715-1.606-.67.98-1.65 1.83-2.143 3.034-.848 1.918-.936 4.283-1.248 6.737-.18.045-.1 0-.18.1-1.426-.356-1.918-1.83-2.453-3.078-1.338-3.168-1.562-8.254-.402-11.913.312-.937 1.652-3.882 1.117-4.774-.27-.848-1.16-1.338-1.652-2.008-.58-.848-1.203-1.918-1.605-2.855-1.07-2.5-1.605-5.265-2.766-7.764-.537-1.16-1.473-2.365-2.232-3.435-.848-1.205-1.783-2.053-2.453-3.48-.223-.5-.535-1.294-.178-1.83.088-.357.268-.5.623-.58.58-.5 2.232.134 2.812.4 1.65.67 3.033 1.294 4.416 2.23.625.446 1.295 1.294 2.098 1.518h.938c1.428.312 3.033.1 4.37.5 2.365.76 4.506 1.874 6.426 3.08 5.844 3.703 10.664 8.968 13.92 15.26.535 1.026.758 1.963 1.25 3.034.938 2.187 2.098 4.417 3.033 6.56.938 2.097 1.83 4.24 3.168 5.98.67.937 3.346 1.427 4.55 1.918.893.4 2.275.76 3.08 1.25 1.516.937 3.033 2.008 4.46 3.034.713.534 2.945 1.65 3.078 2.54zm-45.5-38.772a7.09 7.09 0 0 0-1.828.223v.1h.088c.357.714.982 1.205 1.428 1.83l1.027 2.142.088-.1c.625-.446.938-1.16.938-2.23-.268-.312-.312-.625-.535-.937-.268-.446-.848-.67-1.206-1.026z" transform="matrix(.390229 0 0 .38781 -46.300037 -16.856717)" fill-rule="evenodd" fill="#00678c" /></svg></a>
|
||||
<a href="https://postgresql.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/postgresql.org"></a>
|
||||
<a href="https://couchdb.apache.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/couchdb.apache.org"></a>
|
||||
<a href="https://redis.io"><svg style="width:40px;height:40px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><defs ><path id="a" d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" /><path id="b" d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z" /></defs ><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)" ><use fill="#a41e11" xlink:href="#a" /><path d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z" fill="#d82c20" /><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" fill="#a41e11" /><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff" ><path d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z" /><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g ><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z" fill="#ad2115" /></g ></svg ></a>
|
||||
|
||||
If you have a new database you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
||||
|
||||
|
||||
### Services
|
||||
|
||||
You quickly need to host a self-hostable, open-source service? You can do it with a few clicks!
|
||||
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
||||
- [Ghost](https://ghost.org)
|
||||
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
||||
- [NocoDB](https://nocodb.com)
|
||||
- [VSCode Server](https://github.com/cdr/code-server)
|
||||
- [MinIO](https://min.io)
|
||||
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
|
||||
- [LanguageTool](https://languagetool.org)
|
||||
- [n8n](https://n8n.io)
|
||||
- [Uptime Kuma](https://github.com/louislam/uptime-kuma)
|
||||
- [MeiliSearch](https://github.com/meilisearch/meilisearch)
|
||||
- [Umami](https://github.com/mikecao/umami)
|
||||
- [Fider](https://fider.io)
|
||||
- [Hasura](https://hasura.io)
|
||||
|
||||
|
||||
If you have a new service you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
||||
|
||||
## Migration from v1
|
||||
|
||||
A fresh installation is necessary. v2 and v3 are not compatible with v1.
|
||||
|
||||
## Support
|
||||
|
||||
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
||||
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
|
||||
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
||||
- Discord: [Invitation](https://discord.gg/xhBCC7eGKw)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text.
|
||||
[](https://star-history.com/#coollabsio/coolify&Date)
|
||||
|
||||
44
app/Actions/Application/StopApplication.php
Normal file
44
app/Actions/Application/StopApplication.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopApplication
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Application $application)
|
||||
{
|
||||
$server = $application->destination->server;
|
||||
if ($server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
47
app/Actions/CoolifyTask/PrepareCoolifyTask.php
Normal file
47
app/Actions/CoolifyTask/PrepareCoolifyTask.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CoolifyTask;
|
||||
|
||||
use App\Data\CoolifyTaskArgs;
|
||||
use App\Jobs\CoolifyTask;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
/**
|
||||
* The initial step to run a `CoolifyTask`: a remote SSH process
|
||||
* with monitoring/tracking/trace feature. Such thing is made
|
||||
* possible using an Activity model and some attributes.
|
||||
*/
|
||||
class PrepareCoolifyTask
|
||||
{
|
||||
protected Activity $activity;
|
||||
protected CoolifyTaskArgs $remoteProcessArgs;
|
||||
|
||||
public function __construct(CoolifyTaskArgs $remoteProcessArgs)
|
||||
{
|
||||
$this->remoteProcessArgs = $remoteProcessArgs;
|
||||
|
||||
if ($remoteProcessArgs->model) {
|
||||
$properties = $remoteProcessArgs->toArray();
|
||||
unset($properties['model']);
|
||||
|
||||
$this->activity = activity()
|
||||
->withProperties($properties)
|
||||
->performedOn($remoteProcessArgs->model)
|
||||
->event($remoteProcessArgs->type)
|
||||
->log("[]");
|
||||
} else {
|
||||
$this->activity = activity()
|
||||
->withProperties($remoteProcessArgs->toArray())
|
||||
->event($remoteProcessArgs->type)
|
||||
->log("[]");
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(): Activity
|
||||
{
|
||||
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish);
|
||||
dispatch($job);
|
||||
$this->activity->refresh();
|
||||
return $this->activity;
|
||||
}
|
||||
}
|
||||
194
app/Actions/CoolifyTask/RunRemoteProcess.php
Normal file
194
app/Actions/CoolifyTask/RunRemoteProcess.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CoolifyTask;
|
||||
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Process\ProcessResult;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class RunRemoteProcess
|
||||
{
|
||||
public Activity $activity;
|
||||
|
||||
public bool $hide_from_output;
|
||||
|
||||
public bool $ignore_errors;
|
||||
|
||||
public $call_event_on_finish = null;
|
||||
|
||||
protected $time_start;
|
||||
|
||||
protected $current_time;
|
||||
|
||||
protected $last_write_at = 0;
|
||||
|
||||
protected $throttle_interval_ms = 200;
|
||||
|
||||
protected int $counter = 1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
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) {
|
||||
throw new \RuntimeException('Incompatible Activity to run a remote command.');
|
||||
}
|
||||
|
||||
$this->activity = $activity;
|
||||
$this->hide_from_output = $hide_from_output;
|
||||
$this->ignore_errors = $ignore_errors;
|
||||
$this->call_event_on_finish = $call_event_on_finish;
|
||||
}
|
||||
|
||||
public static function decodeOutput(?Activity $activity = null): string
|
||||
{
|
||||
if (is_null($activity)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
$decoded = json_decode(
|
||||
data_get($activity, 'description'),
|
||||
associative: true,
|
||||
flags: JSON_THROW_ON_ERROR
|
||||
);
|
||||
} catch (\JsonException $exception) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return collect($decoded)
|
||||
->sortBy(fn ($i) => $i['order'])
|
||||
->map(fn ($i) => $i['output'])
|
||||
->implode("");
|
||||
}
|
||||
|
||||
public function __invoke(): ProcessResult
|
||||
{
|
||||
$this->time_start = hrtime(true);
|
||||
|
||||
$status = ProcessStatus::IN_PROGRESS;
|
||||
$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) {
|
||||
$status = ProcessStatus::ERROR;
|
||||
} else {
|
||||
if ($processResult->exitCode() == 0) {
|
||||
$status = ProcessStatus::FINISHED;
|
||||
}
|
||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
||||
$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([
|
||||
'exitCode' => $processResult->exitCode(),
|
||||
'stdout' => $processResult->output(),
|
||||
'stderr' => $processResult->errorOutput(),
|
||||
'status' => $status->value,
|
||||
]);
|
||||
$this->activity->save();
|
||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
||||
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;
|
||||
}
|
||||
|
||||
protected function getCommand(): string
|
||||
{
|
||||
$server_uuid = $this->activity->getExtraProperty('server_uuid');
|
||||
$command = $this->activity->getExtraProperty('command');
|
||||
$server = Server::whereUuid($server_uuid)->firstOrFail();
|
||||
|
||||
return generateSshCommand($server, $command);
|
||||
}
|
||||
|
||||
protected function handleOutput(string $type, string $output)
|
||||
{
|
||||
if ($this->hide_from_output) {
|
||||
return;
|
||||
}
|
||||
$this->current_time = $this->elapsedTime();
|
||||
$this->activity->description = $this->encodeOutput($type, $output);
|
||||
if ($this->isAfterLastThrottle()) {
|
||||
// Let's write to database.
|
||||
DB::transaction(function () {
|
||||
$this->activity->save();
|
||||
$this->last_write_at = $this->current_time;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected function elapsedTime(): int
|
||||
{
|
||||
$timeMs = (hrtime(true) - $this->time_start) / 1_000_000;
|
||||
|
||||
return intval($timeMs);
|
||||
}
|
||||
|
||||
public function encodeOutput($type, $output)
|
||||
{
|
||||
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
$outputStack[] = [
|
||||
'type' => $type,
|
||||
'output' => $output,
|
||||
'timestamp' => hrtime(true),
|
||||
'batch' => ApplicationDeploymentJob::$batch_counter,
|
||||
'order' => $this->getLatestCounter(),
|
||||
];
|
||||
|
||||
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
protected function getLatestCounter(): int
|
||||
{
|
||||
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
if ($description === null || count($description) === 0) {
|
||||
return 1;
|
||||
}
|
||||
return end($description)['order'] + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if it's time to write again to database.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAfterLastThrottle()
|
||||
{
|
||||
// If DB was never written, then we immediately decide we have to write.
|
||||
if ($this->last_write_at === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ($this->current_time - $this->throttle_interval_ms) > $this->last_write_at;
|
||||
}
|
||||
}
|
||||
136
app/Actions/Database/StartDatabaseProxy.php
Normal file
136
app/Actions/Database/StartDatabaseProxy.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
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\StandaloneRedis;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class StartDatabaseProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||
{
|
||||
$internalPort = null;
|
||||
$type = $database->getMorphClass();
|
||||
$network = data_get($database, 'destination.network');
|
||||
$server = data_get($database, 'destination.server');
|
||||
$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;
|
||||
}
|
||||
$configuration_dir = database_proxy_dir($database->uuid);
|
||||
$nginxconf = <<<EOF
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
stream {
|
||||
server {
|
||||
listen $database->public_port;
|
||||
proxy_pass $containerName:$internalPort;
|
||||
}
|
||||
}
|
||||
EOF;
|
||||
$dockerfile = <<< EOF
|
||||
FROM nginx:stable-alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
EOF;
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$proxyContainerName => [
|
||||
'build' => [
|
||||
'context' => $configuration_dir,
|
||||
'dockerfile' => 'Dockerfile',
|
||||
],
|
||||
'image' => "nginx:stable-alpine",
|
||||
'container_name' => $proxyContainerName,
|
||||
'restart' => RESTART_MODE,
|
||||
'ports' => [
|
||||
"$database->public_port:$database->public_port",
|
||||
],
|
||||
'networks' => [
|
||||
$network,
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
'stat /etc/nginx/nginx.conf || exit 1',
|
||||
],
|
||||
'interval' => '5s',
|
||||
'timeout' => '5s',
|
||||
'retries' => 3,
|
||||
'start_period' => '1s'
|
||||
],
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$network => [
|
||||
'external' => true,
|
||||
'name' => $network,
|
||||
'attachable' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
|
||||
$nginxconf_base64 = base64_encode($nginxconf);
|
||||
$dockerfile_base64 = base64_encode($dockerfile);
|
||||
instant_remote_process([
|
||||
"mkdir -p $configuration_dir",
|
||||
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
||||
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||
"docker compose --project-directory {$configuration_dir} pull",
|
||||
"docker compose --project-directory {$configuration_dir} up --build -d",
|
||||
], $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->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->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->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}";
|
||||
}
|
||||
}
|
||||
210
app/Actions/Database/StartPostgresql.php
Normal file
210
app/Actions/Database/StartPostgresql.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartPostgresql
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public StandalonePostgresql $database;
|
||||
public array $commands = [];
|
||||
public array $init_scripts = [];
|
||||
public string $configuration_dir;
|
||||
|
||||
public function handle(StandalonePostgresql $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",
|
||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->generate_init_scripts();
|
||||
$this->add_custom_conf();
|
||||
|
||||
$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-SHELL",
|
||||
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1"
|
||||
],
|
||||
'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()) {
|
||||
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) {
|
||||
$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 (count($this->init_scripts) > 0) {
|
||||
foreach ($this->init_scripts as $init_script) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $init_script,
|
||||
'target' => '/docker-entrypoint-initdb.d/' . basename($init_script),
|
||||
'read_only' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
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_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();
|
||||
ray('Generate Environment Variables')->green();
|
||||
ray($this->database->runtime_environment_variables)->green();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||
$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()) {
|
||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
||||
}
|
||||
return $environment_variables->all();
|
||||
}
|
||||
|
||||
private function generate_init_scripts()
|
||||
{
|
||||
if (is_null($this->database->init_scripts) || count($this->database->init_scripts) === 0) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->database->init_scripts as $init_script) {
|
||||
$filename = data_get($init_script, 'filename');
|
||||
$content = data_get($init_script, 'content');
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $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}";
|
||||
}
|
||||
}
|
||||
172
app/Actions/Database/StartRedis.php
Normal file
172
app/Actions/Database/StartRedis.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartRedis
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public StandaloneRedis $database;
|
||||
public array $commands = [];
|
||||
public string $configuration_dir;
|
||||
|
||||
|
||||
public function handle(StandaloneRedis $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_redis();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
'command' => $startCommand,
|
||||
'container_name' => $container_name,
|
||||
'environment' => $environment_variables,
|
||||
'restart' => RESTART_MODE,
|
||||
'networks' => [
|
||||
$this->database->destination->network,
|
||||
],
|
||||
'labels' => [
|
||||
'coolify.managed' => 'true',
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
'redis-cli',
|
||||
'ping'
|
||||
],
|
||||
'interval' => '5s',
|
||||
'timeout' => '5s',
|
||||
'retries' => 10,
|
||||
'start_period' => '5s'
|
||||
],
|
||||
'mem_limit' => $this->database->limits_memory,
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => (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->redis_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/redis.conf',
|
||||
'target' => '/usr/local/etc/redis/redis.conf',
|
||||
'read_only' => true,
|
||||
];
|
||||
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
|
||||
}
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||
$readme = generate_readme_file($this->database->name, now());
|
||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||
$this->commands[] = "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->value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||
}
|
||||
|
||||
return $environment_variables->all();
|
||||
}
|
||||
private function add_custom_redis()
|
||||
{
|
||||
if (is_null($this->database->redis_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'redis.conf';
|
||||
$content = $this->database->redis_conf;
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||
}
|
||||
}
|
||||
30
app/Actions/Database/StopDatabase.php
Normal file
30
app/Actions/Database/StopDatabase.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
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\StandaloneRedis;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopDatabase
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
instant_remote_process(
|
||||
["docker rm -f {$database->uuid}"],
|
||||
$server
|
||||
);
|
||||
if ($database->is_public) {
|
||||
StopDatabaseProxy::run($database);
|
||||
}
|
||||
// TODO: make notification for services
|
||||
// $database->environment->project->team->notify(new StatusChanged($database));
|
||||
}
|
||||
}
|
||||
27
app/Actions/Database/StopDatabaseProxy.php
Normal file
27
app/Actions/Database/StopDatabaseProxy.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
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\StandaloneRedis;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopDatabaseProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||
{
|
||||
$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->save();
|
||||
}
|
||||
}
|
||||
71
app/Actions/Fortify/CreateNewUser.php
Normal file
71
app/Actions/Fortify/CreateNewUser.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||
|
||||
class CreateNewUser implements CreatesNewUsers
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and create a newly registered user.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function create(array $input): User
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
if (!$settings->is_registration_enabled) {
|
||||
abort(403);
|
||||
}
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique(User::class),
|
||||
],
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
if (User::count() == 0) {
|
||||
// If this is the first user, make them the root user
|
||||
// Team is already created in the database/seeders/ProductionSeeder.php
|
||||
$user = User::create([
|
||||
'id' => 0,
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => Hash::make($input['password']),
|
||||
]);
|
||||
$team = $user->teams()->first();
|
||||
|
||||
// Disable registration after first user is created
|
||||
$settings = InstanceSettings::get();
|
||||
$settings->is_registration_enabled = false;
|
||||
$settings->save();
|
||||
} else {
|
||||
$user = User::create([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => Hash::make($input['password']),
|
||||
]);
|
||||
$team = $user->teams()->first();
|
||||
if (isCloud()) {
|
||||
$user->sendVerificationEmail();
|
||||
} else {
|
||||
$user->markEmailAsVerified();
|
||||
}
|
||||
}
|
||||
// Set session variable
|
||||
session(['currentTeam' => $user->currentTeam = $team]);
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
18
app/Actions/Fortify/PasswordValidationRules.php
Normal file
18
app/Actions/Fortify/PasswordValidationRules.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use Laravel\Fortify\Rules\Password;
|
||||
|
||||
trait PasswordValidationRules
|
||||
{
|
||||
/**
|
||||
* Get the validation rules used to validate passwords.
|
||||
*
|
||||
* @return array<int, \Illuminate\Contracts\Validation\Rule|array|string>
|
||||
*/
|
||||
protected function passwordRules(): array
|
||||
{
|
||||
return ['required', 'string', new Password, 'confirmed'];
|
||||
}
|
||||
}
|
||||
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||
|
||||
class ResetUserPassword implements ResetsUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and reset the user's forgotten password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function reset(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
32
app/Actions/Fortify/UpdateUserPassword.php
Normal file
32
app/Actions/Fortify/UpdateUserPassword.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
|
||||
|
||||
class UpdateUserPassword implements UpdatesUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and update the user's password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'current_password' => ['required', 'string', 'current_password:web'],
|
||||
'password' => $this->passwordRules(),
|
||||
], [
|
||||
'current_password.current_password' => __('The provided password does not match your current password.'),
|
||||
])->validateWithBag('updatePassword');
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
60
app/Actions/Fortify/UpdateUserProfileInformation.php
Normal file
60
app/Actions/Fortify/UpdateUserProfileInformation.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
|
||||
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
{
|
||||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users')->ignore($user->id),
|
||||
],
|
||||
])->validateWithBag('updateProfileInformation');
|
||||
|
||||
if (
|
||||
$input['email'] !== $user->email &&
|
||||
$user instanceof MustVerifyEmail
|
||||
) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given verified user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
protected function updateVerifiedUser(User $user, array $input): void
|
||||
{
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'email_verified_at' => null,
|
||||
])->save();
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
}
|
||||
}
|
||||
69
app/Actions/License/CheckResaleLicense.php
Normal file
69
app/Actions/License/CheckResaleLicense.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\License;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
|
||||
class CheckResaleLicense
|
||||
{
|
||||
use AsAction;
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
if (isDev()) {
|
||||
$settings->update([
|
||||
'is_resale_license_active' => true,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
// if (!$settings->resale_license) {
|
||||
// return;
|
||||
// }
|
||||
$base_url = config('coolify.license_url');
|
||||
$instance_id = config('app.id');
|
||||
|
||||
ray("Checking license key against $base_url/lemon/validate");
|
||||
$data = Http::withHeaders([
|
||||
'Accept' => 'application/json',
|
||||
])->get("$base_url/lemon/validate", [
|
||||
'license_key' => $settings->resale_license,
|
||||
'instance_id' => $instance_id,
|
||||
])->json();
|
||||
if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') {
|
||||
ray('Valid & active license key');
|
||||
$settings->update([
|
||||
'is_resale_license_active' => true,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
$data = Http::withHeaders([
|
||||
'Accept' => 'application/json',
|
||||
])->get("$base_url/lemon/activate", [
|
||||
'license_key' => $settings->resale_license,
|
||||
'instance_id' => $instance_id,
|
||||
])->json();
|
||||
if (data_get($data, 'activated') === true) {
|
||||
ray('Activated license key');
|
||||
$settings->update([
|
||||
'is_resale_license_active' => true,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
if (data_get($data, 'license_key.status') === 'active') {
|
||||
throw new \Exception('Invalid license key.');
|
||||
}
|
||||
throw new \Exception('Cannot activate license key.');
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
$settings->update([
|
||||
'resale_license' => null,
|
||||
'is_resale_license_active' => false,
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
app/Actions/Proxy/CheckConfiguration.php
Normal file
28
app/Actions/Proxy/CheckConfiguration.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CheckConfiguration
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server, bool $reset = false)
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_configuration = instant_remote_process([
|
||||
"mkdir -p $proxy_path",
|
||||
"cat $proxy_path/docker-compose.yml",
|
||||
], $server, false);
|
||||
|
||||
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
|
||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
||||
}
|
||||
if (!$proxy_configuration || is_null($proxy_configuration)) {
|
||||
throw new \Exception("Could not generate proxy configuration");
|
||||
}
|
||||
return $proxy_configuration;
|
||||
}
|
||||
}
|
||||
61
app/Actions/Proxy/CheckProxy.php
Normal file
61
app/Actions/Proxy/CheckProxy.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class CheckProxy
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server, $fromUI = false)
|
||||
{
|
||||
if (!$server->isProxyShouldRun()) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
$status = getContainerStatus($server, 'coolify-proxy_traefik');
|
||||
$server->proxy->set('status', $status);
|
||||
$server->save();
|
||||
if ($status === 'running') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} 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');
|
||||
$connection443 = @fsockopen($ip, '443');
|
||||
$port80 = is_resource($connection80) && fclose($connection80);
|
||||
$port443 = is_resource($connection443) && fclose($connection443);
|
||||
if ($port80) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($port443) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
app/Actions/Proxy/SaveConfiguration.php
Normal file
29
app/Actions/Proxy/SaveConfiguration.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class SaveConfiguration
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server, ?string $proxy_settings = null)
|
||||
{
|
||||
if (is_null($proxy_settings)) {
|
||||
$proxy_settings = CheckConfiguration::run($server, true);
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||
|
||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
|
||||
return instant_remote_process([
|
||||
"mkdir -p $proxy_path",
|
||||
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
|
||||
], $server);
|
||||
}
|
||||
}
|
||||
66
app/Actions/Proxy/StartProxy.php
Normal file
66
app/Actions/Proxy/StartProxy.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class StartProxy
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server, bool $async = true): string|Activity
|
||||
{
|
||||
try {
|
||||
|
||||
$proxyType = $server->proxyType();
|
||||
$commands = collect([]);
|
||||
$proxy_path = get_proxy_path();
|
||||
$configuration = CheckConfiguration::run($server);
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
}
|
||||
SaveConfiguration::run($server, $configuration);
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
if ($server->isSwarm()) {
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Starting coolify-proxy.'",
|
||||
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
||||
"echo 'Proxy started successfully.'"
|
||||
]);
|
||||
} else {
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
"echo 'Stopping existing coolify-proxy.'",
|
||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||
"echo 'Starting coolify-proxy.'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo 'Proxy started successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
}
|
||||
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
} else {
|
||||
instant_remote_process($commands, $server);
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->proxy->set('type', $proxyType);
|
||||
$server->save();
|
||||
return 'OK';
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
96
app/Actions/Server/InstallDocker.php
Normal file
96
app/Actions/Server/InstallDocker.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
|
||||
class InstallDocker
|
||||
{
|
||||
use AsAction;
|
||||
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';
|
||||
$config = base64_encode('{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "10m",
|
||||
"max-file": "3"
|
||||
}
|
||||
}');
|
||||
$found = StandaloneDocker::where('server_id', $server->id);
|
||||
if ($found->count() == 0 && $server->id) {
|
||||
StandaloneDocker::create([
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
}
|
||||
$command = collect([]);
|
||||
if (isDev() && $server->id === 0) {
|
||||
$command = $command->merge([
|
||||
"echo 'Installing Prerequisites...'",
|
||||
"sleep 1",
|
||||
"echo 'Installing Docker Engine...'",
|
||||
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||
"sleep 4",
|
||||
"echo 'Restarting Docker Engine...'",
|
||||
"ls -l /tmp"
|
||||
]);
|
||||
} else {
|
||||
if ($supported_os_type->contains('debian')) {
|
||||
$command = $command->merge([
|
||||
"echo 'Installing Prerequisites...'",
|
||||
"command -v jq >/dev/null || apt-get update -y",
|
||||
"command -v jq >/dev/null || apt install -y curl wget git jq",
|
||||
|
||||
]);
|
||||
} 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",
|
||||
"echo '{$config}' | base64 -d > /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",
|
||||
"echo 'Restarting Docker Engine...'",
|
||||
"systemctl enable docker >/dev/null 2>&1 || true",
|
||||
"systemctl restart docker",
|
||||
]);
|
||||
if ($server->isSwarm()) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
app/Actions/Server/UpdateCoolify.php
Normal file
74
app/Actions/Server/UpdateCoolify.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
|
||||
class UpdateCoolify
|
||||
{
|
||||
use AsAction;
|
||||
public ?Server $server = null;
|
||||
public ?string $latestVersion = null;
|
||||
public ?string $currentVersion = null;
|
||||
|
||||
public function handle(bool $force)
|
||||
{
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
ray('Running InstanceAutoUpdateJob');
|
||||
$this->server = Server::find(0);
|
||||
if (!$this->server) {
|
||||
return;
|
||||
}
|
||||
CleanupDocker::run($this->server, false);
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
|
||||
if ($settings->next_channel) {
|
||||
ray('next channel enabled');
|
||||
$this->latestVersion = 'next';
|
||||
}
|
||||
if ($force) {
|
||||
$this->update();
|
||||
} else {
|
||||
if (!$settings->is_auto_update_enabled) {
|
||||
return 'Auto update is disabled';
|
||||
}
|
||||
if ($this->latestVersion === $this->currentVersion) {
|
||||
return 'Already on latest version';
|
||||
}
|
||||
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
|
||||
return 'Latest version is lower than current version?!';
|
||||
}
|
||||
$this->update();
|
||||
}
|
||||
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
|
||||
} catch (\Throwable $e) {
|
||||
ray('InstanceAutoUpdateJob failed');
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function update()
|
||||
{
|
||||
if (isDev()) {
|
||||
ray("Running update on local docker container. Updating to $this->latestVersion");
|
||||
remote_process([
|
||||
"sleep 10"
|
||||
], $this->server);
|
||||
ray('Update done');
|
||||
return;
|
||||
} else {
|
||||
ray('Running update on production server');
|
||||
remote_process([
|
||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
||||
], $this->server);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
app/Actions/Service/DeleteService.php
Normal file
42
app/Actions/Service/DeleteService.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
|
||||
class DeleteService
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
StopService::run($service);
|
||||
$server = data_get($service, 'server');
|
||||
$storagesToDelete = collect([]);
|
||||
|
||||
$service->environment_variables()->delete();
|
||||
$commands = [];
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
$storages = $application->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
}
|
||||
$application->forceDelete();
|
||||
}
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
}
|
||||
$database->forceDelete();
|
||||
}
|
||||
foreach ($storagesToDelete as $storage) {
|
||||
$commands[] = "docker volume rm -f $storage->name";
|
||||
}
|
||||
$commands[] = "docker rm -f $service->uuid";
|
||||
|
||||
instant_remote_process($commands, $server, false);
|
||||
|
||||
$service->forceDelete();
|
||||
}
|
||||
}
|
||||
35
app/Actions/Service/StartService.php
Normal file
35
app/Actions/Service/StartService.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class StartService
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
ray('Starting service: ' . $service->name);
|
||||
$network = $service->destination->network;
|
||||
$service->saveComposeConfigs();
|
||||
$commands[] = "cd " . $service->workdir();
|
||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||
$commands[] = "echo 'Creating Docker network.'";
|
||||
$commands[] = "docker network 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 'Pulling images.'";
|
||||
$commands[] = "docker compose pull";
|
||||
$commands[] = "echo 'Starting containers.'";
|
||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
|
||||
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||
$compose = data_get($service, 'docker_compose', []);
|
||||
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
||||
}
|
||||
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
||||
return $activity;
|
||||
}
|
||||
}
|
||||
29
app/Actions/Service/StopService.php
Normal file
29
app/Actions/Service/StopService.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
|
||||
class StopService
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
ray('Stopping service: ' . $service->name);
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
||||
// TODO: make notification for databases
|
||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/Cloud.php
Normal file
33
app/Console/Commands/Cloud.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Cloud extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'cloud:unused-servers';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Get Unused Servers from Cloud';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
|
||||
$this->info($server->name);
|
||||
});
|
||||
}
|
||||
}
|
||||
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', '.']);
|
||||
}
|
||||
}
|
||||
280
app/Console/Commands/Emails.php
Normal file
280
app/Console/Commands/Emails.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use App\Notifications\Application\DeploymentFailed;
|
||||
use App\Notifications\Application\DeploymentSuccess;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use App\Notifications\Database\BackupFailed;
|
||||
use App\Notifications\Database\BackupSuccess;
|
||||
use App\Notifications\Test;
|
||||
use App\Notifications\TransactionalEmails\InvitationLink;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Mail;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\select;
|
||||
use function Laravel\Prompts\text;
|
||||
|
||||
class Emails extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'emails';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send out test / prod emails';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
private ?MailMessage $mail = null;
|
||||
private ?string $email = null;
|
||||
public function handle()
|
||||
{
|
||||
$type = select(
|
||||
'Which Email should be sent?',
|
||||
options: [
|
||||
'updates' => 'Send Update Email to all users',
|
||||
'emails-test' => 'Test',
|
||||
'application-deployment-success' => 'Application - Deployment Success',
|
||||
'application-deployment-failed' => 'Application - Deployment Failed',
|
||||
'application-status-changed' => 'Application - Status Changed',
|
||||
'backup-success' => 'Database - Backup Success',
|
||||
'backup-failed' => 'Database - Backup Failed',
|
||||
// 'invitation-link' => 'Invitation Link',
|
||||
'waitlist-invitation-link' => 'Waitlist Invitation Link',
|
||||
'waitlist-confirmation' => 'Waitlist Confirmation',
|
||||
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
|
||||
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
|
||||
],
|
||||
);
|
||||
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
|
||||
if (!in_array($type, $emailsGathered)) {
|
||||
$this->email = text('Email Address to send to');
|
||||
}
|
||||
set_transanctional_email_settings();
|
||||
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->subject("Test Email");
|
||||
switch ($type) {
|
||||
case 'updates':
|
||||
$teams = Team::all();
|
||||
if (!$teams || $teams->isEmpty()) {
|
||||
echo 'No teams found.' . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
$emails = [];
|
||||
foreach ($teams as $team) {
|
||||
foreach ($team->members as $member) {
|
||||
if ($member->email && $member->marketing_emails) {
|
||||
$emails[] = $member->email;
|
||||
}
|
||||
}
|
||||
}
|
||||
$emails = array_unique($emails);
|
||||
$this->info("Sending to " . count($emails) . " emails.");
|
||||
foreach ($emails as $email) {
|
||||
$this->info($email);
|
||||
}
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if ($confirmed) {
|
||||
foreach ($emails as $email) {
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->subject('One-click Services, Docker Compose support');
|
||||
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
||||
'token' => encrypt($email),
|
||||
]);
|
||||
$this->mail->view('emails.updates',["unsubscribeUrl" => $unsubscribeUrl]);
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'emails-test':
|
||||
$this->mail = (new Test())->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-deployment-success':
|
||||
$application = Application::all()->first();
|
||||
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-deployment-failed':
|
||||
$application = Application::all()->first();
|
||||
$preview = ApplicationPreview::all()->first();
|
||||
if (!$preview) {
|
||||
$preview = ApplicationPreview::create([
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => 1,
|
||||
'pull_request_html_url' => 'http://example.com',
|
||||
'fqdn' => $application->fqdn,
|
||||
]);
|
||||
}
|
||||
$this->mail = (new DeploymentFailed($application, 'test'))->toMail();
|
||||
$this->sendEmail();
|
||||
$this->mail = (new DeploymentFailed($application, 'test', $preview))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-status-changed':
|
||||
$application = Application::all()->first();
|
||||
$this->mail = (new StatusChanged($application))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'backup-failed':
|
||||
$backup = ScheduledDatabaseBackup::all()->first();
|
||||
$db = StandalonePostgresql::all()->first();
|
||||
if (!$backup) {
|
||||
$backup = ScheduledDatabaseBackup::create([
|
||||
'enabled' => true,
|
||||
'frequency' => 'daily',
|
||||
'save_s3' => false,
|
||||
'database_id' => $db->id,
|
||||
'database_type' => $db->getMorphClass(),
|
||||
'team_id' => 0,
|
||||
]);
|
||||
}
|
||||
$output = 'Because of an error, the backup of the database ' . $db->name . ' failed.';
|
||||
$this->mail = (new BackupFailed($backup, $db, $output))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'backup-success':
|
||||
$backup = ScheduledDatabaseBackup::all()->first();
|
||||
$db = StandalonePostgresql::all()->first();
|
||||
if (!$backup) {
|
||||
$backup = ScheduledDatabaseBackup::create([
|
||||
'enabled' => true,
|
||||
'frequency' => 'daily',
|
||||
'save_s3' => false,
|
||||
'database_id' => $db->id,
|
||||
'database_type' => $db->getMorphClass(),
|
||||
'team_id' => 0,
|
||||
]);
|
||||
}
|
||||
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
// case 'invitation-link':
|
||||
// $user = User::all()->first();
|
||||
// $invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||
// if (!$invitation) {
|
||||
// $invitation = TeamInvitation::create([
|
||||
// 'uuid' => Str::uuid(),
|
||||
// 'email' => $user->email,
|
||||
// 'team_id' => 1,
|
||||
// 'link' => 'http://example.com',
|
||||
// ]);
|
||||
// }
|
||||
// $this->mail = (new InvitationLink($user))->toMail();
|
||||
// $this->sendEmail();
|
||||
// break;
|
||||
case 'waitlist-invitation-link':
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.waitlist-invitation', [
|
||||
'loginLink' => 'https://coolify.io',
|
||||
]);
|
||||
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'waitlist-confirmation':
|
||||
$found = Waitlist::where('email', $this->email)->first();
|
||||
if ($found) {
|
||||
SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid);
|
||||
} else {
|
||||
throw new Exception('Waitlist not found');
|
||||
}
|
||||
|
||||
break;
|
||||
case 'realusers-before-trial':
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.before-trial-conversion');
|
||||
$this->mail->subject('Trial period has been added for all subscription plans.');
|
||||
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
||||
if (!$teams || $teams->isEmpty()) {
|
||||
echo 'No teams found.' . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
$emails = [];
|
||||
foreach ($teams as $team) {
|
||||
foreach ($team->members as $member) {
|
||||
if ($member->email) {
|
||||
$emails[] = $member->email;
|
||||
}
|
||||
}
|
||||
}
|
||||
$emails = array_unique($emails);
|
||||
$this->info("Sending to " . count($emails) . " emails.");
|
||||
foreach ($emails as $email) {
|
||||
$this->info($email);
|
||||
}
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if ($confirmed) {
|
||||
foreach ($emails as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'realusers-server-lost-connection':
|
||||
$serverId = text('Server Id');
|
||||
$server = Server::find($serverId);
|
||||
if (!$server) {
|
||||
throw new Exception('Server not found');
|
||||
}
|
||||
$admins = [];
|
||||
$members = $server->team->members;
|
||||
foreach ($members as $member) {
|
||||
if ($member->isAdmin()) {
|
||||
$admins[] = $member->email;
|
||||
}
|
||||
}
|
||||
$this->info('Sending to ' . count($admins) . ' admins.');
|
||||
foreach ($admins as $admin) {
|
||||
$this->info($admin);
|
||||
}
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.server-lost-connection', [
|
||||
'name' => $server->name,
|
||||
]);
|
||||
$this->mail->subject('Action required: Server ' . $server->name . ' lost connection.');
|
||||
foreach ($admins as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
private function sendEmail(string $email = null)
|
||||
{
|
||||
if ($email) {
|
||||
$this->email = $email;
|
||||
}
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->to($this->email)
|
||||
->subject($this->mail->subject)
|
||||
->html((string)$this->mail->render())
|
||||
);
|
||||
$this->info("Email sent to $this->email successfully. 📧");
|
||||
}
|
||||
}
|
||||
408
app/Console/Commands/Init.php
Normal file
408
app/Console/Commands/Init.php
Normal file
@@ -0,0 +1,408 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Jobs\CleanupHelperContainersJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
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;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class Init extends Command
|
||||
{
|
||||
protected $signature = 'app:init {--cleanup}';
|
||||
protected $description = 'Cleanup instance related stuffs';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->alive();
|
||||
$cleanup = $this->option('cleanup');
|
||||
if ($cleanup) {
|
||||
echo "Running cleanup\n";
|
||||
$this->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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
{
|
||||
// Cleanup any failed deployments
|
||||
|
||||
try {
|
||||
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', '==', ApplicationDeploymentStatus::QUEUED)->get();
|
||||
foreach ($halted_deployments as $deployment) {
|
||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$deployment->save();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
77
app/Console/Commands/NotifyDemo.php
Normal file
77
app/Console/Commands/NotifyDemo.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use function Termwind\ask;
|
||||
use function Termwind\render;
|
||||
use function Termwind\style;
|
||||
|
||||
class NotifyDemo extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:demo-notify {channel?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send a demo notification, to a given channel. Run to see options.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$channel = $this->argument('channel');
|
||||
|
||||
if (blank($channel)) {
|
||||
$this->showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
ray($channel);
|
||||
}
|
||||
|
||||
private function showHelp()
|
||||
{
|
||||
style('coolify')->color('#9333EA');
|
||||
style('title-box')->apply('mt-1 px-2 py-1 bg-coolify');
|
||||
|
||||
render(
|
||||
<<<'HTML'
|
||||
<div>
|
||||
<div class="title-box">
|
||||
Coolify
|
||||
</div>
|
||||
<p class="mt-1 ml-1 ">
|
||||
Demo Notify <strong class="text-coolify">=></strong> Send a demo notification to a given channel.
|
||||
</p>
|
||||
<p class="px-1 mt-1 ml-1 bg-coolify">
|
||||
php artisan app:demo-notify {channel}
|
||||
</p>
|
||||
<div class="my-1">
|
||||
<div class="text-yellow-500"> Channels: </div>
|
||||
<ul class="text-coolify">
|
||||
<li>email</li>
|
||||
<li>slack</li>
|
||||
<li>discord</li>
|
||||
<li>telegram</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
HTML
|
||||
);
|
||||
|
||||
ask(<<<'HTML'
|
||||
<div class="mr-1">
|
||||
In which manner you wish a <strong class="text-coolify">coolified</strong> notification?
|
||||
</div>
|
||||
HTML, ['email', 'slack', 'discord', 'telegram']);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
app/Console/Commands/RootResetPassword.php
Normal file
49
app/Console/Commands/RootResetPassword.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
use function Laravel\Prompts\password;
|
||||
|
||||
class RootResetPassword extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'root:reset-password';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Reset Root Password';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
$this->info('You are about to reset the root password.');
|
||||
$password = password('Give me a new password for root user: ');
|
||||
$passwordAgain = password('Again');
|
||||
if ($password != $passwordAgain) {
|
||||
$this->error('Passwords do not match.');
|
||||
return;
|
||||
}
|
||||
$this->info('Updating root password...');
|
||||
try {
|
||||
User::find(0)->update(['password' => Hash::make($password)]);
|
||||
$this->info('Root password updated successfully.');
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Failed to update root password.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
147
app/Console/Commands/ServicesDelete.php
Normal file
147
app/Console/Commands/ServicesDelete.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\DeleteResourceJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\multiselect;
|
||||
use function Laravel\Prompts\select;
|
||||
|
||||
class ServicesDelete extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'services:delete';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Delete a service from the database';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$resource = select(
|
||||
'What service do you want to delete?',
|
||||
['Application', 'Database', 'Service', 'Server'],
|
||||
);
|
||||
if ($resource === 'Application') {
|
||||
$this->deleteApplication();
|
||||
} elseif ($resource === 'Database') {
|
||||
$this->deleteDatabase();
|
||||
} elseif ($resource === 'Service') {
|
||||
$this->deleteService();
|
||||
} elseif ($resource === 'Server') {
|
||||
$this->deleteServer();
|
||||
}
|
||||
}
|
||||
private function deleteServer()
|
||||
{
|
||||
$servers = Server::all();
|
||||
if ($servers->count() === 0) {
|
||||
$this->error('There are no applications to delete.');
|
||||
return;
|
||||
}
|
||||
$serversToDelete = multiselect(
|
||||
label: 'What server do you want to delete?',
|
||||
options: $servers->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
|
||||
foreach ($serversToDelete as $server) {
|
||||
$toDelete = $servers->where('id', $server)->first();
|
||||
if ($toDelete) {
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
break;
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
private function deleteApplication()
|
||||
{
|
||||
$applications = Application::all();
|
||||
if ($applications->count() === 0) {
|
||||
$this->error('There are no applications to delete.');
|
||||
return;
|
||||
}
|
||||
$applicationsToDelete = multiselect(
|
||||
'What application do you want to delete?',
|
||||
$applications->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
|
||||
foreach ($applicationsToDelete as $application) {
|
||||
$toDelete = $applications->where('id', $application)->first();
|
||||
if ($toDelete) {
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
||||
if (!$confirmed) {
|
||||
break;
|
||||
}
|
||||
DeleteResourceJob::dispatch($toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
private function deleteDatabase()
|
||||
{
|
||||
$databases = StandalonePostgresql::all();
|
||||
if ($databases->count() === 0) {
|
||||
$this->error('There are no databases to delete.');
|
||||
return;
|
||||
}
|
||||
$databasesToDelete = multiselect(
|
||||
'What database do you want to delete?',
|
||||
$databases->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
|
||||
foreach ($databasesToDelete as $database) {
|
||||
$toDelete = $databases->where('id', $database)->first();
|
||||
if ($toDelete) {
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
DeleteResourceJob::dispatch($toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
private function deleteService()
|
||||
{
|
||||
$services = Service::all();
|
||||
if ($services->count() === 0) {
|
||||
$this->error('There are no services to delete.');
|
||||
return;
|
||||
}
|
||||
$servicesToDelete = multiselect(
|
||||
'What service do you want to delete?',
|
||||
$services->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
|
||||
foreach ($servicesToDelete as $service) {
|
||||
$toDelete = $services->where('id', $service)->first();
|
||||
if ($toDelete) {
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
DeleteResourceJob::dispatch($toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
123
app/Console/Commands/SyncBunny.php
Normal file
123
app/Console/Commands/SyncBunny.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Http\Client\Pool;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
|
||||
class SyncBunny extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sync:bunny {--templates} {--release}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sync files to BunnyCDN';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$that = $this;
|
||||
$only_template = $this->option('templates');
|
||||
$only_version = $this->option('release');
|
||||
$bunny_cdn = "https://cdn.coollabs.io";
|
||||
$bunny_cdn_path = "coolify";
|
||||
$bunny_cdn_storage_name = "coolcdn";
|
||||
|
||||
$parent_dir = realpath(dirname(__FILE__) . '/../../..');
|
||||
|
||||
$compose_file = "docker-compose.yml";
|
||||
$compose_file_prod = "docker-compose.prod.yml";
|
||||
$install_script = "install.sh";
|
||||
$upgrade_script = "upgrade.sh";
|
||||
$production_env = ".env.production";
|
||||
$service_template = "service-templates.json";
|
||||
|
||||
$versions = "versions.json";
|
||||
|
||||
PendingRequest::macro('storage', function ($fileName) use($that) {
|
||||
$headers = [
|
||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/octet-stream'
|
||||
];
|
||||
$fileStream = fopen($fileName, "r");
|
||||
$file = fread($fileStream, filesize($fileName));
|
||||
$that->info('Uploading: ' . $fileName);
|
||||
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
|
||||
});
|
||||
PendingRequest::macro('purge', function ($url) use ($that) {
|
||||
$headers = [
|
||||
'AccessKey' => env('BUNNY_API_KEY'),
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
$that->info('Purging: ' . $url);
|
||||
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
|
||||
"url" => $url,
|
||||
"async" => false
|
||||
]);
|
||||
});
|
||||
try {
|
||||
if (!$only_template && !$only_version) {
|
||||
$this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
|
||||
}
|
||||
if ($only_template) {
|
||||
$this->info('About to sync service-templates.json to BunnyCDN.');
|
||||
}
|
||||
if ($only_version) {
|
||||
$this->info('About to sync versions.json to BunnyCDN.');
|
||||
}
|
||||
$confirmed = confirm('Are you sure you want to sync?');
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
if ($only_template) {
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
||||
]);
|
||||
$this->info('Service template uploaded & purged...');
|
||||
return;
|
||||
}
|
||||
if ($only_version) {
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||
]);
|
||||
$this->info('versions.json uploaded & purged...');
|
||||
return;
|
||||
}
|
||||
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||
$pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
|
||||
$pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
|
||||
$pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
|
||||
]);
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file_prod"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$production_env"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
|
||||
]);
|
||||
$this->info("All files uploaded & purged...");
|
||||
} catch (\Throwable $e) {
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
105
app/Console/Commands/WaitlistInvite.php
Normal file
105
app/Console/Commands/WaitlistInvite.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class WaitlistInvite extends Command
|
||||
{
|
||||
public Waitlist|User|null $next_patient = null;
|
||||
public string|null $password = null;
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'waitlist:invite {--people=1} {--only-email} {email?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send invitation to the next user (or by email) in the waitlist';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$people = $this->option('people');
|
||||
for ($i = 0; $i < $people; $i++) {
|
||||
$this->main();
|
||||
}
|
||||
}
|
||||
private function main() {
|
||||
if ($this->argument('email')) {
|
||||
if ($this->option('only-email')) {
|
||||
$this->next_patient = User::whereEmail($this->argument('email'))->first();
|
||||
$this->password = Str::password();
|
||||
$this->next_patient->update([
|
||||
'password' => Hash::make($this->password),
|
||||
'force_password_reset' => true,
|
||||
]);
|
||||
} else {
|
||||
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
||||
}
|
||||
if (!$this->next_patient) {
|
||||
$this->error("{$this->argument('email')} not found in the waitlist.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
||||
}
|
||||
if ($this->next_patient) {
|
||||
if ($this->option('only-email')) {
|
||||
$this->send_email();
|
||||
return;
|
||||
}
|
||||
$this->register_user();
|
||||
$this->remove_from_waitlist();
|
||||
$this->send_email();
|
||||
} else {
|
||||
$this->info('No verified user found in the waitlist. 👀');
|
||||
}
|
||||
}
|
||||
private function register_user()
|
||||
{
|
||||
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
||||
if (!$already_registered) {
|
||||
$this->password = Str::password();
|
||||
User::create([
|
||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
||||
'email' => $this->next_patient->email,
|
||||
'password' => Hash::make($this->password),
|
||||
'force_password_reset' => true,
|
||||
]);
|
||||
$this->info("User registered ({$this->next_patient->email}) successfully. 🎉");
|
||||
} else {
|
||||
throw new \Exception('User already registered');
|
||||
}
|
||||
}
|
||||
private function remove_from_waitlist()
|
||||
{
|
||||
$this->next_patient->delete();
|
||||
$this->info("User removed from waitlist successfully.");
|
||||
}
|
||||
private function send_email()
|
||||
{
|
||||
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||
$loginLink = route('auth.link', ['token' => $token]);
|
||||
$mail = new MailMessage();
|
||||
$mail->view('emails.waitlist-invitation', [
|
||||
'loginLink' => $loginLink,
|
||||
]);
|
||||
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
send_user_an_email($mail, $this->next_patient->email);
|
||||
$this->info("Email sent successfully. 📧");
|
||||
}
|
||||
}
|
||||
146
app/Console/Kernel.php
Normal file
146
app/Console/Kernel.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\CheckLogDrainContainerJob;
|
||||
use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\ScheduledTaskJob;
|
||||
use App\Jobs\InstanceAutoUpdateJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Jobs\PullHelperImageJob;
|
||||
use App\Jobs\ServerStatusJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
if (isDev()) {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
// Server Jobs
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->pull_helper_image($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
} else {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
|
||||
// Server Jobs
|
||||
$this->instance_auto_update($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->pull_helper_image($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
}
|
||||
}
|
||||
private function pull_helper_image($schedule)
|
||||
{
|
||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function check_resources($schedule)
|
||||
{
|
||||
if (isCloud()) {
|
||||
$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);
|
||||
} else {
|
||||
$servers = Server::all()->where('ip', '!=', '1.2.3.4');
|
||||
$containerServers = $servers->where('settings.is_swarm_worker', 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) {
|
||||
$schedule->job(new ServerStatusJob($server))->everyFiveMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function instance_auto_update($schedule)
|
||||
{
|
||||
if (isDev()) {
|
||||
return;
|
||||
}
|
||||
$settings = InstanceSettings::get();
|
||||
if ($settings->is_auto_update_enabled) {
|
||||
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function check_scheduled_backups($schedule)
|
||||
{
|
||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||
if ($scheduled_backups->isEmpty()) {
|
||||
ray('no scheduled backups');
|
||||
return;
|
||||
}
|
||||
foreach ($scheduled_backups as $scheduled_backup) {
|
||||
if (!$scheduled_backup->enabled) {
|
||||
continue;
|
||||
}
|
||||
if (is_null(data_get($scheduled_backup, 'database'))) {
|
||||
ray('database not found');
|
||||
$scheduled_backup->delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||
}
|
||||
$schedule->job(new DatabaseBackupJob(
|
||||
backup: $scheduled_backup
|
||||
))->cron($scheduled_backup->frequency)->onOneServer();
|
||||
}
|
||||
}
|
||||
|
||||
private function check_scheduled_tasks($schedule) {
|
||||
$scheduled_tasks = ScheduledTask::all();
|
||||
if ($scheduled_tasks->isEmpty()) {
|
||||
ray('no scheduled tasks');
|
||||
return;
|
||||
}
|
||||
foreach ($scheduled_tasks as $scheduled_task) {
|
||||
$service = $scheduled_task->service()->get();
|
||||
$application = $scheduled_task->application()->get();
|
||||
|
||||
if (!$application && !$service) {
|
||||
ray('application/service attached to scheduled task does not exist');
|
||||
$scheduled_task->delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||
}
|
||||
$schedule->job(new ScheduledTaskJob(
|
||||
task: $scheduled_task
|
||||
))->cron($scheduled_task->frequency)->onOneServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
||||
29
app/Data/CoolifyTaskArgs.php
Normal file
29
app/Data/CoolifyTaskArgs.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use App\Enums\ProcessStatus;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
/**
|
||||
* The parameters to execute a CoolifyTask, organized in a DTO.
|
||||
*/
|
||||
class CoolifyTaskArgs extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $server_uuid,
|
||||
public string $command,
|
||||
public string $type,
|
||||
public ?string $type_uuid = null,
|
||||
public ?int $process_id = null,
|
||||
public ?Model $model = null,
|
||||
public ?string $status = null ,
|
||||
public bool $ignore_errors = false,
|
||||
public $call_event_on_finish = null,
|
||||
) {
|
||||
if(is_null($status)){
|
||||
$this->status = ProcessStatus::QUEUED->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
app/Data/ServerMetadata.php
Normal file
16
app/Data/ServerMetadata.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class ServerMetadata extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public ?ProxyTypes $type,
|
||||
public ?ProxyStatus $status
|
||||
) {
|
||||
}
|
||||
}
|
||||
8
app/Enums/ActivityTypes.php
Normal file
8
app/Enums/ActivityTypes.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ActivityTypes: string
|
||||
{
|
||||
case INLINE = 'inline';
|
||||
}
|
||||
12
app/Enums/ApplicationDeploymentStatus.php
Normal file
12
app/Enums/ApplicationDeploymentStatus.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ApplicationDeploymentStatus: string
|
||||
{
|
||||
case QUEUED = 'queued';
|
||||
case IN_PROGRESS = 'in_progress';
|
||||
case FINISHED = 'finished';
|
||||
case FAILED = 'failed';
|
||||
case CANCELLED_BY_USER = 'cancelled-by-user';
|
||||
}
|
||||
13
app/Enums/ProcessStatus.php
Normal file
13
app/Enums/ProcessStatus.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ProcessStatus: string
|
||||
{
|
||||
case QUEUED = 'queued';
|
||||
case IN_PROGRESS = 'in_progress';
|
||||
case FINISHED = 'finished';
|
||||
case ERROR = 'error';
|
||||
case KILLED = 'killed';
|
||||
case CANCELLED = 'cancelled';
|
||||
}
|
||||
17
app/Enums/ProxyTypes.php
Normal file
17
app/Enums/ProxyTypes.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ProxyTypes: string
|
||||
{
|
||||
case NONE = 'NONE';
|
||||
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
||||
case NGINX = 'NGINX';
|
||||
case CADDY = 'CADDY';
|
||||
}
|
||||
|
||||
enum ProxyStatus: string
|
||||
{
|
||||
case EXITED = 'exited';
|
||||
case RUNNING = 'running';
|
||||
}
|
||||
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/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}"),
|
||||
];
|
||||
}
|
||||
}
|
||||
84
app/Exceptions/Handler.php
Normal file
84
app/Exceptions/Handler.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use RuntimeException;
|
||||
use Sentry\Laravel\Integration;
|
||||
use Sentry\State\Scope;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* A list of exception types with their corresponding custom log levels.
|
||||
*
|
||||
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
|
||||
*/
|
||||
protected $levels = [
|
||||
//
|
||||
];
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array<int, class-string<\Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
ProcessException::class
|
||||
];
|
||||
/**
|
||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
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.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
if (isDev()) {
|
||||
// return;
|
||||
}
|
||||
if ($e instanceof RuntimeException) {
|
||||
return;
|
||||
}
|
||||
$this->settings = InstanceSettings::get();
|
||||
if ($this->settings->do_not_track) {
|
||||
return;
|
||||
}
|
||||
app('sentry')->configureScope(
|
||||
function (Scope $scope) {
|
||||
$email = auth()?->user() ? auth()->user()->email : 'guest';
|
||||
$instanceAdmin = User::find(0)->email ?? 'admin@localhost';
|
||||
$scope->setUser(
|
||||
[
|
||||
'email' => $email,
|
||||
'instanceAdmin' => $instanceAdmin
|
||||
]
|
||||
);
|
||||
}
|
||||
);
|
||||
ray('reporting to sentry');
|
||||
Integration::captureUnhandledException($e);
|
||||
});
|
||||
}
|
||||
}
|
||||
10
app/Exceptions/ProcessException.php
Normal file
10
app/Exceptions/ProcessException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ProcessException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
151
app/Http/Controllers/Controller.php
Normal file
151
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\TestEvent;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Fortify\Fortify;
|
||||
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse;
|
||||
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
public function realtime_test() {
|
||||
if (auth()->user()?->currentTeam()->id !== 0) {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
TestEvent::dispatch();
|
||||
return 'Look at your other tab.';
|
||||
}
|
||||
public function verify() {
|
||||
return view('auth.verify-email');
|
||||
}
|
||||
public function email_verify(EmailVerificationRequest $request) {
|
||||
$request->fulfill();
|
||||
$name = request()->user()?->name;
|
||||
send_internal_notification("User {$name} verified their email address.");
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
public function forgot_password(Request $request) {
|
||||
if (is_transactional_emails_active()) {
|
||||
$arrayOfRequest = $request->only(Fortify::email());
|
||||
$request->merge([
|
||||
'email' => Str::lower($arrayOfRequest['email']),
|
||||
]);
|
||||
$type = set_transanctional_email_settings();
|
||||
if (!$type) {
|
||||
return response()->json(['message' => 'Transactional emails are not active'], 400);
|
||||
}
|
||||
$request->validate([Fortify::email() => 'required|email']);
|
||||
$status = Password::broker(config('fortify.passwords'))->sendResetLink(
|
||||
$request->only(Fortify::email())
|
||||
);
|
||||
if ($status == Password::RESET_LINK_SENT) {
|
||||
return app(SuccessfulPasswordResetLinkRequestResponse::class, ['status' => $status]);
|
||||
}
|
||||
if ($status == Password::RESET_THROTTLED) {
|
||||
return response('Already requested a password reset in the past minutes.', 400);
|
||||
}
|
||||
return app(FailedPasswordResetLinkRequestResponse::class, ['status' => $status]);
|
||||
}
|
||||
return response()->json(['message' => 'Transactional emails are not active'], 400);
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
$token = request()->get('token');
|
||||
if ($token) {
|
||||
$decrypted = Crypt::decryptString($token);
|
||||
$email = Str::of($decrypted)->before('@@@');
|
||||
$password = Str::of($decrypted)->after('@@@');
|
||||
$user = User::whereEmail($email)->first();
|
||||
if (!$user) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
if (Hash::check($password, $user->password)) {
|
||||
$invitation = TeamInvitation::whereEmail($email);
|
||||
if ($invitation->exists()) {
|
||||
$team = $invitation->first()->team;
|
||||
$user->teams()->attach($team->id, ['role' => $invitation->first()->role]);
|
||||
$invitation->delete();
|
||||
} else {
|
||||
$team = $user->teams()->first();
|
||||
}
|
||||
if (is_null(data_get($user, 'email_verified_at'))) {
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
}
|
||||
Auth::login($user);
|
||||
session(['currentTeam' => $team]);
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||
}
|
||||
|
||||
public function accept_invitation()
|
||||
{
|
||||
try {
|
||||
$resetPassword = request()->query('reset-password');
|
||||
$invitationUuid = request()->route('uuid');
|
||||
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
|
||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||
$invitationValid = $invitation->isValid();
|
||||
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]);
|
||||
$invitation->delete();
|
||||
if (auth()->user()?->id !== $user->id) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
refreshSession($invitation->team);
|
||||
return redirect()->route('team.index');
|
||||
} else {
|
||||
abort(401);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function revoke_invitation()
|
||||
{
|
||||
try {
|
||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||
if (is_null(auth()->user())) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
if (auth()->user()->id !== $user->id) {
|
||||
abort(401);
|
||||
}
|
||||
$invitation->delete();
|
||||
return redirect()->route('team.index');
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
app/Http/Controllers/MagicController.php
Normal file
80
app/Http/Controllers/MagicController.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
|
||||
class MagicController extends Controller
|
||||
{
|
||||
public function servers()
|
||||
{
|
||||
return response()->json([
|
||||
'servers' => Server::isUsable()->get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function destinations()
|
||||
{
|
||||
return response()->json([
|
||||
'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name')
|
||||
]);
|
||||
}
|
||||
|
||||
public function projects()
|
||||
{
|
||||
return response()->json([
|
||||
'projects' => Project::ownedByCurrentTeam()->get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function environments()
|
||||
{
|
||||
$project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return response()->json([
|
||||
'environments' => []
|
||||
]);
|
||||
}
|
||||
return response()->json([
|
||||
'environments' => $project->environments
|
||||
]);
|
||||
}
|
||||
|
||||
public function newProject()
|
||||
{
|
||||
$project = Project::firstOrCreate(
|
||||
['name' => request()->query('name') ?? generate_random_name()],
|
||||
['team_id' => currentTeam()->id]
|
||||
);
|
||||
return response()->json([
|
||||
'project_uuid' => $project->uuid
|
||||
]);
|
||||
}
|
||||
|
||||
public function newEnvironment()
|
||||
{
|
||||
$environment = Environment::firstOrCreate(
|
||||
['name' => request()->query('name') ?? generate_random_name()],
|
||||
['project_id' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->firstOrFail()->id]
|
||||
);
|
||||
return response()->json([
|
||||
'environment_name' => $environment->name,
|
||||
]);
|
||||
}
|
||||
|
||||
public function newTeam()
|
||||
{
|
||||
$team = Team::create(
|
||||
[
|
||||
'name' => request()->query('name') ?? generate_random_name(),
|
||||
'personal_team' => false,
|
||||
],
|
||||
);
|
||||
auth()->user()->teams()->attach($team, ['role' => 'admin']);
|
||||
refreshSession();
|
||||
return redirect(request()->header('Referer'));
|
||||
}
|
||||
}
|
||||
71
app/Http/Kernel.php
Normal file
71
app/Http/Kernel.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array<int, class-string|string>
|
||||
*/
|
||||
protected $middleware = [
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array<string, array<int, class-string|string>>
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\CheckForcePasswordReset::class,
|
||||
\App\Http\Middleware\DecideWhatToDoWithUser::class,
|
||||
|
||||
],
|
||||
|
||||
'api' => [
|
||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's middleware aliases.
|
||||
*
|
||||
* Aliases may be used to conveniently assign middleware to routes and groups.
|
||||
*
|
||||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected $middlewareAliases = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
];
|
||||
}
|
||||
17
app/Http/Middleware/Authenticate.php
Normal file
17
app/Http/Middleware/Authenticate.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Authenticate extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*/
|
||||
protected function redirectTo(Request $request): ?string
|
||||
{
|
||||
return $request->expectsJson() ? null : route('login');
|
||||
}
|
||||
}
|
||||
35
app/Http/Middleware/CheckForcePasswordReset.php
Normal file
35
app/Http/Middleware/CheckForcePasswordReset.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckForcePasswordReset
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (auth()->user()) {
|
||||
if ($request->path() === 'auth/link') {
|
||||
auth()->logout();
|
||||
request()->session()->invalidate();
|
||||
request()->session()->regenerateToken();
|
||||
return $next($request);
|
||||
}
|
||||
$force_password_reset = auth()->user()->force_password_reset;
|
||||
if ($force_password_reset) {
|
||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect()->route('auth.force-password-reset');
|
||||
}
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
52
app/Http/Middleware/DecideWhatToDoWithUser.php
Normal file
52
app/Http/Middleware/DecideWhatToDoWithUser.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DecideWhatToDoWithUser
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if(auth()?->user()?->currentTeam()){
|
||||
refreshSession(auth()->user()->currentTeam());
|
||||
}
|
||||
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
return redirect()->route('boarding');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
if (!auth()->user()->hasVerifiedEmail()) {
|
||||
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect()->route('verify.email');
|
||||
}
|
||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect()->route('subscription.index');
|
||||
}
|
||||
}
|
||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect()->route('boarding');
|
||||
}
|
||||
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
if (isSubscriptionActive() && $request->path() === 'subscription') {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
17
app/Http/Middleware/EncryptCookies.php
Normal file
17
app/Http/Middleware/EncryptCookies.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
17
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
17
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
|
||||
|
||||
class PreventRequestsDuringMaintenance extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be reachable while maintenance mode is enabled.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
29
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
29
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||
{
|
||||
$guards = empty($guards) ? [null] : $guards;
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
19
app/Http/Middleware/TrimStrings.php
Normal file
19
app/Http/Middleware/TrimStrings.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
}
|
||||
20
app/Http/Middleware/TrustHosts.php
Normal file
20
app/Http/Middleware/TrustHosts.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||
|
||||
class TrustHosts extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
public function hosts(): array
|
||||
{
|
||||
return [
|
||||
$this->allSubdomainsOfApplicationUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
28
app/Http/Middleware/TrustProxies.php
Normal file
28
app/Http/Middleware/TrustProxies.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array<int, string>|string|null
|
||||
*/
|
||||
protected $proxies = '*';
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
}
|
||||
22
app/Http/Middleware/ValidateSignature.php
Normal file
22
app/Http/Middleware/ValidateSignature.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||
|
||||
class ValidateSignature extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
// 'fbclid',
|
||||
// 'utm_campaign',
|
||||
// 'utm_content',
|
||||
// 'utm_medium',
|
||||
// 'utm_source',
|
||||
// 'utm_term',
|
||||
];
|
||||
}
|
||||
17
app/Http/Middleware/VerifyCsrfToken.php
Normal file
17
app/Http/Middleware/VerifyCsrfToken.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||
|
||||
class VerifyCsrfToken extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
1547
app/Jobs/ApplicationDeploymentJob.php
Normal file
1547
app/Jobs/ApplicationDeploymentJob.php
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user