Compare commits
1613 Commits
Author | SHA1 | Date | |
---|---|---|---|
361e0a0430 | |||
|
8901d63677 | ||
|
eb6d9d31a0 | ||
|
1ad3c204ea | ||
|
2cae6807dc | ||
|
ddaaf50319 | ||
|
c2f6689c9a | ||
|
576486a869 | ||
|
19a4ecff3d | ||
|
11999df81a | ||
|
c901b57333 | ||
|
ed24abea0f | ||
|
0d7bae65b9 | ||
|
b7fd1dbbfc | ||
|
e3ef28ec1a | ||
|
cad0a6d81f | ||
|
60c120a1e5 | ||
|
6d8138009e | ||
|
21f646cec5 | ||
|
e33a793c5d | ||
|
75e2a9f4c2 | ||
|
9e7591987c | ||
|
0d8d8b1247 | ||
|
2cc3e11ac7 | ||
|
befd46b3dd | ||
|
6a268ce65b | ||
|
e2bcfbadf8 | ||
|
d8f398eef9 | ||
|
e013bc4db9 | ||
|
0ca2d41e57 | ||
|
7d9281aef2 | ||
|
f15e0a1682 | ||
|
00dee2fa62 | ||
|
a82f360154 | ||
|
cf2642d6d4 | ||
|
a3a0a69992 | ||
|
2b07be51f3 | ||
|
8a2d3e9674 | ||
|
c834d436ef | ||
|
f25bf71d2c | ||
|
e7b3274b24 | ||
|
afbfe4619b | ||
|
fcb310b15b | ||
|
d625c7500c | ||
|
9a171118e9 | ||
|
560ec9d840 | ||
|
5206c7611c | ||
|
1aba61824d | ||
|
0efc847be8 | ||
|
71a7246940 | ||
|
e3b1e8a837 | ||
|
9563ef4d51 | ||
|
7391719705 | ||
|
c385dbe439 | ||
|
e86a523070 | ||
|
7b28ee32bc | ||
|
1b076cfffc | ||
|
f76d45b826 | ||
|
6cc86a3c82 | ||
|
f1e5b61970 | ||
|
65380646f7 | ||
|
189a8347ed | ||
|
e96e8f6fec | ||
|
38299ab507 | ||
|
f134171855 | ||
|
320204d854 | ||
|
b68199a482 | ||
|
6f4436fd5e | ||
|
0d8cc19698 | ||
|
a3a1ff69e1 | ||
|
5df7e23aa4 | ||
|
35d9691b3f | ||
|
465f649641 | ||
|
d909e7d802 | ||
|
06db6b8502 | ||
|
12261b9082 | ||
|
583ec432e8 | ||
|
8ffbccf7db | ||
|
439fe43a04 | ||
|
7fd9a799b5 | ||
|
7459ab22d1 | ||
|
133a68f3eb | ||
|
3224110583 | ||
|
810488b115 | ||
|
b2276147ad | ||
|
6c1293c63e | ||
|
526d675272 | ||
|
14b2442d40 | ||
|
d6d194d414 | ||
|
0e99f97855 | ||
|
14dc933219 | ||
|
9497f123b4 | ||
|
6feb439d0a | ||
|
e4ca5ee5f5 | ||
|
f21c12f39b | ||
|
6c1e50a914 | ||
|
da064def7a | ||
|
3af3fa5773 | ||
|
005bd55fb2 | ||
|
82a5b4c55d | ||
|
b8e95b2099 | ||
|
8ea50dc029 | ||
|
ec191af874 | ||
|
d98c742aff | ||
|
2529496594 | ||
|
1b6114036a | ||
|
b33fb6c39a | ||
|
0a6826af58 | ||
|
1c7034ff78 | ||
|
7e11698c55 | ||
|
1c4eb31d59 | ||
|
b4b6a4294a | ||
|
4c031a7c05 | ||
|
997a262b6c | ||
|
c0e88df3e8 | ||
|
85e1cbad53 | ||
|
c37398af72 | ||
|
19cfe4e514 | ||
|
23a1b1925f | ||
|
1fb8d1e14c | ||
|
804c70b575 | ||
|
548c4a4c64 | ||
|
2978042162 | ||
|
4225ec7060 | ||
|
893339fc8e | ||
|
356e7b57d2 | ||
|
4ee1f1a507 | ||
|
7d64df60cd | ||
|
eb3a4ca157 | ||
|
a7b5157fa6 | ||
|
793e6d19eb | ||
|
674fa4d09c | ||
|
0089e86dd1 | ||
|
e1d802b507 | ||
|
9927b71af9 | ||
|
b1c0f105ab | ||
|
35cae1d4dc | ||
|
3dab3365e2 | ||
|
1bdc7c87ba | ||
|
cab8ad0ca0 | ||
|
43409f3ff0 | ||
|
a5dd4cab52 | ||
|
a815240f4e | ||
|
2a44e7c5bd | ||
|
28c7e439b1 | ||
|
4396c786b4 | ||
|
b67bb8595f | ||
|
bec47487dd | ||
|
b110d0c12b | ||
|
ae425475b4 | ||
|
dc6aee44b3 | ||
|
4ffea311e8 | ||
|
77a6a6e46a | ||
|
2278ba31e7 | ||
|
aaeec3d340 | ||
|
2cbe530b7e | ||
|
6ada6d145c | ||
|
0f55e83591 | ||
|
4017ea7b65 | ||
|
a85066c644 | ||
|
b08d38f339 | ||
|
d4f4632461 | ||
|
666aa041f4 | ||
|
1c565fd502 | ||
|
7de2b8cbd7 | ||
|
852e906736 | ||
|
5778466947 | ||
|
7006239b0d | ||
|
49d011574d | ||
|
046a358ae0 | ||
|
d23f5af957 | ||
|
20a3f4b200 | ||
|
73acda833e | ||
|
fa895db76e | ||
|
88f33be5b6 | ||
|
21612cccf7 | ||
|
39a7332343 | ||
|
21825876fb | ||
|
aaee887d3e | ||
|
cb44373eff | ||
|
4e6ea4f584 | ||
|
62a93d3e51 | ||
|
f60c281e80 | ||
|
43c40cdb09 | ||
|
c851262d81 | ||
|
91783ccc3e | ||
|
6ba3d5f86e | ||
|
a9a20755a9 | ||
|
d2693c1ac8 | ||
|
aaa6f434a9 | ||
|
314a3ac83f | ||
|
36e177479e | ||
|
cbeebed6c9 | ||
|
4b905dbfad | ||
|
6072e7efc7 | ||
|
19097c6692 | ||
|
d37f63c63c | ||
|
574bafd950 | ||
|
2b805f869a | ||
|
36c4be1d17 | ||
|
f2d82e16d6 | ||
|
c6658e1ac7 | ||
|
22a7d85e58 | ||
|
b3421b47b6 | ||
|
b5247f77ec | ||
|
e63e806572 | ||
|
62b84add36 | ||
|
858ae1266f | ||
|
3ae990aa40 | ||
|
deb4b16ae1 | ||
|
b37dc4c73e | ||
|
6b08100819 | ||
|
2c45e7146b | ||
|
7c4a722d72 | ||
|
f4bccefaba | ||
|
491bb93e95 | ||
|
f35700c9ee | ||
|
bd26aca3d9 | ||
|
71d24773b6 | ||
|
781bd29f40 | ||
|
fd4dd1edfa | ||
|
d2db26cb5e | ||
|
01977839f7 | ||
|
0116892b6b | ||
|
4d88873524 | ||
|
8e46c0186d | ||
|
1b0e589aab | ||
|
02ba149e26 | ||
|
2981aa876c | ||
|
82057e1f50 | ||
|
4ce36631e0 | ||
|
995324d6b3 | ||
|
c61ad9cd95 | ||
|
26f4bcc77e | ||
|
c2b2d06e47 | ||
|
cbcc7f6d88 | ||
|
d05e23264b | ||
|
db9faed184 | ||
|
e7feac848a | ||
|
33b965d9db | ||
|
6c33bd9c72 | ||
|
c72fd2fc9d | ||
|
906a3dc9b4 | ||
|
2d3a6a4528 | ||
|
01abc26316 | ||
|
2dfe43fc3c | ||
|
f71861300a | ||
|
52d7841334 | ||
|
9c821e2480 | ||
|
f8f0aa171c | ||
|
38d9999814 | ||
|
920305432b | ||
|
42fb8ab379 | ||
|
88ab385100 | ||
|
479a3540ec | ||
|
47f5a0de81 | ||
|
311c118834 | ||
|
0c40c0d795 | ||
|
25f0a8f0b7 | ||
|
f58a1a9ecf | ||
|
efa2ae5177 | ||
|
5e55c799ec | ||
|
46e61cb409 | ||
|
b24a489c77 | ||
|
65a618d019 | ||
|
4459c9f73d | ||
|
3c13f1ff61 | ||
|
c39d6dd407 | ||
|
1249b1ece9 | ||
|
da6f2da3d0 | ||
|
dbc235d84a | ||
|
b86924bc0e | ||
|
0fb8cf4241 | ||
|
30b7e831c0 | ||
|
f1b4ebcde2 | ||
|
e3c4ebb121 | ||
|
2dd17cfac5 | ||
|
93d04ef426 | ||
|
70bfd4dd8a | ||
|
ca917d9d21 | ||
|
be633f0560 | ||
|
613e980267 | ||
|
4fb37054df | ||
|
07508df8fd | ||
|
2a52fb5872 | ||
|
f45b3cab55 | ||
|
eb76d63117 | ||
|
0964c7a338 | ||
|
7af151d44e | ||
|
7028391e57 | ||
|
cbae0845e7 | ||
|
0e512962c6 | ||
|
ac694b855b | ||
|
490d45e788 | ||
|
b0863eb5ea | ||
|
ee199ed038 | ||
|
41268fa20b | ||
|
7474896368 | ||
|
54c4296a25 | ||
|
116f5afe3c | ||
|
c015c8f45d | ||
|
fe26b3d759 | ||
|
afee2d8ca8 | ||
|
e9158b7305 | ||
|
0f5690db85 | ||
|
3ebb35a5cd | ||
|
f557cd0933 | ||
|
063aa702b1 | ||
|
4f1070083a | ||
|
5e625f71c5 | ||
|
cc36a0ecd1 | ||
|
a849c25672 | ||
|
8b95b93c72 | ||
|
3d9557bc50 | ||
|
01f2f56be6 | ||
|
e658ed993a | ||
|
58fc897ea5 | ||
|
f8c5a35b56 | ||
|
2c92cc40e1 | ||
|
1266810c4d | ||
|
1de8657a56 | ||
|
408c24c700 | ||
|
3a1e5f7f0c | ||
|
8d85976ac0 | ||
|
fe7eaa594f | ||
|
5500a1edb3 | ||
|
eb27e34972 | ||
|
8335c299b8 | ||
|
feadc60b14 | ||
|
b59799dc2b | ||
|
f0b2f6eb00 | ||
|
c3fb126a0a | ||
|
f6708f6e47 | ||
|
d48b5a9079 | ||
|
99354f0d7d | ||
|
0fa8552aa3 | ||
|
4869c388b2 | ||
|
2adade0927 | ||
|
87b7337d9e | ||
|
351c9c1ad3 | ||
|
7759fe2cdf | ||
|
5d2651afc1 | ||
|
ce3b2de5e7 | ||
|
1782f59a96 | ||
|
3612096b56 | ||
|
97aa6139ea | ||
|
e5d915a7a9 | ||
|
5bc31c305c | ||
|
8c7590a249 | ||
|
d54d524cef | ||
|
a3d3ada500 | ||
|
ca43d197f9 | ||
|
73692a0c73 | ||
|
50191221b9 | ||
|
61236b45fb | ||
|
837545f4e7 | ||
|
31810477b2 | ||
|
4c652b5818 | ||
|
5201818f52 | ||
|
7e9e333d24 | ||
|
ab3b72bd1f | ||
|
f2c8a6bac5 | ||
|
758fab9976 | ||
|
3c2b985769 | ||
|
73bc07c7fb | ||
|
8298aa7124 | ||
|
92049cba92 | ||
|
83d8963051 | ||
|
bedcf6c058 | ||
|
f8b7c17efd | ||
|
76ba365325 | ||
|
a3255f3ab0 | ||
|
44af75613f | ||
|
df53e8beda | ||
|
003f97af24 | ||
|
c897aaafa7 | ||
|
76a0659335 | ||
|
4acdd8df12 | ||
|
cdabdb4558 | ||
|
f43fc1e376 | ||
|
70cd5d364c | ||
|
62aa807d0f | ||
|
5f3fed3c8f | ||
|
f95e879a58 | ||
|
34508a2fd1 | ||
|
cd85094113 | ||
|
72033279c2 | ||
|
9919224226 | ||
|
c6a1eac586 | ||
|
b657aa33fa | ||
|
834562190e | ||
|
453b28baf7 | ||
|
28522418ff | ||
|
18bab41605 | ||
|
c17079e045 | ||
|
f5cea7d9e3 | ||
|
c5083ea897 | ||
|
f607aa1233 | ||
|
db39458295 | ||
|
3d75e0773f | ||
|
bdcb467208 | ||
|
575a789d1d | ||
|
fcb3d71cb4 | ||
|
93c890ce41 | ||
|
2421f7c35c | ||
|
44920944db | ||
|
1b135be3c5 | ||
|
fff7ec9ba7 | ||
|
0468f255e7 | ||
|
5da5158b14 | ||
|
6493ce3fe0 | ||
|
078772495a | ||
|
c81ad5cd03 | ||
|
439bee1203 | ||
|
0eccbf64f4 | ||
|
5232ce6e52 | ||
|
ada278d8da | ||
|
24d5fef20f | ||
|
b7fd1b9f4c | ||
|
b16cd1ec5c | ||
|
278ec88dd0 | ||
|
b91699f317 | ||
|
105ed56dfc | ||
|
f9267a96ed | ||
|
1135f45dc9 | ||
|
b74a75f4c6 | ||
|
8a787f48b3 | ||
|
e956642982 | ||
|
d3d08168de | ||
|
919f56a292 | ||
|
b5be17c2d2 | ||
|
5221aa0ab7 | ||
|
49ad8b7cc1 | ||
|
fa99cbce4a | ||
|
07e55b2636 | ||
|
d8984c42b5 | ||
|
36f251e710 | ||
|
f3beacdc3f | ||
|
e023be1f25 | ||
|
d1a5f97f59 | ||
|
a43de75b42 | ||
|
5530f7263f | ||
|
d688244664 | ||
|
c8155c8a32 | ||
|
ce15f8f1dd | ||
|
23ed697b98 | ||
|
55854653b0 | ||
|
83983bbb32 | ||
|
768c917a0e | ||
|
039df94b86 | ||
|
f46cb7a670 | ||
|
9d05276f94 | ||
|
dc4916dc19 | ||
|
1d0a1ab16a | ||
|
1ae6106782 | ||
|
2f87deb10b | ||
|
65253ca54e | ||
|
af38d0cc07 | ||
|
5ec2cbdf19 | ||
|
f6f44d8e8f | ||
|
2b6fc16637 | ||
|
e0a2e3bd0c | ||
|
1ecd0307ed | ||
|
f10f3456d7 | ||
|
b17be37aee | ||
|
ddbef494e3 | ||
|
49d91c498e | ||
|
7cc4a21383 | ||
|
af464c2af7 | ||
|
ef1d06b8dc | ||
|
f1562ccfd5 | ||
|
bfe2fb6da8 | ||
|
6f23f6352d | ||
|
a47816f45d | ||
|
30ac392af4 | ||
|
e4ee149085 | ||
|
a521d8549a | ||
|
566faba6e3 | ||
|
e4e9de0a53 | ||
|
07ae971ae1 | ||
|
4f5102b2dc | ||
|
04d1915121 | ||
|
c35f6e926d | ||
|
dffcee6cf1 | ||
|
7485c1240b | ||
|
95d3ebdc2d | ||
|
6bb565ee67 | ||
|
19b38074a5 | ||
|
25649f578d | ||
|
7a63a17b66 | ||
|
3424cb1f29 | ||
|
5c8277ea1d | ||
|
9e3ffea22c | ||
|
9592076d45 | ||
|
2e01665340 | ||
|
bba0ef522c | ||
|
019cdd2b3a | ||
|
ce24352974 | ||
|
407b8de2b1 | ||
|
f332a73122 | ||
|
2335abac91 | ||
|
5cf6804615 | ||
|
58b04b5fc8 | ||
|
c58f468dc9 | ||
|
e922bc207a | ||
|
784cfb8fba | ||
|
b53bb44e42 | ||
|
e7e85456ea | ||
|
440baf6009 | ||
|
1876e80094 | ||
|
d20b3a5b8b | ||
|
6d9454b351 | ||
|
a138bb61bc | ||
|
85d313a791 | ||
|
de7380fb0c | ||
|
35bf4a111c | ||
|
d5be00d29d | ||
|
e63710dfb4 | ||
|
8b95b3c1bf | ||
|
b8ec3f5704 | ||
|
5dc79159b4 | ||
|
5368c49e68 | ||
|
8731d0416b | ||
|
d05151466c | ||
|
367a373904 | ||
|
533587ce4e | ||
|
db0ddee9f4 | ||
|
d5bad67e17 | ||
|
9e4c8c52d2 | ||
|
8ca84ee6f4 | ||
|
5ebbd769e8 | ||
|
47051127af | ||
|
7ad2e1ca05 | ||
|
7345ccbbee | ||
|
e282686f97 | ||
|
5524f80ea3 | ||
|
d86274cc37 | ||
|
41fb6a1fc9 | ||
|
8e9842df14 | ||
|
f4904047b5 | ||
|
4083f4db9f | ||
|
a125c0032b | ||
|
2e4fd1530c | ||
|
be08a2650c | ||
|
e08dc777df | ||
|
239f7fb35d | ||
|
1cb8354aca | ||
|
2465b4ffd7 | ||
|
4f73ea0879 | ||
|
f4eb17f616 | ||
|
b5ce738ba2 | ||
|
3155d4afdb | ||
|
f444696b84 | ||
|
bdde9b063c | ||
|
ef98bd107f | ||
|
dfb259d822 | ||
|
13c34fd26d | ||
|
665cd454ef | ||
|
b3bde5782a | ||
|
cc8f09f05e | ||
|
2beda08717 | ||
|
b455d153ae | ||
|
5fddf01820 | ||
|
c80434141d | ||
|
82e7348cf2 | ||
|
d1128c7a1e | ||
|
5674879e23 | ||
|
aae81313a6 | ||
|
4667f96b40 | ||
|
d6c2c7ef02 | ||
|
2af1ccd8b2 | ||
|
55e2e29696 | ||
|
b60f8df17a | ||
|
28c320ae97 | ||
|
00ed54799f | ||
|
fae77231a5 | ||
|
2083941361 | ||
|
5ec517e3bc | ||
|
780a3b1827 | ||
|
61aa086cb5 | ||
|
0a08f9d3f8 | ||
|
c3de13e0d8 | ||
|
45017efe00 | ||
|
a20290cac8 | ||
|
31e02a154c | ||
|
023ee5db99 | ||
|
05d2e15ab5 | ||
|
7d6590c60a | ||
|
3152ce183b | ||
|
d9f1a7c4d0 | ||
|
952aed3c49 | ||
|
ab3c433450 | ||
|
2b5e4a34d4 | ||
|
35cea852ca | ||
|
88581c8983 | ||
|
a7a9aab189 | ||
|
370c9b63cf | ||
|
7cb08849de | ||
|
7f052163e3 | ||
|
277d939033 | ||
|
26fbdcfab0 | ||
|
6d63ba9d4d | ||
|
8963f4fd62 | ||
|
463021a9f3 | ||
|
f71a8e9fef | ||
|
2dd5be1b4e | ||
|
608838045f | ||
|
899d506faa | ||
|
21b3e3ea05 | ||
|
a68951541c | ||
|
7fd0deedb1 | ||
|
e9e12ad843 | ||
|
4fd3185d12 | ||
|
f5ccebfd41 | ||
|
294721eef9 | ||
|
8af509992d | ||
|
11fccb8e89 | ||
|
1e126dd2c3 | ||
|
cfe2f889a4 | ||
|
1bd76b0e07 | ||
|
6d8c935cc7 | ||
|
7144cee0f6 | ||
|
f75a8d56f2 | ||
|
2f321bcfd9 | ||
|
a157f4f17b | ||
|
7723c623d5 | ||
|
bc3bb78916 | ||
|
0ebf5e49fb | ||
|
c340921fbb | ||
|
30e26b101c | ||
|
1b17cab663 | ||
|
ab039adf97 | ||
|
62fe10df31 | ||
|
2004a751dd | ||
|
ace127acf4 | ||
|
82c5497a06 | ||
|
dbb7989027 | ||
|
103f677a93 | ||
|
cb6bf78595 | ||
|
62334ddef7 | ||
|
778f67f2e4 | ||
|
48737f8e60 | ||
|
94de62e503 | ||
|
0445052898 | ||
|
ccde90ea91 | ||
|
ed94355019 | ||
|
02fcd1b5fc | ||
|
ca934e7cdf | ||
|
0ddd5f0a79 | ||
|
099d6801b7 | ||
|
62293926ec | ||
|
c96daad12c | ||
|
9cc3be152f | ||
|
f841c0d4ba | ||
|
3cd1d8135e | ||
|
86474d9f90 | ||
|
9bf87a3033 | ||
|
71e32520cf | ||
|
e3e938c8eb | ||
|
03aa440424 | ||
|
85ca38be90 | ||
|
7c9790dff0 | ||
|
40a71a11cb | ||
|
46a500f5e5 | ||
|
2d1d03bf8e | ||
|
1092d00c7a | ||
|
cd58e0d01e | ||
|
30a9783348 | ||
|
c839cf50af | ||
|
f8d607b06f | ||
|
cfadeb07b1 | ||
|
072850be0b | ||
|
71d120bc4e | ||
|
68d3cea528 | ||
|
07e801f44d | ||
|
ee5c694aa2 | ||
|
efa5eb1770 | ||
|
42e37246f3 | ||
|
dabb08ff4a | ||
|
66b0e04cc6 | ||
|
74824b7737 | ||
|
df2bcdb854 | ||
|
a8e9ee2e95 | ||
|
5093697b27 | ||
|
5bacd63805 | ||
|
1d5932e63f | ||
|
022762c0c9 | ||
|
e16bd194a3 | ||
|
a3765c19e3 | ||
|
a845d92d88 | ||
|
668c9e5a64 | ||
|
aaa06f4120 | ||
|
e26f4ce707 | ||
|
a8c3a2d991 | ||
|
6d52cef73a | ||
|
edacfcdec7 | ||
|
683872ef4e | ||
|
7a299ba1f9 | ||
|
f5eaedfc72 | ||
|
11b6afbe09 | ||
|
cd7340915b | ||
|
b38bb3df5d | ||
|
1f7725ada3 | ||
|
622095ebc4 | ||
|
397b7fefe3 | ||
|
10d38b709b | ||
|
98985690f0 | ||
|
f50c483c64 | ||
|
a15eca137d | ||
|
ba4be02e75 | ||
|
d4f6a86a57 | ||
|
6a058372bb | ||
|
e6cce350bd | ||
|
7c0c1e6cf8 | ||
|
39f787b7db | ||
|
424437446d | ||
|
60a1859d89 | ||
|
ad5c1639e8 | ||
|
92828b22fa | ||
|
e470096e4e | ||
|
3c41608ee9 | ||
|
035e145cd1 | ||
|
2f621279c2 | ||
|
c30185c6ae | ||
|
908c74eb27 | ||
|
10c17fc9a9 | ||
|
a6a0cb928a | ||
|
8bca988520 | ||
|
0af0af8d8a | ||
|
97da13c3c4 | ||
|
7134b46cdc | ||
|
aecdf7a3d3 | ||
|
37e37d1998 | ||
|
6103a8590d | ||
|
ba62dadc00 | ||
|
26073b82fd | ||
|
9b73bca79d | ||
|
0a0bb0ca13 | ||
|
652df47c5c | ||
|
9248be2177 | ||
|
b3800fc42e | ||
|
15f304736f | ||
|
21cdf59065 | ||
|
e3693afb75 | ||
|
009a753585 | ||
|
5c72541044 | ||
|
a01e604443 | ||
|
52b339d0b8 | ||
|
579ed5b9c0 | ||
|
63e64b8bcc | ||
|
64f8583975 | ||
|
75e8064044 | ||
|
6f3e38e392 | ||
|
cb4d244f19 | ||
|
de3b8a10a0 | ||
|
8feece702c | ||
|
900308afec | ||
|
b47925a319 | ||
|
853325d9fd | ||
|
494be37715 | ||
|
d35cb5d072 | ||
|
df9ec711c5 | ||
|
d9d0837024 | ||
|
086138fbd9 | ||
|
bdbd4b57b7 | ||
|
5475448af5 | ||
|
c3da3f11d9 | ||
|
244c81587c | ||
|
a3877a2cb1 | ||
|
206df82d63 | ||
|
54e1e7684d | ||
|
27eef36677 | ||
|
d91953e70b | ||
|
bbc5a49054 | ||
|
f89fe9fbab | ||
|
0c042bfe50 | ||
|
1a7894b15e | ||
|
543f983e41 | ||
|
97e7e473b8 | ||
|
79c30f7a94 | ||
|
cccf86d388 | ||
|
c102c23831 | ||
|
80ada9c90a | ||
|
6c3b4070ba | ||
|
4b287b758d | ||
|
b6d129a5c1 | ||
|
4d08147647 | ||
|
ace7c17f2b | ||
|
10f3d8aa0f | ||
|
3237ca0d97 | ||
|
5682ab9570 | ||
|
a3d73634e7 | ||
|
98b6aec203 | ||
|
7feb788ed3 | ||
|
bea490081b | ||
|
7adc3ca003 | ||
|
f8cbc63ab0 | ||
|
418590fb35 | ||
|
56144482f1 | ||
|
59f681e6af | ||
|
d3296f5180 | ||
|
c6fff0aa13 | ||
|
41e0c42282 | ||
|
ede2274816 | ||
|
ead672afb2 | ||
|
73bc7b045e | ||
|
3281502c25 | ||
|
ca35e536db | ||
|
bb8e0eb7bf | ||
|
bb451ac3b5 | ||
|
b0b7842f9c | ||
|
aad661cb65 | ||
|
ed9b63520d | ||
|
0d89d4d0d3 | ||
|
5de1246827 | ||
|
edc3b014cd | ||
|
fec98f45ce | ||
|
94810d5066 | ||
|
431cc796d8 | ||
|
e9d2dbcc92 | ||
|
7a618ef89c | ||
|
5b56249d12 | ||
|
e2ba5abe76 | ||
|
70a4b7c863 | ||
|
10fde1b1ef | ||
|
6131746180 | ||
|
bf953bf1b5 | ||
|
d81da10dfa | ||
|
ed9cdc1ab7 | ||
|
9f506eb83a | ||
|
36e8c52b01 | ||
|
e6d1233bfe | ||
|
6847459022 | ||
|
285623a02b | ||
|
fbf64f8037 | ||
|
2b74ca2746 | ||
|
b106a82308 | ||
|
d71a0ddd66 | ||
|
5700f2f78a | ||
|
c5ca6abb90 | ||
|
6826b6e1f8 | ||
|
27c4fa2fcf | ||
|
6ef1aff991 | ||
|
57a026a7a1 | ||
|
cfc785358e | ||
|
c2d2b3f3b2 | ||
|
4aa153fcf1 | ||
|
824f63a3ad | ||
|
2446dc6950 | ||
|
f98405188d | ||
|
a5cf24773c | ||
|
01c1e4f8cb | ||
|
0759fb6436 | ||
|
ed5188069b | ||
|
aa0a9bde76 | ||
|
56a450a936 | ||
|
1e01106b94 | ||
|
b9a755d6d3 | ||
|
1d9d6c899d | ||
|
444dffb458 | ||
|
3f1b7192ff | ||
|
b992b19c66 | ||
|
e341121f61 | ||
|
cd3e2963b3 | ||
|
346faf1d07 | ||
|
1e09b2bbd8 | ||
|
0ffba45517 | ||
|
32ff346154 | ||
|
0008f44255 | ||
|
a3c519a061 | ||
|
5b8a923cb5 | ||
|
576cbc0b90 | ||
|
e95e2cf152 | ||
|
317dc10af4 | ||
|
f06065337c | ||
|
cc870ca302 | ||
|
80f5506b18 | ||
|
5321a75272 | ||
|
b70a78b7aa | ||
|
5ad08791ea | ||
|
9dc3ec0bf8 | ||
|
69dd9d0cac | ||
|
24f923e88e | ||
|
b5552a216d | ||
|
f3fe4433ae | ||
|
1988c617a0 | ||
|
5e531d6f96 | ||
|
1fb7e97700 | ||
|
64d27156f5 | ||
|
b528a0f4ec | ||
|
7f265a6692 | ||
|
55e00e35c1 | ||
|
d94e1ba55b | ||
|
00e7167174 | ||
|
9d7b69fc0e | ||
|
db9a68e9c9 | ||
|
b5d9d6e268 | ||
|
5ff0c563ec | ||
|
e2131523ec | ||
|
1f5f51e3e5 | ||
|
1bb0d54dce | ||
|
094bb37049 | ||
|
58601db5ef | ||
|
b5bef98a9b | ||
|
1026f1efa5 | ||
|
0c673fb524 | ||
|
7ee2c9478d | ||
|
4b51f8251b | ||
|
e91a64b1cc | ||
|
8920762fc5 | ||
|
ba40f93386 | ||
|
781bf52a8e | ||
|
cca1a9832e | ||
|
1a152a5597 | ||
|
76a5290351 | ||
|
829e17ef2b | ||
|
bc5d3bea14 | ||
|
edf2a2e68d | ||
|
5df443a016 | ||
|
f6396f2e74 | ||
|
c618e58a11 | ||
|
2ea27acdde | ||
|
331cad276e | ||
|
b74eab8377 | ||
|
fb80318553 | ||
|
3eb4aed867 | ||
|
f6f959a897 | ||
|
2b422a542a | ||
|
d0e9d58a43 | ||
|
8a1933b9b2 | ||
|
96a587f343 | ||
|
2bb6a71874 | ||
|
94acd12f1c | ||
|
5e44a61068 | ||
|
a54f0ed94d | ||
|
662c6f3cc2 | ||
|
d93c635a0a | ||
|
17b73aaf91 | ||
|
c194911458 | ||
|
92e99e3fb4 | ||
|
848e6102a1 | ||
|
ef37bf9b1a | ||
|
eb41e023c7 | ||
|
6b4987bf39 | ||
|
d46ff76887 | ||
|
d53a9e672c | ||
|
80ef99a24b | ||
|
6062a1f8c7 | ||
|
d974bd9a07 | ||
|
fb8c9566d5 | ||
|
7431bd69e9 | ||
|
5d7f393e94 | ||
|
ef25d100e1 | ||
|
17f82c8972 | ||
|
05c937743c | ||
|
bf2e7ff130 | ||
|
1b30ee606f | ||
|
3235907266 | ||
|
99c7e417d6 | ||
|
d81906d348 | ||
|
296872d2e4 | ||
|
7cd02b4916 | ||
|
0e217f48be | ||
|
61fdf4b6c7 | ||
|
91dbf1f01a | ||
|
d52aac76c0 | ||
|
6102e441d6 | ||
|
1a5fec39c0 | ||
|
efa5091b98 | ||
|
be4386658a | ||
|
0cddce7a37 | ||
|
a86d13632e | ||
|
d71682a3f7 | ||
|
c901ace21a | ||
|
418398a870 | ||
|
baca57062e | ||
|
52df8e6e8b | ||
|
b51747378a | ||
|
424a6b0428 | ||
|
4549223d6d | ||
|
91dd3468d5 | ||
|
83a3871d43 | ||
|
8b6023c45a | ||
|
a401d4e760 | ||
|
a8520f6593 | ||
|
9a95d207cf | ||
|
c75d779f85 | ||
|
b8f2066408 | ||
|
cc48b6cd96 | ||
|
b36c735445 | ||
|
5f0f26a3ac | ||
|
6b81eaa65d | ||
|
644f15e80d | ||
|
a8ddf4c2df | ||
|
3f9833117e | ||
|
6140d0c849 | ||
|
f4cb7cea21 | ||
|
140ec1f90f | ||
|
31d7220785 | ||
|
38e1d36684 | ||
|
6397655f7d | ||
|
692047e4c8 | ||
|
821bfe68c1 | ||
|
596eaa7590 | ||
|
08fd0f0fa9 | ||
|
77c925ce4b | ||
|
3338352b41 | ||
|
e3aa4143c7 | ||
|
0a93808076 | ||
|
0cc21f9b99 | ||
|
d3543ea291 | ||
|
e824f7a28c | ||
|
56be0744f0 | ||
|
d874c71e0b | ||
|
487177af87 | ||
|
0452a4e1ac | ||
|
959a03214a | ||
|
fe22dfc531 | ||
|
139e258664 | ||
|
d1ab14966b | ||
|
40ff3c731e | ||
|
75d77a3648 | ||
|
921da7242d | ||
|
67c9937e67 | ||
|
bdcb3af6fa | ||
|
17e81ab6bd | ||
|
70e1ec2cd2 | ||
|
c6a7fd405b | ||
|
4f0d1704c4 | ||
|
a966a5097e | ||
|
080db4d462 | ||
|
4af766162e | ||
|
57d67bc4a8 | ||
|
16278f36ec | ||
|
9cb981f068 | ||
|
05c6d67cab | ||
|
0c516843d8 | ||
|
d400ac57d5 | ||
|
2644efd9f7 | ||
|
11baa97ef6 | ||
|
c36636bd2d | ||
|
6bb05a6780 | ||
|
360f5db2cf | ||
|
bbbeacee4d | ||
|
94ad56fc96 | ||
|
3df80f2653 | ||
|
bb6c9cf49e | ||
|
ae12222687 | ||
|
094dfde048 | ||
|
714b887274 | ||
|
8cfa88eff8 | ||
|
3976b57100 | ||
|
bbcf484f7f | ||
|
249f35f948 | ||
|
b71f1a79c8 | ||
|
e4aa3d310f | ||
|
99f57269fb | ||
|
93def3a557 | ||
|
a04674d93d | ||
|
539cc187a8 | ||
|
fcdd975751 | ||
|
8a4c2bf208 | ||
|
50c5d533b0 | ||
|
f952553c76 | ||
|
9a9be466f7 | ||
|
bbad029aa1 | ||
|
eb748554c5 | ||
|
c8b494e909 | ||
|
bd7fd725f6 | ||
|
3e0440ba53 | ||
|
83a29f8d85 | ||
|
8551e6e74a | ||
|
3582cb3f46 | ||
|
23de13b82c | ||
|
93acd4f18f | ||
|
96883dabe3 | ||
|
da521020f6 | ||
|
ac78738061 | ||
|
b78ca71c0f | ||
|
cd7272d00a | ||
|
cd7489c569 | ||
|
1f3f369df0 | ||
|
911e29f2ea | ||
|
d7b359e432 | ||
|
0b2c92cbe5 | ||
|
f626d964c5 | ||
|
b0e0b38549 | ||
|
94af4b2c7b | ||
|
0b235e146f | ||
|
40f3301324 | ||
|
4fe90e59a8 | ||
|
b28611ed08 | ||
|
672ad22e4f | ||
|
c17bd5ec3a | ||
|
2770755f9d | ||
|
ee69cdbf7b | ||
|
a72a25640f | ||
|
e86f8ddd09 | ||
|
66bdb39f1a | ||
|
aa368c3a63 | ||
|
b499a2ab87 | ||
|
49e4482947 | ||
|
6e7e4250e6 | ||
|
5a8ea15c4f | ||
|
f9aa029e8e | ||
|
6653e379b3 | ||
|
930a611374 | ||
|
51e918be6d | ||
|
b0170eea8f | ||
|
144b34ca2e | ||
|
676c022e41 | ||
|
7c0b98bb70 | ||
|
7779713392 | ||
|
5e1396025c | ||
|
d09c88f71c | ||
|
7980f21b1b | ||
|
3843994a05 | ||
|
a1b08ca037 | ||
|
144cdd11ec | ||
|
b6531cdb10 | ||
|
0eef4a5fa1 | ||
|
42baaf8f2d | ||
|
08d9dff8eb | ||
|
01b3aab9bc | ||
|
fde34ef178 | ||
|
5195abec94 | ||
|
aa79acd09e | ||
|
67011ccd72 | ||
|
e2cd7fe17e | ||
|
4c0624f489 | ||
|
8c033250b1 | ||
|
05b5a6fddf | ||
|
df5bde7b8e | ||
|
ca5f52c48c | ||
|
3656adf059 | ||
|
4381792b05 | ||
|
0a3c20b08a | ||
|
4ab3f41665 | ||
|
c75ce9cbba | ||
|
10356a4376 | ||
|
4fb4e19e99 | ||
|
98c2056f53 | ||
|
74e8ae4d79 | ||
|
6b890cf3b3 | ||
|
d62b471afe | ||
|
b97240963e | ||
|
55ed0f2374 | ||
|
2c1b3d5790 | ||
|
9c3b757578 | ||
|
bc04ad96b5 | ||
|
35b07a9c18 | ||
|
17955fc419 | ||
|
ac98f5862a | ||
|
63758c6679 | ||
|
7c765c47e6 | ||
|
58523b0000 | ||
|
41f4b36593 | ||
|
379212b8fe | ||
|
a04c7831b1 | ||
|
1aec16a240 | ||
|
498626748d | ||
|
f24ec97607 | ||
|
f0ad260eab | ||
|
92ee2be021 | ||
|
02e4528f3e | ||
|
f871353acc | ||
|
faa1120e14 | ||
|
8a8000c80a | ||
|
8164610105 | ||
|
3935a3c885 | ||
|
15ec39bc56 | ||
|
5dd2b4c439 | ||
|
1ee05dda4b | ||
|
5a86588cb5 | ||
|
af3269b362 | ||
|
a7a4baa06e | ||
|
9a497b9bba | ||
|
d2a1c449da | ||
|
504524ea56 | ||
|
51e22b406c | ||
|
1ec81482cc | ||
|
fd610414fd | ||
|
1802d5d5da | ||
|
e7e6c76b4e | ||
|
84c7bd63f5 | ||
|
f7187bb798 | ||
|
00db5b69ab | ||
|
b16109ca56 | ||
|
a00b5aa136 | ||
|
0e43e5d295 | ||
|
9f1e2d6192 | ||
|
52c794a259 | ||
|
fcf4c5f328 | ||
|
85b33a60b3 | ||
|
c99bb4cfd7 | ||
|
5b0a942b42 | ||
|
d94c9e522c | ||
|
8fab15abd3 | ||
|
7717304549 | ||
|
b3f1cc3edd | ||
|
16f9c727f2 | ||
|
007ba5fce9 | ||
|
5b36f07493 | ||
|
cbd2580736 | ||
|
fe872a59ca | ||
|
c4dfd99a8c | ||
|
5a34fe4704 | ||
|
2fc3dcce7e | ||
|
1a932fb749 | ||
|
f8a2ee9a8d | ||
|
a972d456ba | ||
|
e9f4c9e687 | ||
|
58f7cdc15e | ||
|
3f9b0643ee | ||
|
24bd4da94b | ||
|
a18b4ffd0f | ||
|
c9e032e350 | ||
|
e1679ad433 | ||
|
933e395945 | ||
|
1f64a00bbf | ||
|
7eebc0c3a5 | ||
|
a2c9dabd77 | ||
|
6187c826b4 | ||
|
b8987963b3 | ||
|
dc7f56b7aa | ||
|
2295ddddee | ||
|
3328d20b2d | ||
|
d444211c68 | ||
|
2de8cd1d6f | ||
|
115813ac44 | ||
|
7c0e8d9421 | ||
|
1c349cf55c | ||
|
7b4d8a8f05 | ||
|
6e7e2de490 | ||
|
0168ef55f0 | ||
|
0a9a92ee7d | ||
|
3bfac7ef3b | ||
|
1581e0e439 | ||
|
6773848874 | ||
|
3e9219173c | ||
|
d46a565e6c | ||
|
6267804d38 | ||
|
25c4d282ce | ||
|
45281360d5 | ||
|
7a32b8d1d2 | ||
|
7b0018b661 | ||
|
9032879e20 | ||
|
8b668cf8b7 | ||
|
3323533fd0 | ||
|
50e96baea1 | ||
|
2ac92df9d3 | ||
|
63e64cea60 | ||
|
0267c76de2 | ||
|
2382a10bba | ||
|
a8db40e99a | ||
|
8553fffffe | ||
|
d9d38a27ff | ||
|
1e7a5562ab | ||
|
e71032a8fc | ||
|
484a3dbe5c | ||
|
9c7f40e4fe | ||
|
f35b7ab6f4 | ||
|
a38dd3d5c8 | ||
|
00ad35fc3e | ||
|
0d78b44c80 | ||
|
fb614637a9 | ||
|
d0482d66c2 | ||
|
ef178dfd24 | ||
|
6cdc6e0780 | ||
|
c961e6d9c7 | ||
|
c6844ff47a | ||
|
605a630411 | ||
|
2b75cf1508 | ||
|
007054301b | ||
|
cf01d32237 | ||
|
5ec553f68d | ||
|
d95e5a169d | ||
|
8debefddad | ||
|
bd25860ccf | ||
|
b21cb5c0e9 | ||
|
ff79a2d3f4 | ||
|
9ae3743a58 | ||
|
ffcdbcc802 | ||
|
3ff78a3a47 | ||
|
4d78ac4789 | ||
|
be24f2d520 | ||
|
ff7fccb6a2 | ||
|
ee5a2b3c38 | ||
|
72b9001447 | ||
|
353245bb7d | ||
|
f9411bf0ed | ||
|
0eedbd2aa1 | ||
|
5e36c37838 | ||
|
35c76c8e2a | ||
|
90ad46b7c5 | ||
|
1c06e1e2a4 | ||
|
15d2c0e436 | ||
|
9984aea42f | ||
|
a9cc5cc351 | ||
|
a6cbabfba5 | ||
|
c3da3499d8 | ||
|
1ef85242ec | ||
|
ffe75b0856 | ||
|
25e3c4fcdc | ||
|
a922497f5d | ||
|
acb100908b | ||
|
7fa2cbc746 | ||
|
b36491af8e | ||
|
ea4fe81cb2 | ||
|
2191f1b826 | ||
|
08f6367752 | ||
|
e1b09f4844 | ||
|
884c46b054 | ||
|
d1cde123dc | ||
|
5eaded7e3a | ||
|
4ae4e88800 | ||
|
85fb27a631 | ||
|
8bf0561009 | ||
|
ddfded048c | ||
|
a2e889587e | ||
|
b63aaad645 | ||
|
2c41b5d4fb | ||
|
34aa4c6412 | ||
|
82372ea252 | ||
|
1f4440bcf9 | ||
|
31fcff4afc | ||
|
082f17f940 | ||
|
aa2ac3865c | ||
|
db8ffe50ac | ||
|
8fe658bacc | ||
|
c71d5e2dfb | ||
|
36c31dcd67 | ||
|
c223408c3c | ||
|
3cdccb49ef | ||
|
0844645a8b | ||
|
30bfad455c | ||
|
d84fdc3cd5 | ||
|
2b64b9de63 | ||
|
cdb3d863db | ||
|
74e3524e92 | ||
|
d31f75d1ec | ||
|
0b34207148 | ||
|
3c2beded68 | ||
|
f33fdb3bfd | ||
|
96a0f29f19 | ||
|
49b3a75a8b | ||
|
f13fc737f1 | ||
|
b7121c5000 | ||
|
22a1d3882e | ||
|
82f74e2264 | ||
|
3dd5699cde | ||
|
4d8553f959 | ||
|
eee1534da7 | ||
|
a198bfc5c0 | ||
|
38c20f5737 | ||
|
132807b55d | ||
|
735081af50 | ||
|
31651aeaab | ||
|
7f4230d026 | ||
|
6333d3fd13 | ||
|
fd9dae6e4b | ||
|
db5d7857c8 | ||
|
0f4eab3cf2 | ||
|
75b9f4fcbf | ||
|
b9b58b8985 | ||
|
64b8aa1c01 | ||
|
6b21dc132d | ||
|
ba85c5159d | ||
|
0388c5d5bc | ||
|
7aca4930db | ||
|
3714c80adb | ||
|
7a92ecfa30 | ||
|
171f6f4608 | ||
|
d569c8d31f | ||
|
51d716253f | ||
|
971b17b364 | ||
|
9616d858cf | ||
|
db5ff7f16d | ||
|
5db045f392 | ||
|
42c143d19e | ||
|
7c1948ebd9 | ||
|
9cd15645a2 | ||
|
c0a4a5c2f5 | ||
|
518004afbc | ||
|
833a4b9367 | ||
|
6aa82724b4 | ||
|
cfbee40ecd | ||
|
79d589c7a9 | ||
|
735c1a23ea | ||
|
6b82fc3011 | ||
|
d921456036 | ||
|
6cc93250b8 | ||
|
39082541ff | ||
|
8cff40fdd4 | ||
|
a777db1234 | ||
|
3fca169096 | ||
|
58b451f616 | ||
|
f9b82f711f | ||
|
d92b5db320 | ||
|
a164e4bf3a | ||
|
be3cbd9e21 | ||
|
43fed96af1 | ||
|
3d3d31ef29 | ||
|
d51e70bcaa | ||
|
32f4c6c982 | ||
|
8f47761200 | ||
|
88cfc96bd3 | ||
|
11f389289d | ||
|
342ebecef2 | ||
|
220a8fe2cc | ||
|
d9389c91ee | ||
|
480cb00098 | ||
|
07ed550dc2 | ||
|
6045870398 | ||
|
2bf102cdf1 | ||
|
889a5b2bce | ||
|
df964a094b | ||
|
e395d4ecee | ||
|
d077e0c83c | ||
|
d931241edc | ||
|
647376ab3f | ||
|
64a9a72457 | ||
|
a8417aca16 | ||
|
b7b2ecad59 | ||
|
dcaa2f4168 | ||
|
7c7f54d224 | ||
|
b942f8c726 | ||
|
f661f23ee5 | ||
|
5a631df2a2 | ||
|
fc9bb7dac6 | ||
|
0a82dc2e8e | ||
|
c5eff85c28 | ||
|
d1627276a6 | ||
|
2be2a2621e | ||
|
995c303f27 | ||
|
9c03525369 | ||
|
afe0673fd1 | ||
|
37333f7fbe | ||
|
c9160cabc5 | ||
|
9e289d5e97 | ||
|
901a580e11 | ||
|
d196292551 | ||
|
8d856b0ec6 | ||
|
90fad52760 | ||
|
2817875461 | ||
|
bcbdee1dcc | ||
|
3de4f2805a | ||
|
995197cad9 | ||
|
89cc4d1df4 | ||
|
61f3b3592f | ||
|
8cb6f67a60 | ||
|
7c580f898c | ||
|
9ad6ce5851 | ||
|
a66090b594 | ||
|
d992a3f7d7 | ||
|
b418a78e2e | ||
|
cae9ae51ad | ||
|
04c92ec4bd | ||
|
54834891fb | ||
|
c9e2f4244d | ||
|
503b86ac13 | ||
|
ec051eba38 | ||
|
2f50f64ecf | ||
|
66fe124dd1 | ||
|
87e56c2f66 | ||
|
f044b0292c | ||
|
ee1d4cd45d | ||
|
b25f83e096 | ||
|
8b7e1e4169 | ||
|
ca9a2cb13a | ||
|
93af92743c | ||
|
0ebef3792d | ||
|
7a3bb14653 | ||
|
fbc0a39a1c | ||
|
473bad24b7 | ||
|
313d968985 | ||
|
b5775ff9d2 | ||
|
553c1eacfc | ||
|
8f66a41c09 | ||
|
6721471c63 | ||
|
638421de40 | ||
|
7bc5338cb3 | ||
|
07c9db9b54 | ||
|
fafc4fb71e | ||
|
6b49d32102 | ||
|
3a391aa3cb | ||
|
42019321e3 | ||
|
b61860b3ab | ||
|
91950e1891 | ||
|
0032d3cf6c | ||
|
42715bba50 | ||
|
0aacad655d | ||
|
bd81cc1cc4 | ||
|
289480c954 | ||
|
3da7746629 | ||
|
8d48051a8d | ||
|
ec16c0f0f4 | ||
|
bc36fcb722 | ||
|
19d19112d9 | ||
|
d9f1c2a406 | ||
|
ef2be40478 | ||
|
8e2ee5e5e4 | ||
|
c3da2bfade | ||
|
bbd6780971 | ||
|
0a6dab1f24 | ||
|
faa9a982a9 | ||
|
de8bb8a951 | ||
|
b766eef5ef | ||
|
a185787044 | ||
|
76f7cd08ee | ||
|
460451bcce | ||
|
71edb68995 | ||
|
e188482247 | ||
|
047d320665 | ||
|
5c4d9a85be | ||
|
56c08056d2 | ||
|
b39ac73cd8 | ||
|
c9054e7d8c | ||
|
adf5c9bd46 | ||
|
3ea3674407 | ||
|
657c7d8cff | ||
|
350e32326f | ||
|
1894573c2f | ||
|
73f889ac9f | ||
|
a336dae84c | ||
|
467c826c04 | ||
|
a4c164a57e | ||
|
11cd553949 | ||
|
8c0c22a925 | ||
|
c8d528ffc4 | ||
|
bb7b1f9e0c | ||
|
f994f83ce1 | ||
|
2af083b2e5 | ||
|
de4d0961da | ||
|
c8d48ccbff | ||
|
695d3b82b5 | ||
|
965625ad01 | ||
|
379733938c | ||
|
3198999746 | ||
|
1f03499fc5 | ||
|
a53d888747 | ||
|
62905f084f | ||
|
ba7ee4fba7 | ||
|
6cb3df9350 | ||
|
5c1c71c625 | ||
|
7fd0cfc85f | ||
|
efad3b1284 | ||
|
a06de9682c | ||
|
0d4ad05c1c | ||
|
aef088a9d2 | ||
|
e8f3aa681e | ||
|
42293fb11a | ||
|
65e0eb5205 | ||
|
2f1a7f8f40 | ||
|
73e9410264 | ||
|
c835c02bf2 | ||
|
336d44a5cc | ||
|
7027931095 | ||
|
87b56d538d | ||
|
0519ce2001 | ||
|
d4d0330f70 | ||
|
25ae54cab7 | ||
|
a67576b447 | ||
|
4d181eef8e | ||
|
1835a91467 | ||
|
bcc61b0d8b | ||
|
f8055e7976 | ||
|
2509406d1c | ||
|
b3d15f91e4 | ||
|
85c36df2a3 | ||
|
6ef79f5213 | ||
|
b576014d07 | ||
|
1f37318f79 | ||
|
6950966b06 | ||
|
8eacf67725 | ||
|
52120e7a38 | ||
|
45a3f82c8d | ||
|
1490828069 | ||
|
9bdad6bb67 | ||
|
f24063cfea | ||
|
1defed27a0 | ||
|
34d6a12d95 | ||
|
5d3de967f0 | ||
|
8b73f9da17 | ||
|
820099622e | ||
|
e9c9a51b8d | ||
|
366d39a7a3 | ||
|
853a14c6b8 | ||
|
15ca68f7e1 | ||
|
8b9548a463 | ||
|
6688120aee | ||
|
93e4e723fa | ||
|
8b74e50c50 | ||
|
129a644781 | ||
|
bbfbd4a105 | ||
|
9d31d990fc | ||
|
c7f15c42fa | ||
|
515d401746 | ||
|
2a03b452d3 | ||
|
7aa8c765f6 | ||
|
038f65aae6 | ||
|
db24828a5a | ||
|
c7693d0ec3 | ||
|
5e2afd4b4d | ||
|
7a21312daf | ||
|
051a1405e7 | ||
|
a6669ed876 | ||
|
f1b00436aa | ||
|
e699103d3e | ||
|
ba9cb88ca3 | ||
|
365850d922 | ||
|
0538c2f478 | ||
|
77a0179822 |
@ -1,8 +0,0 @@
|
|||||||
_____ _ _ __
|
|
||||||
/ ____| | (_)/ _|
|
|
||||||
| | ___ ___ | |_| |_ _ _
|
|
||||||
| | / _ \ / _ \| | | _| | | |
|
|
||||||
| |___| (_) | (_) | | | | | |_| |
|
|
||||||
\_____\___/ \___/|_|_|_| \__, |
|
|
||||||
__/ |
|
|
||||||
|___/
|
|
32
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
32
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@ -1,17 +1,28 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
description: Create a new bug report
|
description: "Create a new bug report."
|
||||||
title: '[Bug]: '
|
title: "[Bug]: "
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: >-
|
||||||
|
# 💎 Bounty program (with
|
||||||
|
[algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||||
|
|
||||||
|
|
||||||
|
If you would like to prioritize the issue resolution, you can add bounty
|
||||||
|
to this issue.
|
||||||
|
|
||||||
|
|
||||||
|
Click [here](https://console.algora.io/org/coollabsio/bounties/new) to
|
||||||
|
get started.
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Description
|
||||||
description: A clear and concise description of the problem
|
description: A clear and concise description of the problem
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Minimal Reproduction (if possible, example repository)
|
label: Minimal Reproduction (if possible, example repository)
|
||||||
description: Please provide a step by step guide to reproduce the issue
|
description: Please provide a step by step guide to reproduce the issue.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
@ -21,6 +32,15 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Coolify's version (see bottom left corner).
|
description: Coolify's version (see top of your screen).
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Cloud?
|
||||||
|
description: "Are you using the cloud version of Coolify?"
|
||||||
|
options:
|
||||||
|
- label: 'Yes'
|
||||||
|
required: false
|
||||||
|
- label: 'No'
|
||||||
|
required: false
|
||||||
|
1
.github/pull_request_template.md
vendored
Normal file
1
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
> Always use `next` branch as destination branch for PRs, not `main`
|
20
.github/workflows/coolify-helper-next.yml
vendored
20
.github/workflows/coolify-helper-next.yml
vendored
@ -18,15 +18,15 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
no-cache: true
|
||||||
context: .
|
context: .
|
||||||
@ -40,15 +40,15 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
no-cache: true
|
||||||
context: .
|
context: .
|
||||||
@ -64,13 +64,13 @@ jobs:
|
|||||||
needs: [ amd64, aarch64 ]
|
needs: [ amd64, aarch64 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
20
.github/workflows/coolify-helper.yml
vendored
20
.github/workflows/coolify-helper.yml
vendored
@ -18,15 +18,15 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
no-cache: true
|
||||||
context: .
|
context: .
|
||||||
@ -40,15 +40,15 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
no-cache: true
|
||||||
context: .
|
context: .
|
||||||
@ -64,13 +64,13 @@ jobs:
|
|||||||
needs: [ amd64, aarch64 ]
|
needs: [ amd64, aarch64 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
20
.github/workflows/coolify-testing-host.yml
vendored
20
.github/workflows/coolify-testing-host.yml
vendored
@ -18,15 +18,15 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
no-cache: true
|
||||||
context: .
|
context: .
|
||||||
@ -40,15 +40,15 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
- name: Build image and push to registry
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
no-cache: true
|
||||||
context: .
|
context: .
|
||||||
@ -64,13 +64,13 @@ jobs:
|
|||||||
needs: [ amd64, aarch64 ]
|
needs: [ amd64, aarch64 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
4
.github/workflows/development-build.yml
vendored
4
.github/workflows/development-build.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod-ssu/Dockerfile
|
file: docker/prod/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||||
@ -47,7 +47,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod-ssu/Dockerfile
|
file: docker/prod/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
|
||||||
|
2
.github/workflows/docker-image.yml
vendored
2
.github/workflows/docker-image.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Cache Docker layers
|
- name: Cache Docker layers
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
|
25
.github/workflows/fix-php-code-style-issues.yml
vendored
Normal file
25
.github/workflows/fix-php-code-style-issues.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Fix PHP code style issues
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
php-code-styling:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
|
- name: Fix PHP code style issues
|
||||||
|
uses: aglipanci/laravel-pint-action@2.4
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
|
with:
|
||||||
|
commit_message: Fix styling
|
6
.github/workflows/production-build.yml
vendored
6
.github/workflows/production-build.yml
vendored
@ -3,6 +3,8 @@ name: Production Build (v4)
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
|
paths-ignore:
|
||||||
|
- templates/service-templates.json
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
@ -27,7 +29,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod-ssu/Dockerfile
|
file: docker/prod/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
@ -49,7 +51,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod-ssu/Dockerfile
|
file: docker/prod/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
@ -3,7 +3,7 @@ tasks:
|
|||||||
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614
|
# 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)
|
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: |
|
init: |
|
||||||
cp .env.example .env &&
|
cp .env.development.example .env &&
|
||||||
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .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#USERID=#USERID=33333#g" .env
|
||||||
sed -i "s#GROUPID=#GROUPID=33333#g" .env
|
sed -i "s#GROUPID=#GROUPID=33333#g" .env
|
||||||
@ -12,7 +12,7 @@ tasks:
|
|||||||
./vendor/bin/spin exec -u webuser coolify php artisan key:generate
|
./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 storage:link
|
||||||
./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed
|
./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed
|
||||||
cat .coolify-logo
|
cat .jesus-is-king
|
||||||
gp sync-done spin-is-ready
|
gp sync-done spin-is-ready
|
||||||
|
|
||||||
- name: Install Node dependencies and run Vite
|
- name: Install Node dependencies and run Vite
|
||||||
@ -20,7 +20,7 @@ tasks:
|
|||||||
echo "Waiting for Sail environment to boot up."
|
echo "Waiting for Sail environment to boot up."
|
||||||
gp sync-await spin-is-ready
|
gp sync-await spin-is-ready
|
||||||
./vendor/bin/spin exec vite npm install
|
./vendor/bin/spin exec vite npm install
|
||||||
./vendor/bin/spin exec vite npm run dev
|
./vendor/bin/spin exec vite npm run dev -- --host
|
||||||
|
|
||||||
- name: Laravel Queue Worker, listening to code changes
|
- name: Laravel Queue Worker, listening to code changes
|
||||||
command: |
|
command: |
|
||||||
|
6
.jesus-is-king
Normal file
6
.jesus-is-king
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
__ _ __ __ _
|
||||||
|
/ /__ _______ _______ (_)____ / //_/(_)___ ____ _
|
||||||
|
__ / / _ \/ ___/ / / / ___/ / / ___/ / ,< / / __ \/ __ `/
|
||||||
|
/ /_/ / __(__ ) /_/ (__ ) / (__ ) / /| |/ / / / / /_/ /
|
||||||
|
\____/\___/____/\__,_/____/ /_/____/ /_/ |_/_/_/ /_/\__, /
|
||||||
|
/____/
|
@ -1,22 +0,0 @@
|
|||||||
<?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();
|
|
||||||
}
|
|
@ -19,7 +19,7 @@
|
|||||||
<<<EOF
|
<<<EOF
|
||||||
Hello,
|
Hello,
|
||||||
|
|
||||||
Welcome to Coolify Cloud.
|
Welcome to Last Hour Cloud.
|
||||||
Here is your user id: $user->id
|
Here is your user id: $user->id
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
# 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._
|
|
@ -30,5 +30,5 @@ ### 4) Start development
|
|||||||
Mails are caught by Mailpit: `localhost:8025`
|
Mails are caught by Mailpit: `localhost:8025`
|
||||||
|
|
||||||
## New Service Contribution
|
## New Service Contribution
|
||||||
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).
|
Check out the docs [here](https://coolify.io/docs/knowledge-base/add-a-service).
|
||||||
|
|
||||||
|
105
README.md
105
README.md
@ -1,105 +0,0 @@
|
|||||||
# About the Project
|
|
||||||
|
|
||||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
|
||||||
|
|
||||||
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
|
||||||
|
|
||||||
For more information, take a look at our landing page [here](https://coolify.io).
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
|
||||||
```
|
|
||||||
You can find the installation script source [here](./scripts/install.sh).
|
|
||||||
|
|
||||||
# Support
|
|
||||||
|
|
||||||
Contact us [here](https://coolify.io/docs/contact).
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
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 ($40+)
|
|
||||||
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
|
||||||
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
|
||||||
<a href="https://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>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
|
||||||
|
|
||||||
## Individuals
|
|
||||||
|
|
||||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
|
||||||
|
|
||||||
# Cloud
|
|
||||||
|
|
||||||
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
|
||||||
|
|
||||||
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
|
||||||
|
|
||||||
## Why should I use the Cloud version?
|
|
||||||
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
|
|
||||||
|
|
||||||
By subscribing to the cloud version, you get the Coolify server for the same price, but with:
|
|
||||||
- High-availability
|
|
||||||
- Free email notifications
|
|
||||||
- Better support
|
|
||||||
- Less maintenance for you
|
|
||||||
|
|
||||||
|
|
||||||
# Recognitions
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://news.ycombinator.com/item?id=26624341">
|
|
||||||
<img
|
|
||||||
style="width: 250px; height: 54px;" width="250" height="54"
|
|
||||||
alt="Featured on Hacker News"
|
|
||||||
src="https://hackernews-badge.vercel.app/api?id=26624341"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
|
||||||
|
|
||||||
# Repo Activity
|
|
||||||
|
|
||||||
![Alt](https://repobeats.axiom.co/api/embed/eab1c8066f9c59d0ad37b76c23ebb5ccac4278ae.svg "Repobeats analytics image")
|
|
||||||
|
|
||||||
# Star History
|
|
||||||
|
|
||||||
[![Star History Chart](https://api.star-history.com/svg?repos=coollabsio/coolify&type=Date)](https://star-history.com/#coollabsio/coolify&Date)
|
|
16
SECURITY.md
Normal file
16
SECURITY.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Use this section to tell people about which versions of your project are
|
||||||
|
currently being supported with security updates.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| > 4 | :white_check_mark: |
|
||||||
|
| 3 | :x: |
|
||||||
|
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you have any vulnerability please report at hi@coollabs.io
|
16
app/Actions/Application/LoadComposeFile.php
Normal file
16
app/Actions/Application/LoadComposeFile.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class LoadComposeFile
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Application $application)
|
||||||
|
{
|
||||||
|
$application->loadComposeFile();
|
||||||
|
}
|
||||||
|
}
|
@ -3,17 +3,17 @@
|
|||||||
namespace App\Actions\Application;
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\StandaloneDocker;
|
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopApplication
|
class StopApplication
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Application $application)
|
|
||||||
|
public function handle(Application $application, bool $previewDeployments = false)
|
||||||
{
|
{
|
||||||
if ($application->destination->server->isSwarm()) {
|
if ($application->destination->server->isSwarm()) {
|
||||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +26,12 @@ public function handle(Application $application)
|
|||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
|
if ($previewDeployments) {
|
||||||
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, includePullrequests: true);
|
||||||
|
} else {
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
|
}
|
||||||
|
ray($containers);
|
||||||
if ($containers->count() > 0) {
|
if ($containers->count() > 0) {
|
||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
$containerName = data_get($container, 'Names');
|
$containerName = data_get($container, 'Names');
|
||||||
@ -38,6 +43,12 @@ public function handle(Application $application)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
// remove network
|
||||||
|
$uuid = $application->uuid;
|
||||||
|
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||||
|
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
class StopApplicationOneServer
|
class StopApplicationOneServer
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Application $application, Server $server)
|
public function handle(Application $application, Server $server)
|
||||||
{
|
{
|
||||||
if ($application->destination->server->isSwarm()) {
|
if ($application->destination->server->isSwarm()) {
|
||||||
@ -32,6 +33,7 @@ public function handle(Application $application, Server $server)
|
|||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Actions\CoolifyTask;
|
namespace App\Actions\CoolifyTask;
|
||||||
|
|
||||||
use App\Data\CoolifyTaskArgs;
|
use App\Data\CoolifyTaskArgs;
|
||||||
|
use App\Enums\ActivityTypes;
|
||||||
use App\Jobs\CoolifyTask;
|
use App\Jobs\CoolifyTask;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
@ -14,6 +15,7 @@
|
|||||||
class PrepareCoolifyTask
|
class PrepareCoolifyTask
|
||||||
{
|
{
|
||||||
protected Activity $activity;
|
protected Activity $activity;
|
||||||
|
|
||||||
protected CoolifyTaskArgs $remoteProcessArgs;
|
protected CoolifyTaskArgs $remoteProcessArgs;
|
||||||
|
|
||||||
public function __construct(CoolifyTaskArgs $remoteProcessArgs)
|
public function __construct(CoolifyTaskArgs $remoteProcessArgs)
|
||||||
@ -28,20 +30,31 @@ public function __construct(CoolifyTaskArgs $remoteProcessArgs)
|
|||||||
->withProperties($properties)
|
->withProperties($properties)
|
||||||
->performedOn($remoteProcessArgs->model)
|
->performedOn($remoteProcessArgs->model)
|
||||||
->event($remoteProcessArgs->type)
|
->event($remoteProcessArgs->type)
|
||||||
->log("[]");
|
->log('[]');
|
||||||
} else {
|
} else {
|
||||||
$this->activity = activity()
|
$this->activity = activity()
|
||||||
->withProperties($remoteProcessArgs->toArray())
|
->withProperties($remoteProcessArgs->toArray())
|
||||||
->event($remoteProcessArgs->type)
|
->event($remoteProcessArgs->type)
|
||||||
->log("[]");
|
->log('[]');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __invoke(): Activity
|
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);
|
$job = new CoolifyTask(
|
||||||
|
activity: $this->activity,
|
||||||
|
ignore_errors: $this->remoteProcessArgs->ignore_errors,
|
||||||
|
call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish,
|
||||||
|
call_event_data: $this->remoteProcessArgs->call_event_data,
|
||||||
|
);
|
||||||
|
if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) {
|
||||||
|
ray('Dispatching a high priority job');
|
||||||
|
dispatch($job)->onQueue('high');
|
||||||
|
} else {
|
||||||
dispatch($job);
|
dispatch($job);
|
||||||
|
}
|
||||||
$this->activity->refresh();
|
$this->activity->refresh();
|
||||||
|
|
||||||
return $this->activity;
|
return $this->activity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ class RunRemoteProcess
|
|||||||
|
|
||||||
public $call_event_on_finish = null;
|
public $call_event_on_finish = null;
|
||||||
|
|
||||||
|
public $call_event_data = null;
|
||||||
|
|
||||||
protected $time_start;
|
protected $time_start;
|
||||||
|
|
||||||
protected $current_time;
|
protected $current_time;
|
||||||
@ -34,10 +36,10 @@ class RunRemoteProcess
|
|||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null)
|
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
|
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value && $activity->getExtraProperty('type') !== ActivityTypes::COMMAND->value) {
|
||||||
throw new \RuntimeException('Incompatible Activity to run a remote command.');
|
throw new \RuntimeException('Incompatible Activity to run a remote command.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +47,7 @@ public function __construct(Activity $activity, bool $hide_from_output = false,
|
|||||||
$this->hide_from_output = $hide_from_output;
|
$this->hide_from_output = $hide_from_output;
|
||||||
$this->ignore_errors = $ignore_errors;
|
$this->ignore_errors = $ignore_errors;
|
||||||
$this->call_event_on_finish = $call_event_on_finish;
|
$this->call_event_on_finish = $call_event_on_finish;
|
||||||
|
$this->call_event_data = $call_event_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function decodeOutput(?Activity $activity = null): string
|
public static function decodeOutput(?Activity $activity = null): string
|
||||||
@ -57,7 +60,7 @@ public static function decodeOutput(?Activity $activity = null): string
|
|||||||
$decoded = json_decode(
|
$decoded = json_decode(
|
||||||
data_get($activity, 'description'),
|
data_get($activity, 'description'),
|
||||||
associative: true,
|
associative: true,
|
||||||
flags: JSON_THROW_ON_ERROR
|
flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE
|
||||||
);
|
);
|
||||||
} catch (\JsonException $exception) {
|
} catch (\JsonException $exception) {
|
||||||
return '';
|
return '';
|
||||||
@ -66,7 +69,7 @@ public static function decodeOutput(?Activity $activity = null): string
|
|||||||
return collect($decoded)
|
return collect($decoded)
|
||||||
->sortBy(fn ($i) => $i['order'])
|
->sortBy(fn ($i) => $i['order'])
|
||||||
->map(fn ($i) => $i['output'])
|
->map(fn ($i) => $i['output'])
|
||||||
->implode("");
|
->implode('');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __invoke(): ProcessResult
|
public function __invoke(): ProcessResult
|
||||||
@ -111,13 +114,20 @@ public function __invoke(): ProcessResult
|
|||||||
}
|
}
|
||||||
if ($this->call_event_on_finish) {
|
if ($this->call_event_on_finish) {
|
||||||
try {
|
try {
|
||||||
|
if ($this->call_event_data) {
|
||||||
|
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||||
|
'data' => $this->call_event_data,
|
||||||
|
]));
|
||||||
|
} else {
|
||||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||||
'userId' => $this->activity->causer_id,
|
'userId' => $this->activity->causer_id,
|
||||||
]));
|
]));
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $processResult;
|
return $processResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,8 +165,7 @@ protected function elapsedTime(): int
|
|||||||
|
|
||||||
public function encodeOutput($type, $output)
|
public function encodeOutput($type, $output)
|
||||||
{
|
{
|
||||||
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
|
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
$outputStack[] = [
|
$outputStack[] = [
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
'output' => $output,
|
'output' => $output,
|
||||||
@ -165,15 +174,16 @@ public function encodeOutput($type, $output)
|
|||||||
'order' => $this->getLatestCounter(),
|
'order' => $this->getLatestCounter(),
|
||||||
];
|
];
|
||||||
|
|
||||||
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR);
|
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getLatestCounter(): int
|
protected function getLatestCounter(): int
|
||||||
{
|
{
|
||||||
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
|
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
|
||||||
if ($description === null || count($description) === 0) {
|
if ($description === null || count($description) === 0) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return end($description)['order'] + 1;
|
return end($description)['order'] + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
app/Actions/Database/RestartDatabase.php
Normal file
29
app/Actions/Database/RestartDatabase.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
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 RestartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
StopDatabase::run($database);
|
||||||
|
|
||||||
|
return StartDatabase::run($database);
|
||||||
|
}
|
||||||
|
}
|
167
app/Actions/Database/StartClickhouse.php
Normal file
167
app/Actions/Database/StartClickhouse.php
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class StartClickhouse
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneClickhouse $database;
|
||||||
|
|
||||||
|
public array $commands = [];
|
||||||
|
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneClickhouse $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();
|
||||||
|
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
|
||||||
|
$docker_compose = [
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'ulimits' => [
|
||||||
|
'nofile' => [
|
||||||
|
'soft' => 262144,
|
||||||
|
'hard' => 262144,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 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()) {
|
||||||
|
$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($persistent_file_volumes) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||||
|
return "$item->fs_path:$item->mount_path";
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
if (count($volume_names) > 0) {
|
||||||
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
}
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
|
$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 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) {
|
||||||
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
|
} else {
|
||||||
|
$volume_name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
}
|
57
app/Actions/Database/StartDatabase.php
Normal file
57
app/Actions/Database/StartDatabase.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
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 StartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
switch ($database->getMorphClass()) {
|
||||||
|
case 'App\Models\StandalonePostgresql':
|
||||||
|
$activity = StartPostgresql::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneRedis':
|
||||||
|
$activity = StartRedis::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMongodb':
|
||||||
|
$activity = StartMongodb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMysql':
|
||||||
|
$activity = StartMysql::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMariadb':
|
||||||
|
$activity = StartMariadb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneKeydb':
|
||||||
|
$activity = StartKeydb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneDragonfly':
|
||||||
|
$activity = StartDragonfly::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneClickhouse':
|
||||||
|
$activity = StartClickhouse::run($database);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($database->is_public && $database->public_port) {
|
||||||
|
StartDatabaseProxy::dispatch($database);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,9 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
use App\Models\StandaloneMongodb;
|
use App\Models\StandaloneMongodb;
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
@ -15,7 +18,7 @@ class StartDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase $database)
|
||||||
{
|
{
|
||||||
$internalPort = null;
|
$internalPort = null;
|
||||||
$type = $database->getMorphClass();
|
$type = $database->getMorphClass();
|
||||||
@ -25,7 +28,8 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
|||||||
$proxyContainerName = "{$database->uuid}-proxy";
|
$proxyContainerName = "{$database->uuid}-proxy";
|
||||||
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
$databaseType = $database->databaseType();
|
$databaseType = $database->databaseType();
|
||||||
$network = data_get($database, 'service.destination.network');
|
// $connectPredefined = data_get($database, 'service.connect_to_docker_network');
|
||||||
|
$network = $database->service->uuid;
|
||||||
$server = data_get($database, 'service.destination.server');
|
$server = data_get($database, 'service.destination.server');
|
||||||
$proxyContainerName = "{$database->service->uuid}-proxy";
|
$proxyContainerName = "{$database->service->uuid}-proxy";
|
||||||
switch ($databaseType) {
|
switch ($databaseType) {
|
||||||
@ -49,6 +53,18 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
|||||||
$type = 'App\Models\StandaloneRedis';
|
$type = 'App\Models\StandaloneRedis';
|
||||||
$containerName = "redis-{$database->service->uuid}";
|
$containerName = "redis-{$database->service->uuid}";
|
||||||
break;
|
break;
|
||||||
|
case 'standalone-keydb':
|
||||||
|
$type = 'App\Models\StandaloneKeydb';
|
||||||
|
$containerName = "keydb-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-dragonfly':
|
||||||
|
$type = 'App\Models\StandaloneDragonfly';
|
||||||
|
$containerName = "dragonfly-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-clickhouse':
|
||||||
|
$type = 'App\Models\StandaloneClickhouse';
|
||||||
|
$containerName = "clickhouse-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($type === 'App\Models\StandaloneRedis') {
|
if ($type === 'App\Models\StandaloneRedis') {
|
||||||
@ -61,6 +77,12 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
|||||||
$internalPort = 3306;
|
$internalPort = 3306;
|
||||||
} elseif ($type === 'App\Models\StandaloneMariadb') {
|
} elseif ($type === 'App\Models\StandaloneMariadb') {
|
||||||
$internalPort = 3306;
|
$internalPort = 3306;
|
||||||
|
} elseif ($type === 'App\Models\StandaloneKeydb') {
|
||||||
|
$internalPort = 6379;
|
||||||
|
} elseif ($type === 'App\Models\StandaloneDragonfly') {
|
||||||
|
$internalPort = 6379;
|
||||||
|
} elseif ($type === 'App\Models\StandaloneClickhouse') {
|
||||||
|
$internalPort = 9000;
|
||||||
}
|
}
|
||||||
$configuration_dir = database_proxy_dir($database->uuid);
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
$nginxconf = <<<EOF
|
$nginxconf = <<<EOF
|
||||||
@ -79,20 +101,19 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF;
|
EOF;
|
||||||
$dockerfile = <<< EOF
|
$dockerfile = <<< 'EOF'
|
||||||
FROM nginx:stable-alpine
|
FROM nginx:stable-alpine
|
||||||
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
EOF;
|
EOF;
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$proxyContainerName => [
|
$proxyContainerName => [
|
||||||
'build' => [
|
'build' => [
|
||||||
'context' => $configuration_dir,
|
'context' => $configuration_dir,
|
||||||
'dockerfile' => 'Dockerfile',
|
'dockerfile' => 'Dockerfile',
|
||||||
],
|
],
|
||||||
'image' => "nginx:stable-alpine",
|
'image' => 'nginx:stable-alpine',
|
||||||
'container_name' => $proxyContainerName,
|
'container_name' => $proxyContainerName,
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'ports' => [
|
'ports' => [
|
||||||
@ -109,26 +130,27 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
|||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
'timeout' => '5s',
|
'timeout' => '5s',
|
||||||
'retries' => 3,
|
'retries' => 3,
|
||||||
'start_period' => '1s'
|
'start_period' => '1s',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
]
|
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$network => [
|
$network => [
|
||||||
'external' => true,
|
'external' => true,
|
||||||
'name' => $network,
|
'name' => $network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
|
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
|
||||||
$nginxconf_base64 = base64_encode($nginxconf);
|
$nginxconf_base64 = base64_encode($nginxconf);
|
||||||
$dockerfile_base64 = base64_encode($dockerfile);
|
$dockerfile_base64 = base64_encode($dockerfile);
|
||||||
|
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
|
||||||
instant_remote_process([
|
instant_remote_process([
|
||||||
"mkdir -p $configuration_dir",
|
"mkdir -p $configuration_dir",
|
||||||
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
"echo '{$dockerfile_base64}' | base64 -d | tee $configuration_dir/Dockerfile > /dev/null",
|
||||||
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
"echo '{$nginxconf_base64}' | base64 -d | tee $configuration_dir/nginx.conf > /dev/null",
|
||||||
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
"echo '{$dockercompose_base64}' | base64 -d | tee $configuration_dir/docker-compose.yaml > /dev/null",
|
||||||
"docker compose --project-directory {$configuration_dir} pull",
|
"docker compose --project-directory {$configuration_dir} pull",
|
||||||
"docker compose --project-directory {$configuration_dir} up --build -d",
|
"docker compose --project-directory {$configuration_dir} up --build -d",
|
||||||
], $server);
|
], $server);
|
||||||
|
163
app/Actions/Database/StartDragonfly.php
Normal file
163
app/Actions/Database/StartDragonfly.php
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class StartDragonfly
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneDragonfly $database;
|
||||||
|
|
||||||
|
public array $commands = [];
|
||||||
|
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneDragonfly $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
||||||
|
|
||||||
|
$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();
|
||||||
|
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
|
||||||
|
$docker_compose = [
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'command' => $startCommand,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'ulimits' => [
|
||||||
|
'memlock' => '-1',
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => "redis-cli -a {$this->database->dragonfly_password} 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($persistent_file_volumes) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||||
|
return "$item->fs_path:$item->mount_path";
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
if (count($volume_names) > 0) {
|
||||||
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
}
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
|
$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 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) {
|
||||||
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
|
} else {
|
||||||
|
$volume_name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
}
|
183
app/Actions/Database/StartKeydb.php
Normal file
183
app/Actions/Database/StartKeydb.php
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class StartKeydb
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneKeydb $database;
|
||||||
|
|
||||||
|
public array $commands = [];
|
||||||
|
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneKeydb $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$startCommand = "keydb-server --requirepass {$this->database->keydb_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();
|
||||||
|
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
$this->add_custom_keydb();
|
||||||
|
|
||||||
|
$docker_compose = [
|
||||||
|
'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' => "keydb-cli --pass {$this->database->keydb_password} 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($persistent_file_volumes) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||||
|
return "$item->fs_path:$item->mount_path";
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
if (count($volume_names) > 0) {
|
||||||
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
}
|
||||||
|
if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir.'/keydb.conf',
|
||||||
|
'target' => '/etc/keydb/keydb.conf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||||
|
}
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
|
$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 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) {
|
||||||
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
|
} else {
|
||||||
|
$volume_name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function add_custom_keydb()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->keydb_conf) || empty($this->database->keydb_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'keydb.conf';
|
||||||
|
Storage::disk('local')->put("tmp/keydb.conf_{$this->database->uuid}", $this->database->keydb_conf);
|
||||||
|
$path = Storage::path("tmp/keydb.conf_{$this->database->uuid}");
|
||||||
|
instant_scp($path, "{$this->configuration_dir}/{$filename}", $this->database->destination->server);
|
||||||
|
Storage::disk('local')->delete("tmp/keydb.conf_{$this->database->uuid}");
|
||||||
|
}
|
||||||
|
}
|
@ -3,16 +3,17 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class StartMariadb
|
class StartMariadb
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public StandaloneMariadb $database;
|
public StandaloneMariadb $database;
|
||||||
|
|
||||||
public array $commands = [];
|
public array $commands = [];
|
||||||
|
|
||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
public function handle(StandaloneMariadb $database)
|
public function handle(StandaloneMariadb $database)
|
||||||
@ -28,11 +29,11 @@ public function handle(StandaloneMariadb $database)
|
|||||||
];
|
];
|
||||||
|
|
||||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->add_custom_mysql();
|
$this->add_custom_mysql();
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@ -46,11 +47,11 @@ public function handle(StandaloneMariadb $database)
|
|||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"],
|
'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'],
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
'timeout' => '5s',
|
'timeout' => '5s',
|
||||||
'retries' => 10,
|
'retries' => 10,
|
||||||
'start_period' => '5s'
|
'start_period' => '5s',
|
||||||
],
|
],
|
||||||
'mem_limit' => $this->database->limits_memory,
|
'mem_limit' => $this->database->limits_memory,
|
||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
@ -58,15 +59,15 @@ public function handle(StandaloneMariadb $database)
|
|||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => (float) $this->database->limits_cpus,
|
'cpus' => (float) $this->database->limits_cpus,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network => [
|
$this->database->destination->network => [
|
||||||
'external' => true,
|
'external' => true,
|
||||||
'name' => $this->database->destination->network,
|
'name' => $this->database->destination->network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
@ -75,10 +76,10 @@ public function handle(StandaloneMariadb $database)
|
|||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
'driver' => 'fluentd',
|
'driver' => 'fluentd',
|
||||||
'options' => [
|
'options' => [
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
||||||
'fluentd-async' => "true",
|
'fluentd-async' => 'true',
|
||||||
'fluentd-sub-second-precision' => "true",
|
'fluentd-sub-second-precision' => 'true',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
@ -87,10 +88,15 @@ public function handle(StandaloneMariadb $database)
|
|||||||
if (count($persistent_storages) > 0) {
|
if (count($persistent_storages) > 0) {
|
||||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
}
|
}
|
||||||
|
if (count($persistent_file_volumes) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||||
|
return "$item->fs_path:$item->mount_path";
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mariadb_conf)) {
|
if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/custom-config.cnf',
|
'source' => $this->configuration_dir.'/custom-config.cnf',
|
||||||
@ -100,13 +106,14 @@ public function handle(StandaloneMariadb $database)
|
|||||||
}
|
}
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "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 pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo 'Database started.'";
|
$this->commands[] = "echo 'Database started.'";
|
||||||
|
|
||||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,9 +121,14 @@ private function generate_local_persistent_volumes()
|
|||||||
{
|
{
|
||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
|
} else {
|
||||||
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +145,7 @@ private function generate_local_persistent_volumes_only_volume_names()
|
|||||||
'external' => false,
|
'external' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,30 +156,32 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function add_custom_mysql()
|
private function add_custom_mysql()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->mariadb_conf)) {
|
if (is_null($this->database->mariadb_conf) || empty($this->database->mariadb_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'custom-config.cnf';
|
$filename = 'custom-config.cnf';
|
||||||
$content = $this->database->mariadb_conf;
|
$content = $this->database->mariadb_conf;
|
||||||
$content_base64 = base64_encode($content);
|
$content_base64 = base64_encode($content);
|
||||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,23 +3,24 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneMongodb;
|
use App\Models\StandaloneMongodb;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class StartMongodb
|
class StartMongodb
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public StandaloneMongodb $database;
|
public StandaloneMongodb $database;
|
||||||
|
|
||||||
public array $commands = [];
|
public array $commands = [];
|
||||||
|
|
||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
public function handle(StandaloneMongodb $database)
|
public function handle(StandaloneMongodb $database)
|
||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$startCommand = "mongod";
|
$startCommand = 'mongod';
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
@ -30,12 +31,12 @@ public function handle(StandaloneMongodb $database)
|
|||||||
];
|
];
|
||||||
|
|
||||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->add_custom_mongo_conf();
|
$this->add_custom_mongo_conf();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@ -51,13 +52,14 @@ public function handle(StandaloneMongodb $database)
|
|||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
'CMD-SHELL',
|
'CMD',
|
||||||
'mongosh --eval "printjson(db.runCommand(\"ping\"))"'
|
'echo',
|
||||||
|
'ok',
|
||||||
],
|
],
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
'timeout' => '5s',
|
'timeout' => '5s',
|
||||||
'retries' => 10,
|
'retries' => 10,
|
||||||
'start_period' => '5s'
|
'start_period' => '5s',
|
||||||
],
|
],
|
||||||
'mem_limit' => $this->database->limits_memory,
|
'mem_limit' => $this->database->limits_memory,
|
||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
@ -65,15 +67,15 @@ public function handle(StandaloneMongodb $database)
|
|||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => (float) $this->database->limits_cpus,
|
'cpus' => (float) $this->database->limits_cpus,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network => [
|
$this->database->destination->network => [
|
||||||
'external' => true,
|
'external' => true,
|
||||||
'name' => $this->database->destination->network,
|
'name' => $this->database->destination->network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
@ -82,10 +84,10 @@ public function handle(StandaloneMongodb $database)
|
|||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
'driver' => 'fluentd',
|
'driver' => 'fluentd',
|
||||||
'options' => [
|
'options' => [
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
||||||
'fluentd-async' => "true",
|
'fluentd-async' => 'true',
|
||||||
'fluentd-sub-second-precision' => "true",
|
'fluentd-sub-second-precision' => 'true',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
@ -94,10 +96,15 @@ public function handle(StandaloneMongodb $database)
|
|||||||
if (count($persistent_storages) > 0) {
|
if (count($persistent_storages) > 0) {
|
||||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
}
|
}
|
||||||
|
if (count($persistent_file_volumes) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||||
|
return "$item->fs_path:$item->mount_path";
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mongo_conf)) {
|
if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/mongod.conf',
|
'source' => $this->configuration_dir.'/mongod.conf',
|
||||||
@ -116,13 +123,14 @@ public function handle(StandaloneMongodb $database)
|
|||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "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 pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo 'Database started.'";
|
$this->commands[] = "echo 'Database started.'";
|
||||||
|
|
||||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,9 +138,14 @@ private function generate_local_persistent_volumes()
|
|||||||
{
|
{
|
||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
|
} else {
|
||||||
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +162,7 @@ private function generate_local_persistent_volumes_only_volume_names()
|
|||||||
'external' => false,
|
'external' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,34 +173,37 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
$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()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
$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()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function add_custom_mongo_conf()
|
private function add_custom_mongo_conf()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->mongo_conf)) {
|
if (is_null($this->database->mongo_conf) || empty($this->database->mongo_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'mongod.conf';
|
$filename = 'mongod.conf';
|
||||||
$content = $this->database->mongo_conf;
|
$content = $this->database->mongo_conf;
|
||||||
$content_base64 = base64_encode($content);
|
$content_base64 = base64_encode($content);
|
||||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
||||||
}
|
}
|
||||||
|
|
||||||
private function add_default_database()
|
private function add_default_database()
|
||||||
{
|
{
|
||||||
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
|
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
|
||||||
$content_base64 = base64_encode($content);
|
$content_base64 = base64_encode($content);
|
||||||
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
|
$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";
|
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,17 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class StartMysql
|
class StartMysql
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public StandaloneMysql $database;
|
public StandaloneMysql $database;
|
||||||
|
|
||||||
public array $commands = [];
|
public array $commands = [];
|
||||||
|
|
||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
public function handle(StandaloneMysql $database)
|
public function handle(StandaloneMysql $database)
|
||||||
@ -28,11 +29,11 @@ public function handle(StandaloneMysql $database)
|
|||||||
];
|
];
|
||||||
|
|
||||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->add_custom_mysql();
|
$this->add_custom_mysql();
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@ -46,11 +47,11 @@ public function handle(StandaloneMysql $database)
|
|||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"],
|
'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"],
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
'timeout' => '5s',
|
'timeout' => '5s',
|
||||||
'retries' => 10,
|
'retries' => 10,
|
||||||
'start_period' => '5s'
|
'start_period' => '5s',
|
||||||
],
|
],
|
||||||
'mem_limit' => $this->database->limits_memory,
|
'mem_limit' => $this->database->limits_memory,
|
||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
@ -58,15 +59,15 @@ public function handle(StandaloneMysql $database)
|
|||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => (float) $this->database->limits_cpus,
|
'cpus' => (float) $this->database->limits_cpus,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network => [
|
$this->database->destination->network => [
|
||||||
'external' => true,
|
'external' => true,
|
||||||
'name' => $this->database->destination->network,
|
'name' => $this->database->destination->network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
@ -75,10 +76,10 @@ public function handle(StandaloneMysql $database)
|
|||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
'driver' => 'fluentd',
|
'driver' => 'fluentd',
|
||||||
'options' => [
|
'options' => [
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
||||||
'fluentd-async' => "true",
|
'fluentd-async' => 'true',
|
||||||
'fluentd-sub-second-precision' => "true",
|
'fluentd-sub-second-precision' => 'true',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
@ -87,10 +88,15 @@ public function handle(StandaloneMysql $database)
|
|||||||
if (count($persistent_storages) > 0) {
|
if (count($persistent_storages) > 0) {
|
||||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
}
|
}
|
||||||
|
if (count($persistent_file_volumes) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||||
|
return "$item->fs_path:$item->mount_path";
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mysql_conf)) {
|
if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/custom-config.cnf',
|
'source' => $this->configuration_dir.'/custom-config.cnf',
|
||||||
@ -100,13 +106,14 @@ public function handle(StandaloneMysql $database)
|
|||||||
}
|
}
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "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 pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo 'Database started.'";
|
$this->commands[] = "echo 'Database started.'";
|
||||||
|
|
||||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,9 +121,14 @@ private function generate_local_persistent_volumes()
|
|||||||
{
|
{
|
||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
|
} else {
|
||||||
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +145,7 @@ private function generate_local_persistent_volumes_only_volume_names()
|
|||||||
'external' => false,
|
'external' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,30 +156,32 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function add_custom_mysql()
|
private function add_custom_mysql()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->mysql_conf)) {
|
if (is_null($this->database->mysql_conf) || empty($this->database->mysql_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'custom-config.cnf';
|
$filename = 'custom-config.cnf';
|
||||||
$content = $this->database->mysql_conf;
|
$content = $this->database->mysql_conf;
|
||||||
$content_base64 = base64_encode($content);
|
$content_base64 = base64_encode($content);
|
||||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,19 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class StartPostgresql
|
class StartPostgresql
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql $database;
|
||||||
|
|
||||||
public array $commands = [];
|
public array $commands = [];
|
||||||
|
|
||||||
public array $init_scripts = [];
|
public array $init_scripts = [];
|
||||||
|
|
||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
public function handle(StandalonePostgresql $database)
|
public function handle(StandalonePostgresql $database)
|
||||||
@ -25,17 +27,17 @@ public function handle(StandalonePostgresql $database)
|
|||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/",
|
||||||
];
|
];
|
||||||
|
|
||||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->generate_init_scripts();
|
$this->generate_init_scripts();
|
||||||
$this->add_custom_conf();
|
$this->add_custom_conf();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@ -50,13 +52,13 @@ public function handle(StandalonePostgresql $database)
|
|||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
"CMD-SHELL",
|
'CMD-SHELL',
|
||||||
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1"
|
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1",
|
||||||
],
|
],
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
'timeout' => '5s',
|
'timeout' => '5s',
|
||||||
'retries' => 10,
|
'retries' => 10,
|
||||||
'start_period' => '5s'
|
'start_period' => '5s',
|
||||||
],
|
],
|
||||||
'mem_limit' => $this->database->limits_memory,
|
'mem_limit' => $this->database->limits_memory,
|
||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
@ -64,28 +66,27 @@ public function handle(StandalonePostgresql $database)
|
|||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => (float) $this->database->limits_cpus,
|
'cpus' => (float) $this->database->limits_cpus,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network => [
|
$this->database->destination->network => [
|
||||||
'external' => true,
|
'external' => true,
|
||||||
'name' => $this->database->destination->network,
|
'name' => $this->database->destination->network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
ray('Log Drain Enabled');
|
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
'driver' => 'fluentd',
|
'driver' => 'fluentd',
|
||||||
'options' => [
|
'options' => [
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
||||||
'fluentd-async' => "true",
|
'fluentd-async' => 'true',
|
||||||
'fluentd-sub-second-precision' => "true",
|
'fluentd-sub-second-precision' => 'true',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
@ -94,6 +95,11 @@ public function handle(StandalonePostgresql $database)
|
|||||||
if (count($persistent_storages) > 0) {
|
if (count($persistent_storages) > 0) {
|
||||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
}
|
}
|
||||||
|
if (count($persistent_file_volumes) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||||
|
return "$item->fs_path:$item->mount_path";
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
@ -107,7 +113,7 @@ public function handle(StandalonePostgresql $database)
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->postgres_conf)) {
|
if (! is_null($this->database->postgres_conf) && ! empty($this->database->postgres_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/custom-postgres.conf',
|
'source' => $this->configuration_dir.'/custom-postgres.conf',
|
||||||
@ -122,13 +128,14 @@ public function handle(StandalonePostgresql $database)
|
|||||||
}
|
}
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "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 pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo 'Database started.'";
|
$this->commands[] = "echo 'Database started.'";
|
||||||
|
|
||||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,9 +143,14 @@ private function generate_local_persistent_volumes()
|
|||||||
{
|
{
|
||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
|
} else {
|
||||||
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,32 +167,32 @@ private function generate_local_persistent_volumes_only_volume_names()
|
|||||||
'external' => false,
|
'external' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_environment_variables()
|
private function generate_environment_variables()
|
||||||
{
|
{
|
||||||
$environment_variables = collect();
|
$environment_variables = collect();
|
||||||
ray('Generate Environment Variables')->green();
|
|
||||||
ray($this->database->runtime_environment_variables)->green();
|
|
||||||
foreach ($this->database->runtime_environment_variables as $env) {
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('PGUSER'))->isEmpty()) {
|
||||||
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,18 +205,24 @@ private function generate_init_scripts()
|
|||||||
$filename = data_get($init_script, 'filename');
|
$filename = data_get($init_script, 'filename');
|
||||||
$content = data_get($init_script, 'content');
|
$content = data_get($init_script, 'content');
|
||||||
$content_base64 = base64_encode($content);
|
$content_base64 = base64_encode($content);
|
||||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/{$filename} > /dev/null";
|
||||||
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function add_custom_conf()
|
private function add_custom_conf()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->postgres_conf)) {
|
if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'custom-postgres.conf';
|
$filename = 'custom-postgres.conf';
|
||||||
$content = $this->database->postgres_conf;
|
$content = $this->database->postgres_conf;
|
||||||
|
if (! str($content)->contains('listen_addresses')) {
|
||||||
|
$content .= "\nlisten_addresses = '*'";
|
||||||
|
$this->database->postgres_conf = $content;
|
||||||
|
$this->database->save();
|
||||||
|
}
|
||||||
$content_base64 = base64_encode($content);
|
$content_base64 = base64_encode($content);
|
||||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,18 @@
|
|||||||
|
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class StartRedis
|
class StartRedis
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public StandaloneRedis $database;
|
public StandaloneRedis $database;
|
||||||
public array $commands = [];
|
|
||||||
public string $configuration_dir;
|
|
||||||
|
|
||||||
|
public array $commands = [];
|
||||||
|
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
public function handle(StandaloneRedis $database)
|
public function handle(StandaloneRedis $database)
|
||||||
{
|
{
|
||||||
@ -32,12 +32,12 @@ public function handle(StandaloneRedis $database)
|
|||||||
];
|
];
|
||||||
|
|
||||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->add_custom_redis();
|
$this->add_custom_redis();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@ -55,12 +55,12 @@ public function handle(StandaloneRedis $database)
|
|||||||
'test' => [
|
'test' => [
|
||||||
'CMD-SHELL',
|
'CMD-SHELL',
|
||||||
'redis-cli',
|
'redis-cli',
|
||||||
'ping'
|
'ping',
|
||||||
],
|
],
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
'timeout' => '5s',
|
'timeout' => '5s',
|
||||||
'retries' => 10,
|
'retries' => 10,
|
||||||
'start_period' => '5s'
|
'start_period' => '5s',
|
||||||
],
|
],
|
||||||
'mem_limit' => $this->database->limits_memory,
|
'mem_limit' => $this->database->limits_memory,
|
||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
@ -68,15 +68,15 @@ public function handle(StandaloneRedis $database)
|
|||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => (float) $this->database->limits_cpus,
|
'cpus' => (float) $this->database->limits_cpus,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network => [
|
$this->database->destination->network => [
|
||||||
'external' => true,
|
'external' => true,
|
||||||
'name' => $this->database->destination->network,
|
'name' => $this->database->destination->network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
@ -85,10 +85,10 @@ public function handle(StandaloneRedis $database)
|
|||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
'driver' => 'fluentd',
|
'driver' => 'fluentd',
|
||||||
'options' => [
|
'options' => [
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
||||||
'fluentd-async' => "true",
|
'fluentd-async' => 'true',
|
||||||
'fluentd-sub-second-precision' => "true",
|
'fluentd-sub-second-precision' => 'true',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
@ -97,10 +97,15 @@ public function handle(StandaloneRedis $database)
|
|||||||
if (count($persistent_storages) > 0) {
|
if (count($persistent_storages) > 0) {
|
||||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
}
|
}
|
||||||
|
if (count($persistent_file_volumes) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||||
|
return "$item->fs_path:$item->mount_path";
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->redis_conf)) {
|
if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/redis.conf',
|
'source' => $this->configuration_dir.'/redis.conf',
|
||||||
@ -111,13 +116,14 @@ public function handle(StandaloneRedis $database)
|
|||||||
}
|
}
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "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 pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo 'Database started.'";
|
$this->commands[] = "echo 'Database started.'";
|
||||||
|
|
||||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,9 +131,14 @@ private function generate_local_persistent_volumes()
|
|||||||
{
|
{
|
||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
|
} else {
|
||||||
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +155,7 @@ private function generate_local_persistent_volumes_only_volume_names()
|
|||||||
'external' => false,
|
'external' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,15 +166,16 @@ private function generate_environment_variables()
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function add_custom_redis()
|
private function add_custom_redis()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->redis_conf)) {
|
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'redis.conf';
|
$filename = 'redis.conf';
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Events\DatabaseStatusChanged;
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
use App\Models\StandaloneMongodb;
|
use App\Models\StandaloneMongodb;
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
@ -14,7 +16,7 @@ class StopDatabase
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
{
|
{
|
||||||
$server = $database->destination->server;
|
$server = $database->destination->server;
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
@ -27,7 +29,5 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
|||||||
if ($database->is_public) {
|
if ($database->is_public) {
|
||||||
StopDatabaseProxy::run($database);
|
StopDatabaseProxy::run($database);
|
||||||
}
|
}
|
||||||
// TODO: make notification for services
|
|
||||||
// $database->environment->project->team->notify(new StatusChanged($database));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Events\DatabaseStatusChanged;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
use App\Models\StandaloneMongodb;
|
use App\Models\StandaloneMongodb;
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
@ -14,14 +18,16 @@ class StopDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|ServiceDatabase|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
{
|
{
|
||||||
$server = data_get($database, 'destination.server');
|
$server = data_get($database, 'destination.server');
|
||||||
|
$uuid = $database->uuid;
|
||||||
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$uuid = $database->service->uuid;
|
||||||
$server = data_get($database, 'service.server');
|
$server = data_get($database, 'service.server');
|
||||||
}
|
}
|
||||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $server);
|
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
||||||
$database->is_public = false;
|
|
||||||
$database->save();
|
$database->save();
|
||||||
|
DatabaseStatusChanged::dispatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
686
app/Actions/Docker/GetContainersStatus.php
Normal file
686
app/Actions/Docker/GetContainersStatus.php
Normal file
@ -0,0 +1,686 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Docker;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Actions\Shared\ComplexStatusCheck;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
|
use App\Notifications\Container\ContainerStopped;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class GetContainersStatus
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public $applications;
|
||||||
|
|
||||||
|
public $server;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
// if (isDev()) {
|
||||||
|
// $server = Server::find(0);
|
||||||
|
// }
|
||||||
|
$this->server = $server;
|
||||||
|
if (! $this->server->isFunctional()) {
|
||||||
|
return 'Server is not ready.';
|
||||||
|
}
|
||||||
|
$this->applications = $this->server->applications();
|
||||||
|
$skip_these_applications = collect([]);
|
||||||
|
foreach ($this->applications as $application) {
|
||||||
|
if ($application->additional_servers->count() > 0) {
|
||||||
|
$skip_these_applications->push($application);
|
||||||
|
ComplexStatusCheck::run($application);
|
||||||
|
$this->applications = $this->applications->filter(function ($value, $key) use ($application) {
|
||||||
|
return $value->id !== $application->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) {
|
||||||
|
return ! $skip_these_applications->pluck('id')->contains($value->id);
|
||||||
|
});
|
||||||
|
$this->old_way();
|
||||||
|
// if ($this->server->isSwarm()) {
|
||||||
|
// $this->old_way();
|
||||||
|
// } else {
|
||||||
|
// if (!$this->server->is_metrics_enabled) {
|
||||||
|
// $this->old_way();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// $sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this->server, false);
|
||||||
|
// $sentinel_found = json_decode($sentinel_found, true);
|
||||||
|
// $status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||||
|
// if ($status === 'running') {
|
||||||
|
// ray('Checking with Sentinel');
|
||||||
|
// $this->sentinel();
|
||||||
|
// } else {
|
||||||
|
// ray('Checking the Old way');
|
||||||
|
// $this->old_way();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sentinel()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$containers = $this->server->getContainers();
|
||||||
|
if ($containers->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$databases = $this->server->databases();
|
||||||
|
$services = $this->server->services()->get();
|
||||||
|
$previews = $this->server->previews();
|
||||||
|
$foundApplications = [];
|
||||||
|
$foundApplicationPreviews = [];
|
||||||
|
$foundDatabases = [];
|
||||||
|
$foundServices = [];
|
||||||
|
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$labels = Arr::undot(data_get($container, 'labels'));
|
||||||
|
$containerStatus = data_get($container, 'state');
|
||||||
|
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
|
if ($applicationId) {
|
||||||
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
|
if ($pullRequestId) {
|
||||||
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
|
if ($preview) {
|
||||||
|
$foundApplicationPreviews[] = $preview->id;
|
||||||
|
$statusFromDb = $preview->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if ($application) {
|
||||||
|
$foundApplications[] = $application->id;
|
||||||
|
$statusFromDb = $application->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$application->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
|
$type = data_get($labels, 'coolify.type');
|
||||||
|
if ($uuid) {
|
||||||
|
if ($type === 'service') {
|
||||||
|
$database_id = data_get($labels, 'coolify.service.subId');
|
||||||
|
if ($database_id) {
|
||||||
|
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||||
|
if ($service_db) {
|
||||||
|
$uuid = $service_db->service->uuid;
|
||||||
|
$isPublic = data_get($service_db, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
// TODO: fix this with sentinel
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'name') === "$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($service_db);
|
||||||
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$database = $databases->where('uuid', $uuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
|
$foundDatabases[] = $database->id;
|
||||||
|
$statusFromDb = $database->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$database->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
// TODO: fix this with sentinel
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'name') === "$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data_get($container, 'name') === 'coolify-db') {
|
||||||
|
$foundDatabases[] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
|
if ($serviceLabelId) {
|
||||||
|
$subType = data_get($labels, 'coolify.service.subType');
|
||||||
|
$subId = data_get($labels, 'coolify.service.subId');
|
||||||
|
$service = $services->where('id', $serviceLabelId)->first();
|
||||||
|
if (! $service) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$service = $service->applications()->where('id', $subId)->first();
|
||||||
|
} else {
|
||||||
|
$service = $service->databases()->where('id', $subId)->first();
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
$foundServices[] = "$service->id-$service->name";
|
||||||
|
$statusFromDb = $service->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
// ray('Updating status: ' . $containerStatus);
|
||||||
|
$service->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = collect([]);
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = $exitedServices->unique('id');
|
||||||
|
foreach ($exitedServices as $exitedService) {
|
||||||
|
if (str($exitedService->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = data_get($exitedService, 'name');
|
||||||
|
$fqdn = data_get($exitedService, 'fqdn');
|
||||||
|
if ($name) {
|
||||||
|
if ($fqdn) {
|
||||||
|
$containerName = "$name, available at $fqdn";
|
||||||
|
} else {
|
||||||
|
$containerName = $name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($fqdn) {
|
||||||
|
$containerName = $fqdn;
|
||||||
|
} else {
|
||||||
|
$containerName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
|
$serviceUuid = data_get($service, 'uuid');
|
||||||
|
$environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
$exitedService->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||||
|
foreach ($notRunningApplications as $applicationId) {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if (str($application->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($application, 'name');
|
||||||
|
$fqdn = data_get($application, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
|
$applicationUuid = data_get($application, 'uuid');
|
||||||
|
$environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environment) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
|
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
|
$preview = $previews->where('id', $previewId)->first();
|
||||||
|
if (str($preview->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$preview->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($preview, 'name');
|
||||||
|
$fqdn = data_get($preview, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
|
$environmentName = data_get($preview, 'application.environment.name');
|
||||||
|
$applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
|
foreach ($notRunningDatabases as $database) {
|
||||||
|
$database = $databases->where('id', $database)->first();
|
||||||
|
if (str($database->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($database, 'name');
|
||||||
|
$fqdn = data_get($database, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name;
|
||||||
|
|
||||||
|
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
|
$environmentName = data_get($database, 'environment.name');
|
||||||
|
$databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if proxy is running
|
||||||
|
$this->server->proxyType();
|
||||||
|
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
// TODO: fix this with sentinel
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'name') === 'coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function old_way()
|
||||||
|
{
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
|
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||||
|
} else {
|
||||||
|
// Precheck for containers
|
||||||
|
$containers = instant_remote_process(['docker container ls -q'], $this->server, false);
|
||||||
|
if (! $containers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
|
$containerReplicates = null;
|
||||||
|
}
|
||||||
|
if (is_null($containers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
if ($containerReplicates) {
|
||||||
|
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||||
|
foreach ($containerReplicates as $containerReplica) {
|
||||||
|
$name = data_get($containerReplica, 'Name');
|
||||||
|
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
|
if (data_get($container, 'Spec.Name') === $name) {
|
||||||
|
$replicas = data_get($containerReplica, 'Replicas');
|
||||||
|
$running = str($replicas)->explode('/')[0];
|
||||||
|
$total = str($replicas)->explode('/')[1];
|
||||||
|
if ($running === $total) {
|
||||||
|
data_set($container, 'State.Status', 'running');
|
||||||
|
data_set($container, 'State.Health.Status', 'healthy');
|
||||||
|
} else {
|
||||||
|
data_set($container, 'State.Status', 'starting');
|
||||||
|
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $container;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$databases = $this->server->databases();
|
||||||
|
$services = $this->server->services()->get();
|
||||||
|
$previews = $this->server->previews();
|
||||||
|
$foundApplications = [];
|
||||||
|
$foundApplicationPreviews = [];
|
||||||
|
$foundDatabases = [];
|
||||||
|
$foundServices = [];
|
||||||
|
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$labels = data_get($container, 'Spec.Labels');
|
||||||
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
|
} else {
|
||||||
|
$labels = data_get($container, 'Config.Labels');
|
||||||
|
}
|
||||||
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
|
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
|
if ($applicationId) {
|
||||||
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
|
if ($pullRequestId) {
|
||||||
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
|
if ($preview) {
|
||||||
|
$foundApplicationPreviews[] = $preview->id;
|
||||||
|
$statusFromDb = $preview->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if ($application) {
|
||||||
|
$foundApplications[] = $application->id;
|
||||||
|
$statusFromDb = $application->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$application->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
|
$type = data_get($labels, 'coolify.type');
|
||||||
|
|
||||||
|
if ($uuid) {
|
||||||
|
if ($type === 'service') {
|
||||||
|
$database_id = data_get($labels, 'coolify.service.subId');
|
||||||
|
if ($database_id) {
|
||||||
|
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||||
|
if ($service_db) {
|
||||||
|
$uuid = data_get($service_db, 'service.uuid');
|
||||||
|
if ($uuid) {
|
||||||
|
$isPublic = data_get($service_db, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($service_db);
|
||||||
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$database = $databases->where('uuid', $uuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
|
$foundDatabases[] = $database->id;
|
||||||
|
$statusFromDb = $database->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$database->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data_get($container, 'Name') === '/coolify-db') {
|
||||||
|
$foundDatabases[] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
|
if ($serviceLabelId) {
|
||||||
|
$subType = data_get($labels, 'coolify.service.subType');
|
||||||
|
$subId = data_get($labels, 'coolify.service.subId');
|
||||||
|
$service = $services->where('id', $serviceLabelId)->first();
|
||||||
|
if (! $service) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$service = $service->applications()->where('id', $subId)->first();
|
||||||
|
} else {
|
||||||
|
$service = $service->databases()->where('id', $subId)->first();
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
$foundServices[] = "$service->id-$service->name";
|
||||||
|
$statusFromDb = $service->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
// ray('Updating status: ' . $containerStatus);
|
||||||
|
$service->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = collect([]);
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = $exitedServices->unique('id');
|
||||||
|
foreach ($exitedServices as $exitedService) {
|
||||||
|
if (str($exitedService->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = data_get($exitedService, 'name');
|
||||||
|
$fqdn = data_get($exitedService, 'fqdn');
|
||||||
|
if ($name) {
|
||||||
|
if ($fqdn) {
|
||||||
|
$containerName = "$name, available at $fqdn";
|
||||||
|
} else {
|
||||||
|
$containerName = $name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($fqdn) {
|
||||||
|
$containerName = $fqdn;
|
||||||
|
} else {
|
||||||
|
$containerName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
|
$serviceUuid = data_get($service, 'uuid');
|
||||||
|
$environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
$exitedService->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||||
|
foreach ($notRunningApplications as $applicationId) {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if (str($application->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($application, 'name');
|
||||||
|
$fqdn = data_get($application, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
|
$applicationUuid = data_get($application, 'uuid');
|
||||||
|
$environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environment) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
|
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
|
$preview = $previews->where('id', $previewId)->first();
|
||||||
|
if (str($preview->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$preview->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($preview, 'name');
|
||||||
|
$fqdn = data_get($preview, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
|
$environmentName = data_get($preview, 'application.environment.name');
|
||||||
|
$applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
|
foreach ($notRunningDatabases as $database) {
|
||||||
|
$database = $databases->where('id', $database)->first();
|
||||||
|
if (str($database->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($database, 'name');
|
||||||
|
$fqdn = data_get($database, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name;
|
||||||
|
|
||||||
|
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
|
$environmentName = data_get($database, 'environment.name');
|
||||||
|
$databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if proxy is running
|
||||||
|
$this->server->proxyType();
|
||||||
|
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -66,6 +66,7 @@ public function create(array $input): User
|
|||||||
}
|
}
|
||||||
// Set session variable
|
// Set session variable
|
||||||
session(['currentTeam' => $user->currentTeam = $team]);
|
session(['currentTeam' => $user->currentTeam = $team]);
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
|
||||||
class CheckResaleLicense
|
class CheckResaleLicense
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -18,6 +18,7 @@ public function handle()
|
|||||||
$settings->update([
|
$settings->update([
|
||||||
'is_resale_license_active' => true,
|
'is_resale_license_active' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// if (!$settings->resale_license) {
|
// if (!$settings->resale_license) {
|
||||||
@ -38,6 +39,7 @@ public function handle()
|
|||||||
$settings->update([
|
$settings->update([
|
||||||
'is_resale_license_active' => true,
|
'is_resale_license_active' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$data = Http::withHeaders([
|
$data = Http::withHeaders([
|
||||||
@ -51,6 +53,7 @@ public function handle()
|
|||||||
$settings->update([
|
$settings->update([
|
||||||
'is_resale_license_active' => true,
|
'is_resale_license_active' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data_get($data, 'license_key.status') === 'active') {
|
if (data_get($data, 'license_key.status') === 'active') {
|
||||||
|
@ -2,27 +2,32 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class CheckConfiguration
|
class CheckConfiguration
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, bool $reset = false)
|
public function handle(Server $server, bool $reset = false)
|
||||||
{
|
{
|
||||||
$proxy_path = get_proxy_path();
|
$proxyType = $server->proxyType();
|
||||||
$proxy_configuration = instant_remote_process([
|
if ($proxyType === 'NONE') {
|
||||||
|
return 'OK';
|
||||||
|
}
|
||||||
|
$proxy_path = $server->proxyPath();
|
||||||
|
$payload = [
|
||||||
"mkdir -p $proxy_path",
|
"mkdir -p $proxy_path",
|
||||||
"cat $proxy_path/docker-compose.yml",
|
"cat $proxy_path/docker-compose.yml",
|
||||||
], $server, false);
|
];
|
||||||
|
$proxy_configuration = instant_remote_process($payload, $server, false);
|
||||||
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
|
||||||
}
|
}
|
||||||
if (! $proxy_configuration || is_null($proxy_configuration)) {
|
if (! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
throw new \Exception("Could not generate proxy configuration");
|
throw new \Exception('Could not generate proxy configuration');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $proxy_configuration;
|
return $proxy_configuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,31 @@
|
|||||||
class CheckProxy
|
class CheckProxy
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, $fromUI = false)
|
public function handle(Server $server, $fromUI = false)
|
||||||
{
|
{
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($server->isBuildServer()) {
|
||||||
|
if ($server->proxy) {
|
||||||
|
$server->proxy = null;
|
||||||
|
$server->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$proxyType = $server->proxyType();
|
||||||
|
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
|
||||||
|
if (! $uptime) {
|
||||||
|
throw new \Exception($error);
|
||||||
|
}
|
||||||
if (! $server->isProxyShouldRun()) {
|
if (! $server->isProxyShouldRun()) {
|
||||||
if ($fromUI) {
|
if ($fromUI) {
|
||||||
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
|
throw new \Exception('Proxy should not run. You selected the Custom Proxy.');
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -24,12 +44,17 @@ public function handle(Server $server, $fromUI = false)
|
|||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
$status = getContainerStatus($server, 'coolify-proxy');
|
$status = getContainerStatus($server, 'coolify-proxy');
|
||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
$server->proxy->set('status', 'running');
|
$server->proxy->set('status', 'running');
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($server->settings->is_cloudflare_tunnel) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$ip = $server->ip;
|
$ip = $server->ip;
|
||||||
@ -55,6 +80,7 @@ public function handle(Server $server, $fromUI = false)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class SaveConfiguration
|
class SaveConfiguration
|
||||||
@ -15,15 +14,15 @@ public function handle(Server $server, ?string $proxy_settings = null)
|
|||||||
if (is_null($proxy_settings)) {
|
if (is_null($proxy_settings)) {
|
||||||
$proxy_settings = CheckConfiguration::run($server, true);
|
$proxy_settings = CheckConfiguration::run($server, true);
|
||||||
}
|
}
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = $server->proxyPath();
|
||||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||||
|
|
||||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_saved_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
return instant_remote_process([
|
return instant_remote_process([
|
||||||
"mkdir -p $proxy_path",
|
"mkdir -p $proxy_path",
|
||||||
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
|
"echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null",
|
||||||
], $server);
|
], $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,63 +2,70 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Events\ProxyStatusChanged;
|
use App\Events\ProxyStarted;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
class StartProxy
|
class StartProxy
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Server $server, bool $async = true): string|Activity
|
|
||||||
|
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
if ($proxyType === 'NONE') {
|
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = $server->proxyPath();
|
||||||
$configuration = CheckConfiguration::run($server);
|
$configuration = CheckConfiguration::run($server);
|
||||||
if (! $configuration) {
|
if (! $configuration) {
|
||||||
throw new \Exception("Configuration is not synced");
|
throw new \Exception('Configuration is not synced');
|
||||||
}
|
}
|
||||||
SaveConfiguration::run($server, $configuration);
|
SaveConfiguration::run($server, $configuration);
|
||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
"mkdir -p $proxy_path/dynamic",
|
||||||
|
"cd $proxy_path",
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
'docker stack deploy -c docker-compose.yml coolify-proxy',
|
||||||
"echo 'Proxy started successfully.'"
|
"echo 'Proxy started successfully.'",
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
$caddfile = 'import /dynamic/*.caddy';
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
"mkdir -p $proxy_path/dynamic",
|
||||||
|
"cd $proxy_path",
|
||||||
|
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Pulling docker image.'",
|
||||||
'docker compose pull',
|
'docker compose pull',
|
||||||
"echo 'Stopping existing coolify-proxy.'",
|
"echo 'Stopping existing coolify-proxy.'",
|
||||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
'docker compose down -v --remove-orphans > /dev/null 2>&1',
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
'docker compose up -d --remove-orphans',
|
'docker compose up -d --remove-orphans',
|
||||||
"echo 'Proxy started successfully.'"
|
"echo 'Proxy started successfully.'",
|
||||||
]);
|
]);
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($async) {
|
if ($async) {
|
||||||
$activity = remote_process($commands, $server);
|
$activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
|
||||||
|
|
||||||
return $activity;
|
return $activity;
|
||||||
} else {
|
} else {
|
||||||
instant_remote_process($commands, $server);
|
instant_remote_process($commands, $server);
|
||||||
$server->proxy->set('status', 'running');
|
$server->proxy->set('status', 'running');
|
||||||
$server->proxy->set('type', $proxyType);
|
$server->proxy->set('type', $proxyType);
|
||||||
$server->save();
|
$server->save();
|
||||||
|
ProxyStarted::dispatch($server);
|
||||||
|
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class CleanupDocker
|
class CleanupDocker
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, bool $force = true)
|
public function handle(Server $server, bool $force = true)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// cleanup docker images, containers, and builder caches
|
||||||
if ($force) {
|
if ($force) {
|
||||||
instant_remote_process(['docker image prune -af'], $server, false);
|
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 container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||||
@ -19,5 +22,15 @@ public function handle(Server $server, bool $force = true)
|
|||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $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);
|
instant_remote_process(['docker builder prune -f'], $server, false);
|
||||||
}
|
}
|
||||||
|
// cleanup networks
|
||||||
|
// $networks = collectDockerNetworksByServer($server);
|
||||||
|
// $proxyNetworks = collectProxyDockerNetworksByServer($server);
|
||||||
|
// $diff = $proxyNetworks->diff($networks);
|
||||||
|
// if ($diff->count() > 0) {
|
||||||
|
// $diff->map(function ($network) use ($server) {
|
||||||
|
// instant_remote_process(["docker network disconnect $network coolify-proxy"], $server);
|
||||||
|
// instant_remote_process(["docker network rm $network"], $server);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
51
app/Actions/Server/ConfigureCloudflared.php
Normal file
51
app/Actions/Server/ConfigureCloudflared.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class ConfigureCloudflared
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server, string $cloudflare_token)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$config = [
|
||||||
|
'services' => [
|
||||||
|
'coolify-cloudflared' => [
|
||||||
|
'container_name' => 'coolify-cloudflared',
|
||||||
|
'image' => 'cloudflare/cloudflared:latest',
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'network_mode' => 'host',
|
||||||
|
'command' => 'tunnel run',
|
||||||
|
'environment' => [
|
||||||
|
"TUNNEL_TOKEN={$cloudflare_token}",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$config = Yaml::dump($config, 12, 2);
|
||||||
|
$docker_compose_yml_base64 = base64_encode($config);
|
||||||
|
$commands = collect([
|
||||||
|
'mkdir -p /tmp/cloudflared',
|
||||||
|
'cd /tmp/cloudflared',
|
||||||
|
"echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
|
||||||
|
'docker compose pull',
|
||||||
|
'docker compose down -v --remove-orphans > /dev/null 2>&1',
|
||||||
|
'docker compose up -d --remove-orphans',
|
||||||
|
]);
|
||||||
|
instant_remote_process($commands, $server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
$commands = collect([
|
||||||
|
'rm -fr /tmp/cloudflared',
|
||||||
|
]);
|
||||||
|
instant_remote_process($commands, $server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,18 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class InstallDocker
|
class InstallDocker
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
$supported_os_type = $server->validateOS();
|
$supported_os_type = $server->validateOS();
|
||||||
if (! $supported_os_type) {
|
if (! $supported_os_type) {
|
||||||
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/servers#install-docker-engine-manually">documentation</a>.');
|
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
|
||||||
}
|
}
|
||||||
ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type);
|
ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type);
|
||||||
$dockerVersion = '24.0';
|
$dockerVersion = '24.0';
|
||||||
@ -36,32 +37,41 @@ public function handle(Server $server)
|
|||||||
if (isDev() && $server->id === 0) {
|
if (isDev() && $server->id === 0) {
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
"echo 'Installing Prerequisites...'",
|
"echo 'Installing Prerequisites...'",
|
||||||
"sleep 1",
|
'sleep 1',
|
||||||
"echo 'Installing Docker Engine...'",
|
"echo 'Installing Docker Engine...'",
|
||||||
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||||
"sleep 4",
|
'sleep 4',
|
||||||
"echo 'Restarting Docker Engine...'",
|
"echo 'Restarting Docker Engine...'",
|
||||||
"ls -l /tmp"
|
'ls -l /tmp',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return remote_process($command, $server);
|
return remote_process($command, $server);
|
||||||
} else {
|
} else {
|
||||||
if ($supported_os_type->contains('debian')) {
|
if ($supported_os_type->contains('debian')) {
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
"echo 'Installing Prerequisites...'",
|
"echo 'Installing Prerequisites...'",
|
||||||
"command -v jq >/dev/null || apt-get update -y",
|
'apt-get update -y',
|
||||||
"command -v jq >/dev/null || apt install -y curl wget git jq",
|
'command -v curl >/dev/null || apt install -y curl',
|
||||||
|
'command -v wget >/dev/null || apt install -y wget',
|
||||||
|
'command -v git >/dev/null || apt install -y git',
|
||||||
|
'command -v jq >/dev/null || apt install -y jq',
|
||||||
]);
|
]);
|
||||||
} elseif ($supported_os_type->contains('rhel')) {
|
} elseif ($supported_os_type->contains('rhel')) {
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
"echo 'Installing Prerequisites...'",
|
"echo 'Installing Prerequisites...'",
|
||||||
"command -v jq >/dev/null || dnf install -y curl wget git jq",
|
'command -v curl >/dev/null || dnf install -y curl',
|
||||||
|
'command -v wget >/dev/null || dnf install -y wget',
|
||||||
|
'command -v git >/dev/null || dnf install -y git',
|
||||||
|
'command -v jq >/dev/null || dnf install -y jq',
|
||||||
]);
|
]);
|
||||||
} elseif ($supported_os_type->contains('sles')) {
|
} elseif ($supported_os_type->contains('sles')) {
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
"echo 'Installing Prerequisites...'",
|
"echo 'Installing Prerequisites...'",
|
||||||
"command -v jq >/dev/null || zypper update -y",
|
'zypper update -y',
|
||||||
"command -v jq >/dev/null || zypper install -y curl wget git jq",
|
'command -v curl >/dev/null || zypper install -y curl',
|
||||||
|
'command -v wget >/dev/null || zypper install -y wget',
|
||||||
|
'command -v git >/dev/null || zypper install -y git',
|
||||||
|
'command -v jq >/dev/null || zypper install -y jq',
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('Unsupported OS');
|
throw new \Exception('Unsupported OS');
|
||||||
@ -70,26 +80,30 @@ public function handle(Server $server)
|
|||||||
"echo 'Installing Docker Engine...'",
|
"echo 'Installing Docker Engine...'",
|
||||||
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
|
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
|
||||||
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||||
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
'test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json "/etc/docker/daemon.json.original-$(date +"%Y%m%d-%H%M%S")"',
|
||||||
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
"test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null",
|
||||||
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
|
"echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null",
|
||||||
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
|
'jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null',
|
||||||
|
'mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify',
|
||||||
|
"jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null",
|
||||||
|
'mv /etc/docker/daemon.json.appended /etc/docker/daemon.json',
|
||||||
"echo 'Restarting Docker Engine...'",
|
"echo 'Restarting Docker Engine...'",
|
||||||
"systemctl enable docker >/dev/null 2>&1 || true",
|
'systemctl enable docker >/dev/null 2>&1 || true',
|
||||||
"systemctl restart docker",
|
'systemctl restart docker',
|
||||||
]);
|
]);
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
"docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true",
|
'docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true',
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
'docker network create --attachable coolify >/dev/null 2>&1 || true',
|
||||||
]);
|
]);
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
"echo 'Done!'",
|
"echo 'Done!'",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return remote_process($command, $server);
|
return remote_process($command, $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class InstallLogDrain
|
class InstallLogDrain
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
if ($server->settings->is_logdrain_newrelic_enabled) {
|
if ($server->settings->is_logdrain_newrelic_enabled) {
|
||||||
@ -25,8 +26,9 @@ public function handle(Server $server)
|
|||||||
if ($type === 'none') {
|
if ($type === 'none') {
|
||||||
$command = [
|
$command = [
|
||||||
"echo 'Stopping old Fluent Bit'",
|
"echo 'Stopping old Fluent Bit'",
|
||||||
"docker rm -f coolify-log-drain || true",
|
'docker rm -f coolify-log-drain || true',
|
||||||
];
|
];
|
||||||
|
|
||||||
return instant_remote_process($command, $server);
|
return instant_remote_process($command, $server);
|
||||||
} elseif ($type === 'newrelic') {
|
} elseif ($type === 'newrelic') {
|
||||||
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
||||||
@ -63,7 +65,7 @@ public function handle(Server $server)
|
|||||||
if (! $server->settings->is_logdrain_highlight_enabled) {
|
if (! $server->settings->is_logdrain_highlight_enabled) {
|
||||||
throw new \Exception('Highlight log drain is not enabled.');
|
throw new \Exception('Highlight log drain is not enabled.');
|
||||||
}
|
}
|
||||||
$config = base64_encode("
|
$config = base64_encode('
|
||||||
[SERVICE]
|
[SERVICE]
|
||||||
Flush 5
|
Flush 5
|
||||||
Daemon off
|
Daemon off
|
||||||
@ -71,7 +73,7 @@ public function handle(Server $server)
|
|||||||
Parsers_File parsers.conf
|
Parsers_File parsers.conf
|
||||||
[INPUT]
|
[INPUT]
|
||||||
Name forward
|
Name forward
|
||||||
tag \${HIGHLIGHT_PROJECT_ID}
|
tag ${HIGHLIGHT_PROJECT_ID}
|
||||||
Buffer_Chunk_Size 1M
|
Buffer_Chunk_Size 1M
|
||||||
Buffer_Max_Size 6M
|
Buffer_Max_Size 6M
|
||||||
[OUTPUT]
|
[OUTPUT]
|
||||||
@ -79,7 +81,7 @@ public function handle(Server $server)
|
|||||||
Match *
|
Match *
|
||||||
Host otel.highlight.io
|
Host otel.highlight.io
|
||||||
Port 24224
|
Port 24224
|
||||||
");
|
');
|
||||||
} elseif ($type === 'axiom') {
|
} elseif ($type === 'axiom') {
|
||||||
if (! $server->settings->is_logdrain_axiom_enabled) {
|
if (! $server->settings->is_logdrain_axiom_enabled) {
|
||||||
throw new \Exception('Axiom log drain is not enabled.');
|
throw new \Exception('Axiom log drain is not enabled.');
|
||||||
@ -133,7 +135,7 @@ public function handle(Server $server)
|
|||||||
Regex /^(?!\s*$).+/
|
Regex /^(?!\s*$).+/
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
$compose = base64_encode("
|
$compose = base64_encode('
|
||||||
services:
|
services:
|
||||||
coolify-log-drain:
|
coolify-log-drain:
|
||||||
image: cr.fluentbit.io/fluent/fluent-bit:2.0
|
image: cr.fluentbit.io/fluent/fluent-bit:2.0
|
||||||
@ -147,7 +149,7 @@ public function handle(Server $server)
|
|||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:24224:24224
|
- 127.0.0.1:24224:24224
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
");
|
');
|
||||||
$readme = base64_encode('# New Relic Log Drain
|
$readme = base64_encode('# New Relic Log Drain
|
||||||
This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder.
|
This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder.
|
||||||
|
|
||||||
@ -168,10 +170,10 @@ public function handle(Server $server)
|
|||||||
$command = [
|
$command = [
|
||||||
"echo 'Saving configuration'",
|
"echo 'Saving configuration'",
|
||||||
"mkdir -p $config_path",
|
"mkdir -p $config_path",
|
||||||
"echo '{$parsers}' | base64 -d > $parsers_config",
|
"echo '{$parsers}' | base64 -d | tee $parsers_config > /dev/null",
|
||||||
"echo '{$config}' | base64 -d > $fluent_bit_config",
|
"echo '{$config}' | base64 -d | tee $fluent_bit_config > /dev/null",
|
||||||
"echo '{$compose}' | base64 -d > $compose_path",
|
"echo '{$compose}' | base64 -d | tee $compose_path > /dev/null",
|
||||||
"echo '{$readme}' | base64 -d > $readme_path",
|
"echo '{$readme}' | base64 -d | tee $readme_path > /dev/null",
|
||||||
"test -f $config_path/.env && rm $config_path/.env",
|
"test -f $config_path/.env && rm $config_path/.env",
|
||||||
|
|
||||||
];
|
];
|
||||||
@ -191,7 +193,7 @@ public function handle(Server $server)
|
|||||||
];
|
];
|
||||||
} elseif ($type === 'custom') {
|
} elseif ($type === 'custom') {
|
||||||
$add_envs_command = [
|
$add_envs_command = [
|
||||||
"touch $config_path/.env"
|
"touch $config_path/.env",
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('Unknown log drain type.');
|
throw new \Exception('Unknown log drain type.');
|
||||||
@ -203,6 +205,7 @@ public function handle(Server $server)
|
|||||||
"cd $config_path && docker compose up -d --remove-orphans",
|
"cd $config_path && docker compose up -d --remove-orphans",
|
||||||
];
|
];
|
||||||
$command = array_merge($command, $add_envs_command, $restart_command);
|
$command = array_merge($command, $add_envs_command, $restart_command);
|
||||||
|
|
||||||
return instant_remote_process($command, $server);
|
return instant_remote_process($command, $server);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
|
19
app/Actions/Server/RunCommand.php
Normal file
19
app/Actions/Server/RunCommand.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Enums\ActivityTypes;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RunCommand
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server, $command)
|
||||||
|
{
|
||||||
|
$activity = remote_process(command: [$command], server: $server, ignore_errors: true, type: ActivityTypes::COMMAND->value);
|
||||||
|
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
|
}
|
26
app/Actions/Server/StartSentinel.php
Normal file
26
app/Actions/Server/StartSentinel.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartSentinel
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server, $version = 'latest', bool $restart = false)
|
||||||
|
{
|
||||||
|
if ($restart) {
|
||||||
|
StopSentinel::run($server);
|
||||||
|
}
|
||||||
|
$metrics_history = $server->settings->metrics_history_days;
|
||||||
|
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
|
||||||
|
$token = $server->settings->metrics_token;
|
||||||
|
instant_remote_process([
|
||||||
|
"docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
|
||||||
|
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
|
||||||
|
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
|
||||||
|
], $server, true);
|
||||||
|
}
|
||||||
|
}
|
16
app/Actions/Server/StopSentinel.php
Normal file
16
app/Actions/Server/StopSentinel.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopSentinel
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
||||||
|
}
|
||||||
|
}
|
@ -2,18 +2,21 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class UpdateCoolify
|
class UpdateCoolify
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public ?Server $server = null;
|
public ?Server $server = null;
|
||||||
|
|
||||||
public ?string $latestVersion = null;
|
public ?string $latestVersion = null;
|
||||||
|
|
||||||
public ?string $currentVersion = null;
|
public ?string $currentVersion = null;
|
||||||
|
|
||||||
public function handle(bool $force)
|
public function handle($manual_update = false)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
@ -22,32 +25,22 @@ public function handle(bool $force)
|
|||||||
if (!$this->server) {
|
if (!$this->server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CleanupDocker::run($this->server, false);
|
CleanupDocker::dispatch($this->server, false)->onQueue('high');
|
||||||
$this->latestVersion = get_latest_version_of_coolify();
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
$this->currentVersion = config('version');
|
$this->currentVersion = config('version');
|
||||||
if ($settings->next_channel) {
|
if (!$manual_update) {
|
||||||
ray('next channel enabled');
|
|
||||||
$this->latestVersion = 'next';
|
|
||||||
}
|
|
||||||
if ($force) {
|
|
||||||
$this->update();
|
|
||||||
} else {
|
|
||||||
if (!$settings->is_auto_update_enabled) {
|
if (!$settings->is_auto_update_enabled) {
|
||||||
return 'Auto update is disabled';
|
return;
|
||||||
}
|
}
|
||||||
if ($this->latestVersion === $this->currentVersion) {
|
if ($this->latestVersion === $this->currentVersion) {
|
||||||
return 'Already on latest version';
|
return;
|
||||||
}
|
}
|
||||||
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
|
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
|
||||||
return 'Latest version is lower than current version?!';
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->update();
|
$this->update();
|
||||||
}
|
|
||||||
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray('InstanceAutoUpdateJob failed');
|
|
||||||
ray($e->getMessage());
|
|
||||||
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,7 +48,7 @@ public function handle(bool $force)
|
|||||||
private function update()
|
private function update()
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
ray("Running update on local docker container. Updating to $this->latestVersion");
|
ray('Running in dev mode');
|
||||||
remote_process([
|
remote_process([
|
||||||
"sleep 10"
|
"sleep 10"
|
||||||
], $this->server);
|
], $this->server);
|
||||||
@ -64,10 +57,15 @@ private function update()
|
|||||||
} else {
|
} else {
|
||||||
ray('Running update on production server');
|
ray('Running update on production server');
|
||||||
remote_process([
|
remote_process([
|
||||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
"curl -fsSL https://cdn.lasthourhosting.org/lasthourcloud/scripts/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
||||||
], $this->server);
|
], $this->server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
remote_process([
|
||||||
|
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
||||||
|
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
|
||||||
|
], $this->server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
67
app/Actions/Server/ValidateServer.php
Normal file
67
app/Actions/Server/ValidateServer.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ValidateServer
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public ?string $uptime = null;
|
||||||
|
|
||||||
|
public ?string $error = null;
|
||||||
|
|
||||||
|
public ?string $supported_os_type = null;
|
||||||
|
|
||||||
|
public ?string $docker_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_compose_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_version = null;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => null,
|
||||||
|
]);
|
||||||
|
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
|
||||||
|
if (! $this->uptime) {
|
||||||
|
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->supported_os_type = $server->validateOS();
|
||||||
|
if (! $this->supported_os_type) {
|
||||||
|
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->docker_installed = $server->validateDockerEngine();
|
||||||
|
$this->docker_compose_installed = $server->validateDockerCompose();
|
||||||
|
if (! $this->docker_installed || ! $this->docker_compose_installed) {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->docker_version = $server->validateDockerEngineVersion();
|
||||||
|
|
||||||
|
if ($this->docker_version) {
|
||||||
|
return 'OK';
|
||||||
|
} else {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class DeleteService
|
class DeleteService
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
18
app/Actions/Service/RestartService.php
Normal file
18
app/Actions/Service/RestartService.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartService
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Service $service)
|
||||||
|
{
|
||||||
|
StopService::run($service);
|
||||||
|
|
||||||
|
return StartService::run($service);
|
||||||
|
}
|
||||||
|
}
|
@ -2,26 +2,27 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class StartService
|
class StartService
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
ray('Starting service: '.$service->name);
|
ray('Starting service: '.$service->name);
|
||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = "cd " . $service->workdir();
|
$commands[] = 'cd '.$service->workdir();
|
||||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
$commands[] = "echo 'Creating Docker network.'";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||||
$commands[] = "echo Starting service.";
|
$commands[] = 'echo Starting service.';
|
||||||
$commands[] = "echo 'Pulling images.'";
|
$commands[] = "echo 'Pulling images.'";
|
||||||
$commands[] = "docker compose pull";
|
$commands[] = 'docker compose pull';
|
||||||
$commands[] = "echo 'Starting containers.'";
|
$commands[] = "echo 'Starting containers.'";
|
||||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
|
$commands[] = 'docker compose up -d --remove-orphans --force-recreate --build';
|
||||||
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||||
if (data_get($service, 'connect_to_docker_network')) {
|
if (data_get($service, 'connect_to_docker_network')) {
|
||||||
$compose = data_get($service, 'docker_compose', []);
|
$compose = data_get($service, 'docker_compose', []);
|
||||||
@ -32,6 +33,7 @@ public function handle(Service $service)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
||||||
|
|
||||||
return $activity;
|
return $activity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopService
|
class StopService
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -18,21 +19,20 @@ public function handle(Service $service)
|
|||||||
ray('Stopping service: '.$service->name);
|
ray('Stopping service: '.$service->name);
|
||||||
$applications = $service->applications()->get();
|
$applications = $service->applications()->get();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
|
||||||
$application->update(['status' => 'exited']);
|
$application->update(['status' => 'exited']);
|
||||||
}
|
}
|
||||||
$dbs = $service->databases()->get();
|
$dbs = $service->databases()->get();
|
||||||
foreach ($dbs as $db) {
|
foreach ($dbs as $db) {
|
||||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server, false);
|
||||||
$db->update(['status' => 'exited']);
|
$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 disconnect {$service->uuid} coolify-proxy"], $service->server);
|
||||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
|
||||||
// TODO: make notification for databases
|
|
||||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
echo $e->getMessage();
|
echo $e->getMessage();
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
class ComplexStatusCheck
|
class ComplexStatusCheck
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Application $application)
|
public function handle(Application $application)
|
||||||
{
|
{
|
||||||
$servers = $application->additional_servers;
|
$servers = $application->additional_servers;
|
||||||
@ -17,9 +18,11 @@ public function handle(Application $application)
|
|||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
if ($is_main_server) {
|
if ($is_main_server) {
|
||||||
$application->update(['status' => 'exited:unhealthy']);
|
$application->update(['status' => 'exited:unhealthy']);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
|
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,9 +47,11 @@ public function handle(Application $application)
|
|||||||
} else {
|
} else {
|
||||||
if ($is_main_server) {
|
if ($is_main_server) {
|
||||||
$application->update(['status' => 'exited:unhealthy']);
|
$application->update(['status' => 'exited:unhealthy']);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
|
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,17 +8,20 @@
|
|||||||
class PullImage
|
class PullImage
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Service $resource)
|
public function handle(Service $resource)
|
||||||
{
|
{
|
||||||
$resource->saveComposeConfigs();
|
$resource->saveComposeConfigs();
|
||||||
|
|
||||||
$commands[] = "cd " . $resource->workdir();
|
$commands[] = 'cd '.$resource->workdir();
|
||||||
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
|
||||||
$commands[] = "docker compose pull";
|
$commands[] = 'docker compose pull';
|
||||||
|
|
||||||
$server = data_get($resource, 'server');
|
$server = data_get($resource, 'server');
|
||||||
|
|
||||||
if (!$server) return;
|
if (! $server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
instant_remote_process($commands, $resource->server);
|
instant_remote_process($commands, $resource->server);
|
||||||
}
|
}
|
||||||
|
56
app/Console/Commands/AdminRemoveUser.php
Normal file
56
app/Console/Commands/AdminRemoveUser.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class AdminRemoveUser extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'admin:remove-user {email}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Remove User from database';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$email = $this->argument('email');
|
||||||
|
$confirm = $this->confirm('Are you sure you want to remove user with email: '.$email.'?');
|
||||||
|
if (! $confirm) {
|
||||||
|
$this->info('User removal cancelled.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->info("Removing user with email: $email");
|
||||||
|
$user = User::whereEmail($email)->firstOrFail();
|
||||||
|
$teams = $user->teams;
|
||||||
|
foreach ($teams as $team) {
|
||||||
|
if ($team->members->count() > 1) {
|
||||||
|
$this->error('User is a member of a team with more than one member. Please remove user from team first.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$team->delete();
|
||||||
|
}
|
||||||
|
$user->delete();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error('Failed to remove user.');
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
class CleanupApplicationDeploymentQueue extends Command
|
class CleanupApplicationDeploymentQueue extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
|
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
|
||||||
|
|
||||||
protected $description = 'CleanupApplicationDeploymentQueue';
|
protected $description = 'CleanupApplicationDeploymentQueue';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
@ -15,7 +16,7 @@ public function handle()
|
|||||||
$team_id = $this->option('team-id');
|
$team_id = $this->option('team-id');
|
||||||
$servers = \App\Models\Server::where('team_id', $team_id)->get();
|
$servers = \App\Models\Server::where('team_id', $team_id)->get();
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$deployments = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->where("server_id", $server->id)->get();
|
$deployments = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->where('server_id', $server->id)->get();
|
||||||
foreach ($deployments as $deployment) {
|
foreach ($deployments as $deployment) {
|
||||||
$deployment->update(['status' => 'failed']);
|
$deployment->update(['status' => 'failed']);
|
||||||
instant_remote_process(['docker rm -f '.$deployment->deployment_uuid], $server, false);
|
instant_remote_process(['docker rm -f '.$deployment->deployment_uuid], $server, false);
|
||||||
|
@ -8,15 +8,20 @@
|
|||||||
class CleanupDatabase extends Command
|
class CleanupDatabase extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:database {--yes}';
|
protected $signature = 'cleanup:database {--yes}';
|
||||||
|
|
||||||
protected $description = 'Cleanup database';
|
protected $description = 'Cleanup database';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
if ($this->option('yes')) {
|
||||||
echo "Running database cleanup...\n";
|
echo "Running database cleanup...\n";
|
||||||
|
} else {
|
||||||
|
echo "Running database cleanup in dry-run mode...\n";
|
||||||
|
}
|
||||||
$keep_days = 60;
|
$keep_days = 60;
|
||||||
|
echo "Keep days: $keep_days\n";
|
||||||
// Cleanup failed jobs table
|
// Cleanup failed jobs table
|
||||||
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(7));
|
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
|
||||||
$count = $failed_jobs->count();
|
$count = $failed_jobs->count();
|
||||||
echo "Delete $count entries from failed_jobs.\n";
|
echo "Delete $count entries from failed_jobs.\n";
|
||||||
if ($this->option('yes')) {
|
if ($this->option('yes')) {
|
||||||
@ -32,7 +37,7 @@ public function handle()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup activity_log table
|
// Cleanup activity_log table
|
||||||
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days));
|
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
|
||||||
$count = $activity_log->count();
|
$count = $activity_log->count();
|
||||||
echo "Delete $count entries from activity_log.\n";
|
echo "Delete $count entries from activity_log.\n";
|
||||||
if ($this->option('yes')) {
|
if ($this->option('yes')) {
|
||||||
@ -40,7 +45,7 @@ public function handle()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup application_deployment_queues table
|
// Cleanup application_deployment_queues table
|
||||||
$application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days));
|
$application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
|
||||||
$count = $application_deployment_queues->count();
|
$count = $application_deployment_queues->count();
|
||||||
echo "Delete $count entries from application_deployment_queues.\n";
|
echo "Delete $count entries from application_deployment_queues.\n";
|
||||||
if ($this->option('yes')) {
|
if ($this->option('yes')) {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
class CleanupQueue extends Command
|
class CleanupQueue extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:queue';
|
protected $signature = 'cleanup:queue';
|
||||||
|
|
||||||
protected $description = 'Cleanup Queue';
|
protected $description = 'Cleanup Queue';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
use App\Models\StandaloneMongodb;
|
use App\Models\StandaloneMongodb;
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
@ -17,6 +20,7 @@
|
|||||||
class CleanupStuckedResources extends Command
|
class CleanupStuckedResources extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:stucked-resources';
|
protected $signature = 'cleanup:stucked-resources';
|
||||||
|
|
||||||
protected $description = 'Cleanup Stucked Resources';
|
protected $description = 'Cleanup Stucked Resources';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
@ -25,6 +29,7 @@ public function handle()
|
|||||||
echo "Running cleanup stucked resources.\n";
|
echo "Running cleanup stucked resources.\n";
|
||||||
$this->cleanup_stucked_resources();
|
$this->cleanup_stucked_resources();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanup_stucked_resources()
|
private function cleanup_stucked_resources()
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -55,6 +60,33 @@ private function cleanup_stucked_resources()
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
$keydbs = StandaloneKeydb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($keydbs as $keydb) {
|
||||||
|
echo "Deleting stuck keydb: {$keydb->name}\n";
|
||||||
|
$keydb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck keydb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$dragonflies = StandaloneDragonfly::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($dragonflies as $dragonfly) {
|
||||||
|
echo "Deleting stuck dragonfly: {$dragonfly->name}\n";
|
||||||
|
$dragonfly->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck dragonfly: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$clickhouses = StandaloneClickhouse::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($clickhouses as $clickhouse) {
|
||||||
|
echo "Deleting stuck clickhouse: {$clickhouse->name}\n";
|
||||||
|
$clickhouse->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck clickhouse: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
foreach ($mongodbs as $mongodb) {
|
foreach ($mongodbs as $mongodb) {
|
||||||
@ -128,16 +160,19 @@ private function cleanup_stucked_resources()
|
|||||||
if (! data_get($application, 'environment')) {
|
if (! data_get($application, 'environment')) {
|
||||||
echo 'Application without environment: '.$application->name.'\n';
|
echo 'Application without environment: '.$application->name.'\n';
|
||||||
$application->forceDelete();
|
$application->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! $application->destination()) {
|
if (! $application->destination()) {
|
||||||
echo 'Application without destination: '.$application->name.'\n';
|
echo 'Application without destination: '.$application->name.'\n';
|
||||||
$application->forceDelete();
|
$application->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! data_get($application, 'destination.server')) {
|
if (! data_get($application, 'destination.server')) {
|
||||||
echo 'Application without server: '.$application->name.'\n';
|
echo 'Application without server: '.$application->name.'\n';
|
||||||
$application->forceDelete();
|
$application->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,16 +185,19 @@ private function cleanup_stucked_resources()
|
|||||||
if (! data_get($postgresql, 'environment')) {
|
if (! data_get($postgresql, 'environment')) {
|
||||||
echo 'Postgresql without environment: '.$postgresql->name.'\n';
|
echo 'Postgresql without environment: '.$postgresql->name.'\n';
|
||||||
$postgresql->forceDelete();
|
$postgresql->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! $postgresql->destination()) {
|
if (! $postgresql->destination()) {
|
||||||
echo 'Postgresql without destination: '.$postgresql->name.'\n';
|
echo 'Postgresql without destination: '.$postgresql->name.'\n';
|
||||||
$postgresql->forceDelete();
|
$postgresql->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! data_get($postgresql, 'destination.server')) {
|
if (! data_get($postgresql, 'destination.server')) {
|
||||||
echo 'Postgresql without server: '.$postgresql->name.'\n';
|
echo 'Postgresql without server: '.$postgresql->name.'\n';
|
||||||
$postgresql->forceDelete();
|
$postgresql->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,16 +210,19 @@ private function cleanup_stucked_resources()
|
|||||||
if (! data_get($redis, 'environment')) {
|
if (! data_get($redis, 'environment')) {
|
||||||
echo 'Redis without environment: '.$redis->name.'\n';
|
echo 'Redis without environment: '.$redis->name.'\n';
|
||||||
$redis->forceDelete();
|
$redis->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! $redis->destination()) {
|
if (! $redis->destination()) {
|
||||||
echo 'Redis without destination: '.$redis->name.'\n';
|
echo 'Redis without destination: '.$redis->name.'\n';
|
||||||
$redis->forceDelete();
|
$redis->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! data_get($redis, 'destination.server')) {
|
if (! data_get($redis, 'destination.server')) {
|
||||||
echo 'Redis without server: '.$redis->name.'\n';
|
echo 'Redis without server: '.$redis->name.'\n';
|
||||||
$redis->forceDelete();
|
$redis->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,16 +236,19 @@ private function cleanup_stucked_resources()
|
|||||||
if (! data_get($mongodb, 'environment')) {
|
if (! data_get($mongodb, 'environment')) {
|
||||||
echo 'Mongodb without environment: '.$mongodb->name.'\n';
|
echo 'Mongodb without environment: '.$mongodb->name.'\n';
|
||||||
$mongodb->forceDelete();
|
$mongodb->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! $mongodb->destination()) {
|
if (! $mongodb->destination()) {
|
||||||
echo 'Mongodb without destination: '.$mongodb->name.'\n';
|
echo 'Mongodb without destination: '.$mongodb->name.'\n';
|
||||||
$mongodb->forceDelete();
|
$mongodb->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! data_get($mongodb, 'destination.server')) {
|
if (! data_get($mongodb, 'destination.server')) {
|
||||||
echo 'Mongodb without server: '.$mongodb->name.'\n';
|
echo 'Mongodb without server: '.$mongodb->name.'\n';
|
||||||
$mongodb->forceDelete();
|
$mongodb->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,16 +262,19 @@ private function cleanup_stucked_resources()
|
|||||||
if (! data_get($mysql, 'environment')) {
|
if (! data_get($mysql, 'environment')) {
|
||||||
echo 'Mysql without environment: '.$mysql->name.'\n';
|
echo 'Mysql without environment: '.$mysql->name.'\n';
|
||||||
$mysql->forceDelete();
|
$mysql->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! $mysql->destination()) {
|
if (! $mysql->destination()) {
|
||||||
echo 'Mysql without destination: '.$mysql->name.'\n';
|
echo 'Mysql without destination: '.$mysql->name.'\n';
|
||||||
$mysql->forceDelete();
|
$mysql->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! data_get($mysql, 'destination.server')) {
|
if (! data_get($mysql, 'destination.server')) {
|
||||||
echo 'Mysql without server: '.$mysql->name.'\n';
|
echo 'Mysql without server: '.$mysql->name.'\n';
|
||||||
$mysql->forceDelete();
|
$mysql->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,16 +288,19 @@ private function cleanup_stucked_resources()
|
|||||||
if (! data_get($mariadb, 'environment')) {
|
if (! data_get($mariadb, 'environment')) {
|
||||||
echo 'Mariadb without environment: '.$mariadb->name.'\n';
|
echo 'Mariadb without environment: '.$mariadb->name.'\n';
|
||||||
$mariadb->forceDelete();
|
$mariadb->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! $mariadb->destination()) {
|
if (! $mariadb->destination()) {
|
||||||
echo 'Mariadb without destination: '.$mariadb->name.'\n';
|
echo 'Mariadb without destination: '.$mariadb->name.'\n';
|
||||||
$mariadb->forceDelete();
|
$mariadb->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! data_get($mariadb, 'destination.server')) {
|
if (! data_get($mariadb, 'destination.server')) {
|
||||||
echo 'Mariadb without server: '.$mariadb->name.'\n';
|
echo 'Mariadb without server: '.$mariadb->name.'\n';
|
||||||
$mariadb->forceDelete();
|
$mariadb->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,16 +314,19 @@ private function cleanup_stucked_resources()
|
|||||||
if (! data_get($service, 'environment')) {
|
if (! data_get($service, 'environment')) {
|
||||||
echo 'Service without environment: '.$service->name.'\n';
|
echo 'Service without environment: '.$service->name.'\n';
|
||||||
$service->forceDelete();
|
$service->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! $service->destination()) {
|
if (! $service->destination()) {
|
||||||
echo 'Service without destination: '.$service->name.'\n';
|
echo 'Service without destination: '.$service->name.'\n';
|
||||||
$service->forceDelete();
|
$service->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! data_get($service, 'server')) {
|
if (! data_get($service, 'server')) {
|
||||||
echo 'Service without server: '.$service->name.'\n';
|
echo 'Service without server: '.$service->name.'\n';
|
||||||
$service->forceDelete();
|
$service->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,6 +339,7 @@ private function cleanup_stucked_resources()
|
|||||||
if (! data_get($service, 'service')) {
|
if (! data_get($service, 'service')) {
|
||||||
echo 'ServiceApplication without service: '.$service->name.'\n';
|
echo 'ServiceApplication without service: '.$service->name.'\n';
|
||||||
$service->forceDelete();
|
$service->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,6 +352,7 @@ private function cleanup_stucked_resources()
|
|||||||
if (! data_get($service, 'service')) {
|
if (! data_get($service, 'service')) {
|
||||||
echo 'ServiceDatabase without service: '.$service->name.'\n';
|
echo 'ServiceDatabase without service: '.$service->name.'\n';
|
||||||
$service->forceDelete();
|
$service->forceDelete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
class CleanupUnreachableServers extends Command
|
class CleanupUnreachableServers extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:unreachable-servers';
|
protected $signature = 'cleanup:unreachable-servers';
|
||||||
|
|
||||||
protected $description = 'Cleanup Unreachable Servers (7 days)';
|
protected $description = 'Cleanup Unreachable Servers (7 days)';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
@ -17,9 +18,9 @@ public function handle()
|
|||||||
if ($servers->count() > 0) {
|
if ($servers->count() > 0) {
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||||
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
// send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
||||||
$server->update([
|
$server->update([
|
||||||
'ip' => '1.2.3.4'
|
'ip' => '1.2.3.4',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
<?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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
101
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
101
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CloudCleanupSubscriptions extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cloud:cleanup-subs';
|
||||||
|
|
||||||
|
protected $description = 'Cleanup subcriptions teams';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! isCloud()) {
|
||||||
|
$this->error('This command can only be run on cloud');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ray()->clearAll();
|
||||||
|
$this->info('Cleaning up subcriptions teams');
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
|
||||||
|
$teams = Team::all()->filter(function ($team) {
|
||||||
|
return $team->id !== 0;
|
||||||
|
})->sortBy('id');
|
||||||
|
foreach ($teams as $team) {
|
||||||
|
if ($team) {
|
||||||
|
$this->info("Checking team {$team->id}");
|
||||||
|
}
|
||||||
|
if (! data_get($team, 'subscription')) {
|
||||||
|
$this->disableServers($team);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the team has no subscription id and the invoice is paid, we need to reset the invoice paid status
|
||||||
|
if (! (data_get($team, 'subscription.stripe_subscription_id'))) {
|
||||||
|
$this->info("Resetting invoice paid status for team {$team->id} {$team->name}");
|
||||||
|
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
'stripe_subscription_id' => null,
|
||||||
|
]);
|
||||||
|
$this->disableServers($team);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$subscription = $stripe->subscriptions->retrieve(data_get($team, 'subscription.stripe_subscription_id'), []);
|
||||||
|
$status = data_get($subscription, 'status');
|
||||||
|
if ($status === 'active' || $status === 'past_due') {
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => true,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->info('Subscription status: '.$status);
|
||||||
|
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
|
||||||
|
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
|
||||||
|
if (! $confirm) {
|
||||||
|
$this->info("Skipping team {$team->id} {$team->name}");
|
||||||
|
} else {
|
||||||
|
$this->info("Cancelling subscription for team {$team->id} {$team->name}");
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
'stripe_subscription_id' => null,
|
||||||
|
]);
|
||||||
|
$this->disableServers($team);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function disableServers(Team $team)
|
||||||
|
{
|
||||||
|
foreach ($team->servers as $server) {
|
||||||
|
if ($server->settings->is_usable === true || $server->settings->is_reachable === true || $server->ip !== '1.2.3.4') {
|
||||||
|
$this->info("Disabling server {$server->id} {$server->name}");
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_usable' => false,
|
||||||
|
'is_reachable' => false,
|
||||||
|
]);
|
||||||
|
$server->update([
|
||||||
|
'ip' => '1.2.3.4',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -9,12 +9,41 @@
|
|||||||
|
|
||||||
class Dev extends Command
|
class Dev extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'dev:init';
|
protected $signature = 'dev {--init} {--generate-openapi}';
|
||||||
protected $description = 'Init the app in dev mode';
|
|
||||||
|
protected $description = 'Helper commands for development.';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
|
{
|
||||||
|
if ($this->option('init')) {
|
||||||
|
$this->init();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->option('generate-openapi')) {
|
||||||
|
$this->generateOpenApi();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateOpenApi()
|
||||||
|
{
|
||||||
|
// Generate OpenAPI documentation
|
||||||
|
echo "Generating OpenAPI documentation.\n";
|
||||||
|
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||||
|
$error = $process->errorOutput();
|
||||||
|
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||||
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
|
echo $error;
|
||||||
|
echo $process->output();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
{
|
{
|
||||||
// Generate APP_KEY if not exists
|
// Generate APP_KEY if not exists
|
||||||
|
|
||||||
if (empty(env('APP_KEY'))) {
|
if (empty(env('APP_KEY'))) {
|
||||||
echo "Generating APP_KEY.\n";
|
echo "Generating APP_KEY.\n";
|
||||||
Artisan::call('key:generate');
|
Artisan::call('key:generate');
|
||||||
|
@ -2,12 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Jobs\DatabaseBackupStatusJob;
|
|
||||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
@ -49,7 +47,9 @@ class Emails extends Command
|
|||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
*/
|
*/
|
||||||
private ?MailMessage $mail = null;
|
private ?MailMessage $mail = null;
|
||||||
|
|
||||||
private ?string $email = null;
|
private ?string $email = null;
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$type = select(
|
$type = select(
|
||||||
@ -73,7 +73,7 @@ public function handle()
|
|||||||
);
|
);
|
||||||
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
|
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$this->email = "test@example.com";
|
$this->email = 'test@example.com';
|
||||||
} else {
|
} else {
|
||||||
if (! in_array($type, $emailsGathered)) {
|
if (! in_array($type, $emailsGathered)) {
|
||||||
$this->email = text('Email Address to send to:');
|
$this->email = text('Email Address to send to:');
|
||||||
@ -81,13 +81,14 @@ public function handle()
|
|||||||
}
|
}
|
||||||
set_transanctional_email_settings();
|
set_transanctional_email_settings();
|
||||||
|
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->subject("Test Email");
|
$this->mail->subject('Test Email');
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'updates':
|
case 'updates':
|
||||||
$teams = Team::all();
|
$teams = Team::all();
|
||||||
if (! $teams || $teams->isEmpty()) {
|
if (! $teams || $teams->isEmpty()) {
|
||||||
echo 'No teams found.'.PHP_EOL;
|
echo 'No teams found.'.PHP_EOL;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$emails = [];
|
$emails = [];
|
||||||
@ -99,25 +100,25 @@ public function handle()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$emails = array_unique($emails);
|
$emails = array_unique($emails);
|
||||||
$this->info("Sending to " . count($emails) . " emails.");
|
$this->info('Sending to '.count($emails).' emails.');
|
||||||
foreach ($emails as $email) {
|
foreach ($emails as $email) {
|
||||||
$this->info($email);
|
$this->info($email);
|
||||||
}
|
}
|
||||||
$confirmed = confirm('Are you sure?');
|
$confirmed = confirm('Are you sure?');
|
||||||
if ($confirmed) {
|
if ($confirmed) {
|
||||||
foreach ($emails as $email) {
|
foreach ($emails as $email) {
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->subject('One-click Services, Docker Compose support');
|
$this->mail->subject('One-click Services, Docker Compose support');
|
||||||
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
||||||
'token' => encrypt($email),
|
'token' => encrypt($email),
|
||||||
]);
|
]);
|
||||||
$this->mail->view('emails.updates', ["unsubscribeUrl" => $unsubscribeUrl]);
|
$this->mail->view('emails.updates', ['unsubscribeUrl' => $unsubscribeUrl]);
|
||||||
$this->sendEmail($email);
|
$this->sendEmail($email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'emails-test':
|
case 'emails-test':
|
||||||
$this->mail = (new Test())->toMail();
|
$this->mail = (new Test)->toMail();
|
||||||
$this->sendEmail();
|
$this->sendEmail();
|
||||||
break;
|
break;
|
||||||
case 'database-backup-statuses-daily':
|
case 'database-backup-statuses-daily':
|
||||||
@ -223,7 +224,7 @@ public function handle()
|
|||||||
// $this->sendEmail();
|
// $this->sendEmail();
|
||||||
// break;
|
// break;
|
||||||
case 'waitlist-invitation-link':
|
case 'waitlist-invitation-link':
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.waitlist-invitation', [
|
$this->mail->view('emails.waitlist-invitation', [
|
||||||
'loginLink' => 'https://coolify.io',
|
'loginLink' => 'https://coolify.io',
|
||||||
]);
|
]);
|
||||||
@ -240,12 +241,13 @@ public function handle()
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 'realusers-before-trial':
|
case 'realusers-before-trial':
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.before-trial-conversion');
|
$this->mail->view('emails.before-trial-conversion');
|
||||||
$this->mail->subject('Trial period has been added for all subscription plans.');
|
$this->mail->subject('Trial period has been added for all subscription plans.');
|
||||||
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
||||||
if (! $teams || $teams->isEmpty()) {
|
if (! $teams || $teams->isEmpty()) {
|
||||||
echo 'No teams found.'.PHP_EOL;
|
echo 'No teams found.'.PHP_EOL;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$emails = [];
|
$emails = [];
|
||||||
@ -257,7 +259,7 @@ public function handle()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$emails = array_unique($emails);
|
$emails = array_unique($emails);
|
||||||
$this->info("Sending to " . count($emails) . " emails.");
|
$this->info('Sending to '.count($emails).' emails.');
|
||||||
foreach ($emails as $email) {
|
foreach ($emails as $email) {
|
||||||
$this->info($email);
|
$this->info($email);
|
||||||
}
|
}
|
||||||
@ -285,7 +287,7 @@ public function handle()
|
|||||||
foreach ($admins as $admin) {
|
foreach ($admins as $admin) {
|
||||||
$this->info($admin);
|
$this->info($admin);
|
||||||
}
|
}
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.server-lost-connection', [
|
$this->mail->view('emails.server-lost-connection', [
|
||||||
'name' => $server->name,
|
'name' => $server->name,
|
||||||
]);
|
]);
|
||||||
@ -296,7 +298,8 @@ public function handle()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function sendEmail(string $email = null)
|
|
||||||
|
private function sendEmail(?string $email = null)
|
||||||
{
|
{
|
||||||
if ($email) {
|
if ($email) {
|
||||||
$this->email = $email;
|
$this->email = $email;
|
||||||
|
23
app/Console/Commands/Horizon.php
Normal file
23
app/Console/Commands/Horizon.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class Horizon extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'start:horizon';
|
||||||
|
|
||||||
|
protected $description = 'Start Horizon';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if (config('coolify.is_horizon_enabled')) {
|
||||||
|
$this->info('Horizon is enabled. Starting.');
|
||||||
|
$this->call('horizon');
|
||||||
|
exit(0);
|
||||||
|
} else {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Actions\Server\StopSentinel;
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Environment;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@ -15,16 +17,31 @@
|
|||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
||||||
|
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->alive();
|
$this->alive();
|
||||||
|
get_public_ips();
|
||||||
|
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||||
|
$servers = Server::all();
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
$server->settings->update(['is_metrics_enabled' => false]);
|
||||||
|
if ($server->isFunctional()) {
|
||||||
|
StopSentinel::dispatch($server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$full_cleanup = $this->option('full-cleanup');
|
$full_cleanup = $this->option('full-cleanup');
|
||||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||||
|
|
||||||
|
$this->replace_slash_in_environment_name();
|
||||||
if ($cleanup_deployments) {
|
if ($cleanup_deployments) {
|
||||||
echo "Running cleanup deployments.\n";
|
echo "Running cleanup deployments.\n";
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($full_cleanup) {
|
if ($full_cleanup) {
|
||||||
@ -34,11 +51,14 @@ public function handle()
|
|||||||
$this->cleanup_stucked_helper_containers();
|
$this->cleanup_stucked_helper_containers();
|
||||||
$this->call('cleanup:queue');
|
$this->call('cleanup:queue');
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
|
if (! isCloud()) {
|
||||||
try {
|
try {
|
||||||
setup_dynamic_configuration();
|
$server = Server::find(0)->first();
|
||||||
|
$server->setupDynamicProxyConfiguration();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
if (! is_null(env('AUTOUPDATE', null))) {
|
if (! is_null(env('AUTOUPDATE', null))) {
|
||||||
@ -48,17 +68,19 @@ public function handle()
|
|||||||
$settings->update(['is_auto_update_enabled' => false]);
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->cleanup_stucked_helper_containers();
|
$this->cleanup_stucked_helper_containers();
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function restore_coolify_db_backup()
|
private function restore_coolify_db_backup()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$database = StandalonePostgresql::withTrashed()->find(0);
|
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||||
if ($database && $database->trashed()) {
|
if ($database && $database->trashed()) {
|
||||||
echo "Restoring coolify db backup\n";
|
echo "Restoring Last Hour Cloud db backup\n";
|
||||||
$database->restore();
|
$database->restore();
|
||||||
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
||||||
if (! $scheduledBackup) {
|
if (! $scheduledBackup) {
|
||||||
@ -74,9 +96,10 @@ private function restore_coolify_db_backup()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
|
echo "Error in restoring Last Hour Cloud db backup: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanup_stucked_helper_containers()
|
private function cleanup_stucked_helper_containers()
|
||||||
{
|
{
|
||||||
$servers = Server::all();
|
$servers = Server::all();
|
||||||
@ -86,6 +109,7 @@ private function cleanup_stucked_helper_containers()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function alive()
|
private function alive()
|
||||||
{
|
{
|
||||||
$id = config('app.id');
|
$id = config('app.id');
|
||||||
@ -94,10 +118,11 @@ private function alive()
|
|||||||
$do_not_track = data_get($settings, 'do_not_track');
|
$do_not_track = data_get($settings, 'do_not_track');
|
||||||
if ($do_not_track == true) {
|
if ($do_not_track == true) {
|
||||||
echo "Skipping alive as do_not_track is enabled\n";
|
echo "Skipping alive as do_not_track is enabled\n";
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
Http::get("");
|
||||||
echo "I am alive!\n";
|
echo "I am alive!\n";
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Error in alive: {$e->getMessage()}\n";
|
echo "Error in alive: {$e->getMessage()}\n";
|
||||||
@ -139,4 +164,15 @@ private function cleanup_in_progress_application_deployments()
|
|||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function replace_slash_in_environment_name()
|
||||||
|
{
|
||||||
|
$environments = Environment::all();
|
||||||
|
foreach ($environments as $environment) {
|
||||||
|
if (str_contains($environment->name, '/')) {
|
||||||
|
$environment->name = str_replace('/', '-', $environment->name);
|
||||||
|
$environment->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
use function Termwind\ask;
|
use function Termwind\ask;
|
||||||
use function Termwind\render;
|
use function Termwind\render;
|
||||||
use function Termwind\style;
|
use function Termwind\style;
|
||||||
@ -32,6 +33,7 @@ public function handle()
|
|||||||
|
|
||||||
if (blank($channel)) {
|
if (blank($channel)) {
|
||||||
$this->showHelp();
|
$this->showHelp();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ private function showHelp()
|
|||||||
<<<'HTML'
|
<<<'HTML'
|
||||||
<div>
|
<div>
|
||||||
<div class="title-box">
|
<div class="title-box">
|
||||||
Coolify
|
Last Hour Cloud
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 ml-1 ">
|
<p class="mt-1 ml-1 ">
|
||||||
Demo Notify <strong class="text-coolify">=></strong> Send a demo notification to a given channel.
|
Demo Notify <strong class="text-coolify">=></strong> Send a demo notification to a given channel.
|
||||||
@ -70,7 +72,7 @@ private function showHelp()
|
|||||||
|
|
||||||
ask(<<<'HTML'
|
ask(<<<'HTML'
|
||||||
<div class="mr-1">
|
<div class="mr-1">
|
||||||
In which manner you wish a <strong class="text-coolify">coolified</strong> notification?
|
In which manner you wish a <strong class="text-coolify">Last Hour Cloud</strong> notification?
|
||||||
</div>
|
</div>
|
||||||
HTML, ['email', 'slack', 'discord', 'telegram']);
|
HTML, ['email', 'slack', 'discord', 'telegram']);
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ public function handle()
|
|||||||
$this->info('Root user\'s email updated successfully.');
|
$this->info('Root user\'s email updated successfully.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->error('Failed to update root user\'s email.');
|
$this->error('Failed to update root user\'s email.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,12 @@ class RootResetPassword extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
//
|
|
||||||
$this->info('You are about to reset the root password.');
|
$this->info('You are about to reset the root password.');
|
||||||
$password = password('Give me a new password for root user: ');
|
$password = password('Give me a new password for root user: ');
|
||||||
$passwordAgain = password('Again');
|
$passwordAgain = password('Again');
|
||||||
if ($password != $passwordAgain) {
|
if ($password != $passwordAgain) {
|
||||||
$this->error('Passwords do not match.');
|
$this->error('Passwords do not match.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->info('Updating root password...');
|
$this->info('Updating root password...');
|
||||||
@ -43,6 +43,7 @@ public function handle()
|
|||||||
$this->info('Root password updated successfully.');
|
$this->info('Root password updated successfully.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->error('Failed to update root password.');
|
$this->error('Failed to update root password.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
app/Console/Commands/Scheduler.php
Normal file
23
app/Console/Commands/Scheduler.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class Scheduler extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'start:scheduler';
|
||||||
|
|
||||||
|
protected $description = 'Start Scheduler';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if (config('coolify.is_scheduler_enabled')) {
|
||||||
|
$this->info('Scheduler is enabled. Starting.');
|
||||||
|
$this->call('schedule:work');
|
||||||
|
exit(0);
|
||||||
|
} else {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,11 +48,13 @@ public function handle()
|
|||||||
$this->deleteServer();
|
$this->deleteServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteServer()
|
private function deleteServer()
|
||||||
{
|
{
|
||||||
$servers = Server::all();
|
$servers = Server::all();
|
||||||
if ($servers->count() === 0) {
|
if ($servers->count() === 0) {
|
||||||
$this->error('There are no applications to delete.');
|
$this->error('There are no applications to delete.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$serversToDelete = multiselect(
|
$serversToDelete = multiselect(
|
||||||
@ -64,7 +66,7 @@ private function deleteServer()
|
|||||||
$toDelete = $servers->where('id', $server)->first();
|
$toDelete = $servers->where('id', $server)->first();
|
||||||
if ($toDelete) {
|
if ($toDelete) {
|
||||||
$this->info($toDelete);
|
$this->info($toDelete);
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
$confirmed = confirm('Are you sure you want to delete all selected resources?');
|
||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -72,11 +74,13 @@ private function deleteServer()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteApplication()
|
private function deleteApplication()
|
||||||
{
|
{
|
||||||
$applications = Application::all();
|
$applications = Application::all();
|
||||||
if ($applications->count() === 0) {
|
if ($applications->count() === 0) {
|
||||||
$this->error('There are no applications to delete.');
|
$this->error('There are no applications to delete.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$applicationsToDelete = multiselect(
|
$applicationsToDelete = multiselect(
|
||||||
@ -88,7 +92,7 @@ private function deleteApplication()
|
|||||||
$toDelete = $applications->where('id', $application)->first();
|
$toDelete = $applications->where('id', $application)->first();
|
||||||
if ($toDelete) {
|
if ($toDelete) {
|
||||||
$this->info($toDelete);
|
$this->info($toDelete);
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
$confirmed = confirm('Are you sure you want to delete all selected resources? ');
|
||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -96,11 +100,13 @@ private function deleteApplication()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteDatabase()
|
private function deleteDatabase()
|
||||||
{
|
{
|
||||||
$databases = StandalonePostgresql::all();
|
$databases = StandalonePostgresql::all();
|
||||||
if ($databases->count() === 0) {
|
if ($databases->count() === 0) {
|
||||||
$this->error('There are no databases to delete.');
|
$this->error('There are no databases to delete.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$databasesToDelete = multiselect(
|
$databasesToDelete = multiselect(
|
||||||
@ -112,7 +118,7 @@ private function deleteDatabase()
|
|||||||
$toDelete = $databases->where('id', $database)->first();
|
$toDelete = $databases->where('id', $database)->first();
|
||||||
if ($toDelete) {
|
if ($toDelete) {
|
||||||
$this->info($toDelete);
|
$this->info($toDelete);
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
$confirmed = confirm('Are you sure you want to delete all selected resources?');
|
||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -120,11 +126,13 @@ private function deleteDatabase()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteService()
|
private function deleteService()
|
||||||
{
|
{
|
||||||
$services = Service::all();
|
$services = Service::all();
|
||||||
if ($services->count() === 0) {
|
if ($services->count() === 0) {
|
||||||
$this->error('There are no services to delete.');
|
$this->error('There are no services to delete.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$servicesToDelete = multiselect(
|
$servicesToDelete = multiselect(
|
||||||
@ -136,7 +144,7 @@ private function deleteService()
|
|||||||
$toDelete = $services->where('id', $service)->first();
|
$toDelete = $services->where('id', $service)->first();
|
||||||
if ($toDelete) {
|
if ($toDelete) {
|
||||||
$this->info($toDelete);
|
$this->info($toDelete);
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
$confirmed = confirm('Are you sure you want to delete all selected resources?');
|
||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ class ServicesGenerate extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
// ray()->clearAll();
|
|
||||||
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
|
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
|
||||||
$files = array_filter($files, function ($file) {
|
$files = array_filter($files, function ($file) {
|
||||||
return strpos($file, '.yaml') !== false;
|
return strpos($file, '.yaml') !== false;
|
||||||
@ -40,7 +39,7 @@ public function handle()
|
|||||||
$serviceTemplatesJson[$name] = $parsed;
|
$serviceTemplatesJson[$name] = $parsed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT);
|
$serviceTemplatesJson = json_encode($serviceTemplatesJson);
|
||||||
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
|
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,12 +56,14 @@ private function process_file($file)
|
|||||||
}
|
}
|
||||||
if ($ignore) {
|
if ($ignore) {
|
||||||
$this->info("Ignoring $file");
|
$this->info("Ignoring $file");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->info("Processing $file");
|
$this->info("Processing $file");
|
||||||
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
|
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
|
||||||
if ($documentation->count() > 0) {
|
if ($documentation->count() > 0) {
|
||||||
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
|
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
|
||||||
|
$documentation = str($documentation)->append('?utm_source=coolify.io');
|
||||||
} else {
|
} else {
|
||||||
$documentation = 'https://coolify.io/docs';
|
$documentation = 'https://coolify.io/docs';
|
||||||
}
|
}
|
||||||
@ -100,6 +101,12 @@ private function process_file($file)
|
|||||||
} else {
|
} else {
|
||||||
$tags = null;
|
$tags = null;
|
||||||
}
|
}
|
||||||
|
$port = collect(preg_grep('/^# port:/', explode("\n", $content)))->values();
|
||||||
|
if ($port->count() > 0) {
|
||||||
|
$port = str($port[0])->after('# port:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$port = null;
|
||||||
|
}
|
||||||
$json = Yaml::parse($content);
|
$json = Yaml::parse($content);
|
||||||
$yaml = base64_encode(Yaml::dump($json, 10, 2));
|
$yaml = base64_encode(Yaml::dump($json, 10, 2));
|
||||||
$payload = [
|
$payload = [
|
||||||
@ -111,11 +118,15 @@ private function process_file($file)
|
|||||||
'logo' => $logo,
|
'logo' => $logo,
|
||||||
'minversion' => $minversion,
|
'minversion' => $minversion,
|
||||||
];
|
];
|
||||||
|
if ($port) {
|
||||||
|
$payload['port'] = $port;
|
||||||
|
}
|
||||||
if ($env_file) {
|
if ($env_file) {
|
||||||
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
|
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
|
||||||
$env_file_base64 = base64_encode($env_file_content);
|
$env_file_base64 = base64_encode($env_file_content);
|
||||||
$payload['envs'] = $env_file_base64;
|
$payload['envs'] = $env_file_base64;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,30 +33,31 @@ public function handle()
|
|||||||
$that = $this;
|
$that = $this;
|
||||||
$only_template = $this->option('templates');
|
$only_template = $this->option('templates');
|
||||||
$only_version = $this->option('release');
|
$only_version = $this->option('release');
|
||||||
$bunny_cdn = "https://cdn.coollabs.io";
|
$bunny_cdn = 'https://cdn.coollabs.io';
|
||||||
$bunny_cdn_path = "coolify";
|
$bunny_cdn_path = 'coolify';
|
||||||
$bunny_cdn_storage_name = "coolcdn";
|
$bunny_cdn_storage_name = 'coolcdn';
|
||||||
|
|
||||||
$parent_dir = realpath(dirname(__FILE__).'/../../..');
|
$parent_dir = realpath(dirname(__FILE__).'/../../..');
|
||||||
|
|
||||||
$compose_file = "docker-compose.yml";
|
$compose_file = 'docker-compose.yml';
|
||||||
$compose_file_prod = "docker-compose.prod.yml";
|
$compose_file_prod = 'docker-compose.prod.yml';
|
||||||
$install_script = "install.sh";
|
$install_script = 'install.sh';
|
||||||
$upgrade_script = "upgrade.sh";
|
$upgrade_script = 'upgrade.sh';
|
||||||
$production_env = ".env.production";
|
$production_env = '.env.production';
|
||||||
$service_template = "service-templates.json";
|
$service_template = 'service-templates.json';
|
||||||
|
|
||||||
$versions = "versions.json";
|
$versions = 'versions.json';
|
||||||
|
|
||||||
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
||||||
$headers = [
|
$headers = [
|
||||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
'Content-Type' => 'application/octet-stream'
|
'Content-Type' => 'application/octet-stream',
|
||||||
];
|
];
|
||||||
$fileStream = fopen($fileName, "r");
|
$fileStream = fopen($fileName, 'r');
|
||||||
$file = fread($fileStream, filesize($fileName));
|
$file = fread($fileStream, filesize($fileName));
|
||||||
$that->info('Uploading: '.$fileName);
|
$that->info('Uploading: '.$fileName);
|
||||||
|
|
||||||
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
|
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
|
||||||
});
|
});
|
||||||
PendingRequest::macro('purge', function ($url) use ($that) {
|
PendingRequest::macro('purge', function ($url) use ($that) {
|
||||||
@ -65,9 +66,10 @@ public function handle()
|
|||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
];
|
];
|
||||||
$that->info('Purging: '.$url);
|
$that->info('Purging: '.$url);
|
||||||
|
|
||||||
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
|
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
|
||||||
"url" => $url,
|
'url' => $url,
|
||||||
"async" => false
|
'async' => false,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
@ -76,7 +78,7 @@ public function handle()
|
|||||||
}
|
}
|
||||||
if ($only_template) {
|
if ($only_template) {
|
||||||
$this->info('About to sync service-templates.json to BunnyCDN.');
|
$this->info('About to sync service-templates.json to BunnyCDN.');
|
||||||
$confirmed = confirm("Are you sure you want to sync?");
|
$confirmed = confirm('Are you sure you want to sync?');
|
||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -85,6 +87,7 @@ public function handle()
|
|||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
||||||
]);
|
]);
|
||||||
$this->info('Service template uploaded & purged...');
|
$this->info('Service template uploaded & purged...');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} elseif ($only_version) {
|
} elseif ($only_version) {
|
||||||
$this->info('About to sync versions.json to BunnyCDN.');
|
$this->info('About to sync versions.json to BunnyCDN.');
|
||||||
@ -101,10 +104,10 @@ public function handle()
|
|||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||||
]);
|
]);
|
||||||
$this->info('versions.json uploaded & purged...');
|
$this->info('versions.json uploaded & purged...');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||||
@ -119,9 +122,9 @@ public function handle()
|
|||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
|
||||||
]);
|
]);
|
||||||
$this->info("All files uploaded & purged...");
|
$this->info('All files uploaded & purged...');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->error("Error: " . $e->getMessage());
|
$this->error('Error: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
class WaitlistInvite extends Command
|
class WaitlistInvite extends Command
|
||||||
{
|
{
|
||||||
public Waitlist|User|null $next_patient = null;
|
public Waitlist|User|null $next_patient = null;
|
||||||
public string|null $password = null;
|
|
||||||
|
public ?string $password = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
@ -38,7 +40,9 @@ public function handle()
|
|||||||
$this->main();
|
$this->main();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function main() {
|
|
||||||
|
private function main()
|
||||||
|
{
|
||||||
if ($this->argument('email')) {
|
if ($this->argument('email')) {
|
||||||
if ($this->option('only-email')) {
|
if ($this->option('only-email')) {
|
||||||
$this->next_patient = User::whereEmail($this->argument('email'))->first();
|
$this->next_patient = User::whereEmail($this->argument('email'))->first();
|
||||||
@ -52,6 +56,7 @@ private function main() {
|
|||||||
}
|
}
|
||||||
if (! $this->next_patient) {
|
if (! $this->next_patient) {
|
||||||
$this->error("{$this->argument('email')} not found in the waitlist.");
|
$this->error("{$this->argument('email')} not found in the waitlist.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -60,6 +65,7 @@ private function main() {
|
|||||||
if ($this->next_patient) {
|
if ($this->next_patient) {
|
||||||
if ($this->option('only-email')) {
|
if ($this->option('only-email')) {
|
||||||
$this->send_email();
|
$this->send_email();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->register_user();
|
$this->register_user();
|
||||||
@ -69,13 +75,14 @@ private function main() {
|
|||||||
$this->info('No verified user found in the waitlist. 👀');
|
$this->info('No verified user found in the waitlist. 👀');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function register_user()
|
private function register_user()
|
||||||
{
|
{
|
||||||
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
||||||
if (! $already_registered) {
|
if (! $already_registered) {
|
||||||
$this->password = Str::password();
|
$this->password = Str::password();
|
||||||
User::create([
|
User::create([
|
||||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
'name' => str($this->next_patient->email)->before('@'),
|
||||||
'email' => $this->next_patient->email,
|
'email' => $this->next_patient->email,
|
||||||
'password' => Hash::make($this->password),
|
'password' => Hash::make($this->password),
|
||||||
'force_password_reset' => true,
|
'force_password_reset' => true,
|
||||||
@ -85,21 +92,23 @@ private function register_user()
|
|||||||
throw new \Exception('User already registered');
|
throw new \Exception('User already registered');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function remove_from_waitlist()
|
private function remove_from_waitlist()
|
||||||
{
|
{
|
||||||
$this->next_patient->delete();
|
$this->next_patient->delete();
|
||||||
$this->info("User removed from waitlist successfully.");
|
$this->info('User removed from waitlist successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function send_email()
|
private function send_email()
|
||||||
{
|
{
|
||||||
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||||
$loginLink = route('auth.link', ['token' => $token]);
|
$loginLink = route('auth.link', ['token' => $token]);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->view('emails.waitlist-invitation', [
|
$mail->view('emails.waitlist-invitation', [
|
||||||
'loginLink' => $loginLink,
|
'loginLink' => $loginLink,
|
||||||
]);
|
]);
|
||||||
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||||
send_user_an_email($mail, $this->next_patient->email);
|
send_user_an_email($mail, $this->next_patient->email);
|
||||||
$this->info("Email sent successfully. 📧");
|
$this->info('Email sent successfully. 📧');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,15 @@
|
|||||||
|
|
||||||
use App\Jobs\CheckLogDrainContainerJob;
|
use App\Jobs\CheckLogDrainContainerJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
|
||||||
use App\Jobs\ScheduledTaskJob;
|
|
||||||
use App\Jobs\InstanceAutoUpdateJob;
|
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
|
use App\Jobs\DatabaseBackupJob;
|
||||||
|
use App\Jobs\DockerCleanupJob;
|
||||||
|
use App\Jobs\PullCoolifyImageJob;
|
||||||
use App\Jobs\PullHelperImageJob;
|
use App\Jobs\PullHelperImageJob;
|
||||||
|
use App\Jobs\PullSentinelImageJob;
|
||||||
|
use App\Jobs\PullTemplatesFromCDN;
|
||||||
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\ServerStatusJob;
|
use App\Jobs\ServerStatusJob;
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@ -20,55 +22,62 @@
|
|||||||
|
|
||||||
class Kernel extends ConsoleKernel
|
class Kernel extends ConsoleKernel
|
||||||
{
|
{
|
||||||
|
private $all_servers;
|
||||||
|
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
|
$this->all_servers = Server::all();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
$this->pull_helper_image($schedule);
|
|
||||||
$this->check_scheduled_tasks($schedule);
|
$this->check_scheduled_tasks($schedule);
|
||||||
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->command('cleanup:unreachable-servers')->daily();
|
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||||
|
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
|
||||||
|
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->instance_auto_update($schedule);
|
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->pull_helper_image($schedule);
|
$this->pull_images($schedule);
|
||||||
$this->check_scheduled_tasks($schedule);
|
$this->check_scheduled_tasks($schedule);
|
||||||
|
|
||||||
if (!isCloud()) {
|
|
||||||
$schedule->command('cleanup:database --yes')->daily();
|
$schedule->command('cleanup:database --yes')->daily();
|
||||||
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
private function pull_helper_image($schedule)
|
private function pull_images($schedule)
|
||||||
{
|
{
|
||||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
|
if ($server->isSentinelEnabled()) {
|
||||||
|
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||||
|
}
|
||||||
|
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function check_resources($schedule)
|
private function check_resources($schedule)
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||||
$own = Team::find(0)->servers;
|
$own = Team::find(0)->servers;
|
||||||
$servers = $servers->merge($own);
|
$servers = $servers->merge($own);
|
||||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
||||||
} else {
|
} else {
|
||||||
$servers = Server::all()->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
||||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
||||||
}
|
}
|
||||||
foreach ($containerServers as $server) {
|
foreach ($containerServers as $server) {
|
||||||
@ -79,47 +88,10 @@ private function check_resources($schedule)
|
|||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
||||||
}
|
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
|
||||||
// Delayed Jobs
|
|
||||||
// foreach ($containerServers as $server) {
|
|
||||||
// $schedule
|
|
||||||
// ->call(function () use ($server) {
|
|
||||||
// $randomSeconds = rand(1, 40);
|
|
||||||
// $job = new ContainerStatusJob($server);
|
|
||||||
// $job->delay($randomSeconds);
|
|
||||||
// ray('dispatching container status job in ' . $randomSeconds . ' seconds');
|
|
||||||
// dispatch($job);
|
|
||||||
// })->name('container-status-' . $server->id)->everyMinute()->onOneServer();
|
|
||||||
// if ($server->isLogDrainEnabled()) {
|
|
||||||
// $schedule
|
|
||||||
// ->call(function () use ($server) {
|
|
||||||
// $randomSeconds = rand(1, 40);
|
|
||||||
// $job = new CheckLogDrainContainerJob($server);
|
|
||||||
// $job->delay($randomSeconds);
|
|
||||||
// dispatch($job);
|
|
||||||
// })->name('log-drain-container-check-' . $server->id)->everyMinute()->onOneServer();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// foreach ($servers as $server) {
|
|
||||||
// $schedule
|
|
||||||
// ->call(function () use ($server) {
|
|
||||||
// $randomSeconds = rand(1, 40);
|
|
||||||
// $job = new ServerStatusJob($server);
|
|
||||||
// $job->delay($randomSeconds);
|
|
||||||
// dispatch($job);
|
|
||||||
// })->name('server-status-job-' . $server->id)->everyMinute()->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)
|
private function check_scheduled_backups($schedule)
|
||||||
{
|
{
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||||
@ -133,6 +105,7 @@ private function check_scheduled_backups($schedule)
|
|||||||
if (is_null(data_get($scheduled_backup, 'database'))) {
|
if (is_null(data_get($scheduled_backup, 'database'))) {
|
||||||
ray('database not found');
|
ray('database not found');
|
||||||
$scheduled_backup->delete();
|
$scheduled_backup->delete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,15 +125,28 @@ private function check_scheduled_tasks($schedule)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($scheduled_tasks as $scheduled_task) {
|
foreach ($scheduled_tasks as $scheduled_task) {
|
||||||
|
if ($scheduled_task->enabled === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$service = $scheduled_task->service;
|
$service = $scheduled_task->service;
|
||||||
$application = $scheduled_task->application;
|
$application = $scheduled_task->application;
|
||||||
|
|
||||||
if (! $application && ! $service) {
|
if (! $application && ! $service) {
|
||||||
ray('application/service attached to scheduled task does not exist');
|
ray('application/service attached to scheduled task does not exist');
|
||||||
$scheduled_task->delete();
|
$scheduled_task->delete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if ($application) {
|
||||||
|
if (str($application->status)->contains('running') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
if (str($service->status())->contains('running') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||||
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ public function __construct(
|
|||||||
public ?string $status = null,
|
public ?string $status = null,
|
||||||
public bool $ignore_errors = false,
|
public bool $ignore_errors = false,
|
||||||
public $call_event_on_finish = null,
|
public $call_event_on_finish = null,
|
||||||
|
public $call_event_data = null
|
||||||
) {
|
) {
|
||||||
if (is_null($status)) {
|
if (is_null($status)) {
|
||||||
$this->status = ProcessStatus::QUEUED->value;
|
$this->status = ProcessStatus::QUEUED->value;
|
||||||
|
@ -11,6 +11,5 @@ class ServerMetadata extends Data
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
public ?ProxyTypes $type,
|
public ?ProxyTypes $type,
|
||||||
public ?ProxyStatus $status
|
public ?ProxyStatus $status
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,4 +5,5 @@
|
|||||||
enum ActivityTypes: string
|
enum ActivityTypes: string
|
||||||
{
|
{
|
||||||
case INLINE = 'inline';
|
case INLINE = 'inline';
|
||||||
|
case COMMAND = 'command';
|
||||||
}
|
}
|
||||||
|
11
app/Enums/BuildPackTypes.php
Normal file
11
app/Enums/BuildPackTypes.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum BuildPackTypes: string
|
||||||
|
{
|
||||||
|
case NIXPACKS = 'nixpacks';
|
||||||
|
case STATIC = 'static';
|
||||||
|
case DOCKERFILE = 'dockerfile';
|
||||||
|
case DOCKERCOMPOSE = 'dockercompose';
|
||||||
|
}
|
15
app/Enums/NewDatabaseTypes.php
Normal file
15
app/Enums/NewDatabaseTypes.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum NewDatabaseTypes: string
|
||||||
|
{
|
||||||
|
case POSTGRESQL = 'postgresql';
|
||||||
|
case MYSQL = 'mysql';
|
||||||
|
case MONGODB = 'mongodb';
|
||||||
|
case REDIS = 'redis';
|
||||||
|
case MARIADB = 'mariadb';
|
||||||
|
case KEYDB = 'keydb';
|
||||||
|
case DRAGONFLY = 'dragonfly';
|
||||||
|
case CLICKHOUSE = 'clickhouse';
|
||||||
|
}
|
22
app/Enums/NewResourceTypes.php
Normal file
22
app/Enums/NewResourceTypes.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum NewResourceTypes: string
|
||||||
|
{
|
||||||
|
case PUBLIC = 'public';
|
||||||
|
case PRIVATE_GH_APP = 'private-gh-app';
|
||||||
|
case PRIVATE_DEPLOY_KEY = 'private-deploy-key';
|
||||||
|
case DOCKERFILE = 'dockerfile';
|
||||||
|
case DOCKERCOMPOSE = 'dockercompose';
|
||||||
|
case DOCKER_IMAGE = 'docker-image';
|
||||||
|
case SERVICE = 'service';
|
||||||
|
case POSTGRESQL = 'postgresql';
|
||||||
|
case MYSQL = 'mysql';
|
||||||
|
case MONGODB = 'mongodb';
|
||||||
|
case REDIS = 'redis';
|
||||||
|
case MARIADB = 'mariadb';
|
||||||
|
case KEYDB = 'keydb';
|
||||||
|
case DRAGONFLY = 'dragonfly';
|
||||||
|
case CLICKHOUSE = 'clickhouse';
|
||||||
|
}
|
10
app/Enums/RedirectTypes.php
Normal file
10
app/Enums/RedirectTypes.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum RedirectTypes: string
|
||||||
|
{
|
||||||
|
case BOTH = 'both';
|
||||||
|
case WWW = 'www';
|
||||||
|
case NON_WWW = 'non-www';
|
||||||
|
}
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Events;
|
namespace App\Events;
|
||||||
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
use Illuminate\Broadcasting\PresenceChannel;
|
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
@ -13,14 +11,16 @@
|
|||||||
class ApplicationStatusChanged implements ShouldBroadcast
|
class ApplicationStatusChanged implements ShouldBroadcast
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $teamId;
|
public $teamId;
|
||||||
|
|
||||||
public function __construct($teamId = null)
|
public function __construct($teamId = null)
|
||||||
{
|
{
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
$teamId = auth()->user()->currentTeam()->id ?? null;
|
$teamId = auth()->user()->currentTeam()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
throw new \Exception("Team id is null");
|
throw new \Exception('Team id is null');
|
||||||
}
|
}
|
||||||
$this->teamId = $teamId;
|
$this->teamId = $teamId;
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Events;
|
namespace App\Events;
|
||||||
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
use Illuminate\Broadcasting\PresenceChannel;
|
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
@ -13,14 +11,16 @@
|
|||||||
class BackupCreated implements ShouldBroadcast
|
class BackupCreated implements ShouldBroadcast
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $teamId;
|
public $teamId;
|
||||||
|
|
||||||
public function __construct($teamId = null)
|
public function __construct($teamId = null)
|
||||||
{
|
{
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
$teamId = auth()->user()->currentTeam()->id ?? null;
|
$teamId = auth()->user()->currentTeam()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
throw new \Exception("Team id is null");
|
throw new \Exception('Team id is null');
|
||||||
}
|
}
|
||||||
$this->teamId = $teamId;
|
$this->teamId = $teamId;
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Events;
|
namespace App\Events;
|
||||||
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
use Illuminate\Broadcasting\PresenceChannel;
|
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
@ -13,22 +11,28 @@
|
|||||||
class DatabaseStatusChanged implements ShouldBroadcast
|
class DatabaseStatusChanged implements ShouldBroadcast
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
public $userId;
|
|
||||||
|
public ?string $userId = null;
|
||||||
|
|
||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
$userId = auth()->user()->id ?? null;
|
$userId = auth()->user()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
throw new \Exception("User id is null");
|
return false;
|
||||||
}
|
}
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
|
if ($this->userId) {
|
||||||
return [
|
return [
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
14
app/Events/ProxyStarted.php
Normal file
14
app/Events/ProxyStarted.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ProxyStarted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(public $data) {}
|
||||||
|
}
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Events;
|
namespace App\Events;
|
||||||
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
use Illuminate\Broadcasting\PresenceChannel;
|
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
@ -13,14 +11,16 @@
|
|||||||
class ProxyStatusChanged implements ShouldBroadcast
|
class ProxyStatusChanged implements ShouldBroadcast
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $teamId;
|
public $teamId;
|
||||||
|
|
||||||
public function __construct($teamId = null)
|
public function __construct($teamId = null)
|
||||||
{
|
{
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
$teamId = auth()->user()->currentTeam()->id ?? null;
|
$teamId = auth()->user()->currentTeam()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
throw new \Exception("Team id is null");
|
throw new \Exception('Team id is null');
|
||||||
}
|
}
|
||||||
$this->teamId = $teamId;
|
$this->teamId = $teamId;
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Events;
|
namespace App\Events;
|
||||||
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
use Illuminate\Broadcasting\PresenceChannel;
|
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
@ -13,22 +11,28 @@
|
|||||||
class ServiceStatusChanged implements ShouldBroadcast
|
class ServiceStatusChanged implements ShouldBroadcast
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
public $userId;
|
|
||||||
|
public ?string $userId = null;
|
||||||
|
|
||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
$userId = auth()->user()->id ?? null;
|
$userId = auth()->user()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
throw new \Exception("User id is null");
|
return false;
|
||||||
}
|
}
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
|
if (! is_null($this->userId)) {
|
||||||
return [
|
return [
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Events;
|
namespace App\Events;
|
||||||
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
use Illuminate\Broadcasting\PresenceChannel;
|
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
@ -13,7 +11,9 @@
|
|||||||
class TestEvent implements ShouldBroadcast
|
class TestEvent implements ShouldBroadcast
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $teamId;
|
public $teamId;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->teamId = auth()->user()->currentTeam()->id;
|
$this->teamId = auth()->user()->currentTeam()->id;
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
class Handler extends ExceptionHandler
|
class Handler extends ExceptionHandler
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of exception types with their corresponding custom log levels.
|
* A list of exception types with their corresponding custom log levels.
|
||||||
*
|
*
|
||||||
@ -22,14 +21,16 @@ class Handler extends ExceptionHandler
|
|||||||
protected $levels = [
|
protected $levels = [
|
||||||
//
|
//
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of the exception types that are not reported.
|
* A list of the exception types that are not reported.
|
||||||
*
|
*
|
||||||
* @var array<int, class-string<\Throwable>>
|
* @var array<int, class-string<\Throwable>>
|
||||||
*/
|
*/
|
||||||
protected $dontReport = [
|
protected $dontReport = [
|
||||||
ProcessException::class
|
ProcessException::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||||
*
|
*
|
||||||
@ -40,6 +41,7 @@ class Handler extends ExceptionHandler
|
|||||||
'password',
|
'password',
|
||||||
'password_confirmation',
|
'password_confirmation',
|
||||||
];
|
];
|
||||||
|
|
||||||
private InstanceSettings $settings;
|
private InstanceSettings $settings;
|
||||||
|
|
||||||
protected function unauthenticated($request, AuthenticationException $exception)
|
protected function unauthenticated($request, AuthenticationException $exception)
|
||||||
@ -47,8 +49,10 @@ protected function unauthenticated($request, AuthenticationException $exception)
|
|||||||
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
|
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
|
||||||
return response()->json(['message' => $exception->getMessage()], 401);
|
return response()->json(['message' => $exception->getMessage()], 401);
|
||||||
}
|
}
|
||||||
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
|
||||||
|
return redirect()->guest($exception->redirectTo($request) ?? route('login'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the exception handling callbacks for the application.
|
* Register the exception handling callbacks for the application.
|
||||||
*/
|
*/
|
||||||
@ -56,12 +60,12 @@ public function register(): void
|
|||||||
{
|
{
|
||||||
$this->reportable(function (Throwable $e) {
|
$this->reportable(function (Throwable $e) {
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// return;
|
return;
|
||||||
}
|
}
|
||||||
if ($e instanceof RuntimeException) {
|
if ($e instanceof RuntimeException) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = \App\Models\InstanceSettings::get();
|
||||||
if ($this->settings->do_not_track) {
|
if ($this->settings->do_not_track) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -72,7 +76,7 @@ function (Scope $scope) {
|
|||||||
$scope->setUser(
|
$scope->setUser(
|
||||||
[
|
[
|
||||||
'email' => $email,
|
'email' => $email,
|
||||||
'instanceAdmin' => $instanceAdmin
|
'instanceAdmin' => $instanceAdmin,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,4 @@
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
class ProcessException extends Exception
|
class ProcessException extends Exception {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
2570
app/Http/Controllers/Api/ApplicationsController.php
Normal file
2570
app/Http/Controllers/Api/ApplicationsController.php
Normal file
File diff suppressed because it is too large
Load Diff
1818
app/Http/Controllers/Api/DatabasesController.php
Normal file
1818
app/Http/Controllers/Api/DatabasesController.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,13 +9,33 @@
|
|||||||
use App\Actions\Database\StartRedis;
|
use App\Actions\Database\StartRedis;
|
||||||
use App\Actions\Service\StartService;
|
use App\Actions\Service\StartService;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Server;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class Deploy extends Controller
|
class Deploy extends Controller
|
||||||
{
|
{
|
||||||
|
public function deployments(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = get_team_id_from_token();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalid_token();
|
||||||
|
}
|
||||||
|
$servers = Server::whereTeamId($teamId)->get();
|
||||||
|
$deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $servers->pluck("id"))->get([
|
||||||
|
"id",
|
||||||
|
"application_id",
|
||||||
|
"application_name",
|
||||||
|
"deployment_url",
|
||||||
|
"pull_request_id",
|
||||||
|
"server_name",
|
||||||
|
"server_id",
|
||||||
|
"status"
|
||||||
|
])->sortBy('id')->toArray();
|
||||||
|
return response()->json($deployments_per_server, 200);
|
||||||
|
}
|
||||||
public function deploy(Request $request)
|
public function deploy(Request $request)
|
||||||
{
|
{
|
||||||
$teamId = get_team_id_from_token();
|
$teamId = get_team_id_from_token();
|
||||||
@ -24,17 +44,17 @@ public function deploy(Request $request)
|
|||||||
$force = $request->query->get('force') ?? false;
|
$force = $request->query->get('force') ?? false;
|
||||||
|
|
||||||
if ($uuids && $tags) {
|
if ($uuids && $tags) {
|
||||||
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
}
|
}
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
return invalid_token();
|
||||||
}
|
}
|
||||||
if ($tags) {
|
if ($tags) {
|
||||||
return $this->by_tags($tags, $teamId, $force);
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
} else if ($uuids) {
|
} else if ($uuids) {
|
||||||
return $this->by_uuids($uuids, $teamId, $force);
|
return $this->by_uuids($uuids, $teamId, $force);
|
||||||
}
|
}
|
||||||
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
return response()->json(['error' => 'You must provide uuid or tag.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
}
|
}
|
||||||
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||||
{
|
{
|
||||||
@ -42,20 +62,26 @@ private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
|||||||
$uuids = collect(array_filter($uuids));
|
$uuids = collect(array_filter($uuids));
|
||||||
|
|
||||||
if (count($uuids) === 0) {
|
if (count($uuids) === 0) {
|
||||||
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
return response()->json(['error' => 'No UUIDs provided.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
}
|
}
|
||||||
$message = collect([]);
|
$deployments = collect();
|
||||||
|
$payload = collect();
|
||||||
foreach ($uuids as $uuid) {
|
foreach ($uuids as $uuid) {
|
||||||
$resource = getResourceByUuid($uuid, $teamId);
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
if ($resource) {
|
if ($resource) {
|
||||||
$return_message = $this->deploy_resource($resource, $force);
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
$message = $message->merge($return_message);
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
} else {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($message->count() > 0) {
|
|
||||||
return response()->json(['message' => $message->toArray()], 200);
|
|
||||||
}
|
}
|
||||||
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('deployments', $deployments->toArray());
|
||||||
|
return response()->json($payload->toArray(), 200);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => "No resources found.", 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
}
|
}
|
||||||
public function by_tags(string $tags, int $team_id, bool $force = false)
|
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||||
{
|
{
|
||||||
@ -63,13 +89,15 @@ public function by_tags(string $tags, int $team_id, bool $force = false)
|
|||||||
$tags = collect(array_filter($tags));
|
$tags = collect(array_filter($tags));
|
||||||
|
|
||||||
if (count($tags) === 0) {
|
if (count($tags) === 0) {
|
||||||
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
return response()->json(['error' => 'No TAGs provided.', 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
}
|
}
|
||||||
$message = collect([]);
|
$message = collect([]);
|
||||||
|
$deployments = collect();
|
||||||
|
$payload = collect();
|
||||||
foreach ($tags as $tag) {
|
foreach ($tags as $tag) {
|
||||||
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||||
if (!$found_tag) {
|
if (!$found_tag) {
|
||||||
$message->push("Tag {$tag} not found.");
|
// $message->push("Tag {$tag} not found.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$applications = $found_tag->applications()->get();
|
$applications = $found_tag->applications()->get();
|
||||||
@ -79,83 +107,78 @@ public function by_tags(string $tags, int $team_id, bool $force = false)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
foreach ($applications as $resource) {
|
foreach ($applications as $resource) {
|
||||||
$return_message = $this->deploy_resource($resource, $force);
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
}
|
||||||
$message = $message->merge($return_message);
|
$message = $message->merge($return_message);
|
||||||
}
|
}
|
||||||
foreach ($services as $resource) {
|
foreach ($services as $resource) {
|
||||||
$return_message = $this->deploy_resource($resource, $force);
|
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
||||||
$message = $message->merge($return_message);
|
$message = $message->merge($return_message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ray($message);
|
||||||
if ($message->count() > 0) {
|
if ($message->count() > 0) {
|
||||||
return response()->json(['message' => $message->toArray()], 200);
|
$payload->put('message', $message->toArray());
|
||||||
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('details', $deployments->toArray());
|
||||||
|
}
|
||||||
|
return response()->json($payload->toArray(), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
return response()->json(['error' => "No resources found with this tag.", 'upstream docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
}
|
}
|
||||||
public function deploy_resource($resource, bool $force = false): Collection
|
public function deploy_resource($resource, bool $force = false): array
|
||||||
{
|
{
|
||||||
$message = collect([]);
|
$message = null;
|
||||||
|
$deployment_uuid = null;
|
||||||
if (gettype($resource) !== 'object') {
|
if (gettype($resource) !== 'object') {
|
||||||
return $message->push("Resource ($resource) not found.");
|
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
||||||
}
|
}
|
||||||
$type = $resource?->getMorphClass();
|
$type = $resource?->getMorphClass();
|
||||||
if ($type === 'App\Models\Application') {
|
if ($type === 'App\Models\Application') {
|
||||||
|
$deployment_uuid = new Cuid2(7);
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $resource,
|
application: $resource,
|
||||||
deployment_uuid: new Cuid2(7),
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: $force,
|
force_rebuild: $force,
|
||||||
);
|
);
|
||||||
$message->push("Application {$resource->name} deployment queued.");
|
$message = "Application {$resource->name} deployment queued.";
|
||||||
} else if ($type === 'App\Models\StandalonePostgresql') {
|
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartPostgresql::run($resource);
|
StartPostgresql::run($resource);
|
||||||
$resource->update([
|
$resource->update([
|
||||||
'started_at' => now(),
|
'started_at' => now(),
|
||||||
]);
|
]);
|
||||||
$message->push("Database {$resource->name} started.");
|
$message = "Database {$resource->name} started.";
|
||||||
} else if ($type === 'App\Models\StandaloneRedis') {
|
} else if ($type === 'App\Models\StandaloneRedis') {
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartRedis::run($resource);
|
StartRedis::run($resource);
|
||||||
$resource->update([
|
$resource->update([
|
||||||
'started_at' => now(),
|
'started_at' => now(),
|
||||||
]);
|
]);
|
||||||
$message->push("Database {$resource->name} started.");
|
$message = "Database {$resource->name} started.";
|
||||||
} else if ($type === 'App\Models\StandaloneMongodb') {
|
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartMongodb::run($resource);
|
StartMongodb::run($resource);
|
||||||
$resource->update([
|
$resource->update([
|
||||||
'started_at' => now(),
|
'started_at' => now(),
|
||||||
]);
|
]);
|
||||||
$message->push("Database {$resource->name} started.");
|
$message = "Database {$resource->name} started.";
|
||||||
} else if ($type === 'App\Models\StandaloneMysql') {
|
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartMysql::run($resource);
|
StartMysql::run($resource);
|
||||||
$resource->update([
|
$resource->update([
|
||||||
'started_at' => now(),
|
'started_at' => now(),
|
||||||
]);
|
]);
|
||||||
$message->push("Database {$resource->name} started.");
|
$message = "Database {$resource->name} started.";
|
||||||
} else if ($type === 'App\Models\StandaloneMariadb') {
|
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartMariadb::run($resource);
|
StartMariadb::run($resource);
|
||||||
$resource->update([
|
$resource->update([
|
||||||
'started_at' => now(),
|
'started_at' => now(),
|
||||||
]);
|
]);
|
||||||
$message->push("Database {$resource->name} started.");
|
$message = "Database {$resource->name} started.";
|
||||||
} else if ($type === 'App\Models\Service') {
|
} else if ($type === 'App\Models\Service') {
|
||||||
StartService::run($resource);
|
StartService::run($resource);
|
||||||
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||||
}
|
}
|
||||||
return $message;
|
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
317
app/Http/Controllers/Api/DeployController.php
Normal file
317
app/Http/Controllers/Api/DeployController.php
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabase;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class DeployController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($deployment)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
$deployment->makeHidden([
|
||||||
|
'logs',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List currently running deployments',
|
||||||
|
path: '/deployments',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all currently running deployments.',
|
||||||
|
content: [
|
||||||
|
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/ApplicationDeploymentQueue'),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deployments(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$servers = Server::whereTeamId($teamId)->get();
|
||||||
|
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get()->sortBy('id');
|
||||||
|
$deployments_per_server = $deployments_per_server->map(function ($deployment) {
|
||||||
|
return $this->removeSensitiveData($deployment);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($deployments_per_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get deployment by UUID.',
|
||||||
|
path: '/deployments/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get deployment by UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/ApplicationDeploymentQueue',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deployment_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first();
|
||||||
|
if (! $deployment) {
|
||||||
|
return response()->json(['message' => 'Deployment not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($deployment));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Deploy',
|
||||||
|
description: 'Deploy by tag or uuid. `Post` request also accepted.',
|
||||||
|
path: '/deploy',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
|
||||||
|
],
|
||||||
|
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get deployment(s) Uuid\'s',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'deployments' => new OA\Property(
|
||||||
|
property: 'deployments',
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string'],
|
||||||
|
'resource_uuid' => ['type' => 'string'],
|
||||||
|
'deployment_uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deploy(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
$uuids = $request->query->get('uuid');
|
||||||
|
$tags = $request->query->get('tag');
|
||||||
|
$force = $request->query->get('force') ?? false;
|
||||||
|
|
||||||
|
if ($uuids && $tags) {
|
||||||
|
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($tags) {
|
||||||
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
|
} elseif ($uuids) {
|
||||||
|
return $this->by_uuids($uuids, $teamId, $force);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'You must provide uuid or tag.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||||
|
{
|
||||||
|
$uuids = explode(',', $uuid);
|
||||||
|
$uuids = collect(array_filter($uuids));
|
||||||
|
|
||||||
|
if (count($uuids) === 0) {
|
||||||
|
return response()->json(['message' => 'No UUIDs provided.'], 400);
|
||||||
|
}
|
||||||
|
$deployments = collect();
|
||||||
|
$payload = collect();
|
||||||
|
foreach ($uuids as $uuid) {
|
||||||
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
|
if ($resource) {
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
} else {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('deployments', $deployments->toArray());
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($payload->toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'No resources found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||||
|
{
|
||||||
|
$tags = explode(',', $tags);
|
||||||
|
$tags = collect(array_filter($tags));
|
||||||
|
|
||||||
|
if (count($tags) === 0) {
|
||||||
|
return response()->json(['message' => 'No TAGs provided.'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
$deployments = collect();
|
||||||
|
$payload = collect();
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||||
|
if (! $found_tag) {
|
||||||
|
// $message->push("Tag {$tag} not found.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$applications = $found_tag->applications()->get();
|
||||||
|
$services = $found_tag->services()->get();
|
||||||
|
if ($applications->count() === 0 && $services->count() === 0) {
|
||||||
|
$message->push("No resources found for tag {$tag}.");
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($applications as $resource) {
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
}
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
foreach ($services as $resource) {
|
||||||
|
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
$payload->put('message', $message->toArray());
|
||||||
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('details', $deployments->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($payload->toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'No resources found with this tag.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deploy_resource($resource, bool $force = false): array
|
||||||
|
{
|
||||||
|
$message = null;
|
||||||
|
$deployment_uuid = null;
|
||||||
|
if (gettype($resource) !== 'object') {
|
||||||
|
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
||||||
|
}
|
||||||
|
switch ($resource?->getMorphClass()) {
|
||||||
|
case 'App\Models\Application':
|
||||||
|
$deployment_uuid = new Cuid2(7);
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $resource,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
force_rebuild: $force,
|
||||||
|
);
|
||||||
|
$message = "Application {$resource->name} deployment queued.";
|
||||||
|
break;
|
||||||
|
case 'App\Models\Service':
|
||||||
|
StartService::run($resource);
|
||||||
|
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Database resource
|
||||||
|
StartDatabase::dispatch($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message = "Database {$resource->name} started.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||||
|
}
|
||||||
|
}
|
35
app/Http/Controllers/Api/EnvironmentVariablesController.php
Normal file
35
app/Http/Controllers/Api/EnvironmentVariablesController.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\EnvironmentVariable;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EnvironmentVariablesController extends Controller
|
||||||
|
{
|
||||||
|
public function delete_env_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
|
||||||
|
if (! $env) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Environment variable not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
|
||||||
|
if (! $found_app) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Environment variable not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$env->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Environment variable deleted.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user