mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 20:59:24 +00:00
Compare commits
3053 Commits
v4.0.0-bet
...
fix-server
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
742df4f5df | ||
|
|
2522ecf832 | ||
|
|
40f2c7a1f9 | ||
|
|
3f8324d09e | ||
|
|
eee292b02f | ||
|
|
2b4dc1f9c8 | ||
|
|
dd29827a14 | ||
|
|
dfe290b546 | ||
|
|
4eeb7ee7c6 | ||
|
|
51813463b8 | ||
|
|
da8a45391c | ||
|
|
673081ffb3 | ||
|
|
8f6c01ba49 | ||
|
|
40852995b4 | ||
|
|
e6bb1deaa9 | ||
|
|
2dc3177a7a | ||
|
|
facbd9a50d | ||
|
|
a0532afb24 | ||
|
|
a725a6eaf2 | ||
|
|
3fd3660960 | ||
|
|
5898e0ac4a | ||
|
|
4f4743cc7f | ||
|
|
3b3362daf0 | ||
|
|
cd199ed81d | ||
|
|
7e32a37729 | ||
|
|
58c3cea0c0 | ||
|
|
1c10a43321 | ||
|
|
370a0b1eec | ||
|
|
282ff4e670 | ||
|
|
af09472679 | ||
|
|
053ee63606 | ||
|
|
a8d8df7d8b | ||
|
|
283fe47f5c | ||
|
|
88367d8af1 | ||
|
|
506f733632 | ||
|
|
9f2e7851f7 | ||
|
|
8269e9d29d | ||
|
|
8803cae54c | ||
|
|
c1930e3dfc | ||
|
|
c717b6859b | ||
|
|
4624a381b1 | ||
|
|
6928b0a2c8 | ||
|
|
2da6f66e85 | ||
|
|
a1124a885d | ||
|
|
9448d0f0d2 | ||
|
|
e446354d97 | ||
|
|
c8aa09359f | ||
|
|
128d732438 | ||
|
|
3b97bb1341 | ||
|
|
a4c8f83d17 | ||
|
|
0ca9885ddf | ||
|
|
4f4453b17c | ||
|
|
d60d90de92 | ||
|
|
ce49f4806d | ||
|
|
82daf6c420 | ||
|
|
f1eb43815e | ||
|
|
be9041d592 | ||
|
|
c2c0afa0ba | ||
|
|
611f70d79f | ||
|
|
59c54b8aec | ||
|
|
f0f685b78a | ||
|
|
c54d77515b | ||
|
|
9435be7cce | ||
|
|
f2ccc4059d | ||
|
|
21c6510e9d | ||
|
|
2ca73695b3 | ||
|
|
31193a9f66 | ||
|
|
9f5257bf8a | ||
|
|
78a34dbef1 | ||
|
|
f4d650b03f | ||
|
|
97ee34f1a1 | ||
|
|
9014062b48 | ||
|
|
a8f230b5eb | ||
|
|
94ee525e0a | ||
|
|
d4ace01633 | ||
|
|
49bcd7b8e6 | ||
|
|
5f613df4e0 | ||
|
|
65aeebd9fa | ||
|
|
f26d2e1d0b | ||
|
|
a1d395ff0c | ||
|
|
4e9126887f | ||
|
|
5fa650122d | ||
|
|
ee7f8200ac | ||
|
|
d2a8f31a1c | ||
|
|
2468d0044b | ||
|
|
81b8a58415 | ||
|
|
7442d19611 | ||
|
|
8c024ddb57 | ||
|
|
d990a5691d | ||
|
|
2181ef381b | ||
|
|
c80f5be974 | ||
|
|
358f6575f8 | ||
|
|
de0f34734b | ||
|
|
33cb2d150d | ||
|
|
5f07b473e9 | ||
|
|
5bcd813792 | ||
|
|
a0bb523507 | ||
|
|
0b4fc38d6b | ||
|
|
82c834915d | ||
|
|
d637675ce3 | ||
|
|
e71c04a0c7 | ||
|
|
885b3bdea7 | ||
|
|
9d6757aeb7 | ||
|
|
d84d0a816b | ||
|
|
0da31c34b5 | ||
|
|
ccdaf59ecb | ||
|
|
3fc9cf90ab | ||
|
|
c4be720b20 | ||
|
|
aabe27efd1 | ||
|
|
a8982379c9 | ||
|
|
6dd0bd0742 | ||
|
|
1d3494a6ba | ||
|
|
ee5eb427c9 | ||
|
|
ad7b5e6e1c | ||
|
|
73bd344147 | ||
|
|
ef448280d8 | ||
|
|
5282248cb4 | ||
|
|
14c9f25c57 | ||
|
|
44b3d08d86 | ||
|
|
5da1f48ae1 | ||
|
|
2bc1b9027c | ||
|
|
1c7ca56756 | ||
|
|
d70faea845 | ||
|
|
fdb5cab875 | ||
|
|
bb6cb8edc9 | ||
|
|
a6ec2b92fb | ||
|
|
436a1d945f | ||
|
|
a6a3abc273 | ||
|
|
c4e702f096 | ||
|
|
31df222798 | ||
|
|
e91939a4ea | ||
|
|
ceccd093d2 | ||
|
|
66bb4e0fc1 | ||
|
|
dd3ff38df7 | ||
|
|
69553ec314 | ||
|
|
7bb1bf0ae3 | ||
|
|
a1a8f1336a | ||
|
|
207fe1d709 | ||
|
|
059535a676 | ||
|
|
765a74ca4f | ||
|
|
e03e4f2e91 | ||
|
|
0ab432d5e6 | ||
|
|
6f25b548c7 | ||
|
|
d55e4bf381 | ||
|
|
ec216254b5 | ||
|
|
fbb36bfe8e | ||
|
|
dd782e75f5 | ||
|
|
2be2f0ac79 | ||
|
|
97943db5f4 | ||
|
|
bbd2748ad7 | ||
|
|
a530804a71 | ||
|
|
8ca8ab82b0 | ||
|
|
024ad8e943 | ||
|
|
4d86b556a4 | ||
|
|
73e38ff951 | ||
|
|
5354561c39 | ||
|
|
223cd37031 | ||
|
|
93a0725a4e | ||
|
|
4d1d4598b6 | ||
|
|
888e96448c | ||
|
|
0eea31544e | ||
|
|
8065da9fa0 | ||
|
|
5cd81fe255 | ||
|
|
b00828d4aa | ||
|
|
5a770f9ddb | ||
|
|
903d84e0dd | ||
|
|
16ccc87fbe | ||
|
|
e4108863a8 | ||
|
|
83549965ca | ||
|
|
4d9dcfb84c | ||
|
|
4db50bd025 | ||
|
|
817bd9c35d | ||
|
|
2e10d670be | ||
|
|
cec0d7d1f7 | ||
|
|
1f45532477 | ||
|
|
bcc92e1f32 | ||
|
|
31cf3294bf | ||
|
|
26307334a4 | ||
|
|
9212f3b24c | ||
|
|
31b2196ad9 | ||
|
|
2c38af1df0 | ||
|
|
89e84c5b48 | ||
|
|
39d2fdc08b | ||
|
|
ac9f817b9f | ||
|
|
852e881805 | ||
|
|
001dec6604 | ||
|
|
cf9c62cbf0 | ||
|
|
99a4f721f1 | ||
|
|
505be9dc97 | ||
|
|
cfec5b3a70 | ||
|
|
95483f1464 | ||
|
|
987b90ead2 | ||
|
|
68f541ded3 | ||
|
|
afdf4cd5a8 | ||
|
|
11dbce1471 | ||
|
|
e1037ac031 | ||
|
|
88c2b1e841 | ||
|
|
f46fd8872a | ||
|
|
b6b4d93658 | ||
|
|
8385b7dfe8 | ||
|
|
a660117015 | ||
|
|
6fe31c26a3 | ||
|
|
cccd05f322 | ||
|
|
1b0e2e1257 | ||
|
|
795af8de52 | ||
|
|
606d46eef0 | ||
|
|
56ec0a3004 | ||
|
|
b84576ce87 | ||
|
|
019a8bfaa6 | ||
|
|
fe20480fdc | ||
|
|
71e4b0f9d6 | ||
|
|
bc46b0371d | ||
|
|
f5785b1f17 | ||
|
|
2219219906 | ||
|
|
9ec3233c0c | ||
|
|
f2a9a04461 | ||
|
|
7c269bd0bf | ||
|
|
b99474ac73 | ||
|
|
fcadb20d58 | ||
|
|
24733d0670 | ||
|
|
d3898ee320 | ||
|
|
09cd087cd0 | ||
|
|
d75e55111a | ||
|
|
8e6c7eaeda | ||
|
|
9489a0881f | ||
|
|
691f606fa0 | ||
|
|
ac60b8cb22 | ||
|
|
46ae2a4ae1 | ||
|
|
f0981078ce | ||
|
|
0816169cb6 | ||
|
|
df8c5c6bf2 | ||
|
|
ea1be05c17 | ||
|
|
eafba5d115 | ||
|
|
40eec96a1e | ||
|
|
46268f915f | ||
|
|
c237cea280 | ||
|
|
26f18e25aa | ||
|
|
8d02fb9254 | ||
|
|
c52fe571f5 | ||
|
|
4995675745 | ||
|
|
65356e9bae | ||
|
|
6726964f18 | ||
|
|
9a766aedc1 | ||
|
|
96b8ddf664 | ||
|
|
8adf4051fd | ||
|
|
dedf2cf87b | ||
|
|
72e12aaf5c | ||
|
|
6c78580f1f | ||
|
|
75c8f6c94a | ||
|
|
55847d90c0 | ||
|
|
b374addc37 | ||
|
|
5541c737b7 | ||
|
|
39015e7d6d | ||
|
|
4c3beb6715 | ||
|
|
a807016cab | ||
|
|
2c2173e773 | ||
|
|
2364fac240 | ||
|
|
68c065baa0 | ||
|
|
bfc4474815 | ||
|
|
0e31c02f9e | ||
|
|
6ee80c3ea7 | ||
|
|
b9d24913e8 | ||
|
|
9a1c9124ae | ||
|
|
306c4dcbc5 | ||
|
|
ed7e4360c8 | ||
|
|
f02db282ca | ||
|
|
3535dbb98f | ||
|
|
84b2af53d8 | ||
|
|
ba70451765 | ||
|
|
f3ec4ca4a3 | ||
|
|
174923de98 | ||
|
|
68ab8dc14c | ||
|
|
b218356f2d | ||
|
|
e83aecebc3 | ||
|
|
0bb1f57ea7 | ||
|
|
49144552b3 | ||
|
|
111e9ba3a3 | ||
|
|
d006edc485 | ||
|
|
aa28c99827 | ||
|
|
47920f1191 | ||
|
|
6bd8583eab | ||
|
|
92dbd44c8c | ||
|
|
2cf0f4facc | ||
|
|
be6caf8d76 | ||
|
|
6cf5a2802b | ||
|
|
9c756ad6c5 | ||
|
|
20de0c56a4 | ||
|
|
0058f9aa13 | ||
|
|
567bbe9d0b | ||
|
|
59d2c9748a | ||
|
|
bd2e1ad9fe | ||
|
|
43df918e43 | ||
|
|
1a8d15390d | ||
|
|
4af6caa79c | ||
|
|
dfd6fdf0a8 | ||
|
|
b0635327a1 | ||
|
|
8e91d958be | ||
|
|
17d55630d5 | ||
|
|
70dfa101ef | ||
|
|
d6b4e33db3 | ||
|
|
a9670bd6eb | ||
|
|
02165c2b9f | ||
|
|
afbdd2eb06 | ||
|
|
b0e6014e8f | ||
|
|
0f13af2eca | ||
|
|
d4d9268f12 | ||
|
|
f9dbc30812 | ||
|
|
62459f9a95 | ||
|
|
eb80a7e2f7 | ||
|
|
57d8930f9e | ||
|
|
688c27c901 | ||
|
|
480ae3de8a | ||
|
|
21a5b4ce80 | ||
|
|
12a0d71e47 | ||
|
|
5ed7ae3d3e | ||
|
|
6dd3adba4d | ||
|
|
08d58eb8f8 | ||
|
|
46a7937761 | ||
|
|
13a5536cff | ||
|
|
2381706465 | ||
|
|
7a0f054f1a | ||
|
|
00e1464185 | ||
|
|
fac4a8aaf9 | ||
|
|
2841675691 | ||
|
|
573e5c4913 | ||
|
|
6934a746f7 | ||
|
|
b570ccd7d3 | ||
|
|
68efd4b553 | ||
|
|
e00ec2f75b | ||
|
|
1bb192f3e2 | ||
|
|
e039f183da | ||
|
|
7aab44645f | ||
|
|
7c52d7ef9e | ||
|
|
83c6dcb063 | ||
|
|
6bb21da9f3 | ||
|
|
5a46ef42c8 | ||
|
|
04e504bb8b | ||
|
|
960f970822 | ||
|
|
ae62781c6a | ||
|
|
b42c15b2e5 | ||
|
|
c77cc45e0d | ||
|
|
310708f1b6 | ||
|
|
8386aaf95b | ||
|
|
6d8d91802b | ||
|
|
c0a1f15d34 | ||
|
|
0222aa137d | ||
|
|
4d36bc4742 | ||
|
|
6639379ba6 | ||
|
|
d60ff107c4 | ||
|
|
a468ce77f0 | ||
|
|
1fb0ee0db5 | ||
|
|
9281dda03f | ||
|
|
d3485cea8d | ||
|
|
92772f9132 | ||
|
|
6f8c4a4ce4 | ||
|
|
85ab772acd | ||
|
|
6336125ca2 | ||
|
|
341c7e3598 | ||
|
|
ff9b68b450 | ||
|
|
a072a00926 | ||
|
|
b84d39ba56 | ||
|
|
164a213ab1 | ||
|
|
696acb71fe | ||
|
|
ff5e445b43 | ||
|
|
4e167dc539 | ||
|
|
76655a7c7d | ||
|
|
a26c1f0052 | ||
|
|
cb4f7755c3 | ||
|
|
b7139f8af8 | ||
|
|
a5b3fef1d8 | ||
|
|
fca64d6726 | ||
|
|
b9a026b488 | ||
|
|
a4d0f1da9e | ||
|
|
eb9bbf3eda | ||
|
|
be42f15711 | ||
|
|
768b39dd25 | ||
|
|
000a348c90 | ||
|
|
56f7eb769d | ||
|
|
cc77775f20 | ||
|
|
ca21545d7b | ||
|
|
fa375e2fd8 | ||
|
|
760cf8aeb5 | ||
|
|
f9238ce263 | ||
|
|
c0898f0568 | ||
|
|
5b00b66f24 | ||
|
|
b81f9114d9 | ||
|
|
be8573c828 | ||
|
|
f1881d5c35 | ||
|
|
699e76637b | ||
|
|
dbc723089b | ||
|
|
fc6f5d82db | ||
|
|
1815c9dccf | ||
|
|
7d54fe9c18 | ||
|
|
e9fbb7d2b0 | ||
|
|
2d94ebf7f1 | ||
|
|
c874261c5b | ||
|
|
1c5fd8d668 | ||
|
|
874e04e844 | ||
|
|
c46715db3c | ||
|
|
aa0fe922ea | ||
|
|
60e9fcc202 | ||
|
|
91485b5d4d | ||
|
|
6ec1b9b3f5 | ||
|
|
240352f4b2 | ||
|
|
cfd9ae9817 | ||
|
|
f65789bdbb | ||
|
|
9518040d23 | ||
|
|
631b4e6438 | ||
|
|
d47bd047bf | ||
|
|
a65b62332b | ||
|
|
eb10bbb888 | ||
|
|
4b02debf97 | ||
|
|
430fdcf2e9 | ||
|
|
636de24b6c | ||
|
|
2e3267ee94 | ||
|
|
98e744e808 | ||
|
|
532f5e351e | ||
|
|
182087cf1b | ||
|
|
0494f9a7e5 | ||
|
|
6676c6bd64 | ||
|
|
22326ec0b4 | ||
|
|
8153e2f63b | ||
|
|
b0793c879a | ||
|
|
dce8555aa7 | ||
|
|
35857de697 | ||
|
|
cb177dae58 | ||
|
|
4e8708051d | ||
|
|
5b27eef984 | ||
|
|
d1f28b42e5 | ||
|
|
513ca6f57d | ||
|
|
5ec45d547a | ||
|
|
4aed40fb65 | ||
|
|
d2cf53578a | ||
|
|
1fa42ec82d | ||
|
|
e42c7e258c | ||
|
|
2c210abf57 | ||
|
|
98ba7ac28c | ||
|
|
5772f2c3d9 | ||
|
|
cb9c569d4b | ||
|
|
5616e588e9 | ||
|
|
40e844fab4 | ||
|
|
2e56edd939 | ||
|
|
8179a5c6a3 | ||
|
|
d0518153fb | ||
|
|
29236bf101 | ||
|
|
2d306d56ab | ||
|
|
e5768999e4 | ||
|
|
b076db2eed | ||
|
|
3534424dc8 | ||
|
|
d6f498e6bb | ||
|
|
3e0a5191b9 | ||
|
|
28cde9fd50 | ||
|
|
e415a99f71 | ||
|
|
3b6c3609c3 | ||
|
|
95a97cf9cd | ||
|
|
7b33ef705b | ||
|
|
39449584ac | ||
|
|
19458e7445 | ||
|
|
2d9c728a64 | ||
|
|
12a8e9b0e1 | ||
|
|
649cc2dac2 | ||
|
|
c169f1f64b | ||
|
|
5ecf31d1fc | ||
|
|
e937d30545 | ||
|
|
bf48b33e64 | ||
|
|
595a2414b1 | ||
|
|
07ed726c88 | ||
|
|
d373815f98 | ||
|
|
d9181bd00b | ||
|
|
d13e2c0865 | ||
|
|
42ff7b19a4 | ||
|
|
283fcc87a5 | ||
|
|
ea3501ada6 | ||
|
|
175f4b9ae1 | ||
|
|
2bc74c75e1 | ||
|
|
4ac2758d70 | ||
|
|
bdc0fc87f0 | ||
|
|
845d32c94c | ||
|
|
6a6b947fba | ||
|
|
2ec66fd146 | ||
|
|
ccbbfd8908 | ||
|
|
43895419ff | ||
|
|
1c78067386 | ||
|
|
871d09bd96 | ||
|
|
2d8bda4fa6 | ||
|
|
95070ab48d | ||
|
|
8bb8a7faa3 | ||
|
|
428c40aab5 | ||
|
|
52c4994d44 | ||
|
|
6f578855a0 | ||
|
|
8967315c49 | ||
|
|
162fb7bfc5 | ||
|
|
144508218e | ||
|
|
4bdb5c9030 | ||
|
|
a2ea8814cf | ||
|
|
cbc2f1f015 | ||
|
|
35b9b7fdf2 | ||
|
|
d92819ab60 | ||
|
|
ea877f3623 | ||
|
|
5818c9cf6b | ||
|
|
f9375f91ec | ||
|
|
86722939cd | ||
|
|
70b757df5b | ||
|
|
451272bf11 | ||
|
|
a68fbefadb | ||
|
|
b79b4015d7 | ||
|
|
0bfdc1c531 | ||
|
|
b09017ea46 | ||
|
|
95fcf38d45 | ||
|
|
54c03fae41 | ||
|
|
b2bab451d3 | ||
|
|
7b4559c5e6 | ||
|
|
ba636a95dc | ||
|
|
682b45a2b5 | ||
|
|
d44e3a1091 | ||
|
|
d2d56f136b | ||
|
|
9b48a99798 | ||
|
|
1322dc9c23 | ||
|
|
f71fb7266d | ||
|
|
35f23cfb96 | ||
|
|
0c4ce55a15 | ||
|
|
77ee80b562 | ||
|
|
2621292dc1 | ||
|
|
62a4d7055a | ||
|
|
3fd41c0a92 | ||
|
|
0e8291cd86 | ||
|
|
175b89ced2 | ||
|
|
3aee8e030e | ||
|
|
02017334e5 | ||
|
|
f9b7841572 | ||
|
|
7d39a5089c | ||
|
|
2313fed546 | ||
|
|
e1a6c3e776 | ||
|
|
b41a19d94d | ||
|
|
7037c779e2 | ||
|
|
f124a1e60d | ||
|
|
4450ee382f | ||
|
|
7ebb0a4579 | ||
|
|
4962c606bc | ||
|
|
b728d69ab0 | ||
|
|
3d6e53602c | ||
|
|
1c6450da24 | ||
|
|
15fe5bd864 | ||
|
|
08e4afbbc4 | ||
|
|
06fd7286da | ||
|
|
fce1e34fc8 | ||
|
|
0739e0f5e7 | ||
|
|
faf7ba50e6 | ||
|
|
e8179ec519 | ||
|
|
220f77e737 | ||
|
|
7948a0309f | ||
|
|
47277a68ec | ||
|
|
60f75f9deb | ||
|
|
e90d1af884 | ||
|
|
e3046698a5 | ||
|
|
bb773e1118 | ||
|
|
dcf91cc034 | ||
|
|
cddf8476de | ||
|
|
51c43e7457 | ||
|
|
7cac243589 | ||
|
|
0cfd8ed5f0 | ||
|
|
8318598cb5 | ||
|
|
2f692da1c9 | ||
|
|
ecd98bfcd5 | ||
|
|
ba860398f3 | ||
|
|
1f9af39fa7 | ||
|
|
62b995d26c | ||
|
|
b28d118609 | ||
|
|
81d0589c55 | ||
|
|
07893b432b | ||
|
|
568d47d1dd | ||
|
|
85cce4e453 | ||
|
|
888c1f7697 | ||
|
|
79c8ce7572 | ||
|
|
121afaa18c | ||
|
|
e48ad87cdb | ||
|
|
a587cae251 | ||
|
|
c04c1530f5 | ||
|
|
f4ee61fc6a | ||
|
|
b6080c2c8e | ||
|
|
1738286983 | ||
|
|
703bf51705 | ||
|
|
aa4980289d | ||
|
|
dd8a2dd3c1 | ||
|
|
3086ef1462 | ||
|
|
0b33315991 | ||
|
|
b6b9179bb3 | ||
|
|
9122983f22 | ||
|
|
b9b33c831c | ||
|
|
ccdcea665d | ||
|
|
7b5aa78557 | ||
|
|
9310af0301 | ||
|
|
e38b29d833 | ||
|
|
0d3d5f40fd | ||
|
|
c402d7f543 | ||
|
|
894f2c66d3 | ||
|
|
d4679a8be1 | ||
|
|
7ac45aa706 | ||
|
|
8862b50c98 | ||
|
|
b5a56892fd | ||
|
|
664a990c60 | ||
|
|
16e472da19 | ||
|
|
7dd0588bfe | ||
|
|
a95ebb4d56 | ||
|
|
75f266fa9f | ||
|
|
90fd0ebf12 | ||
|
|
4aeb8ff02b | ||
|
|
8bc22e185b | ||
|
|
5244ef60bd | ||
|
|
f3748fc294 | ||
|
|
03d419e591 | ||
|
|
9e90daf414 | ||
|
|
420f1df998 | ||
|
|
f672a08afb | ||
|
|
67d44713e7 | ||
|
|
0b950f444a | ||
|
|
8712af7379 | ||
|
|
35dfb1b0f8 | ||
|
|
2edcd01493 | ||
|
|
72c08e4496 | ||
|
|
baa236f934 | ||
|
|
1a40bebaae | ||
|
|
25f5060333 | ||
|
|
6e48c94c6e | ||
|
|
410b55cf03 | ||
|
|
0881d001d6 | ||
|
|
483b4f8eb7 | ||
|
|
c9a52c552b | ||
|
|
72f0118506 | ||
|
|
bc13ad6b82 | ||
|
|
b145691e1e | ||
|
|
5d476e3924 | ||
|
|
3f3bf075fa | ||
|
|
6498e5b6f0 | ||
|
|
a3caad239c | ||
|
|
117fbeb07c | ||
|
|
33e9c9b0f9 | ||
|
|
577927a20b | ||
|
|
409d5ef60e | ||
|
|
f1a1deff26 | ||
|
|
9d42a7f146 | ||
|
|
826b64d056 | ||
|
|
2c9491d81c | ||
|
|
d378bb94be | ||
|
|
d74cfd09ce | ||
|
|
91c845732e | ||
|
|
83698ac8f2 | ||
|
|
f718604ac4 | ||
|
|
3e188bf947 | ||
|
|
f70160a17c | ||
|
|
5722d1a220 | ||
|
|
bae9ea79de | ||
|
|
f3845ce30a | ||
|
|
d57c9d8aa0 | ||
|
|
c7c2924990 | ||
|
|
aa30e83f4a | ||
|
|
77455cd444 | ||
|
|
2777fbc0ec | ||
|
|
2c929b300a | ||
|
|
27ad4441ee | ||
|
|
63729c7bbf | ||
|
|
1e68444f10 | ||
|
|
f8dfe643f4 | ||
|
|
66cf6df4b3 | ||
|
|
29acc4ee25 | ||
|
|
f73983e3dd | ||
|
|
44d417c07e | ||
|
|
1d72f76072 | ||
|
|
565cb54dba | ||
|
|
c2f7e85022 | ||
|
|
9e6486fa66 | ||
|
|
28bcd0023c | ||
|
|
84093afaf6 | ||
|
|
40347744e0 | ||
|
|
060988d923 | ||
|
|
b093c9757c | ||
|
|
25976a4870 | ||
|
|
4f9e1a3e5e | ||
|
|
c8218e6901 | ||
|
|
cc10d08a7c | ||
|
|
e5a980d544 | ||
|
|
59c7f2cb56 | ||
|
|
d2a306dab9 | ||
|
|
681a745fc7 | ||
|
|
abb6655fef | ||
|
|
aa586a5677 | ||
|
|
87ee3c511c | ||
|
|
a17daf96be | ||
|
|
a419496066 | ||
|
|
c668d2d1ff | ||
|
|
4611332a8f | ||
|
|
8b3eed5895 | ||
|
|
9861ad4045 | ||
|
|
5fb560dbbf | ||
|
|
435c0baa87 | ||
|
|
bb1454d255 | ||
|
|
a882d3c713 | ||
|
|
6010b3209d | ||
|
|
504133232a | ||
|
|
d816863d31 | ||
|
|
6b475cc1bf | ||
|
|
5ef2d476a4 | ||
|
|
3eb909d3bc | ||
|
|
a102e812cf | ||
|
|
254611dda8 | ||
|
|
d8f9e5910f | ||
|
|
68d4a30eb0 | ||
|
|
5c05ea4463 | ||
|
|
a2dac9394a | ||
|
|
7fabeec08d | ||
|
|
a9228b110e | ||
|
|
148c7d212c | ||
|
|
98154549cf | ||
|
|
3d4f02afde | ||
|
|
b57e2960f2 | ||
|
|
110fac944d | ||
|
|
518ba3502d | ||
|
|
676cee9e3e | ||
|
|
ec7b18556e | ||
|
|
8d2b280b03 | ||
|
|
ba0e29aaf4 | ||
|
|
fccd31009c | ||
|
|
34e213202a | ||
|
|
e7ab43c018 | ||
|
|
0c87a8f4ec | ||
|
|
54e69de2af | ||
|
|
545613b554 | ||
|
|
5c7b9473f8 | ||
|
|
a32e53c5a3 | ||
|
|
b44f4e49e8 | ||
|
|
321b8559e0 | ||
|
|
69794f4ca8 | ||
|
|
4e1663e9db | ||
|
|
2e5ed5969d | ||
|
|
ab055e0692 | ||
|
|
6c2717ad08 | ||
|
|
3ac52af673 | ||
|
|
22d99dec52 | ||
|
|
6a654e31bd | ||
|
|
d07cf59460 | ||
|
|
90e435d392 | ||
|
|
dfadadda10 | ||
|
|
0a7c7036f1 | ||
|
|
9215054d72 | ||
|
|
eb5765979f | ||
|
|
fc3c69f687 | ||
|
|
22de8a7d2b | ||
|
|
6f5b92d322 | ||
|
|
9ef44f9ffb | ||
|
|
5a604b2fc4 | ||
|
|
65b8d3c6d4 | ||
|
|
e05e0da058 | ||
|
|
e4854aaa1b | ||
|
|
2531574a9a | ||
|
|
3aa31f3da6 | ||
|
|
645337b09c | ||
|
|
bd3220ad73 | ||
|
|
6528bc5766 | ||
|
|
93af54ef84 | ||
|
|
08b9c79298 | ||
|
|
22bd305e8f | ||
|
|
dbad08f4dd | ||
|
|
b5232f3d32 | ||
|
|
58234ef65e | ||
|
|
d211362ab3 | ||
|
|
629316d68a | ||
|
|
05084cb69c | ||
|
|
401c410adb | ||
|
|
4888f4c405 | ||
|
|
b241f17eee | ||
|
|
bc22ec63e6 | ||
|
|
458a461388 | ||
|
|
a725ff0a75 | ||
|
|
a8fd7db9a8 | ||
|
|
fbb7568786 | ||
|
|
fae175039a | ||
|
|
9196571737 | ||
|
|
08df814408 | ||
|
|
a7b78dcf41 | ||
|
|
0dad77af33 | ||
|
|
2b5df8d2fd | ||
|
|
f4263ee022 | ||
|
|
44f3f6001e | ||
|
|
9105c1aa51 | ||
|
|
3e04a7958e | ||
|
|
505127dae5 | ||
|
|
371fe53911 | ||
|
|
9515bc6162 | ||
|
|
93a4a3e09c | ||
|
|
bbbd5cbaa1 | ||
|
|
f43298a610 | ||
|
|
7fe3b78d45 | ||
|
|
02f950edc7 | ||
|
|
a29353c1ae | ||
|
|
817a72e753 | ||
|
|
9c2f4ec04e | ||
|
|
c16e914be4 | ||
|
|
bec974dca4 | ||
|
|
3f34df251e | ||
|
|
b314b08f25 | ||
|
|
bfeaae9caa | ||
|
|
ba90a52344 | ||
|
|
a3a61dbe55 | ||
|
|
5799e6d8b0 | ||
|
|
3b533c7d06 | ||
|
|
b2944f11db | ||
|
|
25e2b812cb | ||
|
|
59383d3678 | ||
|
|
7b2d09f9d1 | ||
|
|
d30faf7bc4 | ||
|
|
b9d3f9da62 | ||
|
|
86ee9c09ce | ||
|
|
4f32b48d29 | ||
|
|
04ce622465 | ||
|
|
ed7817906a | ||
|
|
e0748ee240 | ||
|
|
25480fe624 | ||
|
|
b0039885eb | ||
|
|
cadb12986c | ||
|
|
63a07e7649 | ||
|
|
9a2d5be354 | ||
|
|
37915234ad | ||
|
|
eaabf94cd7 | ||
|
|
3badbafd89 | ||
|
|
7b041f3f22 | ||
|
|
8150b6fdaf | ||
|
|
d94e39ccc7 | ||
|
|
d5b7e9ed83 | ||
|
|
f29bc52fa5 | ||
|
|
3d21f1a2a4 | ||
|
|
8d9a7f0b3c | ||
|
|
a7d67e44ca | ||
|
|
08a80876f9 | ||
|
|
20558d438a | ||
|
|
792f6bc163 | ||
|
|
3d1c730703 | ||
|
|
c3188958b4 | ||
|
|
c24fec9c45 | ||
|
|
70043c24cf | ||
|
|
ff1e08cf8b | ||
|
|
a4d1ae1341 | ||
|
|
5944ee5524 | ||
|
|
1b0c5f8d69 | ||
|
|
dfd218ec06 | ||
|
|
ae4c889fa2 | ||
|
|
eb6add358a | ||
|
|
dfd5cc9cef | ||
|
|
a2ab23529f | ||
|
|
62de2b88e6 | ||
|
|
b67f363a1b | ||
|
|
12a3aa6356 | ||
|
|
f8d6ce2730 | ||
|
|
4169d727fd | ||
|
|
f072823f00 | ||
|
|
843e3fb599 | ||
|
|
a97ccd206c | ||
|
|
776d41613d | ||
|
|
f857bbc437 | ||
|
|
f8226cf892 | ||
|
|
d2a0621f93 | ||
|
|
38845d7eb0 | ||
|
|
3b3bc6c33b | ||
|
|
2adac01034 | ||
|
|
b656cabb33 | ||
|
|
830c047ccf | ||
|
|
a3dd48de1d | ||
|
|
b118a627d0 | ||
|
|
bcfca40f3a | ||
|
|
76cb473db8 | ||
|
|
73dfdb83a7 | ||
|
|
c6df243623 | ||
|
|
59c95c483a | ||
|
|
887f21acdd | ||
|
|
d2aeed9aff | ||
|
|
2fc4e98062 | ||
|
|
ad3ed10345 | ||
|
|
eebb99ca22 | ||
|
|
bff6964d4a | ||
|
|
b807601d19 | ||
|
|
9136d7acdc | ||
|
|
bf475e538c | ||
|
|
90267a96c1 | ||
|
|
5f0a8bc4f2 | ||
|
|
eee5539765 | ||
|
|
14fc067057 | ||
|
|
fe477ba325 | ||
|
|
da0398f35d | ||
|
|
6820fcc084 | ||
|
|
d984bec175 | ||
|
|
182af1ec18 | ||
|
|
278c8c6ec6 | ||
|
|
5e4eb7dead | ||
|
|
a22e757ab7 | ||
|
|
301a3596e8 | ||
|
|
0df1163718 | ||
|
|
e7b050a4da | ||
|
|
b64a126c89 | ||
|
|
2e55b55e2c | ||
|
|
8b5f4a597c | ||
|
|
065e1b588b | ||
|
|
9cdff0393f | ||
|
|
c588cef5be | ||
|
|
2d4385c273 | ||
|
|
020256b9b5 | ||
|
|
162cdf6f28 | ||
|
|
d586aa0377 | ||
|
|
e0bac2499d | ||
|
|
4a64374bb3 | ||
|
|
cfc6518157 | ||
|
|
354c74e920 | ||
|
|
b8a37d897e | ||
|
|
d27b1766e6 | ||
|
|
d25b6d6ea6 | ||
|
|
5525c02c7f | ||
|
|
39804a7b20 | ||
|
|
be8a8bf2ee | ||
|
|
43f2f1ef2b | ||
|
|
62b7900855 | ||
|
|
c92659994d | ||
|
|
a2651ab3c1 | ||
|
|
1b51d46b3d | ||
|
|
f6d649307c | ||
|
|
141752b9ad | ||
|
|
b51065a003 | ||
|
|
73068aaa75 | ||
|
|
954d82207d | ||
|
|
0cf595e552 | ||
|
|
c87732b065 | ||
|
|
2d9c583af3 | ||
|
|
7103dccc72 | ||
|
|
a245d16e05 | ||
|
|
eb22155dcc | ||
|
|
49a53236b0 | ||
|
|
1a0a115fc1 | ||
|
|
d8d821e7a9 | ||
|
|
8d2a02dc3c | ||
|
|
4726676248 | ||
|
|
9040f5d2a1 | ||
|
|
ac50d8b4d8 | ||
|
|
2a581147aa | ||
|
|
8d9314347f | ||
|
|
ed2e5f59f9 | ||
|
|
d7c685af1f | ||
|
|
2398afc74f | ||
|
|
b4e41a2a9e | ||
|
|
0425a47f69 | ||
|
|
c9663c4108 | ||
|
|
19881aa83a | ||
|
|
3c413ff5c9 | ||
|
|
a03dc8ea5e | ||
|
|
182ddeefcb | ||
|
|
3ec8556c5f | ||
|
|
5bfddfbb95 | ||
|
|
68169f75d1 | ||
|
|
8d24e93a16 | ||
|
|
cd73a9a94e | ||
|
|
cda66dadef | ||
|
|
ca3268ce56 | ||
|
|
9776e14e93 | ||
|
|
73e14e37ac | ||
|
|
b8ff0540e2 | ||
|
|
3d73c98779 | ||
|
|
4d7877ce1d | ||
|
|
36b434efb2 | ||
|
|
e52f762cf9 | ||
|
|
d748d8b7c6 | ||
|
|
1a9e1bcc88 | ||
|
|
040115d6b1 | ||
|
|
7fd06a49db | ||
|
|
bfc538b6ed | ||
|
|
cb39544808 | ||
|
|
1a232b9b10 | ||
|
|
e9720dc5e0 | ||
|
|
b1317a0bf6 | ||
|
|
d0ce31c9e0 | ||
|
|
f98805c68b | ||
|
|
7d78e0171d | ||
|
|
fe89269b4b | ||
|
|
2a9a2dd7c4 | ||
|
|
eece96e717 | ||
|
|
b12f768c56 | ||
|
|
dfa30bbb7f | ||
|
|
9d5556aea2 | ||
|
|
ba4315fabb | ||
|
|
355352417e | ||
|
|
6fbdfee3e7 | ||
|
|
af1b479d73 | ||
|
|
0f9076562f | ||
|
|
7e154ba3c3 | ||
|
|
a26bc65137 | ||
|
|
79a120cd85 | ||
|
|
5394223f6b | ||
|
|
1e24ab9146 | ||
|
|
0243ddd52b | ||
|
|
d2eb7046e8 | ||
|
|
cf505fa500 | ||
|
|
62d63037e2 | ||
|
|
df03b950eb | ||
|
|
ecb2c3b7b8 | ||
|
|
9462915c83 | ||
|
|
578db6cc9c | ||
|
|
7d7cdf41f7 | ||
|
|
3f9f197282 | ||
|
|
b73db2d725 | ||
|
|
8168b564ea | ||
|
|
1e46b63041 | ||
|
|
8dc26a18d8 | ||
|
|
7d4261b71a | ||
|
|
0337c4e79d | ||
|
|
012c23586d | ||
|
|
5d32bd250b | ||
|
|
4ab13ecf00 | ||
|
|
c813373d21 | ||
|
|
abd2aefd6c | ||
|
|
ef40b79a5f | ||
|
|
f3df26ea9a | ||
|
|
71bb1f5e17 | ||
|
|
ce926afdaa | ||
|
|
bf6404ab24 | ||
|
|
a0689ca5fc | ||
|
|
9ab03e52a3 | ||
|
|
48734e53d0 | ||
|
|
80b90b3a2c | ||
|
|
7a001cea3b | ||
|
|
62ecc45f21 | ||
|
|
789adc77fd | ||
|
|
4af7b8f451 | ||
|
|
165275cb68 | ||
|
|
f366854671 | ||
|
|
a981b49502 | ||
|
|
74bf4629d6 | ||
|
|
3e3b92638b | ||
|
|
00f20c708f | ||
|
|
6242243ced | ||
|
|
fda6c03505 | ||
|
|
7b4182352d | ||
|
|
4a476586df | ||
|
|
883a52afe9 | ||
|
|
dab5f0fe09 | ||
|
|
d906bb2381 | ||
|
|
1e711de52a | ||
|
|
d2eaf4f2e3 | ||
|
|
e3b9884247 | ||
|
|
f106e6e37b | ||
|
|
ea4b085dbe | ||
|
|
1892ce4e12 | ||
|
|
c15740aa57 | ||
|
|
2227858f58 | ||
|
|
eaefb3a6fb | ||
|
|
0c98958f72 | ||
|
|
9ec7d748df | ||
|
|
0139c4e404 | ||
|
|
ac468f616b | ||
|
|
2b8c9920d8 | ||
|
|
0e844533de | ||
|
|
548fc21e40 | ||
|
|
c2ea8996ee | ||
|
|
fcfbba4dc6 | ||
|
|
a478ebef2e | ||
|
|
f281e92954 | ||
|
|
1069e33601 | ||
|
|
39b132f7e9 | ||
|
|
a1915e40f7 | ||
|
|
a51b306c08 | ||
|
|
29192a79d0 | ||
|
|
c7a79514cc | ||
|
|
4b744bc88a | ||
|
|
1376846077 | ||
|
|
9864d380a3 | ||
|
|
ef6cfd47c3 | ||
|
|
f0b9bd2cf2 | ||
|
|
44f319460f | ||
|
|
99f2d4d711 | ||
|
|
777913923f | ||
|
|
dab0cc8cbd | ||
|
|
f1f6dea04f | ||
|
|
f93fe75de9 | ||
|
|
a4f80fd260 | ||
|
|
8aa161d530 | ||
|
|
53bcf81af5 | ||
|
|
a5f526a6c8 | ||
|
|
f90316b050 | ||
|
|
9e380ac942 | ||
|
|
dcb9bec3c2 | ||
|
|
0a02317248 | ||
|
|
8867ecbaee | ||
|
|
7adb38e64e | ||
|
|
dea5e5f6e2 | ||
|
|
7059b94051 | ||
|
|
e70c2431ab | ||
|
|
f01f09a79d | ||
|
|
4d12447715 | ||
|
|
4a89d61a59 | ||
|
|
12c9691bb4 | ||
|
|
1b6c0aef93 | ||
|
|
c9fc8fa687 | ||
|
|
adf7a4422a | ||
|
|
0286d639fb | ||
|
|
460b992994 | ||
|
|
0acc8b1414 | ||
|
|
529dc0f7b0 | ||
|
|
14937970e2 | ||
|
|
14db362d63 | ||
|
|
ea3e4f3188 | ||
|
|
094499e1a3 | ||
|
|
de34150451 | ||
|
|
992d8b780c | ||
|
|
06013e77e4 | ||
|
|
da6dea7f13 | ||
|
|
096e366547 | ||
|
|
80aeb096c9 | ||
|
|
77044d51c7 | ||
|
|
fbde257166 | ||
|
|
a15f4d86de | ||
|
|
228b885946 | ||
|
|
ba136d43fa | ||
|
|
555dc1a9b5 | ||
|
|
0e54ed1343 | ||
|
|
e28289ce1e | ||
|
|
122491808c | ||
|
|
1d7c833b7c | ||
|
|
93444ea872 | ||
|
|
c5aba34a6f | ||
|
|
d13c321c3d | ||
|
|
f6cb39732a | ||
|
|
365f957b8a | ||
|
|
b85b5e67bb | ||
|
|
4eaee6f474 | ||
|
|
a5db3b85fa | ||
|
|
f87e6bcfc6 | ||
|
|
991c215a10 | ||
|
|
e068581862 | ||
|
|
4904b33a0f | ||
|
|
0d994aa2c3 | ||
|
|
0e5cd3de9b | ||
|
|
b738e5c000 | ||
|
|
27a15138b7 | ||
|
|
adc3346f7b | ||
|
|
5b54dc8792 | ||
|
|
b5360e5e75 | ||
|
|
840e225aa8 | ||
|
|
7d1179e7c8 | ||
|
|
16a5c601e3 | ||
|
|
c566152f37 | ||
|
|
2ca6ffb84e | ||
|
|
41be1f7666 | ||
|
|
450351921e | ||
|
|
a4bb87d13b | ||
|
|
1cfddfd529 | ||
|
|
72bcf03cbb | ||
|
|
d177e49e62 | ||
|
|
5595853379 | ||
|
|
97c2bedda2 | ||
|
|
d980c7a425 | ||
|
|
53dff4ca4f | ||
|
|
7722809c52 | ||
|
|
e67e03f73f | ||
|
|
86a087056e | ||
|
|
51071da700 | ||
|
|
70aa05bde9 | ||
|
|
0135e2b5c0 | ||
|
|
4e0e064725 | ||
|
|
2f95349888 | ||
|
|
69fc4c7f52 | ||
|
|
2e06acf653 | ||
|
|
d635799b80 | ||
|
|
0590456b62 | ||
|
|
e2726d9dfb | ||
|
|
2be92a1d29 | ||
|
|
d75ed0b208 | ||
|
|
8fa1fcf96e | ||
|
|
f089185c39 | ||
|
|
fa28e952de | ||
|
|
c2e431d631 | ||
|
|
4d0acee95c | ||
|
|
6fb88ed479 | ||
|
|
58c6d066f2 | ||
|
|
1280d1721f | ||
|
|
74bea37b43 | ||
|
|
671b22e964 | ||
|
|
777bff6553 | ||
|
|
d3b3c2f7fd | ||
|
|
070daee28e | ||
|
|
df796dffa2 | ||
|
|
6ca49eb1ac | ||
|
|
bfd79c5270 | ||
|
|
bacd2531b5 | ||
|
|
0b000919cf | ||
|
|
92f90d4e52 | ||
|
|
59702c6dbc | ||
|
|
13e8d3c17c | ||
|
|
5d384b1149 | ||
|
|
7b31955409 | ||
|
|
cf09290b51 | ||
|
|
78998110d7 | ||
|
|
1564e3c371 | ||
|
|
ddf0ff8f25 | ||
|
|
4336acc16e | ||
|
|
efb4049966 | ||
|
|
6da359bc60 | ||
|
|
f5f3c77d9e | ||
|
|
dc5c324f9c | ||
|
|
d283be4917 | ||
|
|
f0278bc33d | ||
|
|
052565f4e8 | ||
|
|
64146a46fc | ||
|
|
5a82395bb7 | ||
|
|
d3085e1ade | ||
|
|
af41ed26ba | ||
|
|
961ba49d89 | ||
|
|
a2150f2f7d | ||
|
|
46b549ab8d | ||
|
|
e4c3d61b76 | ||
|
|
869f0878c2 | ||
|
|
dfb6d4da3d | ||
|
|
5d4a379e8d | ||
|
|
73a265107a | ||
|
|
b77171d2f2 | ||
|
|
8b817dad87 | ||
|
|
c4436aadfa | ||
|
|
c9a7af0ffa | ||
|
|
c648e0dff9 | ||
|
|
e897eb2999 | ||
|
|
35e62a3003 | ||
|
|
b66c511160 | ||
|
|
a866bf437d | ||
|
|
a4f107e191 | ||
|
|
f22d7741a3 | ||
|
|
f4d64e121c | ||
|
|
818d5e1159 | ||
|
|
7eacdf23f9 | ||
|
|
0459baa55e | ||
|
|
a426c00a03 | ||
|
|
fd36e143e0 | ||
|
|
2243a3304b | ||
|
|
bb23141138 | ||
|
|
5f1e1c0ac4 | ||
|
|
ef8be5f133 | ||
|
|
f205c0ab9e | ||
|
|
c4cf116e6e | ||
|
|
f7b1aaca92 | ||
|
|
0ce41d2c1c | ||
|
|
d9edb1c72f | ||
|
|
93322dc3cf | ||
|
|
22f04e4708 | ||
|
|
d9a079c289 | ||
|
|
f4690bf15a | ||
|
|
a81faa68b8 | ||
|
|
4b388b463d | ||
|
|
3c98b558f6 | ||
|
|
e3c7c615c6 | ||
|
|
74e8a4a703 | ||
|
|
c5de1a25c3 | ||
|
|
d4cb7e25dc | ||
|
|
50ede5cab9 | ||
|
|
b64d4881cb | ||
|
|
4dfec6771c | ||
|
|
92ebc3f0c6 | ||
|
|
3e0821e471 | ||
|
|
38976dac12 | ||
|
|
ea5101c814 | ||
|
|
8133a8b770 | ||
|
|
3269ca3eb8 | ||
|
|
012d660886 | ||
|
|
ddb4b4d215 | ||
|
|
2725a93bfd | ||
|
|
27e82f0bde | ||
|
|
5755965b4f | ||
|
|
e4b92bb660 | ||
|
|
bc48b42ff1 | ||
|
|
94b2d67a6e | ||
|
|
7d6a895449 | ||
|
|
39e3ea9f07 | ||
|
|
465b254813 | ||
|
|
e2bc3f4841 | ||
|
|
b38a651a08 | ||
|
|
76c39a987c | ||
|
|
1d8c496906 | ||
|
|
820693ac22 | ||
|
|
d7b45a6dd2 | ||
|
|
cb2fc68dde | ||
|
|
2e1e1cd8b3 | ||
|
|
5184426ad0 | ||
|
|
e6e48c5812 | ||
|
|
fd855847ff | ||
|
|
35dbced3c5 | ||
|
|
e8b2d8bf03 | ||
|
|
9887d5eedd | ||
|
|
18ae29ba99 | ||
|
|
1feb8488a3 | ||
|
|
342ef4d367 | ||
|
|
85d080a042 | ||
|
|
69c48a511e | ||
|
|
10b9c4bcfa | ||
|
|
38d914076e | ||
|
|
102dd6bfb1 | ||
|
|
04379b76f2 | ||
|
|
d6fb54f3c3 | ||
|
|
1d419c6ab8 | ||
|
|
8a4e958663 | ||
|
|
b04f7686fd | ||
|
|
5aabdefaa7 | ||
|
|
a2e439686d | ||
|
|
219b21767b | ||
|
|
281c6e39a5 | ||
|
|
4069631ae1 | ||
|
|
6a19a34d65 | ||
|
|
95548b9d9b | ||
|
|
2ed1529b95 | ||
|
|
85f037702a | ||
|
|
2caa91d772 | ||
|
|
d7350fad76 | ||
|
|
dcfb716711 | ||
|
|
0e0c81c32b | ||
|
|
802cab4a70 | ||
|
|
2c0b40ac8c | ||
|
|
001e8493ae | ||
|
|
e9256152e8 | ||
|
|
25410cb31a | ||
|
|
3dedf5548f | ||
|
|
2d2bb23708 | ||
|
|
6afc0b6303 | ||
|
|
f558f01ffd | ||
|
|
47fa955e71 | ||
|
|
96c4f5b8da | ||
|
|
6a4aa492c0 | ||
|
|
debd2a3433 | ||
|
|
192bc0f13b | ||
|
|
8756141f1d | ||
|
|
b97abc600e | ||
|
|
4b29636b42 | ||
|
|
321c51f8ed | ||
|
|
69d8f706cf | ||
|
|
98f67c5c6c | ||
|
|
27adf864ed | ||
|
|
1cf8d0b886 | ||
|
|
613830e6a6 | ||
|
|
2ea146333e | ||
|
|
f76d45b826 | ||
|
|
6cc86a3c82 | ||
|
|
f1e5b61970 | ||
|
|
65380646f7 | ||
|
|
189a8347ed | ||
|
|
e96e8f6fec | ||
|
|
38299ab507 | ||
|
|
f134171855 | ||
|
|
320204d854 | ||
|
|
b68199a482 | ||
|
|
6f4436fd5e | ||
|
|
0d8cc19698 | ||
|
|
a3a1ff69e1 | ||
|
|
5df7e23aa4 | ||
|
|
35d9691b3f | ||
|
|
465f649641 | ||
|
|
d909e7d802 | ||
|
|
06db6b8502 | ||
|
|
12261b9082 | ||
|
|
583ec432e8 | ||
|
|
8ffbccf7db | ||
|
|
097ca209bc | ||
|
|
1138ec0dea | ||
|
|
439fe43a04 | ||
|
|
7fd9a799b5 | ||
|
|
7459ab22d1 | ||
|
|
133a68f3eb | ||
|
|
3224110583 | ||
|
|
810488b115 | ||
|
|
b2276147ad | ||
|
|
6c1293c63e | ||
|
|
526d675272 | ||
|
|
14b2442d40 | ||
|
|
d6d194d414 | ||
|
|
0e99f97855 | ||
|
|
14dc933219 | ||
|
|
9497f123b4 | ||
|
|
6feb439d0a | ||
|
|
e4ca5ee5f5 | ||
|
|
f21c12f39b | ||
|
|
6c1e50a914 | ||
|
|
2dbba366b7 | ||
|
|
da064def7a | ||
|
|
3af3fa5773 | ||
|
|
005bd55fb2 | ||
|
|
82a5b4c55d | ||
|
|
b8e95b2099 | ||
|
|
8ea50dc029 | ||
|
|
ec191af874 | ||
|
|
d98c742aff | ||
|
|
2529496594 | ||
|
|
1b6114036a | ||
|
|
b33fb6c39a | ||
|
|
0a6826af58 | ||
|
|
1c7034ff78 | ||
|
|
7e11698c55 | ||
|
|
1c4eb31d59 | ||
|
|
b4b6a4294a | ||
|
|
4c031a7c05 | ||
|
|
1b4a8aa58f | ||
|
|
be6d74a6a3 | ||
|
|
997a262b6c | ||
|
|
5aae65f62f | ||
|
|
3be06ced92 | ||
|
|
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 | ||
|
|
53032469e7 | ||
|
|
73bc07c7fb | ||
|
|
8298aa7124 | ||
|
|
92049cba92 | ||
|
|
83d8963051 | ||
|
|
bedcf6c058 | ||
|
|
f8b7c17efd | ||
|
|
76ba365325 | ||
|
|
768e27d68c | ||
|
|
691ae04ca9 | ||
|
|
6585db1f9c | ||
|
|
42aa2d0088 | ||
|
|
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 | ||
|
|
21fc8efb86 | ||
|
|
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 | ||
|
|
46ed17c99e | ||
|
|
fadff798a7 | ||
|
|
81512bb3b7 | ||
|
|
3dd00dd91a | ||
|
|
fd97c5085b | ||
|
|
fa6a249fb4 | ||
|
|
0aa9b1735b | ||
|
|
6027bee3b8 | ||
|
|
4d72787c83 | ||
|
|
c6740cfea0 | ||
|
|
9c1d585c43 | ||
|
|
863acf988e | ||
|
|
a6b3beafbb | ||
|
|
2ffc3f497b | ||
|
|
f3a279be26 | ||
|
|
9ad6631747 | ||
|
|
0131f5e341 | ||
|
|
b5ab9a8da6 | ||
|
|
57fa2709da | ||
|
|
96c6a198d7 | ||
|
|
d106d4bd4e | ||
|
|
53cd3091f7 | ||
|
|
f1e7b870aa | ||
|
|
99fe076b5a | ||
|
|
65fcaa17d9 | ||
|
|
89c6563e00 | ||
|
|
76b0bef32e | ||
|
|
c20aa0b256 | ||
|
|
b4908cfcb4 | ||
|
|
4fb5b04d27 | ||
|
|
8385bbb0a0 | ||
|
|
cee6b54033 | ||
|
|
0dd591a5ff | ||
|
|
62278126e4 | ||
|
|
0aa85a3701 | ||
|
|
0e1ba64836 | ||
|
|
f7e1ce8656 | ||
|
|
5030c14dc2 | ||
|
|
1333cd1d84 | ||
|
|
112c259d27 | ||
|
|
130d1e1756 | ||
|
|
a7df9fa625 | ||
|
|
9064aedc89 | ||
|
|
fda5d23d32 | ||
|
|
60be51dbe0 | ||
|
|
e9f451339f | ||
|
|
4d8ffd05a9 | ||
|
|
b630105572 | ||
|
|
9fa71f847f | ||
|
|
f70a9c6974 | ||
|
|
a4d173c733 | ||
|
|
2eb7712e09 | ||
|
|
54923b7640 | ||
|
|
bb927505fe | ||
|
|
5e66e314d2 | ||
|
|
6fe791c1f1 | ||
|
|
860c537f81 | ||
|
|
a352e4cbf7 | ||
|
|
5322d446bd | ||
|
|
604ab0afd8 | ||
|
|
3d87a88d3d | ||
|
|
10f9e22a8e | ||
|
|
8edda0cdda | ||
|
|
21047afc02 | ||
|
|
2e9793ffb2 | ||
|
|
fcd100df39 | ||
|
|
dfd564a3a4 | ||
|
|
a43c916009 | ||
|
|
c8332ca9bf | ||
|
|
e98170f921 | ||
|
|
b8f25406cd | ||
|
|
76dcc12b13 | ||
|
|
baa2228c9b | ||
|
|
5275ae8e9c | ||
|
|
c71e1e107e | ||
|
|
8ab72c7e10 | ||
|
|
a8970df91b | ||
|
|
2468251f56 | ||
|
|
6e74f3e40e | ||
|
|
407f84a4bb | ||
|
|
91632f0adb | ||
|
|
af3c575d84 | ||
|
|
bf1475441d | ||
|
|
9268f9db1d | ||
|
|
600c43827a | ||
|
|
74092ea95b | ||
|
|
b67abe58e8 | ||
|
|
678647f39a | ||
|
|
453956172b | ||
|
|
b550c32f9b | ||
|
|
f6b886adbc | ||
|
|
9642453052 | ||
|
|
64fca99c26 | ||
|
|
c7da43f50d | ||
|
|
6efa2dd9ba | ||
|
|
5e980c5fe0 | ||
|
|
c8c7a415ea | ||
|
|
c3cfb8d23b | ||
|
|
1b055f0316 | ||
|
|
1fcbf0b363 | ||
|
|
61dbc81765 | ||
|
|
b8b76dfa40 | ||
|
|
297b314904 | ||
|
|
55dd1ab0a1 | ||
|
|
8c803f1c4b | ||
|
|
3b942049a2 | ||
|
|
f78fd212bb | ||
|
|
f931ebece8 | ||
|
|
ea0a9763bf | ||
|
|
b59e47dcf9 | ||
|
|
ce09ef8848 | ||
|
|
188727daba | ||
|
|
1150633fef | ||
|
|
62ae845f4b | ||
|
|
0757fd741e | ||
|
|
a07fa8ccd2 | ||
|
|
c77f32e696 | ||
|
|
4391771416 | ||
|
|
01ab820459 | ||
|
|
c7218f2856 | ||
|
|
592221b4bf | ||
|
|
836458ad85 | ||
|
|
7233c86f3d | ||
|
|
154b1b05e4 | ||
|
|
4d88638d4d | ||
|
|
9403986643 | ||
|
|
ad48610b2f | ||
|
|
63487cf3ec | ||
|
|
4ae2087c2e | ||
|
|
5179129a6b | ||
|
|
6a00d8c88c | ||
|
|
50f43f9396 | ||
|
|
6f205f8931 | ||
|
|
3776ffa49b | ||
|
|
318b5beac1 | ||
|
|
344c5d6d12 | ||
|
|
4d319a8caa | ||
|
|
74b24a0690 | ||
|
|
1ca0464957 | ||
|
|
f7ebc8a88c | ||
|
|
a102099ac1 | ||
|
|
59ac22aa05 | ||
|
|
cd7244b3d7 | ||
|
|
f81b676abe | ||
|
|
234b154053 | ||
|
|
a1d09ad574 | ||
|
|
0538c2f478 | ||
|
|
77a0179822 |
@@ -1,16 +1,34 @@
|
|||||||
APP_NAME=Coolify-localhost
|
# Coolify Configuration
|
||||||
APP_ID=development
|
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
|
APP_NAME="Coolify Development"
|
||||||
|
APP_ID=development
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
MUX_ENABLED=false
|
APP_DEBUG=true
|
||||||
|
SSH_MUX_ENABLED=true
|
||||||
|
|
||||||
|
# PostgreSQL Database Configuration
|
||||||
|
DB_DATABASE=coolify
|
||||||
|
DB_USERNAME=coolify
|
||||||
|
DB_PASSWORD=password
|
||||||
|
DB_HOST=host.docker.internal
|
||||||
|
DB_PORT=5432
|
||||||
|
|
||||||
|
# Ray Configuration
|
||||||
|
# Set to true to enable Ray
|
||||||
|
RAY_ENABLED=false
|
||||||
|
# Set custom ray port
|
||||||
|
# RAY_PORT=
|
||||||
|
|
||||||
|
# Enable Laravel Telescope for debugging
|
||||||
|
TELESCOPE_ENABLED=false
|
||||||
|
|
||||||
|
# Selenium Driver URL for Dusk
|
||||||
DUSK_DRIVER_URL=http://selenium:4444
|
DUSK_DRIVER_URL=http://selenium:4444
|
||||||
|
|
||||||
## For Andras only
|
# Special Keys for Andras
|
||||||
# To purge cache
|
# For cache purging
|
||||||
BUNNY_API_KEY=
|
BUNNY_API_KEY=
|
||||||
# To upload assets
|
# For asset uploads
|
||||||
BUNNY_STORAGE_API_KEY=
|
BUNNY_STORAGE_API_KEY=
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
# Coolify Configuration
|
||||||
APP_ID=
|
APP_ID=
|
||||||
APP_NAME=Coolify
|
APP_NAME=Coolify
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
|
|
||||||
|
# PostgreSQL Database Configuration
|
||||||
|
DB_USERNAME=coolify
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
# Redis Configuration
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
# Pusher Configuration
|
||||||
PUSHER_APP_ID=
|
PUSHER_APP_ID=
|
||||||
PUSHER_APP_KEY=
|
PUSHER_APP_KEY=
|
||||||
PUSHER_APP_SECRET=
|
PUSHER_APP_SECRET=
|
||||||
|
|||||||
65
.github/ISSUE_TEMPLATE/01_BUG_REPORT.yml
vendored
Normal file
65
.github/ISSUE_TEMPLATE/01_BUG_REPORT.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
name: 🐞 Bug Report
|
||||||
|
description: "File a new bug report."
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["🐛 Bug", "🔍 Triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **Please ensure you are using the latest version of Coolify before submitting an issue, as the bug may have already been fixed in a recent update.** (Of course, if you're experiencing an issue on the latest version that wasn't present in a previous version, please let us know.)
|
||||||
|
|
||||||
|
# 💎 Bounty Program (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||||
|
- If you would like to prioritize the issue resolution, consider adding a bounty to this issue through our [Bounty Program](https://console.algora.io/org/coollabsio/bounties/new).
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Error Message and Logs
|
||||||
|
description: Provide a detailed description of the error or exception you encountered, along with any relevant log output.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce
|
||||||
|
description: Please provide a step-by-step guide to reproduce the issue. Be as detailed as possible, otherwise we may not be able to assist you.
|
||||||
|
value: |
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Example Repository URL
|
||||||
|
description: If applicable, provide a URL to a repository demonstrating the issue.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Coolify Version
|
||||||
|
description: Please provide the Coolify version you are using. This can be found in the top left corner of your Coolify dashboard.
|
||||||
|
placeholder: "v4.0.0-beta.335"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Are you using Coolify Cloud?
|
||||||
|
options:
|
||||||
|
- "No (self-hosted)"
|
||||||
|
- "Yes (Coolify Cloud)"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Operating System and Version (self-hosted)
|
||||||
|
description: Run `cat /etc/os-release` or `lsb_release -a` in your terminal and provide the operating system and version.
|
||||||
|
placeholder: "Ubuntu 22.04"
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional Information
|
||||||
|
description: Any other relevant details about the issue.
|
||||||
31
.github/ISSUE_TEMPLATE/02_ENHANCEMENT_BOUNTY.yml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/02_ENHANCEMENT_BOUNTY.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: 💎 Enhancement Bounty
|
||||||
|
description: "Propose a new feature, service, or improvement with an attached bounty."
|
||||||
|
title: "[Enhancement]: "
|
||||||
|
labels: ["✨ Enhancement", "🔍 Triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **This issue template is exclusively for proposing new features, services, or improvements with an attached bounty.** Enhancements without a bounty can be discussed in the appropriate category of [Github Discussions](https://github.com/coollabsio/coolify/discussions).
|
||||||
|
|
||||||
|
# 💎 Add a Bounty (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||||
|
- [Click here to add the required bounty](https://console.algora.io/org/coollabsio/bounties/new)
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Request Type
|
||||||
|
description: Select the type of request you are making.
|
||||||
|
options:
|
||||||
|
- New Feature
|
||||||
|
- New Service
|
||||||
|
- Improvement
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: Provide a detailed description of the feature, improvement, or service you are proposing.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -1,26 +0,0 @@
|
|||||||
name: Bug report
|
|
||||||
description: Create a new bug report
|
|
||||||
title: '[Bug]: '
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: A clear and concise description of the problem
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Minimal Reproduction (if possible, example repository)
|
|
||||||
description: Please provide a step by step guide to reproduce the issue
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Exception or Error
|
|
||||||
description: Please provide error logs if possible.
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: Version
|
|
||||||
description: Coolify's version (see bottom left corner).
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
20
.github/ISSUE_TEMPLATE/config.yml
vendored
20
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,18 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
|
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 🤔 Community Support (Chat)
|
- name: 🤔 Questions and Community Support
|
||||||
url: https://coollabs.io/discord
|
url: https://coollabs.io/discord
|
||||||
about: Reach out to us on Discord.
|
about: If you have any questions, reach out to us on Discord inside the "#support" channel.
|
||||||
- name: 🙋♂️ Feature Requests
|
|
||||||
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
|
- name: 💡 Feature Request
|
||||||
about: All feature requests will be discussed here.
|
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests
|
||||||
|
about: Suggest a new feature for Coolify.
|
||||||
|
|
||||||
|
- name: ⚙️ Service Request
|
||||||
|
url: https://github.com/coollabsio/coolify/discussions/categories/service-requests
|
||||||
|
about: Request a new service integration for Coolify.
|
||||||
|
|
||||||
|
- name: 🔧 Improvements
|
||||||
|
url: https://github.com/coollabsio/coolify/discussions/categories/improvements
|
||||||
|
about: Suggest improvements to existing features for Coolify.
|
||||||
|
|||||||
13
.github/pull_request_template.md
vendored
Normal file
13
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
## Submit Checklist (REMOVE THIS SECTION BEFORE SUBMITTING)
|
||||||
|
- [ ] I have selected the `next` branch as the destination for my PR, not `main`.
|
||||||
|
- [ ] I have listed all changes in the `Changes` section.
|
||||||
|
- [ ] I have filled out the `Issues` section with the issue/discussion link(s) (if applicable).
|
||||||
|
- [ ] I have tested my changes.
|
||||||
|
- [ ] I have considered backwards compatibility.
|
||||||
|
- [ ] I have removed this checklist and any unused sections.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
-
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
- fix #
|
||||||
43
.github/workflows/coolify-helper-next.yml
vendored
43
.github/workflows/coolify-helper-next.yml
vendored
@@ -18,44 +18,56 @@ 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: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- 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: .
|
||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [ self-hosted, arm64 ]
|
runs-on: [ self-hosted, arm64 ]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- 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: .
|
||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -64,21 +76,26 @@ 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 }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
|
|
||||||
|
|||||||
43
.github/workflows/coolify-helper.yml
vendored
43
.github/workflows/coolify-helper.yml
vendored
@@ -18,44 +18,56 @@ 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: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- 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: .
|
||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [ self-hosted, arm64 ]
|
runs-on: [ self-hosted, arm64 ]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- 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: .
|
||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -64,21 +76,26 @@ 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 }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||||
|
|
||||||
|
|||||||
103
.github/workflows/coolify-realtime-next.yml
vendored
Normal file
103
.github/workflows/coolify-realtime-next.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
name: Coolify Realtime Development (v4)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "next" ]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/coolify-realtime.yml
|
||||||
|
- docker/coolify-realtime/Dockerfile
|
||||||
|
- docker/coolify-realtime/terminal-server.js
|
||||||
|
- docker/coolify-realtime/package.json
|
||||||
|
- docker/coolify-realtime/soketi-entrypoint.sh
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify-realtime"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
aarch64:
|
||||||
|
runs-on: [ self-hosted, arm64 ]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [ amd64, aarch64 ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||||
103
.github/workflows/coolify-realtime.yml
vendored
Normal file
103
.github/workflows/coolify-realtime.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
name: Coolify Realtime (v4)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/coolify-realtime.yml
|
||||||
|
- docker/coolify-realtime/Dockerfile
|
||||||
|
- docker/coolify-realtime/terminal-server.js
|
||||||
|
- docker/coolify-realtime/package.json
|
||||||
|
- docker/coolify-realtime/soketi-entrypoint.sh
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify-realtime"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
aarch64:
|
||||||
|
runs-on: [ self-hosted, arm64 ]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [ amd64, aarch64 ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||||
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
|
||||||
|
|||||||
44
.github/workflows/docker-image.yml
vendored
44
.github/workflows/docker-image.yml
vendored
@@ -1,44 +0,0 @@
|
|||||||
name: Docker Image CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
# push:
|
|
||||||
# branches: [ "main" ]
|
|
||||||
# pull_request:
|
|
||||||
# branches: [ "*" ]
|
|
||||||
push:
|
|
||||||
branches: ["this-does-not-exist"]
|
|
||||||
pull_request:
|
|
||||||
branches: ["this-does-not-exist"]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
/usr/local/share/ca-certificates
|
|
||||||
/var/cache/apt/archives
|
|
||||||
/var/lib/apt/lists
|
|
||||||
~/.cache
|
|
||||||
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-docker-
|
|
||||||
- name: Build the Docker image
|
|
||||||
run: |
|
|
||||||
cp .env.example .env
|
|
||||||
docker run --rm -u "$(id -u):$(id -g)" \
|
|
||||||
-v "$(pwd):/app" \
|
|
||||||
-w /app composer:2 \
|
|
||||||
composer install --ignore-platform-reqs
|
|
||||||
./vendor/bin/spin build
|
|
||||||
- name: Start the stack
|
|
||||||
run: |
|
|
||||||
./vendor/bin/spin up -d
|
|
||||||
./vendor/bin/spin exec coolify php artisan key:generate
|
|
||||||
./vendor/bin/spin exec coolify php artisan migrate:fresh --seed
|
|
||||||
- name: Test (missing E2E tests)
|
|
||||||
run: |
|
|
||||||
./vendor/bin/spin exec coolify php artisan test
|
|
||||||
17
.github/workflows/lock-closed-issues-discussions-and-prs.yml
vendored
Normal file
17
.github/workflows/lock-closed-issues-discussions-and-prs.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Lock closed Issues, Discussions, and PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock-threads:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Lock threads after 30 days of inactivity
|
||||||
|
uses: dessant/lock-threads@v5
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
issue-inactive-days: '30'
|
||||||
|
pr-inactive-days: '30'
|
||||||
|
discussion-inactive-days: '30'
|
||||||
28
.github/workflows/manage-stale-issues-and-prs.yml
vendored
Normal file
28
.github/workflows/manage-stale-issues-and-prs.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: Manage Stale Issues and PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
manage-stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Manage stale issues and PRs
|
||||||
|
uses: actions/stale@v9
|
||||||
|
id: stale
|
||||||
|
with:
|
||||||
|
stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.'
|
||||||
|
stale-pr-message: 'This pull request will be automatically closed in a few days if no response is received. Please update your PR or comment if you would like to continue working on it.'
|
||||||
|
close-issue-message: 'This issue has been automatically closed due to inactivity.'
|
||||||
|
close-pr-message: 'This pull request has been automatically closed due to inactivity.'
|
||||||
|
days-before-stale: 14
|
||||||
|
days-before-close: 7
|
||||||
|
stale-issue-label: '⏱︎ Stale'
|
||||||
|
stale-pr-label: '⏱︎ Stale'
|
||||||
|
only-labels: '💤 Waiting for feedback'
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
operations-per-run: 100
|
||||||
|
labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback'
|
||||||
|
close-issue-reason: 'not_planned'
|
||||||
|
exempt-all-milestones: false
|
||||||
8
.github/workflows/production-build.yml
vendored
8
.github/workflows/production-build.yml
vendored
@@ -3,6 +3,10 @@ name: Production Build (v4)
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
|
paths-ignore:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
|
- templates/service-templates.json
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
@@ -27,7 +31,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 +53,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
|
||||||
|
|||||||
78
.github/workflows/remove-labels-and-assignees-on-close.yml
vendored
Normal file
78
.github/workflows/remove-labels-and-assignees-on-close.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
name: Remove Labels and Assignees on Issue Close
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [closed]
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
remove-labels-and-assignees:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Remove labels and assignees
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
|
||||||
|
async function processIssue(issueNumber) {
|
||||||
|
try {
|
||||||
|
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelsToKeep = currentLabels
|
||||||
|
.filter(label => label.name === '⏱︎ Stale')
|
||||||
|
.map(label => label.name);
|
||||||
|
|
||||||
|
await github.rest.issues.setLabels({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
labels: labelsToKeep
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: issue } = await github.rest.issues.get({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
if (issue.assignees && issue.assignees.length > 0) {
|
||||||
|
await github.rest.issues.removeAssignees({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
assignees: issue.assignees.map(assignee => assignee.login)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status !== 404) {
|
||||||
|
console.error(`Error processing issue ${issueNumber}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.eventName === 'issues' || context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
|
||||||
|
const issue = context.payload.issue || context.payload.pull_request;
|
||||||
|
await processIssue(issue.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
|
||||||
|
const pr = context.payload.pull_request;
|
||||||
|
if (pr.body) {
|
||||||
|
const issueReferences = pr.body.match(/#(\d+)/g);
|
||||||
|
if (issueReferences) {
|
||||||
|
for (const reference of issueReferences) {
|
||||||
|
const issueNumber = parseInt(reference.substring(1));
|
||||||
|
await processIssue(issueNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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: |
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* @label Send Email
|
|
||||||
* @description Send email to all users
|
|
||||||
*/
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Support\Facades\Mail;
|
|
||||||
|
|
||||||
set_transanctional_email_settings();
|
|
||||||
|
|
||||||
$users = User::whereEmail('test@example.com');
|
|
||||||
foreach ($users as $user) {
|
|
||||||
Mail::send([], [], function ($message) use ($user) {
|
|
||||||
$message
|
|
||||||
->to($user->email)
|
|
||||||
->subject("Testing")
|
|
||||||
->text(
|
|
||||||
<<<EOF
|
|
||||||
Hello,
|
|
||||||
|
|
||||||
Welcome to Coolify Cloud.
|
|
||||||
Here is your user id: $user->id
|
|
||||||
|
|
||||||
EOF
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
243
CONTRIBUTING.md
Normal file
243
CONTRIBUTING.md
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# Contributing to Coolify
|
||||||
|
|
||||||
|
> "First, thanks for considering contributing to my project. It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
|
||||||
|
|
||||||
|
You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Setup Development Environment](#1-setup-development-environment)
|
||||||
|
2. [Verify Installation](#2-verify-installation-optional)
|
||||||
|
3. [Fork and Setup Local Repository](#3-fork-and-setup-local-repository)
|
||||||
|
4. [Set up Environment Variables](#4-set-up-environment-variables)
|
||||||
|
5. [Start Coolify](#5-start-coolify)
|
||||||
|
6. [Start Development](#6-start-development)
|
||||||
|
7. [Create a Pull Request](#7-create-a-pull-request)
|
||||||
|
8. [Development Notes](#development-notes)
|
||||||
|
9. [Resetting Development Environment](#resetting-development-environment)
|
||||||
|
10. [Additional Contribution Guidelines](#additional-contribution-guidelines)
|
||||||
|
|
||||||
|
## 1. Setup Development Environment
|
||||||
|
|
||||||
|
Follow the steps below for your operating system:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Windows</strong></summary>
|
||||||
|
|
||||||
|
1. Install `docker-ce`, Docker Desktop (or similar):
|
||||||
|
- Docker CE (recommended):
|
||||||
|
- Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install?ref=coolify)
|
||||||
|
- After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/?ref=coolify)
|
||||||
|
- Make sure to choose the appropriate Linux distribution (e.g., Ubuntu) when following the Docker installation guide
|
||||||
|
- Install Docker Desktop (easier):
|
||||||
|
- Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/?ref=coolify)
|
||||||
|
- Ensure WSL2 backend is enabled in Docker Desktop settings
|
||||||
|
|
||||||
|
2. Install Spin:
|
||||||
|
- Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2?ref=coolify)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>MacOS</strong></summary>
|
||||||
|
|
||||||
|
1. Install Orbstack, Docker Desktop (or similar):
|
||||||
|
- Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
|
||||||
|
- Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation?ref=coolify)
|
||||||
|
- Docker Desktop:
|
||||||
|
- Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/?ref=coolify)
|
||||||
|
|
||||||
|
2. Install Spin:
|
||||||
|
- Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin?ref=coolify)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Linux</strong></summary>
|
||||||
|
|
||||||
|
1. Install Docker Engine, Docker Desktop (or similar):
|
||||||
|
- Docker Engine (recommended, as there is no VM overhead):
|
||||||
|
- Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/?ref=coolify) for your Linux distribution
|
||||||
|
- Docker Desktop:
|
||||||
|
- If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/?ref=coolify)
|
||||||
|
|
||||||
|
2. Install Spin:
|
||||||
|
- Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions?ref=coolify)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 2. Verify Installation (Optional)
|
||||||
|
|
||||||
|
After installing Docker (or Orbstack) and Spin, verify the installation:
|
||||||
|
|
||||||
|
1. Open a terminal or command prompt
|
||||||
|
2. Run the following commands:
|
||||||
|
```bash
|
||||||
|
docker --version
|
||||||
|
spin --version
|
||||||
|
```
|
||||||
|
You should see version information for both Docker and Spin.
|
||||||
|
|
||||||
|
## 3. Fork and Setup Local Repository
|
||||||
|
|
||||||
|
1. Fork the [Coolify](https://github.com/coollabsio/coolify) repository to your GitHub account.
|
||||||
|
|
||||||
|
2. Install a code editor on your machine (choose one):
|
||||||
|
|
||||||
|
| Editor | Platform | Download Link |
|
||||||
|
|--------|----------|---------------|
|
||||||
|
| Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download?ref=coolify) |
|
||||||
|
| Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/?ref=coolify) |
|
||||||
|
| Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download?ref=coolify) |
|
||||||
|
|
||||||
|
3. Clone the Coolify Repository from your fork to your local machine
|
||||||
|
- Use `git clone` in the command line, or
|
||||||
|
- Use GitHub Desktop (recommended):
|
||||||
|
- Download and install from [https://desktop.github.com/](https://desktop.github.com/?ref=coolify)
|
||||||
|
- Open GitHub Desktop and login with your GitHub account
|
||||||
|
- Click on `File` -> `Clone Repository` select `github.com` as the repository location, then select your forked Coolify repository, choose the local path and then click `Clone`
|
||||||
|
|
||||||
|
4. Open the cloned Coolify Repository in your chosen code editor.
|
||||||
|
|
||||||
|
## 4. Set up Environment Variables
|
||||||
|
|
||||||
|
1. In the Code Editor, locate the `.env.development.example` file in the root directory of your local Coolify repository.
|
||||||
|
2. Duplicate the `.env.development.example` file and rename the copy to `.env`.
|
||||||
|
3. Open the new `.env` file and review its contents. Adjust any environment variables as needed for your development setup.
|
||||||
|
4. If you encounter errors during database migrations, update the database connection settings in your `.env` file. Use the IP address or hostname of your PostgreSQL database container. You can find this information by running `docker ps` after executing `spin up`.
|
||||||
|
5. Save the changes to your `.env` file.
|
||||||
|
|
||||||
|
## 5. Start Coolify
|
||||||
|
|
||||||
|
1. Open a terminal in the local Coolify directory.
|
||||||
|
2. Run the following command in the terminal (leave that terminal open):
|
||||||
|
```bash
|
||||||
|
spin up
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> You may see some errors, but don't worry; this is expected.
|
||||||
|
|
||||||
|
3. If you encounter permission errors, especially on macOS, use:
|
||||||
|
```bash
|
||||||
|
sudo spin up
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If you change environment variables afterwards or anything seems broken, press Ctrl + C to stop the process and run `spin up` again.
|
||||||
|
|
||||||
|
## 6. Start Development
|
||||||
|
|
||||||
|
1. Access your Coolify instance:
|
||||||
|
- URL: `http://localhost:8000`
|
||||||
|
- Login: `test@example.com`
|
||||||
|
- Password: `password`
|
||||||
|
|
||||||
|
2. Additional development tools:
|
||||||
|
| Tool | URL | Note |
|
||||||
|
|------|-----|------|
|
||||||
|
| Laravel Horizon (scheduler) | `http://localhost:8000/horizon` | Only accessible when logged in as root user |
|
||||||
|
| Mailpit (email catcher) | `http://localhost:8025` | |
|
||||||
|
| Telescope (debugging tool) | `http://localhost:8000/telescope` | Disabled by default |
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> To enable Telescope, add the following to your `.env` file:
|
||||||
|
> ```env
|
||||||
|
> TELESCOPE_ENABLED=true
|
||||||
|
> ```
|
||||||
|
|
||||||
|
## 7. Create a Pull Request
|
||||||
|
|
||||||
|
1. After making changes or adding a new service:
|
||||||
|
- Commit your changes to your forked repository.
|
||||||
|
- Push the changes to your GitHub account.
|
||||||
|
|
||||||
|
2. Creating the Pull Request (PR):
|
||||||
|
- Navigate to the main Coolify repository on GitHub.
|
||||||
|
- Click the "Pull requests" tab.
|
||||||
|
- Click the green "New pull request" button.
|
||||||
|
- Choose your fork and branch as the compare branch.
|
||||||
|
- Click "Create pull request".
|
||||||
|
|
||||||
|
3. Filling out the PR details:
|
||||||
|
- Give your PR a descriptive title.
|
||||||
|
- Use the Pull Request Template provided and fill in the details.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
|
||||||
|
|
||||||
|
4. Submit your PR:
|
||||||
|
- Review your changes one last time.
|
||||||
|
- Click "Create pull request" to submit.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Make sure your PR is out of draft mode as soon as it's ready for review. PRs that are in draft mode for a long time may be closed by maintainers.
|
||||||
|
|
||||||
|
After submission, maintainers will review your PR and may request changes or provide feedback.
|
||||||
|
|
||||||
|
## Development Notes
|
||||||
|
|
||||||
|
When working on Coolify, keep the following in mind:
|
||||||
|
|
||||||
|
1. **Database Migrations**: After switching branches or making changes to the database structure, always run migrations:
|
||||||
|
```bash
|
||||||
|
docker exec -it coolify php artisan migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Resetting Development Setup**: To reset your development setup to a clean database with default values:
|
||||||
|
```bash
|
||||||
|
docker exec -it coolify php artisan migrate:fresh --seed
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Troubleshooting**: If you encounter unexpected behavior, ensure your database is up-to-date with the latest migrations and if possible reset the development setup to eliminate any environment-specific issues.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches.
|
||||||
|
|
||||||
|
## Resetting Development Environment
|
||||||
|
|
||||||
|
If you encounter issues or break your database or something else, follow these steps to start from a clean slate (works since `v4.0.0-beta.342`):
|
||||||
|
|
||||||
|
1. Stop all running containers `ctrl + c`.
|
||||||
|
|
||||||
|
2. Remove all Coolify containers:
|
||||||
|
```bash
|
||||||
|
docker rm coolify coolify-db coolify-redis coolify-realtime coolify-testing-host coolify-minio coolify-vite-1 coolify-mail
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Remove Coolify volumes (it is possible that the volumes have no `coolify` prefix on your machine, in that case remove the prefix from the command):
|
||||||
|
```bash
|
||||||
|
docker volume rm coolify_dev_backups_data coolify_dev_postgres_data coolify_dev_redis_data coolify_dev_coolify_data coolify_dev_minio_data
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Remove unused images:
|
||||||
|
```bash
|
||||||
|
docker image prune -a
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Start Coolify again:
|
||||||
|
```bash
|
||||||
|
spin up
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Run database migrations and seeders:
|
||||||
|
```bash
|
||||||
|
docker exec -it coolify php artisan migrate:fresh --seed
|
||||||
|
```
|
||||||
|
|
||||||
|
After completing these steps, you'll have a fresh development setup.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Always run database migrations and seeders after switching branches or pulling updates to ensure your local database structure matches the current codebase and includes necessary seed data.
|
||||||
|
|
||||||
|
## Additional Contribution Guidelines
|
||||||
|
|
||||||
|
### Contributing a New Service
|
||||||
|
|
||||||
|
To add a new service to Coolify, please refer to our documentation:
|
||||||
|
[Adding a New Service](https://coolify.io/docs/knowledge-base/contribute/service)
|
||||||
|
|
||||||
|
### Contributing to Documentation
|
||||||
|
|
||||||
|
To contribute to the Coolify documentation, please refer to this guide:
|
||||||
|
[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/CONTRIBUTING.md)
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
> "First, thanks for considering to contribute to my project.
|
|
||||||
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
|
|
||||||
|
|
||||||
You can ask for guidance anytime on our
|
|
||||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
|
||||||
|
|
||||||
## Code Contribution
|
|
||||||
|
|
||||||
### 1) Setup your development environment
|
|
||||||
|
|
||||||
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
|
||||||
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
|
||||||
|
|
||||||
### 2) Set your environment variables
|
|
||||||
|
|
||||||
- Copy [.env.development.example](./.env.development.example) to .env.
|
|
||||||
|
|
||||||
## 3) Start & setup Coolify
|
|
||||||
|
|
||||||
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
|
|
||||||
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
|
|
||||||
|
|
||||||
### 4) Start development
|
|
||||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
|
||||||
|
|
||||||
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
|
||||||
|
|
||||||
Mails are caught by Mailpit: `localhost:8025`
|
|
||||||
|
|
||||||
## New Service Contribution
|
|
||||||
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).
|
|
||||||
|
|
||||||
76
README.md
76
README.md
@@ -1,14 +1,19 @@
|
|||||||
|

|
||||||
|
|
||||||
|
[](https://console.algora.io/org/coollabsio/bounties/new)
|
||||||
|
|
||||||
# About the Project
|
# About the Project
|
||||||
|
|
||||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
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.
|
It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else.
|
||||||
|
|
||||||
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
Imagine having the ease of a cloud but with your own servers. That is **Coolify**.
|
||||||
|
|
||||||
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
No vendor lock-in, which means that all the configurations 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 lose the automations and all the magic. 🪄️
|
||||||
|
|
||||||
For more information, take a look at our landing page [here](https://coolify.io).
|
For more information, take a look at our landing page at [coolify.io](https://coolify.io).
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@@ -19,23 +24,58 @@ You can find the installation script source [here](./scripts/install.sh).
|
|||||||
|
|
||||||
# Support
|
# Support
|
||||||
|
|
||||||
Contact us [here](https://coolify.io/docs/contact).
|
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
|
||||||
|
|
||||||
# Donations
|
# 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.
|
To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development.
|
||||||
|
|
||||||
https://coolify.io/sponsorships
|
[coolify.io/sponsorships](https://coolify.io/sponsorships)
|
||||||
|
|
||||||
Thank you so much!
|
Thank you so much!
|
||||||
|
|
||||||
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
|
Special thanks to our biggest sponsors!
|
||||||
|
|
||||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
|
### Special Sponsors
|
||||||
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
|
|
||||||
|
|
||||||
## Github Sponsors ($15+)
|

|
||||||
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
|
|
||||||
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a>
|
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
||||||
|
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
||||||
|
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities.
|
||||||
|
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies.
|
||||||
|
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
|
||||||
|
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
|
||||||
|
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
||||||
|
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
|
||||||
|
* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions.
|
||||||
|
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
|
||||||
|
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
|
||||||
|
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
|
||||||
|
* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses.
|
||||||
|
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
|
||||||
|
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries.
|
||||||
|
* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools.
|
||||||
|
* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services.
|
||||||
|
* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - An open-source alternative to hyperscale cloud providers, offering high-performance cloud computing services.
|
||||||
|
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses.
|
||||||
|
* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly.
|
||||||
|
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||||
|
|
||||||
|
|
||||||
|
## Github Sponsors ($40+)
|
||||||
|
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||||
|
<a href="https://typebot.io/?ref=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
||||||
|
<a href="https://www.runpod.io/?ref=coolify.io">
|
||||||
|
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
||||||
|
<a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
||||||
|
<a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
||||||
|
<a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
||||||
|
<a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
||||||
|
<a href="https://codext.link/coolify-io?ref=coolify.io"><img src="./other/logos/codext.jpg" width="60px" alt="Codext" /></a>
|
||||||
|
<a href="https://x.com/mrsmith9ja?ref=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
|
||||||
|
<a href="https://www.uxwizz.com/?ref=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
||||||
|
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
|
||||||
|
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></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/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/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/pixelinfinito"><img src="https://github.com/pixelinfinito.png" width="60px" alt="Pixel Infinito" /></a>
|
||||||
@@ -43,8 +83,11 @@ Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and
|
|||||||
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
|
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
|
||||||
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
|
<a href="https://github.com/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://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
|
||||||
|
<a href="https://www.breakcold.com/?utm_source=coolify.io"><img src="https://github.com/breakcold.png" width="60px" alt="Breakcold" /></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/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>
|
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
||||||
|
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
|
||||||
|
<a href="https://x.com/adithsuhas17?utm_source=coolify.io"><img src="https://github.com/adith-suhas-sv.png" width="60px" alt="Adith Suhas" /></a>
|
||||||
|
|
||||||
## Organizations
|
## Organizations
|
||||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||||
@@ -58,15 +101,16 @@ Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and
|
|||||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||||
|
|
||||||
|
|
||||||
## Individuals
|
## Individuals
|
||||||
|
|
||||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||||
|
|
||||||
# Cloud
|
# Cloud
|
||||||
|
|
||||||
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io)
|
||||||
|
|
||||||
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
For more information & pricing, take a look at our landing page [coolify.io](https://coolify.io).
|
||||||
|
|
||||||
## Why should I use the Cloud version?
|
## 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.
|
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.
|
||||||
@@ -90,7 +134,7 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</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://www.producthunt.com/posts/coolify?ref=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>
|
<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>
|
||||||
|
|
||||||
|
|||||||
130
RELEASE.md
Normal file
130
RELEASE.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Coolify Release Guide
|
||||||
|
|
||||||
|
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Release Process](#release-process)
|
||||||
|
- [Version Types](#version-types)
|
||||||
|
- [Stable](#stable)
|
||||||
|
- [Nightly](#nightly)
|
||||||
|
- [Beta](#beta)
|
||||||
|
- [Version Availability](#version-availability)
|
||||||
|
- [Self-Hosted](#self-hosted)
|
||||||
|
- [Cloud](#cloud)
|
||||||
|
- [Manually Update to Specific Versions](#manually-update-to-specific-versions)
|
||||||
|
|
||||||
|
## Release Process
|
||||||
|
|
||||||
|
1. **Development on `next` or Feature Branches**
|
||||||
|
- Improvements, fixes, and new features are developed on the `next` branch or separate feature branches.
|
||||||
|
|
||||||
|
2. **Merging to `main`**
|
||||||
|
- Once ready, changes are merged from the `next` branch into the `main` branch.
|
||||||
|
|
||||||
|
3. **Building the Release**
|
||||||
|
- After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry with the version tag and the `latest` tag.
|
||||||
|
|
||||||
|
4. **Creating a GitHub Release**
|
||||||
|
- A new GitHub release is manually created with details of the changes made in the version.
|
||||||
|
|
||||||
|
5. **Updating the CDN**
|
||||||
|
- To make a new version publicly available, the version information on the CDN needs to be updated: [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated.**
|
||||||
|
|
||||||
|
## Version Types
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Stable (coming soon)</strong></summary>
|
||||||
|
|
||||||
|
- **Stable**
|
||||||
|
- The production version suitable for stable, production environments (generally recommended).
|
||||||
|
- **Update Frequency:** Every 2 to 4 weeks, with more frequent possible hotfixes.
|
||||||
|
- **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release.
|
||||||
|
- **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`).
|
||||||
|
- **Installation Command:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Nightly</strong></summary>
|
||||||
|
|
||||||
|
- **Nightly**
|
||||||
|
- The latest development version, suitable for testing the latest changes and experimenting with new features.
|
||||||
|
- **Update Frequency:** Daily or bi-weekly updates.
|
||||||
|
- **Release Size:** Smaller, more frequent releases.
|
||||||
|
- **Versioning Scheme:** TO BE DETERMINED
|
||||||
|
- **Installation Command:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Beta</strong></summary>
|
||||||
|
|
||||||
|
- **Beta**
|
||||||
|
- Test releases for the upcoming stable version.
|
||||||
|
- **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable.
|
||||||
|
- **Update Frequency:** Available if we think beta testing is necessary.
|
||||||
|
- **Release Size:** Same size as stable release as it will become the next stabe release after some time.
|
||||||
|
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`).
|
||||||
|
- **Installation Command:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Do not use nightly/beta builds in production as there is no guarantee of stability.
|
||||||
|
|
||||||
|
## Version Availability
|
||||||
|
|
||||||
|
When a new version is released and a new GitHub release is created, it doesn't immediately become available for your instance. Here's how version availability works for different instance types.
|
||||||
|
|
||||||
|
### Self-Hosted
|
||||||
|
|
||||||
|
- **Update Frequency:** More frequent updates, especially on the nightly release channel.
|
||||||
|
- **Update Availability:** New versions are available once the CDN has been updated.
|
||||||
|
- **Update Methods:**
|
||||||
|
1. **Manual Update in Instance Settings:**
|
||||||
|
- Go to `Settings > Update Check Frequency` and click the `Check Manually` button.
|
||||||
|
- If an update is available, an upgrade button will appear on the sidebar.
|
||||||
|
2. **Automatic Update:**
|
||||||
|
- If enabled, the instance will update automatically at the time set in the settings.
|
||||||
|
3. **Re-run Installation Script:**
|
||||||
|
- Run the installation script again to upgrade to the latest version available on the CDN:
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> If a new release is available on GitHub but your instance hasn't updated yet or no upgrade button is shown in the UI, the CDN might not have been updated yet. This intentional delay ensures stability and allows for hotfixes before official release.
|
||||||
|
|
||||||
|
### Cloud
|
||||||
|
|
||||||
|
- **Update Frequency:** Less frequent as it's a managed service.
|
||||||
|
- **Update Availability:** New versions are available once Andras has updated the cloud version manually.
|
||||||
|
- **Update Method:**
|
||||||
|
- Updates are managed by Andras, who ensures each cloud version is thoroughly tested and stable before releasing it.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> The cloud version of Coolify may be several versions behind the latest GitHub releases even if the CDN is updated. This is intentional to ensure stability and reliability for cloud users and Andras will manully update the cloud version when the update is ready.
|
||||||
|
|
||||||
|
## Manually Update to Specific Versions
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Updating to unreleased versions is not recommended and may cause issues. Use at your own risk!
|
||||||
|
|
||||||
|
To update your Coolify instance to a specific (unreleased) version, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
||||||
|
```
|
||||||
|
Replace `<version>` with the version you want to update to (for example `4.0.0-beta.332`).
|
||||||
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,42 +2,43 @@
|
|||||||
|
|
||||||
namespace App\Actions\Application;
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
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)
|
|
||||||
{
|
|
||||||
if ($application->destination->server->isSwarm()) {
|
|
||||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$servers = collect([]);
|
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true)
|
||||||
$servers->push($application->destination->server);
|
{
|
||||||
$application->additional_servers->map(function ($server) use ($servers) {
|
try {
|
||||||
$servers->push($server);
|
$server = $application->destination->server;
|
||||||
});
|
if (! $server->isFunctional()) {
|
||||||
foreach ($servers as $server) {
|
|
||||||
if (!$server->isFunctional()) {
|
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
ray('Stopping application: '.$application->name);
|
||||||
if ($containers->count() > 0) {
|
|
||||||
foreach ($containers as $container) {
|
if ($server->isSwarm()) {
|
||||||
$containerName = data_get($container, 'Names');
|
instant_remote_process(["docker stack rm {$application->uuid}"], $server);
|
||||||
if ($containerName) {
|
|
||||||
instant_remote_process(
|
return;
|
||||||
["docker rm -f {$containerName}"],
|
|
||||||
$server
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$containersToStop = $application->getContainersToStop($previewDeployments);
|
||||||
|
$application->stopContainers($containersToStop, $server);
|
||||||
|
|
||||||
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
$application->delete_connected_networks($application->uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
|
||||||
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ use Lorisleiva\Actions\Concerns\AsAction;
|
|||||||
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()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!$server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -32,6 +33,7 @@ class StopApplicationOneServer
|
|||||||
}
|
}
|
||||||
} 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 @@ use Spatie\Activitylog\Models\Activity;
|
|||||||
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 @@ class PrepareCoolifyTask
|
|||||||
->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(
|
||||||
dispatch($job);
|
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);
|
||||||
|
}
|
||||||
$this->activity->refresh();
|
$this->activity->refresh();
|
||||||
|
|
||||||
return $this->activity;
|
return $this->activity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Actions\CoolifyTask;
|
|||||||
|
|
||||||
use App\Enums\ActivityTypes;
|
use App\Enums\ActivityTypes;
|
||||||
use App\Enums\ProcessStatus;
|
use App\Enums\ProcessStatus;
|
||||||
|
use App\Helpers\SshMultiplexingHelper;
|
||||||
use App\Jobs\ApplicationDeploymentJob;
|
use App\Jobs\ApplicationDeploymentJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Process\ProcessResult;
|
use Illuminate\Process\ProcessResult;
|
||||||
@@ -21,6 +22,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 +37,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 +48,7 @@ class RunRemoteProcess
|
|||||||
$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 +61,7 @@ class RunRemoteProcess
|
|||||||
$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 +70,7 @@ class RunRemoteProcess
|
|||||||
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
|
||||||
@@ -88,7 +92,7 @@ class RunRemoteProcess
|
|||||||
if ($processResult->exitCode() == 0) {
|
if ($processResult->exitCode() == 0) {
|
||||||
$status = ProcessStatus::FINISHED;
|
$status = ProcessStatus::FINISHED;
|
||||||
}
|
}
|
||||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
if ($processResult->exitCode() != 0 && ! $this->ignore_errors) {
|
||||||
$status = ProcessStatus::ERROR;
|
$status = ProcessStatus::ERROR;
|
||||||
}
|
}
|
||||||
// if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
|
// if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
|
||||||
@@ -106,18 +110,25 @@ class RunRemoteProcess
|
|||||||
'status' => $status->value,
|
'status' => $status->value,
|
||||||
]);
|
]);
|
||||||
$this->activity->save();
|
$this->activity->save();
|
||||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
if ($processResult->exitCode() != 0 && ! $this->ignore_errors) {
|
||||||
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
|
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
|
||||||
}
|
}
|
||||||
if ($this->call_event_on_finish) {
|
if ($this->call_event_on_finish) {
|
||||||
try {
|
try {
|
||||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
if ($this->call_event_data) {
|
||||||
'userId' => $this->activity->causer_id,
|
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||||
]));
|
'data' => $this->call_event_data,
|
||||||
|
]));
|
||||||
|
} else {
|
||||||
|
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||||
|
'userId' => $this->activity->causer_id,
|
||||||
|
]));
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $processResult;
|
return $processResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +138,7 @@ class RunRemoteProcess
|
|||||||
$command = $this->activity->getExtraProperty('command');
|
$command = $this->activity->getExtraProperty('command');
|
||||||
$server = Server::whereUuid($server_uuid)->firstOrFail();
|
$server = Server::whereUuid($server_uuid)->firstOrFail();
|
||||||
|
|
||||||
return generateSshCommand($server, $command);
|
return SshMultiplexingHelper::generateSshCommand($server, $command);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function handleOutput(string $type, string $output)
|
protected function handleOutput(string $type, string $output)
|
||||||
@@ -155,8 +166,7 @@ class RunRemoteProcess
|
|||||||
|
|
||||||
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 +175,16 @@ class RunRemoteProcess
|
|||||||
'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'] = generate_fluentd_configuration();
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
|
$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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
|
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 @@ class StartDatabaseProxy
|
|||||||
$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,18 +53,36 @@ class StartDatabaseProxy
|
|||||||
$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') {
|
||||||
$internalPort = 6379;
|
$internalPort = 6379;
|
||||||
} else if ($type === 'App\Models\StandalonePostgresql') {
|
} elseif ($type === 'App\Models\StandalonePostgresql') {
|
||||||
$internalPort = 5432;
|
$internalPort = 5432;
|
||||||
} else if ($type === 'App\Models\StandaloneMongodb') {
|
} elseif ($type === 'App\Models\StandaloneMongodb') {
|
||||||
$internalPort = 27017;
|
$internalPort = 27017;
|
||||||
} else if ($type === 'App\Models\StandaloneMysql') {
|
} elseif ($type === 'App\Models\StandaloneMysql') {
|
||||||
$internalPort = 3306;
|
$internalPort = 3306;
|
||||||
} else if ($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 @@ class StartDatabaseProxy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 @@ class StartDatabaseProxy
|
|||||||
'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);
|
||||||
|
|||||||
158
app/Actions/Database/StartDragonfly.php
Normal file
158
app/Actions/Database/StartDragonfly.php
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<?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,
|
||||||
|
],
|
||||||
|
'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'] = generate_fluentd_configuration();
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
|
$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();
|
||||||
|
}
|
||||||
|
}
|
||||||
182
app/Actions/Database/StartKeydb.php
Normal file
182
app/Actions/Database/StartKeydb.php
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<?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'] = generate_fluentd_configuration();
|
||||||
|
}
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
$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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -20,7 +21,7 @@ class StartMariadb
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -28,11 +29,11 @@ class StartMariadb
|
|||||||
];
|
];
|
||||||
|
|
||||||
$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 @@ class StartMariadb
|
|||||||
'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,28 +59,21 @@ class StartMariadb
|
|||||||
'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()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
|
||||||
'fluentd-async' => "true",
|
|
||||||
'fluentd-sub-second-precision' => "true",
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -87,26 +81,37 @@ class StartMariadb
|
|||||||
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',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$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 +119,14 @@ class StartMariadb
|
|||||||
{
|
{
|
||||||
$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[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$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;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +143,7 @@ class StartMariadb
|
|||||||
'external' => false,
|
'external' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,30 +154,34 @@ class StartMariadb
|
|||||||
$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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
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,26 +3,27 @@
|
|||||||
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;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -30,12 +31,12 @@ class StartMongodb
|
|||||||
];
|
];
|
||||||
|
|
||||||
$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 @@ class StartMongodb
|
|||||||
],
|
],
|
||||||
'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,28 +67,21 @@ class StartMongodb
|
|||||||
'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()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
|
||||||
'fluentd-async' => "true",
|
|
||||||
'fluentd-sub-second-precision' => "true",
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -94,35 +89,45 @@ class StartMongodb
|
|||||||
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',
|
||||||
'target' => '/etc/mongo/mongod.conf',
|
'target' => '/etc/mongo/mongod.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
|
$docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf';
|
||||||
}
|
}
|
||||||
$this->add_default_database();
|
$this->add_default_database();
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
|
'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d',
|
||||||
'target' => '/docker-entrypoint-initdb.d',
|
'target' => '/docker-entrypoint-initdb.d',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$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 +135,14 @@ class StartMongodb
|
|||||||
{
|
{
|
||||||
$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[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$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;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +159,7 @@ class StartMongodb
|
|||||||
'external' => false,
|
'external' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,34 +170,39 @@ class StartMongodb
|
|||||||
$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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
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)
|
||||||
@@ -20,7 +21,7 @@ class StartMysql
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -28,11 +29,11 @@ class StartMysql
|
|||||||
];
|
];
|
||||||
|
|
||||||
$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 @@ class StartMysql
|
|||||||
'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,28 +59,21 @@ class StartMysql
|
|||||||
'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()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
|
||||||
'fluentd-async' => "true",
|
|
||||||
'fluentd-sub-second-precision' => "true",
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -87,36 +81,52 @@ class StartMysql
|
|||||||
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',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$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');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
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[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$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;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +143,7 @@ class StartMysql
|
|||||||
'external' => false,
|
'external' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,30 +154,34 @@ class StartMysql
|
|||||||
$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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
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,39 +3,41 @@
|
|||||||
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)
|
||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/",
|
||||||
];
|
];
|
||||||
|
|
||||||
$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 @@ class StartPostgresql
|
|||||||
],
|
],
|
||||||
'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,29 +66,21 @@ class StartPostgresql
|
|||||||
'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'] = generate_fluentd_configuration();
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
|
||||||
'fluentd-async' => "true",
|
|
||||||
'fluentd-sub-second-precision' => "true",
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -94,6 +88,11 @@ class StartPostgresql
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@@ -102,15 +101,15 @@ class StartPostgresql
|
|||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $init_script,
|
'source' => $init_script,
|
||||||
'target' => '/docker-entrypoint-initdb.d/' . basename($init_script),
|
'target' => '/docker-entrypoint-initdb.d/'.basename($init_script),
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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',
|
||||||
'target' => '/etc/postgresql/postgresql.conf',
|
'target' => '/etc/postgresql/postgresql.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -120,15 +119,20 @@ class StartPostgresql
|
|||||||
'config_file=/etc/postgresql/postgresql.conf',
|
'config_file=/etc/postgresql/postgresql.conf',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$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 +140,14 @@ class StartPostgresql
|
|||||||
{
|
{
|
||||||
$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[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$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;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,32 +164,34 @@ class StartPostgresql
|
|||||||
'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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,18 +204,24 @@ class StartPostgresql
|
|||||||
$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 @@ namespace App\Actions\Database;
|
|||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -24,7 +24,7 @@ class StartRedis
|
|||||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -32,12 +32,12 @@ class StartRedis
|
|||||||
];
|
];
|
||||||
|
|
||||||
$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 @@ class StartRedis
|
|||||||
'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,28 +68,21 @@ class StartRedis
|
|||||||
'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()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
|
||||||
'fluentd-async' => "true",
|
|
||||||
'fluentd-sub-second-precision' => "true",
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -97,27 +90,38 @@ class StartRedis
|
|||||||
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',
|
||||||
'target' => '/usr/local/etc/redis/redis.conf',
|
'target' => '/usr/local/etc/redis/redis.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
|
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$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 +129,14 @@ class StartRedis
|
|||||||
{
|
{
|
||||||
$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[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$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;
|
return $local_persistent_volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +153,7 @@ class StartRedis
|
|||||||
'external' => false,
|
'external' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,15 +164,18 @@ class StartRedis
|
|||||||
$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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
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,32 +2,74 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Events\DatabaseStatusChanged;
|
use App\Actions\Server\CleanupDocker;
|
||||||
|
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;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopDatabase
|
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, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||||
{
|
{
|
||||||
$server = $database->destination->server;
|
$server = $database->destination->server;
|
||||||
if (!$server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
instant_remote_process(
|
|
||||||
["docker rm -f {$database->uuid}"],
|
$this->stopContainer($database, $database->uuid, 300);
|
||||||
$server
|
if (! $isDeleteOperation) {
|
||||||
);
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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));
|
return 'Database stopped successfully';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function stopContainer($database, string $containerName, int $timeout = 300): void
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
|
||||||
|
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||||
|
|
||||||
|
$startTime = time();
|
||||||
|
while ($process->running()) {
|
||||||
|
if (time() - $startTime >= $timeout) {
|
||||||
|
$this->forceStopContainer($containerName, $server);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
usleep(100000);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->removeContainer($containerName, $server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function forceStopContainer(string $containerName, $server): void
|
||||||
|
{
|
||||||
|
instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeContainer(string $containerName, $server): void
|
||||||
|
{
|
||||||
|
instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteConnectedNetworks($uuid, $server)
|
||||||
|
{
|
||||||
|
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||||
|
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
681
app/Actions/Docker/GetContainersStatus.php
Normal file
681
app/Actions/Docker/GetContainersStatus.php
Normal file
@@ -0,0 +1,681 @@
|
|||||||
|
<?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 Illuminate\Support\Collection;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class GetContainersStatus
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public $applications;
|
||||||
|
|
||||||
|
public ?Collection $containers;
|
||||||
|
|
||||||
|
public ?Collection $containerReplicates;
|
||||||
|
|
||||||
|
public $server;
|
||||||
|
|
||||||
|
public function handle(Server $server, ?Collection $containers = null, ?Collection $containerReplicates = null)
|
||||||
|
{
|
||||||
|
$this->containers = $containers;
|
||||||
|
$this->containerReplicates = $containerReplicates;
|
||||||
|
$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 {
|
||||||
|
// $this->containers = $this->server->getContainersWithSentinel();
|
||||||
|
// if ($this->containers->count() === 0) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// $databases = $this->server->databases();
|
||||||
|
// $services = $this->server->services()->get();
|
||||||
|
// $previews = $this->server->previews();
|
||||||
|
// $foundApplications = [];
|
||||||
|
// $foundApplicationPreviews = [];
|
||||||
|
// $foundDatabases = [];
|
||||||
|
// $foundServices = [];
|
||||||
|
|
||||||
|
// foreach ($this->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 = $this->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 = $this->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 = $this->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->containers === null) {
|
||||||
|
['containers' => $this->containers,'containerReplicates' => $this->containerReplicates] = $this->server->getContainers();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($this->containers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->containerReplicates) {
|
||||||
|
foreach ($this->containerReplicates as $containerReplica) {
|
||||||
|
$name = data_get($containerReplica, 'Name');
|
||||||
|
$this->containers = $this->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 ($this->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 = $this->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 = $this->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('uuid');
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->server->proxySet() || $this->server->proxy->force_stop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$foundProxyContainer = $this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\Fortify;
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
@@ -16,12 +15,12 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
/**
|
/**
|
||||||
* Validate and create a newly registered user.
|
* Validate and create a newly registered user.
|
||||||
*
|
*
|
||||||
* @param array<string, string> $input
|
* @param array<string, string> $input
|
||||||
*/
|
*/
|
||||||
public function create(array $input): User
|
public function create(array $input): User
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
if (!$settings->is_registration_enabled) {
|
if (! $settings->is_registration_enabled) {
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
Validator::make($input, [
|
Validator::make($input, [
|
||||||
@@ -48,7 +47,7 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
$team = $user->teams()->first();
|
$team = $user->teams()->first();
|
||||||
|
|
||||||
// Disable registration after first user is created
|
// Disable registration after first user is created
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$settings->is_registration_enabled = false;
|
$settings->is_registration_enabled = false;
|
||||||
$settings->save();
|
$settings->save();
|
||||||
} else {
|
} else {
|
||||||
@@ -66,6 +65,7 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
}
|
}
|
||||||
// Set session variable
|
// Set session variable
|
||||||
session(['currentTeam' => $user->currentTeam = $team]);
|
session(['currentTeam' => $user->currentTeam = $team]);
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class ResetUserPassword implements ResetsUserPasswords
|
|||||||
/**
|
/**
|
||||||
* Validate and reset the user's forgotten password.
|
* Validate and reset the user's forgotten password.
|
||||||
*
|
*
|
||||||
* @param array<string, string> $input
|
* @param array<string, string> $input
|
||||||
*/
|
*/
|
||||||
public function reset(User $user, array $input): void
|
public function reset(User $user, array $input): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
|
|||||||
/**
|
/**
|
||||||
* Validate and update the user's password.
|
* Validate and update the user's password.
|
||||||
*
|
*
|
||||||
* @param array<string, string> $input
|
* @param array<string, string> $input
|
||||||
*/
|
*/
|
||||||
public function update(User $user, array $input): void
|
public function update(User $user, array $input): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
|||||||
/**
|
/**
|
||||||
* Validate and update the given user's profile information.
|
* Validate and update the given user's profile information.
|
||||||
*
|
*
|
||||||
* @param array<string, string> $input
|
* @param array<string, string> $input
|
||||||
*/
|
*/
|
||||||
public function update(User $user, array $input): void
|
public function update(User $user, array $input): void
|
||||||
{
|
{
|
||||||
@@ -45,7 +45,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
|||||||
/**
|
/**
|
||||||
* Update the given verified user's profile information.
|
* Update the given verified user's profile information.
|
||||||
*
|
*
|
||||||
* @param array<string, string> $input
|
* @param array<string, string> $input
|
||||||
*/
|
*/
|
||||||
protected function updateVerifiedUser(User $user, array $input): void
|
protected function updateVerifiedUser(User $user, array $input): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,22 +2,22 @@
|
|||||||
|
|
||||||
namespace App\Actions\License;
|
namespace App\Actions\License;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
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 {
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$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 +38,7 @@ class CheckResaleLicense
|
|||||||
$settings->update([
|
$settings->update([
|
||||||
'is_resale_license_active' => true,
|
'is_resale_license_active' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$data = Http::withHeaders([
|
$data = Http::withHeaders([
|
||||||
@@ -51,6 +52,7 @@ class CheckResaleLicense
|
|||||||
$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)) {
|
||||||
|
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value();
|
||||||
|
}
|
||||||
|
if (! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
|
throw new \Exception('Could not generate proxy configuration');
|
||||||
|
}
|
||||||
|
|
||||||
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
|
|
||||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
|
||||||
}
|
|
||||||
if (!$proxy_configuration || is_null($proxy_configuration)) {
|
|
||||||
throw new \Exception("Could not generate proxy configuration");
|
|
||||||
}
|
|
||||||
return $proxy_configuration;
|
return $proxy_configuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,40 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class CheckProxy
|
class CheckProxy
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Server $server, $fromUI = false)
|
|
||||||
|
// It should return if the proxy should be started (true) or not (false)
|
||||||
|
public function handle(Server $server, $fromUI = false): bool
|
||||||
{
|
{
|
||||||
if (!$server->isProxyShouldRun()) {
|
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(false);
|
||||||
|
if (! $uptime) {
|
||||||
|
throw new \Exception($error);
|
||||||
|
}
|
||||||
|
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 +47,17 @@ class CheckProxy
|
|||||||
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;
|
||||||
@@ -37,24 +65,45 @@ class CheckProxy
|
|||||||
$ip = 'host.docker.internal';
|
$ip = 'host.docker.internal';
|
||||||
}
|
}
|
||||||
|
|
||||||
$connection80 = @fsockopen($ip, '80');
|
$portsToCheck = ['80', '443'];
|
||||||
$connection443 = @fsockopen($ip, '443');
|
|
||||||
$port80 = is_resource($connection80) && fclose($connection80);
|
try {
|
||||||
$port443 = is_resource($connection443) && fclose($connection443);
|
if ($server->proxyType() !== ProxyTypes::NONE->value) {
|
||||||
if ($port80) {
|
$proxyCompose = CheckConfiguration::run($server);
|
||||||
if ($fromUI) {
|
if (isset($proxyCompose)) {
|
||||||
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
$yaml = Yaml::parse($proxyCompose);
|
||||||
|
$portsToCheck = [];
|
||||||
|
if ($server->proxyType() === ProxyTypes::TRAEFIK->value) {
|
||||||
|
$ports = data_get($yaml, 'services.traefik.ports');
|
||||||
|
} elseif ($server->proxyType() === ProxyTypes::CADDY->value) {
|
||||||
|
$ports = data_get($yaml, 'services.caddy.ports');
|
||||||
|
}
|
||||||
|
if (isset($ports)) {
|
||||||
|
foreach ($ports as $port) {
|
||||||
|
$portsToCheck[] = str($port)->before(':')->value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
$portsToCheck = [];
|
||||||
}
|
}
|
||||||
}
|
} catch (\Exception $e) {
|
||||||
if ($port443) {
|
ray($e->getMessage());
|
||||||
if ($fromUI) {
|
}
|
||||||
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
if (count($portsToCheck) === 0) {
|
||||||
} else {
|
return false;
|
||||||
return false;
|
}
|
||||||
|
foreach ($portsToCheck as $port) {
|
||||||
|
$connection = @fsockopen($ip, $port);
|
||||||
|
if (is_resource($connection) && fclose($connection)) {
|
||||||
|
if ($fromUI) {
|
||||||
|
throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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 @@ class SaveConfiguration
|
|||||||
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,60 +2,73 @@
|
|||||||
|
|
||||||
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 ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
|
||||||
|
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 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
} 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.'",
|
'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then',
|
||||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
" echo 'Stopping and removing existing coolify-proxy.'",
|
||||||
|
' docker rm -f coolify-proxy || true',
|
||||||
|
" echo 'Successfully stopped and removed existing coolify-proxy.'",
|
||||||
|
'fi',
|
||||||
"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 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
$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,22 +2,38 @@
|
|||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
if ($force) {
|
$settings = instanceSettings();
|
||||||
instant_remote_process(['docker image prune -af'], $server, false);
|
$helperImageVersion = data_get($settings, 'helper_version');
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
$helperImage = config('coolify.helper_image');
|
||||||
instant_remote_process(['docker builder prune -af'], $server, false);
|
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
||||||
} else {
|
|
||||||
instant_remote_process(['docker image prune -f'], $server, false);
|
$commands = [
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=true"',
|
||||||
instant_remote_process(['docker builder prune -f'], $server, false);
|
'docker image prune -af --filter "label!=coolify.managed=true"',
|
||||||
|
'docker builder prune -af',
|
||||||
|
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
|
||||||
|
];
|
||||||
|
|
||||||
|
$serverSettings = $server->settings;
|
||||||
|
if ($serverSettings->delete_unused_volumes) {
|
||||||
|
$commands[] = 'docker volume prune -af';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($serverSettings->delete_unused_networks) {
|
||||||
|
$commands[] = 'docker network prune -f';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
instant_remote_process([$command], $server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
57
app/Actions/Server/ConfigureCloudflared.php
Normal file
57
app/Actions/Server/ConfigureCloudflared.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Events\CloudflareTunnelConfigured;
|
||||||
|
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);
|
||||||
|
$server->settings->is_cloudflare_tunnel = false;
|
||||||
|
$server->settings->save();
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
CloudflareTunnelConfigured::dispatch($server->team_id);
|
||||||
|
|
||||||
|
$commands = collect([
|
||||||
|
'rm -fr /tmp/cloudflared',
|
||||||
|
]);
|
||||||
|
instant_remote_process($commands, $server);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,20 +2,21 @@
|
|||||||
|
|
||||||
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';
|
||||||
$config = base64_encode('{
|
$config = base64_encode('{
|
||||||
"log-driver": "json-file",
|
"log-driver": "json-file",
|
||||||
@@ -36,32 +37,41 @@ class InstallDocker
|
|||||||
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',
|
||||||
]);
|
]);
|
||||||
} else if ($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',
|
||||||
]);
|
]);
|
||||||
} else if ($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 @@ class InstallDocker
|
|||||||
"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,34 +2,31 @@
|
|||||||
|
|
||||||
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) {
|
||||||
$type = 'newrelic';
|
$type = 'newrelic';
|
||||||
} else if ($server->settings->is_logdrain_highlight_enabled) {
|
} elseif ($server->settings->is_logdrain_highlight_enabled) {
|
||||||
$type = 'highlight';
|
$type = 'highlight';
|
||||||
} else if ($server->settings->is_logdrain_axiom_enabled) {
|
} elseif ($server->settings->is_logdrain_axiom_enabled) {
|
||||||
$type = 'axiom';
|
$type = 'axiom';
|
||||||
} else if ($server->settings->is_logdrain_custom_enabled) {
|
} elseif ($server->settings->is_logdrain_custom_enabled) {
|
||||||
$type = 'custom';
|
$type = 'custom';
|
||||||
} else {
|
} else {
|
||||||
$type = 'none';
|
$type = 'none';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($type === 'none') {
|
if ($type === 'none') {
|
||||||
$command = [
|
return 'No log drain is enabled.';
|
||||||
"echo 'Stopping old Fluent Bit'",
|
} elseif ($type === 'newrelic') {
|
||||||
"docker rm -f coolify-log-drain || true",
|
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
||||||
];
|
|
||||||
return instant_remote_process($command, $server);
|
|
||||||
} else if ($type === 'newrelic') {
|
|
||||||
if (!$server->settings->is_logdrain_newrelic_enabled) {
|
|
||||||
throw new \Exception('New Relic log drain is not enabled.');
|
throw new \Exception('New Relic log drain is not enabled.');
|
||||||
}
|
}
|
||||||
$config = base64_encode("
|
$config = base64_encode("
|
||||||
@@ -50,7 +47,11 @@ class InstallLogDrain
|
|||||||
[FILTER]
|
[FILTER]
|
||||||
Name modify
|
Name modify
|
||||||
Match *
|
Match *
|
||||||
Set server_name {$server->name}
|
Set coolify.server_name {$server->name}
|
||||||
|
Rename COOLIFY_APP_NAME coolify.app_name
|
||||||
|
Rename COOLIFY_PROJECT_NAME coolify.project_name
|
||||||
|
Rename COOLIFY_SERVER_IP coolify.server_ip
|
||||||
|
Rename COOLIFY_ENVIRONMENT_NAME coolify.environment_name
|
||||||
[OUTPUT]
|
[OUTPUT]
|
||||||
Name nrlogs
|
Name nrlogs
|
||||||
Match *
|
Match *
|
||||||
@@ -59,11 +60,11 @@ class InstallLogDrain
|
|||||||
# https://log-api.newrelic.com/log/v1 - US
|
# https://log-api.newrelic.com/log/v1 - US
|
||||||
base_uri \${BASE_URI}
|
base_uri \${BASE_URI}
|
||||||
");
|
");
|
||||||
} else if ($type === 'highlight') {
|
} elseif ($type === 'highlight') {
|
||||||
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 +72,7 @@ class InstallLogDrain
|
|||||||
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,9 +80,9 @@ class InstallLogDrain
|
|||||||
Match *
|
Match *
|
||||||
Host otel.highlight.io
|
Host otel.highlight.io
|
||||||
Port 24224
|
Port 24224
|
||||||
");
|
');
|
||||||
} else if ($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.');
|
||||||
}
|
}
|
||||||
$config = base64_encode("
|
$config = base64_encode("
|
||||||
@@ -101,7 +102,11 @@ class InstallLogDrain
|
|||||||
[FILTER]
|
[FILTER]
|
||||||
Name modify
|
Name modify
|
||||||
Match *
|
Match *
|
||||||
Set server_name {$server->name}
|
Set coolify.server_name {$server->name}
|
||||||
|
Rename COOLIFY_APP_NAME coolify.app_name
|
||||||
|
Rename COOLIFY_PROJECT_NAME coolify.project_name
|
||||||
|
Rename COOLIFY_SERVER_IP coolify.server_ip
|
||||||
|
Rename COOLIFY_ENVIRONMENT_NAME coolify.environment_name
|
||||||
[OUTPUT]
|
[OUTPUT]
|
||||||
Name http
|
Name http
|
||||||
Match *
|
Match *
|
||||||
@@ -116,8 +121,8 @@ class InstallLogDrain
|
|||||||
json_date_format iso8601
|
json_date_format iso8601
|
||||||
tls On
|
tls On
|
||||||
");
|
");
|
||||||
} else if ($type === 'custom') {
|
} elseif ($type === 'custom') {
|
||||||
if (!$server->settings->is_logdrain_custom_enabled) {
|
if (! $server->settings->is_logdrain_custom_enabled) {
|
||||||
throw new \Exception('Custom log drain is not enabled.');
|
throw new \Exception('Custom log drain is not enabled.');
|
||||||
}
|
}
|
||||||
$config = base64_encode($server->settings->logdrain_custom_config);
|
$config = base64_encode($server->settings->logdrain_custom_config);
|
||||||
@@ -133,7 +138,7 @@ class InstallLogDrain
|
|||||||
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 +152,7 @@ services:
|
|||||||
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.
|
||||||
|
|
||||||
@@ -160,18 +165,18 @@ Files:
|
|||||||
$base_uri = $server->settings->logdrain_newrelic_base_uri;
|
$base_uri = $server->settings->logdrain_newrelic_base_uri;
|
||||||
$base_path = config('coolify.base_config_path');
|
$base_path = config('coolify.base_config_path');
|
||||||
|
|
||||||
$config_path = $base_path . '/log-drains';
|
$config_path = $base_path.'/log-drains';
|
||||||
$fluent_bit_config = $config_path . '/fluent-bit.conf';
|
$fluent_bit_config = $config_path.'/fluent-bit.conf';
|
||||||
$parsers_config = $config_path . '/parsers.conf';
|
$parsers_config = $config_path.'/parsers.conf';
|
||||||
$compose_path = $config_path . '/docker-compose.yml';
|
$compose_path = $config_path.'/docker-compose.yml';
|
||||||
$readme_path = $config_path . '/README.md';
|
$readme_path = $config_path.'/README.md';
|
||||||
$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",
|
||||||
|
|
||||||
];
|
];
|
||||||
@@ -180,18 +185,18 @@ Files:
|
|||||||
"echo LICENSE_KEY=$license_key >> $config_path/.env",
|
"echo LICENSE_KEY=$license_key >> $config_path/.env",
|
||||||
"echo BASE_URI=$base_uri >> $config_path/.env",
|
"echo BASE_URI=$base_uri >> $config_path/.env",
|
||||||
];
|
];
|
||||||
} else if ($type === 'highlight') {
|
} elseif ($type === 'highlight') {
|
||||||
$add_envs_command = [
|
$add_envs_command = [
|
||||||
"echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env",
|
"echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env",
|
||||||
];
|
];
|
||||||
} else if ($type === 'axiom') {
|
} elseif ($type === 'axiom') {
|
||||||
$add_envs_command = [
|
$add_envs_command = [
|
||||||
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
|
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
|
||||||
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
|
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
|
||||||
];
|
];
|
||||||
} else if ($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 +208,7 @@ Files:
|
|||||||
"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);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
app/Actions/Server/StopLogDrain.php
Normal file
20
app/Actions/Server/StopLogDrain.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopLogDrain
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return instant_remote_process(['docker rm -f coolify-log-drain || true'], $server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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,52 +2,46 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use App\Jobs\PullHelperImageJob;
|
||||||
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();
|
||||||
ray('Running InstanceAutoUpdateJob');
|
|
||||||
$this->server = Server::find(0);
|
$this->server = Server::find(0);
|
||||||
if (!$this->server) {
|
if (! $this->server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CleanupDocker::run($this->server, false);
|
CleanupDocker::dispatch($this->server)->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');
|
if (! $settings->is_auto_update_enabled) {
|
||||||
$this->latestVersion = 'next';
|
return;
|
||||||
}
|
|
||||||
if ($force) {
|
|
||||||
$this->update();
|
|
||||||
} else {
|
|
||||||
if (!$settings->is_auto_update_enabled) {
|
|
||||||
return 'Auto update is disabled';
|
|
||||||
}
|
}
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
|
$this->update();
|
||||||
|
$settings->new_version_available = false;
|
||||||
|
$settings->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray('InstanceAutoUpdateJob failed');
|
|
||||||
ray($e->getMessage());
|
|
||||||
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,19 +49,24 @@ class UpdateCoolify
|
|||||||
private function update()
|
private function update()
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
ray("Running update on local docker container. Updating to $this->latestVersion");
|
|
||||||
remote_process([
|
remote_process([
|
||||||
"sleep 10"
|
'sleep 10',
|
||||||
], $this->server);
|
|
||||||
ray('Update done');
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
ray('Running update on production server');
|
|
||||||
remote_process([
|
|
||||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
|
||||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
|
||||||
], $this->server);
|
], $this->server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$all_servers = Server::all();
|
||||||
|
$servers = $all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
PullHelperImageJob::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
|
||||||
|
|
||||||
|
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,17 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use App\Actions\Server\CleanupDocker;
|
||||||
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, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = data_get($service, 'server');
|
$server = data_get($service, 'server');
|
||||||
if ($server->isFunctional()) {
|
if ($deleteVolumes && $server->isFunctional()) {
|
||||||
$storagesToDelete = collect([]);
|
$storagesToDelete = collect([]);
|
||||||
|
|
||||||
$service->environment_variables()->delete();
|
$service->environment_variables()->delete();
|
||||||
@@ -32,13 +34,29 @@ class DeleteService
|
|||||||
foreach ($storagesToDelete as $storage) {
|
foreach ($storagesToDelete as $storage) {
|
||||||
$commands[] = "docker volume rm -f $storage->name";
|
$commands[] = "docker volume rm -f $storage->name";
|
||||||
}
|
}
|
||||||
$commands[] = "docker rm -f $service->uuid";
|
|
||||||
|
|
||||||
instant_remote_process($commands, $server, false);
|
// Execute volume deletion first, this must be done first otherwise volumes will not be deleted.
|
||||||
|
if (! empty($commands)) {
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
$result = instant_remote_process([$command], $server, false);
|
||||||
|
if ($result !== 0) {
|
||||||
|
ray("Failed to execute: $command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($deleteConnectedNetworks) {
|
||||||
|
$service->delete_connected_networks($service->uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
instant_remote_process(["docker rm -f $service->uuid"], $server, throwError: false);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception($e->getMessage());
|
throw new \Exception($e->getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
|
if ($deleteConfigurations) {
|
||||||
|
$service->delete_configurations();
|
||||||
|
}
|
||||||
foreach ($service->applications()->get() as $application) {
|
foreach ($service->applications()->get() as $application) {
|
||||||
$application->forceDelete();
|
$application->forceDelete();
|
||||||
}
|
}
|
||||||
@@ -49,6 +67,11 @@ class DeleteService
|
|||||||
$task->delete();
|
$task->delete();
|
||||||
}
|
}
|
||||||
$service->tags()->detach();
|
$service->tags()->detach();
|
||||||
|
$service->forceDelete();
|
||||||
|
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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,36 +2,40 @@
|
|||||||
|
|
||||||
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.'";
|
if ($service->networks()->count() > 0) {
|
||||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid >/dev/null 2>&1 || true";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "echo Starting service.";
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||||
|
}
|
||||||
|
$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', []);
|
||||||
$network = $service->destination->network;
|
$network = $service->destination->network;
|
||||||
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||||
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
||||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} >/dev/null 2>&1 || true";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$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,39 +2,35 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use App\Actions\Server\CleanupDocker;
|
||||||
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, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = $service->destination->server;
|
$server = $service->destination->server;
|
||||||
if (!$server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
ray('Stopping service: ' . $service->name);
|
|
||||||
$applications = $service->applications()->get();
|
$containersToStop = $service->getContainersToStop();
|
||||||
foreach ($applications as $application) {
|
$service->stopContainers($containersToStop, $server);
|
||||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
|
||||||
$application->update(['status' => 'exited']);
|
if (! $isDeleteOperation) {
|
||||||
|
$service->delete_connected_networks($service->uuid);
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$dbs = $service->databases()->get();
|
|
||||||
foreach ($dbs as $db) {
|
|
||||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
|
||||||
$db->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
|
||||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
|
||||||
// TODO: make notification for databases
|
|
||||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
echo $e->getMessage();
|
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,21 @@ use Lorisleiva\Actions\Concerns\AsAction;
|
|||||||
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;
|
||||||
$servers->push($application->destination->server);
|
$servers->push($application->destination->server);
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$is_main_server = $application->destination->server->id === $server->id;
|
$is_main_server = $application->destination->server->id === $server->id;
|
||||||
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 @@ class ComplexStatusCheck
|
|||||||
} 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 @@ use Lorisleiva\Actions\Concerns\AsAction;
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/Console/Commands/CheckApplicationDeploymentQueue.php
Normal file
50
app/Console/Commands/CheckApplicationDeploymentQueue.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CheckApplicationDeploymentQueue extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'check:deployment-queue {--force} {--seconds=3600}';
|
||||||
|
|
||||||
|
protected $description = 'Check application deployment queue.';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$seconds = $this->option('seconds');
|
||||||
|
$deployments = ApplicationDeploymentQueue::whereIn('status', [
|
||||||
|
ApplicationDeploymentStatus::IN_PROGRESS,
|
||||||
|
ApplicationDeploymentStatus::QUEUED,
|
||||||
|
])->where('created_at', '<=', now()->subSeconds($seconds))->get();
|
||||||
|
if ($deployments->isEmpty()) {
|
||||||
|
$this->info('No deployments found in the last '.$seconds.' seconds.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');
|
||||||
|
|
||||||
|
foreach ($deployments as $deployment) {
|
||||||
|
if ($this->option('force')) {
|
||||||
|
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
|
||||||
|
$this->cancelDeployment($deployment);
|
||||||
|
} else {
|
||||||
|
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
|
||||||
|
if ($this->confirm('Do you want to cancel this deployment?', true)) {
|
||||||
|
$this->cancelDeployment($deployment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cancelDeployment(ApplicationDeploymentQueue $deployment)
|
||||||
|
{
|
||||||
|
$deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
|
||||||
|
if ($deployment->server?->isFunctional()) {
|
||||||
|
remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Console/Commands/CleanupApplicationDeploymentQueue.php
Normal file
26
app/Console/Commands/CleanupApplicationDeploymentQueue.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanupApplicationDeploymentQueue extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:deployment-queue {--team-id=}';
|
||||||
|
|
||||||
|
protected $description = 'Cleanup application deployment queue.';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$team_id = $this->option('team-id');
|
||||||
|
$servers = \App\Models\Server::where('team_id', $team_id)->get();
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
$deployments = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->where('server_id', $server->id)->get();
|
||||||
|
foreach ($deployments as $deployment) {
|
||||||
|
$deployment->update(['status' => 'failed']);
|
||||||
|
instant_remote_process(['docker rm -f '.$deployment->deployment_uuid], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
app/Console/Commands/CleanupDatabase.php
Normal file
69
app/Console/Commands/CleanupDatabase.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class CleanupDatabase extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:database {--yes}';
|
||||||
|
|
||||||
|
protected $description = 'Cleanup database';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
echo "Running database cleanup...\n";
|
||||||
|
} else {
|
||||||
|
echo "Running database cleanup in dry-run mode...\n";
|
||||||
|
}
|
||||||
|
if (isCloud()) {
|
||||||
|
// Later on we can increase this to 180 days or dynamically set
|
||||||
|
$keep_days = 60;
|
||||||
|
} else {
|
||||||
|
$keep_days = 60;
|
||||||
|
}
|
||||||
|
echo "Keep days: $keep_days\n";
|
||||||
|
// Cleanup failed jobs table
|
||||||
|
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
|
||||||
|
$count = $failed_jobs->count();
|
||||||
|
echo "Delete $count entries from failed_jobs.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$failed_jobs->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup sessions table
|
||||||
|
$sessions = DB::table('sessions')->where('last_activity', '<', now()->subDays($keep_days)->timestamp);
|
||||||
|
$count = $sessions->count();
|
||||||
|
echo "Delete $count entries from sessions.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$sessions->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup activity_log table
|
||||||
|
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
|
||||||
|
$count = $activity_log->count();
|
||||||
|
echo "Delete $count entries from activity_log.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$activity_log->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup application_deployment_queues table
|
||||||
|
$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();
|
||||||
|
echo "Delete $count entries from application_deployment_queues.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$application_deployment_queues->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup webhooks table
|
||||||
|
$webhooks = DB::table('webhooks')->where('created_at', '<', now()->subDays($keep_days));
|
||||||
|
$count = $webhooks->count();
|
||||||
|
echo "Delete $count entries from webhooks.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$webhooks->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
|
|
||||||
class CleanupQueue extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'cleanup:queue';
|
|
||||||
protected $description = 'Cleanup Queue';
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
echo "Running queue cleanup...\n";
|
|
||||||
$prefix = config('database.redis.options.prefix');
|
|
||||||
$keys = Redis::connection()->keys('*:laravel*');
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
|
||||||
Redis::connection()->del($keyWithoutPrefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
app/Console/Commands/CleanupRedis.php
Normal file
31
app/Console/Commands/CleanupRedis.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
class CleanupRedis extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:redis';
|
||||||
|
|
||||||
|
protected $description = 'Cleanup Redis';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Cleanup Redis keys.\n";
|
||||||
|
$prefix = config('database.redis.options.prefix');
|
||||||
|
|
||||||
|
$keys = Redis::connection()->keys('*:laravel*');
|
||||||
|
collect($keys)->each(function ($key) use ($prefix) {
|
||||||
|
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
||||||
|
Redis::connection()->del($keyWithoutPrefix);
|
||||||
|
});
|
||||||
|
|
||||||
|
$queueOverlaps = Redis::connection()->keys('*laravel-queue-overlap*');
|
||||||
|
collect($queueOverlaps)->each(function ($key) {
|
||||||
|
Redis::connection()->del($key);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
|
use App\Models\Server;
|
||||||
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 +25,7 @@ use Illuminate\Console\Command;
|
|||||||
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,9 +34,31 @@ class CleanupStuckedResources extends Command
|
|||||||
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()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$servers = Server::all()->filter(function ($server) {
|
||||||
|
return $server->isFunctional();
|
||||||
|
});
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
CleanupHelperContainersJob::dispatch($server);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
|
||||||
|
foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
|
||||||
|
if (is_null($applicationDeploymentQueue->application)) {
|
||||||
|
echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
|
||||||
|
$applicationDeploymentQueue->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
@@ -37,6 +68,17 @@ class CleanupStuckedResources extends Command
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
$applicationsPreviews = ApplicationPreview::get();
|
||||||
|
foreach ($applicationsPreviews as $applicationPreview) {
|
||||||
|
if (! data_get($applicationPreview, 'application')) {
|
||||||
|
echo "Deleting stuck application preview: {$applicationPreview->uuid}\n";
|
||||||
|
$applicationPreview->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
foreach ($postgresqls as $postgresql) {
|
foreach ($postgresqls as $postgresql) {
|
||||||
@@ -55,6 +97,33 @@ class CleanupStuckedResources extends Command
|
|||||||
} 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) {
|
||||||
@@ -112,7 +181,7 @@ class CleanupStuckedResources extends Command
|
|||||||
try {
|
try {
|
||||||
$scheduled_tasks = ScheduledTask::all();
|
$scheduled_tasks = ScheduledTask::all();
|
||||||
foreach ($scheduled_tasks as $scheduled_task) {
|
foreach ($scheduled_tasks as $scheduled_task) {
|
||||||
if (!$scheduled_task->service && !$scheduled_task->application) {
|
if (! $scheduled_task->service && ! $scheduled_task->application) {
|
||||||
echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n";
|
echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n";
|
||||||
$scheduled_task->delete();
|
$scheduled_task->delete();
|
||||||
}
|
}
|
||||||
@@ -121,23 +190,38 @@ class CleanupStuckedResources extends Command
|
|||||||
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
|
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||||
|
foreach ($scheduled_backups as $scheduled_backup) {
|
||||||
|
if (! $scheduled_backup->server()) {
|
||||||
|
echo "Deleting stuck scheduledbackup: {$scheduled_backup->name}\n";
|
||||||
|
$scheduled_backup->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck scheduledbackups: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup any resources that are not attached to any environment or destination or server
|
// Cleanup any resources that are not attached to any environment or destination or server
|
||||||
try {
|
try {
|
||||||
$applications = Application::all();
|
$applications = Application::all();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,19 +231,22 @@ class CleanupStuckedResources extends Command
|
|||||||
try {
|
try {
|
||||||
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
||||||
foreach ($postgresqls as $postgresql) {
|
foreach ($postgresqls as $postgresql) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,19 +256,22 @@ class CleanupStuckedResources extends Command
|
|||||||
try {
|
try {
|
||||||
$redis = StandaloneRedis::all();
|
$redis = StandaloneRedis::all();
|
||||||
foreach ($redis as $redis) {
|
foreach ($redis as $redis) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,19 +282,22 @@ class CleanupStuckedResources extends Command
|
|||||||
try {
|
try {
|
||||||
$mongodbs = StandaloneMongodb::all();
|
$mongodbs = StandaloneMongodb::all();
|
||||||
foreach ($mongodbs as $mongodb) {
|
foreach ($mongodbs as $mongodb) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,19 +308,22 @@ class CleanupStuckedResources extends Command
|
|||||||
try {
|
try {
|
||||||
$mysqls = StandaloneMysql::all();
|
$mysqls = StandaloneMysql::all();
|
||||||
foreach ($mysqls as $mysql) {
|
foreach ($mysqls as $mysql) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,19 +334,22 @@ class CleanupStuckedResources extends Command
|
|||||||
try {
|
try {
|
||||||
$mariadbs = StandaloneMariadb::all();
|
$mariadbs = StandaloneMariadb::all();
|
||||||
foreach ($mariadbs as $mariadb) {
|
foreach ($mariadbs as $mariadb) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,19 +360,22 @@ class CleanupStuckedResources extends Command
|
|||||||
try {
|
try {
|
||||||
$services = Service::all();
|
$services = Service::all();
|
||||||
foreach ($services as $service) {
|
foreach ($services as $service) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,9 +385,10 @@ class CleanupStuckedResources extends Command
|
|||||||
try {
|
try {
|
||||||
$serviceApplications = ServiceApplication::all();
|
$serviceApplications = ServiceApplication::all();
|
||||||
foreach ($serviceApplications as $service) {
|
foreach ($serviceApplications as $service) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,9 +398,10 @@ class CleanupStuckedResources extends Command
|
|||||||
try {
|
try {
|
||||||
$serviceDatabases = ServiceDatabase::all();
|
$serviceDatabases = ServiceDatabase::all();
|
||||||
foreach ($serviceDatabases as $service) {
|
foreach ($serviceDatabases as $service) {
|
||||||
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,17 +8,19 @@ use Illuminate\Console\Command;
|
|||||||
class CleanupUnreachableServers extends Command
|
class CleanupUnreachableServers extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:unreachable-servers';
|
protected $signature = 'cleanup:unreachable-servers';
|
||||||
protected $description = 'Cleanup Unreachable Servers (3 days)';
|
|
||||||
|
protected $description = 'Cleanup Unreachable Servers (7 days)';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
echo "Running unreachable server cleanup...\n";
|
echo "Running unreachable server cleanup...\n";
|
||||||
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get();
|
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(7))->get();
|
||||||
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...");
|
||||||
$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,19 +9,55 @@ use Illuminate\Support\Facades\Process;
|
|||||||
|
|
||||||
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate STORAGE link if not exists
|
||||||
|
if (! file_exists(public_path('storage'))) {
|
||||||
|
echo "Generating STORAGE link.\n";
|
||||||
|
Artisan::call('storage:link');
|
||||||
|
}
|
||||||
|
|
||||||
// Seed database if it's empty
|
// Seed database if it's empty
|
||||||
$settings = InstanceSettings::find(0);
|
$settings = InstanceSettings::find(0);
|
||||||
if (!$settings) {
|
if (! $settings) {
|
||||||
echo "Initializing instance, seeding database.\n";
|
echo "Initializing instance, seeding database.\n";
|
||||||
Artisan::call('migrate --seed');
|
Artisan::call('migrate --seed');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use App\Notifications\Application\DeploymentSuccess;
|
|||||||
use App\Notifications\Application\StatusChanged;
|
use App\Notifications\Application\StatusChanged;
|
||||||
use App\Notifications\Database\BackupFailed;
|
use App\Notifications\Database\BackupFailed;
|
||||||
use App\Notifications\Database\BackupSuccess;
|
use App\Notifications\Database\BackupSuccess;
|
||||||
|
use App\Notifications\Database\DailyBackup;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
@@ -46,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(
|
||||||
@@ -54,6 +57,8 @@ class Emails extends Command
|
|||||||
options: [
|
options: [
|
||||||
'updates' => 'Send Update Email to all users',
|
'updates' => 'Send Update Email to all users',
|
||||||
'emails-test' => 'Test',
|
'emails-test' => 'Test',
|
||||||
|
'database-backup-statuses-daily' => 'Database - Backup Statuses (Daily)',
|
||||||
|
'application-deployment-success-daily' => 'Application - Deployment Success (Daily)',
|
||||||
'application-deployment-success' => 'Application - Deployment Success',
|
'application-deployment-success' => 'Application - Deployment Success',
|
||||||
'application-deployment-failed' => 'Application - Deployment Failed',
|
'application-deployment-failed' => 'Application - Deployment Failed',
|
||||||
'application-status-changed' => 'Application - Status Changed',
|
'application-status-changed' => 'Application - Status Changed',
|
||||||
@@ -67,18 +72,23 @@ class Emails extends Command
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
|
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
|
||||||
if (!in_array($type, $emailsGathered)) {
|
if (isDev()) {
|
||||||
$this->email = text('Email Address to send to');
|
$this->email = 'test@example.com';
|
||||||
|
} else {
|
||||||
|
if (! in_array($type, $emailsGathered)) {
|
||||||
|
$this->email = text('Email Address to send to:');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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 = [];
|
||||||
@@ -90,27 +100,56 @@ class Emails extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$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':
|
||||||
|
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||||
|
$databases = collect();
|
||||||
|
foreach ($scheduled_backups as $scheduled_backup) {
|
||||||
|
$last_days_backups = $scheduled_backup->get_last_days_backup_status();
|
||||||
|
if ($last_days_backups->isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$failed = $last_days_backups->where('status', 'failed');
|
||||||
|
$database = $scheduled_backup->database;
|
||||||
|
$databases->put($database->name, [
|
||||||
|
'failed_count' => $failed->count(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->mail = (new DailyBackup($databases))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'application-deployment-success-daily':
|
||||||
|
$applications = Application::all();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$deployments = $application->get_last_days_deployments();
|
||||||
|
ray($deployments);
|
||||||
|
if ($deployments->isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'application-deployment-success':
|
case 'application-deployment-success':
|
||||||
$application = Application::all()->first();
|
$application = Application::all()->first();
|
||||||
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||||
@@ -119,7 +158,7 @@ class Emails extends Command
|
|||||||
case 'application-deployment-failed':
|
case 'application-deployment-failed':
|
||||||
$application = Application::all()->first();
|
$application = Application::all()->first();
|
||||||
$preview = ApplicationPreview::all()->first();
|
$preview = ApplicationPreview::all()->first();
|
||||||
if (!$preview) {
|
if (! $preview) {
|
||||||
$preview = ApplicationPreview::create([
|
$preview = ApplicationPreview::create([
|
||||||
'application_id' => $application->id,
|
'application_id' => $application->id,
|
||||||
'pull_request_id' => 1,
|
'pull_request_id' => 1,
|
||||||
@@ -140,7 +179,7 @@ class Emails extends Command
|
|||||||
case 'backup-failed':
|
case 'backup-failed':
|
||||||
$backup = ScheduledDatabaseBackup::all()->first();
|
$backup = ScheduledDatabaseBackup::all()->first();
|
||||||
$db = StandalonePostgresql::all()->first();
|
$db = StandalonePostgresql::all()->first();
|
||||||
if (!$backup) {
|
if (! $backup) {
|
||||||
$backup = ScheduledDatabaseBackup::create([
|
$backup = ScheduledDatabaseBackup::create([
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'frequency' => 'daily',
|
'frequency' => 'daily',
|
||||||
@@ -150,14 +189,14 @@ class Emails extends Command
|
|||||||
'team_id' => 0,
|
'team_id' => 0,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$output = 'Because of an error, the backup of the database ' . $db->name . ' failed.';
|
$output = 'Because of an error, the backup of the database '.$db->name.' failed.';
|
||||||
$this->mail = (new BackupFailed($backup, $db, $output))->toMail();
|
$this->mail = (new BackupFailed($backup, $db, $output))->toMail();
|
||||||
$this->sendEmail();
|
$this->sendEmail();
|
||||||
break;
|
break;
|
||||||
case 'backup-success':
|
case 'backup-success':
|
||||||
$backup = ScheduledDatabaseBackup::all()->first();
|
$backup = ScheduledDatabaseBackup::all()->first();
|
||||||
$db = StandalonePostgresql::all()->first();
|
$db = StandalonePostgresql::all()->first();
|
||||||
if (!$backup) {
|
if (! $backup) {
|
||||||
$backup = ScheduledDatabaseBackup::create([
|
$backup = ScheduledDatabaseBackup::create([
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'frequency' => 'daily',
|
'frequency' => 'daily',
|
||||||
@@ -185,7 +224,7 @@ class Emails extends Command
|
|||||||
// $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',
|
||||||
]);
|
]);
|
||||||
@@ -202,12 +241,13 @@ class Emails extends Command
|
|||||||
|
|
||||||
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 = [];
|
||||||
@@ -219,7 +259,7 @@ class Emails extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$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);
|
||||||
}
|
}
|
||||||
@@ -233,7 +273,7 @@ class Emails extends Command
|
|||||||
case 'realusers-server-lost-connection':
|
case 'realusers-server-lost-connection':
|
||||||
$serverId = text('Server Id');
|
$serverId = text('Server Id');
|
||||||
$server = Server::find($serverId);
|
$server = Server::find($serverId);
|
||||||
if (!$server) {
|
if (! $server) {
|
||||||
throw new Exception('Server not found');
|
throw new Exception('Server not found');
|
||||||
}
|
}
|
||||||
$admins = [];
|
$admins = [];
|
||||||
@@ -243,22 +283,23 @@ class Emails extends Command
|
|||||||
$admins[] = $member->email;
|
$admins[] = $member->email;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->info('Sending to ' . count($admins) . ' admins.');
|
$this->info('Sending to '.count($admins).' admins.');
|
||||||
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,
|
||||||
]);
|
]);
|
||||||
$this->mail->subject('Action required: Server ' . $server->name . ' lost connection.');
|
$this->mail->subject('Action required: Server '.$server->name.' lost connection.');
|
||||||
foreach ($admins as $email) {
|
foreach ($admins as $email) {
|
||||||
$this->sendEmail($email);
|
$this->sendEmail($email);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function sendEmail(string $email = null)
|
|
||||||
|
private function sendEmail(?string $email = null)
|
||||||
{
|
{
|
||||||
if ($email) {
|
if ($email) {
|
||||||
$this->email = $email;
|
$this->email = $email;
|
||||||
@@ -269,7 +310,7 @@ class Emails extends Command
|
|||||||
fn (Message $message) => $message
|
fn (Message $message) => $message
|
||||||
->to($this->email)
|
->to($this->email)
|
||||||
->subject($this->mail->subject)
|
->subject($this->mail->subject)
|
||||||
->html((string)$this->mail->render())
|
->html((string) $this->mail->render())
|
||||||
);
|
);
|
||||||
$this->info("Email sent to $this->email successfully. 📧");
|
$this->info("Email sent to $this->email successfully. 📧");
|
||||||
}
|
}
|
||||||
|
|||||||
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,98 +2,204 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Actions\Server\StopSentinel;
|
||||||
|
use App\Enums\ActivityTypes;
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\Environment;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
protected $signature = 'app:init {--force-cloud}';
|
||||||
|
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
|
public $servers = null;
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->alive();
|
if (isCloud() && ! $this->option('force-cloud')) {
|
||||||
$full_cleanup = $this->option('full-cleanup');
|
echo "Skipping init as we are on cloud and --force-cloud option is not set\n";
|
||||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
|
||||||
if ($cleanup_deployments) {
|
|
||||||
echo "Running cleanup deployments.\n";
|
|
||||||
$this->cleanup_in_progress_application_deployments();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($full_cleanup) {
|
|
||||||
// Required for falsely deleted coolify db
|
$this->servers = Server::all();
|
||||||
$this->restore_coolify_db_backup();
|
if (isCloud()) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->send_alive_signal();
|
||||||
|
get_public_ips();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward compatibility
|
||||||
|
$this->disable_metrics();
|
||||||
|
$this->replace_slash_in_environment_name();
|
||||||
|
$this->restore_coolify_db_backup();
|
||||||
|
//
|
||||||
|
$this->update_traefik_labels();
|
||||||
|
if (! isCloud() || $this->option('force-cloud')) {
|
||||||
|
$this->cleanup_unused_network_from_coolify_proxy();
|
||||||
|
}
|
||||||
|
if (isCloud()) {
|
||||||
|
$this->cleanup_unnecessary_dynamic_proxy_configuration();
|
||||||
|
} else {
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
$this->cleanup_stucked_helper_containers();
|
}
|
||||||
$this->call('cleanup:queue');
|
$this->call('cleanup:redis');
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
|
|
||||||
|
if (isCloud()) {
|
||||||
|
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||||
|
if ($response->successful()) {
|
||||||
|
$services = $response->json();
|
||||||
|
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
setup_dynamic_configuration();
|
$localhost = $this->servers->where('id', 0)->first();
|
||||||
|
$localhost->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();
|
||||||
$settings = InstanceSettings::get();
|
if (! is_null(env('AUTOUPDATE', null))) {
|
||||||
if (!is_null(env('AUTOUPDATE', null))) {
|
|
||||||
if (env('AUTOUPDATE') == true) {
|
if (env('AUTOUPDATE') == true) {
|
||||||
$settings->update(['is_auto_update_enabled' => true]);
|
$settings->update(['is_auto_update_enabled' => true]);
|
||||||
} else {
|
} else {
|
||||||
$settings->update(['is_auto_update_enabled' => false]);
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
$this->cleanup_stucked_helper_containers();
|
|
||||||
$this->call('cleanup:stucked-resources');
|
|
||||||
}
|
}
|
||||||
private function restore_coolify_db_backup()
|
|
||||||
|
private function disable_metrics()
|
||||||
{
|
{
|
||||||
try {
|
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||||
$database = StandalonePostgresql::withTrashed()->find(0);
|
foreach ($this->servers as $server) {
|
||||||
if ($database && $database->trashed()) {
|
if ($server->settings->is_metrics_enabled === true) {
|
||||||
echo "Restoring coolify db backup\n";
|
$server->settings->update(['is_metrics_enabled' => false]);
|
||||||
$database->restore();
|
}
|
||||||
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
if ($server->isFunctional()) {
|
||||||
if (!$scheduledBackup) {
|
StopSentinel::dispatch($server);
|
||||||
ScheduledDatabaseBackup::create([
|
|
||||||
'id' => 0,
|
|
||||||
'enabled' => true,
|
|
||||||
'save_s3' => false,
|
|
||||||
'frequency' => '0 0 * * *',
|
|
||||||
'database_id' => $database->id,
|
|
||||||
'database_type' => 'App\Models\StandalonePostgresql',
|
|
||||||
'team_id' => 0,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function cleanup_stucked_helper_containers()
|
|
||||||
|
private function update_traefik_labels()
|
||||||
{
|
{
|
||||||
$servers = Server::all();
|
try {
|
||||||
foreach ($servers as $server) {
|
Server::where('proxy->type', 'TRAEFIK_V2')->update(['proxy->type' => 'TRAEFIK']);
|
||||||
if ($server->isFunctional()) {
|
} catch (\Throwable $e) {
|
||||||
CleanupHelperContainersJob::dispatch($server);
|
echo "Error in updating traefik labels: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
||||||
|
{
|
||||||
|
foreach ($this->servers as $server) {
|
||||||
|
try {
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($server->id === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$file = $server->proxyPath().'/dynamic/coolify.yaml';
|
||||||
|
|
||||||
|
return instant_remote_process([
|
||||||
|
"rm -f $file",
|
||||||
|
], $server, false);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanup_unused_network_from_coolify_proxy()
|
||||||
|
{
|
||||||
|
foreach ($this->servers as $server) {
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! $server->isProxyShouldRun()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
['networks' => $networks, 'allNetworks' => $allNetworks] = collectDockerNetworksByServer($server);
|
||||||
|
$removeNetworks = $allNetworks->diff($networks);
|
||||||
|
$commands = collect();
|
||||||
|
foreach ($removeNetworks as $network) {
|
||||||
|
$out = instant_remote_process(["docker network inspect -f json $network | jq '.[].Containers | if . == {} then null else . end'"], $server, false);
|
||||||
|
if (empty($out)) {
|
||||||
|
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||||
|
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||||
|
} else {
|
||||||
|
$data = collect(json_decode($out, true));
|
||||||
|
if ($data->count() === 1) {
|
||||||
|
// If only coolify-proxy itself is connected to that network (it should not be possible, but who knows)
|
||||||
|
$isCoolifyProxyItself = data_get($data->first(), 'Name') === 'coolify-proxy';
|
||||||
|
if ($isCoolifyProxyItself) {
|
||||||
|
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||||
|
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($commands->isNotEmpty()) {
|
||||||
|
echo "Cleaning up unused networks from coolify proxy\n";
|
||||||
|
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning up unused networks from coolify proxy: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function alive()
|
|
||||||
|
private function restore_coolify_db_backup()
|
||||||
|
{
|
||||||
|
if (version_compare('4.0.0-beta.179', config('version'), '<=')) {
|
||||||
|
try {
|
||||||
|
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||||
|
if ($database && $database->trashed()) {
|
||||||
|
echo "Restoring coolify db backup\n";
|
||||||
|
$database->restore();
|
||||||
|
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
||||||
|
if (! $scheduledBackup) {
|
||||||
|
ScheduledDatabaseBackup::create([
|
||||||
|
'id' => 0,
|
||||||
|
'enabled' => true,
|
||||||
|
'save_s3' => false,
|
||||||
|
'frequency' => '0 0 * * *',
|
||||||
|
'database_id' => $database->id,
|
||||||
|
'database_type' => 'App\Models\StandalonePostgresql',
|
||||||
|
'team_id' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function send_alive_signal()
|
||||||
{
|
{
|
||||||
$id = config('app.id');
|
$id = config('app.id');
|
||||||
$version = config('version');
|
$version = config('version');
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$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 {
|
||||||
@@ -103,27 +209,10 @@ class Init extends Command
|
|||||||
echo "Error in alive: {$e->getMessage()}\n";
|
echo "Error in alive: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// private function cleanup_ssh()
|
|
||||||
// {
|
|
||||||
|
|
||||||
// TODO: it will cleanup id.root@host.docker.internal
|
|
||||||
// try {
|
|
||||||
// $files = Storage::allFiles('ssh/keys');
|
|
||||||
// foreach ($files as $file) {
|
|
||||||
// Storage::delete($file);
|
|
||||||
// }
|
|
||||||
// $files = Storage::allFiles('ssh/mux');
|
|
||||||
// foreach ($files as $file) {
|
|
||||||
// Storage::delete($file);
|
|
||||||
// }
|
|
||||||
// } catch (\Throwable $e) {
|
|
||||||
// echo "Error in cleaning ssh: {$e->getMessage()}\n";
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanup_in_progress_application_deployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
return;
|
return;
|
||||||
@@ -139,4 +228,17 @@ class Init extends Command
|
|||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function replace_slash_in_environment_name()
|
||||||
|
{
|
||||||
|
if (version_compare('4.0.0-beta.298', config('version'), '<=')) {
|
||||||
|
$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 @@ class NotifyDemo extends Command
|
|||||||
|
|
||||||
if (blank($channel)) {
|
if (blank($channel)) {
|
||||||
$this->showHelp();
|
$this->showHelp();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
26
app/Console/Commands/OpenApi.php
Normal file
26
app/Console/Commands/OpenApi.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
|
||||||
|
class OpenApi extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'openapi';
|
||||||
|
|
||||||
|
protected $description = 'Generate OpenApi file.';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ class RootChangeEmail extends Command
|
|||||||
$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 @@ class RootResetPassword extends Command
|
|||||||
$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 @@ class ServicesDelete extends Command
|
|||||||
$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,19 +66,21 @@ class ServicesDelete extends Command
|
|||||||
$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;
|
||||||
}
|
}
|
||||||
$toDelete->delete();
|
$toDelete->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,19 +92,21 @@ class ServicesDelete extends Command
|
|||||||
$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;
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($toDelete);
|
DeleteResourceJob::dispatch($toDelete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,19 +118,21 @@ class ServicesDelete extends Command
|
|||||||
$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;
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($toDelete);
|
DeleteResourceJob::dispatch($toDelete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,8 +144,8 @@ class ServicesDelete extends Command
|
|||||||
$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;
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($toDelete);
|
DeleteResourceJob::dispatch($toDelete);
|
||||||
|
|||||||
@@ -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 @@ class ServicesGenerate extends Command
|
|||||||
$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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,18 +50,20 @@ class ServicesGenerate extends Command
|
|||||||
// $this->info($content);
|
// $this->info($content);
|
||||||
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
|
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
|
||||||
if ($ignore->count() > 0) {
|
if ($ignore->count() > 0) {
|
||||||
$ignore = (bool)str($ignore[0])->after('# ignore:')->trim()->value();
|
$ignore = (bool) str($ignore[0])->after('# ignore:')->trim()->value();
|
||||||
} else {
|
} else {
|
||||||
$ignore = false;
|
$ignore = false;
|
||||||
}
|
}
|
||||||
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';
|
||||||
}
|
}
|
||||||
@@ -73,6 +74,18 @@ class ServicesGenerate extends Command
|
|||||||
} else {
|
} else {
|
||||||
$slogan = str($file)->headline()->value();
|
$slogan = str($file)->headline()->value();
|
||||||
}
|
}
|
||||||
|
$logo = collect(preg_grep('/^# logo:/', explode("\n", $content)))->values();
|
||||||
|
if ($logo->count() > 0) {
|
||||||
|
$logo = str($logo[0])->after('# logo:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$logo = 'svgs/coolify.png';
|
||||||
|
}
|
||||||
|
$minversion = collect(preg_grep('/^# minversion:/', explode("\n", $content)))->values();
|
||||||
|
if ($minversion->count() > 0) {
|
||||||
|
$minversion = str($minversion[0])->after('# minversion:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$minversion = '0.0.0';
|
||||||
|
}
|
||||||
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
|
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
|
||||||
if ($env_file->count() > 0) {
|
if ($env_file->count() > 0) {
|
||||||
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
|
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
|
||||||
@@ -88,6 +101,12 @@ class ServicesGenerate extends Command
|
|||||||
} 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 = [
|
||||||
@@ -96,12 +115,18 @@ class ServicesGenerate extends Command
|
|||||||
'slogan' => $slogan,
|
'slogan' => $slogan,
|
||||||
'compose' => $yaml,
|
'compose' => $yaml,
|
||||||
'tags' => $tags,
|
'tags' => $tags,
|
||||||
|
'logo' => $logo,
|
||||||
|
'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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class SyncBunny extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'sync:bunny {--templates} {--release}';
|
protected $signature = 'sync:bunny {--templates} {--release} {--nightly}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -33,30 +33,38 @@ class SyncBunny extends Command
|
|||||||
$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";
|
$nightly = $this->option('nightly');
|
||||||
$bunny_cdn_path = "coolify";
|
$bunny_cdn = 'https://cdn.coollabs.io';
|
||||||
$bunny_cdn_storage_name = "coolcdn";
|
$bunny_cdn_path = 'coolify';
|
||||||
|
$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";
|
$compose_file_location = "$parent_dir/$compose_file";
|
||||||
|
$compose_file_prod_location = "$parent_dir/$compose_file_prod";
|
||||||
|
$install_script_location = "$parent_dir/scripts/install.sh";
|
||||||
|
$upgrade_script_location = "$parent_dir/scripts/upgrade.sh";
|
||||||
|
$production_env_location = "$parent_dir/.env.production";
|
||||||
|
$versions_location = "$parent_dir/$versions";
|
||||||
|
|
||||||
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) {
|
||||||
@@ -64,20 +72,39 @@ class SyncBunny extends Command
|
|||||||
'AccessKey' => env('BUNNY_API_KEY'),
|
'AccessKey' => env('BUNNY_API_KEY'),
|
||||||
'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 {
|
||||||
if (!$only_template && !$only_version) {
|
if ($nightly) {
|
||||||
$this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
|
$bunny_cdn_path = 'coolify-nightly';
|
||||||
|
|
||||||
|
$compose_file_location = "$parent_dir/other/nightly/$compose_file";
|
||||||
|
$compose_file_prod_location = "$parent_dir/other/nightly/$compose_file_prod";
|
||||||
|
$production_env_location = "$parent_dir/other/nightly/$production_env";
|
||||||
|
$upgrade_script_location = "$parent_dir/other/nightly/$upgrade_script";
|
||||||
|
$install_script_location = "$parent_dir/other/nightly/$install_script";
|
||||||
|
$versions_location = "$parent_dir/other/nightly/$versions";
|
||||||
|
}
|
||||||
|
if (! $only_template && ! $only_version) {
|
||||||
|
if ($nightly) {
|
||||||
|
$this->info('About to sync files NIGHTLY (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
|
||||||
|
} else {
|
||||||
|
$this->info('About to sync files PRODUCTION (docker-compose.yml, docker-compose.prod.yml, upgrade.sh, install.sh, etc) to BunnyCDN.');
|
||||||
|
}
|
||||||
|
$confirmed = confirm('Are you sure you want to sync?');
|
||||||
|
if (! $confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
@@ -85,32 +112,37 @@ class SyncBunny extends Command
|
|||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
||||||
]);
|
]);
|
||||||
$this->info('Service template uploaded & purged...');
|
$this->info('Service template uploaded & purged...');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else if ($only_version) {
|
} elseif ($only_version) {
|
||||||
$this->info('About to sync versions.json to BunnyCDN.');
|
if ($nightly) {
|
||||||
$file = file_get_contents("$parent_dir/$versions");
|
$this->info('About to sync NIGHLTY versions.json to BunnyCDN.');
|
||||||
|
} else {
|
||||||
|
$this->info('About to sync PRODUCTION versions.json to BunnyCDN.');
|
||||||
|
}
|
||||||
|
$file = file_get_contents($versions_location);
|
||||||
$json = json_decode($file, true);
|
$json = json_decode($file, true);
|
||||||
$actual_version = data_get($json, 'coolify.v4.version');
|
$actual_version = data_get($json, 'coolify.v4.version');
|
||||||
|
|
||||||
$confirmed = confirm("Are you sure you want to sync to {$actual_version}?");
|
$confirmed = confirm("Are you sure you want to sync to {$actual_version}?");
|
||||||
if (!$confirmed) {
|
if (! $confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
$pool->storage(fileName: $versions_location)->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||||
]);
|
]);
|
||||||
$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: "$compose_file_location")->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: "$compose_file_prod_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||||
$pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
|
$pool->storage(fileName: "$production_env_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
|
||||||
$pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
|
$pool->storage(fileName: "$upgrade_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
|
||||||
$pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
|
$pool->storage(fileName: "$install_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
|
||||||
]);
|
]);
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
||||||
@@ -119,9 +151,9 @@ class SyncBunny extends Command
|
|||||||
$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 @@ use Illuminate\Support\Str;
|
|||||||
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 @@ class WaitlistInvite extends Command
|
|||||||
$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();
|
||||||
@@ -50,8 +54,9 @@ class WaitlistInvite extends Command
|
|||||||
} else {
|
} else {
|
||||||
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
||||||
}
|
}
|
||||||
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 @@ class WaitlistInvite extends Command
|
|||||||
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 @@ class WaitlistInvite extends Command
|
|||||||
$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 @@ class WaitlistInvite extends Command
|
|||||||
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. 📧');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Jobs\CheckLogDrainContainerJob;
|
use App\Jobs\CheckForUpdatesJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\ComplexContainerStatusJob;
|
use App\Jobs\CleanupStaleMultiplexedConnections;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
use App\Jobs\ScheduledTaskJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\InstanceAutoUpdateJob;
|
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Jobs\PullHelperImageJob;
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Jobs\ServerStatusJob;
|
use App\Jobs\PullSentinelImageJob;
|
||||||
use App\Models\InstanceSettings;
|
use App\Jobs\PullTemplatesFromCDN;
|
||||||
|
use App\Jobs\ScheduledTaskJob;
|
||||||
|
use App\Jobs\ServerCheckJob;
|
||||||
|
use App\Jobs\ServerStorageCheckJob;
|
||||||
|
use App\Jobs\UpdateCoolifyJob;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -21,73 +23,109 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
|||||||
|
|
||||||
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();
|
||||||
|
$settings = instanceSettings();
|
||||||
|
|
||||||
|
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||||
|
|
||||||
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();
|
|
||||||
// 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->pull_helper_image($schedule);
|
|
||||||
$this->check_scheduled_tasks($schedule);
|
$this->check_scheduled_tasks($schedule);
|
||||||
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
|
|
||||||
|
$schedule->command('telescope:prune')->daily();
|
||||||
|
|
||||||
|
$schedule->job(new PullHelperImageJob)->everyFiveMinutes()->onOneServer();
|
||||||
} 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()->onOneServer();
|
||||||
|
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
$this->schedule_updates($schedule);
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
|
$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');
|
$settings = instanceSettings();
|
||||||
|
$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(function () use ($server) {
|
||||||
|
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $server, false);
|
||||||
|
$sentinel_found = json_decode($sentinel_found, true);
|
||||||
|
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||||
|
if ($status !== 'running') {
|
||||||
|
PullSentinelImageJob::dispatch($server);
|
||||||
|
}
|
||||||
|
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$schedule->job(new PullHelperImageJob)
|
||||||
|
->cron($settings->update_check_frequency)
|
||||||
|
->timezone($settings->instance_timezone)
|
||||||
|
->onOneServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function schedule_updates($schedule)
|
||||||
|
{
|
||||||
|
$settings = instanceSettings();
|
||||||
|
|
||||||
|
$updateCheckFrequency = $settings->update_check_frequency;
|
||||||
|
$schedule->job(new CheckForUpdatesJob)
|
||||||
|
->cron($updateCheckFrequency)
|
||||||
|
->timezone($settings->instance_timezone)
|
||||||
|
->onOneServer();
|
||||||
|
|
||||||
|
if ($settings->is_auto_update_enabled) {
|
||||||
|
$autoUpdateFrequency = $settings->auto_update_frequency;
|
||||||
|
$schedule->job(new UpdateCoolifyJob)
|
||||||
|
->cron($autoUpdateFrequency)
|
||||||
|
->timezone($settings->instance_timezone)
|
||||||
|
->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);
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
foreach ($containerServers as $server) {
|
|
||||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
|
||||||
if ($server->isLogDrainEnabled()) {
|
|
||||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||||
}
|
// $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
|
||||||
}
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
private function instance_auto_update($schedule)
|
if ($server->settings->force_docker_cleanup) {
|
||||||
{
|
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
if (isDev()) {
|
} else {
|
||||||
return;
|
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
$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();
|
||||||
@@ -95,21 +133,29 @@ class Kernel extends ConsoleKernel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($scheduled_backups as $scheduled_backup) {
|
foreach ($scheduled_backups as $scheduled_backup) {
|
||||||
if (!$scheduled_backup->enabled) {
|
if (! $scheduled_backup->enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$server = $scheduled_backup->server();
|
||||||
|
|
||||||
|
if (! $server) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new DatabaseBackupJob(
|
$schedule->job(new DatabaseBackupJob(
|
||||||
backup: $scheduled_backup
|
backup: $scheduled_backup
|
||||||
))->cron($scheduled_backup->frequency)->onOneServer();
|
))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,27 +166,47 @@ class Kernel extends ConsoleKernel
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = $scheduled_task->server();
|
||||||
|
if (! $server) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$serverTimezone = $server->settings->server_timezone ?: config('app.timezone');
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
$schedule->job(new ScheduledTaskJob(
|
$schedule->job(new ScheduledTaskJob(
|
||||||
task: $scheduled_task
|
task: $scheduled_task
|
||||||
))->cron($scheduled_task->frequency)->onOneServer();
|
))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function commands(): void
|
protected function commands(): void
|
||||||
{
|
{
|
||||||
$this->load(__DIR__ . '/Commands');
|
$this->load(__DIR__.'/Commands');
|
||||||
|
|
||||||
require base_path('routes/console.php');
|
require base_path('routes/console.php');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,17 +12,18 @@ use Spatie\LaravelData\Data;
|
|||||||
class CoolifyTaskArgs extends Data
|
class CoolifyTaskArgs extends Data
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $server_uuid,
|
public string $server_uuid,
|
||||||
public string $command,
|
public string $command,
|
||||||
public string $type,
|
public string $type,
|
||||||
public ?string $type_uuid = null,
|
public ?string $type_uuid = null,
|
||||||
public ?int $process_id = null,
|
public ?int $process_id = null,
|
||||||
public ?Model $model = null,
|
public ?Model $model = null,
|
||||||
public ?string $status = null ,
|
public ?string $status = null,
|
||||||
public bool $ignore_errors = false,
|
public bool $ignore_errors = false,
|
||||||
public $call_event_on_finish = null,
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ use Spatie\LaravelData\Data;
|
|||||||
class ServerMetadata extends Data
|
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 @@ namespace App\Enums;
|
|||||||
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';
|
||||||
|
}
|
||||||
14
app/Enums/ContainerStatusTypes.php
Normal file
14
app/Enums/ContainerStatusTypes.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum ContainerStatusTypes: string
|
||||||
|
{
|
||||||
|
case PAUSED = 'paused';
|
||||||
|
case RESTARTING = 'restarting';
|
||||||
|
case REMOVING = 'removing';
|
||||||
|
case RUNNING = 'running';
|
||||||
|
case DEAD = 'dead';
|
||||||
|
case CREATED = 'created';
|
||||||
|
case EXITED = 'exited';
|
||||||
|
}
|
||||||
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';
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user