mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
2505 Commits
v3.10.1
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
242d2fb283 | ||
|
|
5646818965 | ||
|
|
ffc5320940 | ||
|
|
7c10c55b1c | ||
|
|
7c96b6207a | ||
|
|
0be8ffbdc9 | ||
|
|
24fa56762e | ||
|
|
84d8e35411 | ||
|
|
3d3ccc435c | ||
|
|
be3b01472e | ||
|
|
de6f5b1105 | ||
|
|
14d9c06dcd | ||
|
|
8abfaa1967 | ||
|
|
46f7ae9588 | ||
|
|
f2c32b9aeb | ||
|
|
3dab1eb92e | ||
|
|
9c22e01716 | ||
|
|
d1c47a4062 | ||
|
|
6c3f97d9ae | ||
|
|
ebd8e2ce40 | ||
|
|
b650f3f754 | ||
|
|
f33ba40478 | ||
|
|
5cea9c4603 | ||
|
|
d32832fabc | ||
|
|
165f0a3d4a | ||
|
|
f14995200b | ||
|
|
12bb2ecc4a | ||
|
|
933ec5741d | ||
|
|
8004a40139 | ||
|
|
a6209fbe5c | ||
|
|
b47c327b55 | ||
|
|
25434a7acd | ||
|
|
0de042dbac | ||
|
|
eb9e2203b0 | ||
|
|
dcaa7a6ad7 | ||
|
|
5b584a6c6d | ||
|
|
c40ea6f1da | ||
|
|
61a7b9ac94 | ||
|
|
9e81416fef | ||
|
|
c58706e3e4 | ||
|
|
45bca8649b | ||
|
|
b095b88281 | ||
|
|
cb41584137 | ||
|
|
6659153804 | ||
|
|
44c429a224 | ||
|
|
fe9c501c1d | ||
|
|
4e94b4a0c1 | ||
|
|
2f4d7c0e43 | ||
|
|
e443fc394a | ||
|
|
5b56c50f03 | ||
|
|
3adeb2f73f | ||
|
|
9eaa13a08a | ||
|
|
df5a4a9667 | ||
|
|
26048339d6 | ||
|
|
d85af3fefc | ||
|
|
575338609b | ||
|
|
d32e43ef37 | ||
|
|
a96ef1bfab | ||
|
|
1bfedf69f2 | ||
|
|
208fe7d87b | ||
|
|
a6d58b5d72 | ||
|
|
277b4276e6 | ||
|
|
f6adc9285a | ||
|
|
f03bbe0e95 | ||
|
|
535375193c | ||
|
|
d79c063fd6 | ||
|
|
35f45492e3 | ||
|
|
ab8a7893d9 | ||
|
|
76b8d048d4 | ||
|
|
0e583334e7 | ||
|
|
0ad8ca224f | ||
|
|
050e56f69a | ||
|
|
6099ac11d9 | ||
|
|
adac728a60 | ||
|
|
e2e64e36a0 | ||
|
|
762af66cbf | ||
|
|
b08f525bd4 | ||
|
|
5ae16b195c | ||
|
|
91db1953ff | ||
|
|
1c8f92d3b7 | ||
|
|
4075572dbc | ||
|
|
2971e360d7 | ||
|
|
32bb2780f2 | ||
|
|
af69575b29 | ||
|
|
d4a7d0d25f | ||
|
|
45f9def0f6 | ||
|
|
5a90eed7ef | ||
|
|
38e1f17edf | ||
|
|
1651845e20 | ||
|
|
38e96548b5 | ||
|
|
47e4126dca | ||
|
|
e0b175ab07 | ||
|
|
93ec785f4f | ||
|
|
e849addab8 | ||
|
|
a5e6975dac | ||
|
|
4ac8e1cc67 | ||
|
|
1a5e3a7836 | ||
|
|
4498d1ed4b | ||
|
|
c8b974820b | ||
|
|
527373e297 | ||
|
|
a84be8dc33 | ||
|
|
bd856f7f67 | ||
|
|
8ff216e5fb | ||
|
|
5255311a2e | ||
|
|
774a245e84 | ||
|
|
194675c838 | ||
|
|
75862ca8de | ||
|
|
5580a4e704 | ||
|
|
51e601a303 | ||
|
|
734e9fd68d | ||
|
|
09fc950ae8 | ||
|
|
cf6caa279d | ||
|
|
68c976ab70 | ||
|
|
1560ab2a50 | ||
|
|
e3a6458506 | ||
|
|
1768b9374f | ||
|
|
51d0a30a6c | ||
|
|
9701c65297 | ||
|
|
58e3bb2571 | ||
|
|
31cbd1602d | ||
|
|
dd5723d596 | ||
|
|
620f26a6f1 | ||
|
|
540717e809 | ||
|
|
d446cd4103 | ||
|
|
7d1a76570c | ||
|
|
e18766ec21 | ||
|
|
3d0354cf7e | ||
|
|
af5b9fced1 | ||
|
|
ab5202515e | ||
|
|
f863db7ea5 | ||
|
|
3adc0bdd6e | ||
|
|
97027875bf | ||
|
|
fd9c13009f | ||
|
|
46a72fac47 | ||
|
|
5d6ee04991 | ||
|
|
d523becb29 | ||
|
|
41672f75d0 | ||
|
|
acd8541e68 | ||
|
|
ed6af777a4 | ||
|
|
05f162f4e8 | ||
|
|
5e0adc3777 | ||
|
|
7d06fc4403 | ||
|
|
0e1bcceb8e | ||
|
|
a922f2fedf | ||
|
|
1e0226c8ed | ||
|
|
5c45908087 | ||
|
|
aefdc76805 | ||
|
|
b3c8c881b7 | ||
|
|
9ab5a1f7bd | ||
|
|
23968e7886 | ||
|
|
390d24b6d7 | ||
|
|
e4296345b3 | ||
|
|
bcffbe418b | ||
|
|
bfbee4e78f | ||
|
|
2bdb44dac3 | ||
|
|
4daa1b8c16 | ||
|
|
febc399568 | ||
|
|
9b9d4f9941 | ||
|
|
f49b87870c | ||
|
|
ed49d4e3a0 | ||
|
|
7811c75139 | ||
|
|
068a1b4bc4 | ||
|
|
c618d912db | ||
|
|
3d43f2127a | ||
|
|
79fde593a9 | ||
|
|
64ce41df0f | ||
|
|
23e205b6cd | ||
|
|
4161ea7eb6 | ||
|
|
77037f8933 | ||
|
|
bdcc0c8de5 | ||
|
|
ac133875fa | ||
|
|
f4819a849b | ||
|
|
2cd1785a92 | ||
|
|
797352a5db | ||
|
|
03a3405524 | ||
|
|
925326b885 | ||
|
|
ffdf51a490 | ||
|
|
272d5547c8 | ||
|
|
fbaec769f0 | ||
|
|
d93640a8d9 | ||
|
|
3749970831 | ||
|
|
7d45bb1335 | ||
|
|
da04e0c027 | ||
|
|
c637d2d90d | ||
|
|
1bb8860f37 | ||
|
|
38a22dcf4d | ||
|
|
91e1eb7664 | ||
|
|
c7946e7551 | ||
|
|
958645e37f | ||
|
|
acdfa89ec1 | ||
|
|
62ec85ee2e | ||
|
|
8f24a66456 | ||
|
|
0dd0888775 | ||
|
|
5040ddea28 | ||
|
|
1a04a57c01 | ||
|
|
9b6c162224 | ||
|
|
e22c5d22f5 | ||
|
|
84e5b39830 | ||
|
|
cdb6964b0b | ||
|
|
df4ecd47a7 | ||
|
|
4a84c7238a | ||
|
|
2b3057e1b4 | ||
|
|
ae65172946 | ||
|
|
3a2d17bc05 | ||
|
|
4c1067cf36 | ||
|
|
b046a3e9f7 | ||
|
|
199881c596 | ||
|
|
99c8607ff4 | ||
|
|
e61fcc77f9 | ||
|
|
20dca179fb | ||
|
|
0f542c65ae | ||
|
|
d609fcaee1 | ||
|
|
fe8a7fc54f | ||
|
|
398f122593 | ||
|
|
f0abdcc2da | ||
|
|
c9a278b750 | ||
|
|
6990c593a4 | ||
|
|
8f54b51ecd | ||
|
|
3eb628b773 | ||
|
|
fabb97330a | ||
|
|
03c9793d11 | ||
|
|
a4320b7cee | ||
|
|
cbf9bc99ea | ||
|
|
0fbc382467 | ||
|
|
0f8ccac775 | ||
|
|
ee20c3339e | ||
|
|
0b11093d18 | ||
|
|
de8118b59d | ||
|
|
58522b59b7 | ||
|
|
356394c03d | ||
|
|
3e4db2f5b2 | ||
|
|
872981b8b4 | ||
|
|
51c468ae0b | ||
|
|
80a797aec8 | ||
|
|
cfdab13d77 | ||
|
|
4fc8988ff4 | ||
|
|
ab5619292e | ||
|
|
b02e5d3f27 | ||
|
|
5f0c9c3a31 | ||
|
|
ea6ec07a45 | ||
|
|
36be325d0d | ||
|
|
6138ddcac6 | ||
|
|
0509da6730 | ||
|
|
5b877b84c2 | ||
|
|
0c35726a8d | ||
|
|
e74899611b | ||
|
|
e9149e534d | ||
|
|
7cded7a36d | ||
|
|
ba74d55b4c | ||
|
|
a1d13fc14e | ||
|
|
cdb2a3a8e5 | ||
|
|
074c56edaa | ||
|
|
66bc03b4cd | ||
|
|
2dcb7fec05 | ||
|
|
249bccb49e | ||
|
|
a55eaa10ac | ||
|
|
ca2c75ce19 | ||
|
|
250d7cbc53 | ||
|
|
92a53a151e | ||
|
|
db3148c080 | ||
|
|
c46eeac4b5 | ||
|
|
19111ba059 | ||
|
|
9bc61a0a17 | ||
|
|
78b9166bdb | ||
|
|
1752448050 | ||
|
|
3fc78bc760 | ||
|
|
0c30b6222d | ||
|
|
3f74609c7f | ||
|
|
31b0ccba99 | ||
|
|
3fc544e0b9 | ||
|
|
67078fdc71 | ||
|
|
c91f426af3 | ||
|
|
9c2fea4b2e | ||
|
|
53d1fa0331 | ||
|
|
4ae7e46e81 | ||
|
|
ebfc0bd1e1 | ||
|
|
e1a1490911 | ||
|
|
6b75ff7de4 | ||
|
|
301469de6b | ||
|
|
b4d69a22df | ||
|
|
a86e971020 | ||
|
|
145af41c82 | ||
|
|
69c0b7240a | ||
|
|
6543132bcb | ||
|
|
9134437218 | ||
|
|
b7acf99cde | ||
|
|
21d52d7846 | ||
|
|
ab5929cc69 | ||
|
|
1452cdf5ad | ||
|
|
3eb1a1f48c | ||
|
|
5f7a97c31f | ||
|
|
9cba0a6df3 | ||
|
|
af57b2aa73 | ||
|
|
b9b9582601 | ||
|
|
c8ba98b93d | ||
|
|
a50e1e2f0c | ||
|
|
1093294f06 | ||
|
|
c023be2348 | ||
|
|
deece51e83 | ||
|
|
e2ab569244 | ||
|
|
93b202bde4 | ||
|
|
6bb6de188c | ||
|
|
61f58fa30f | ||
|
|
026f3fd72d | ||
|
|
9a706d55b4 | ||
|
|
9646969107 | ||
|
|
df433efe62 | ||
|
|
efa7cab3e2 | ||
|
|
a386a1bde5 | ||
|
|
cc1bf023be | ||
|
|
edb06fa233 | ||
|
|
abdb8074b1 | ||
|
|
157da798dd | ||
|
|
9c5501326e | ||
|
|
3ea462efc9 | ||
|
|
f77df5b732 | ||
|
|
b77074fe4e | ||
|
|
a7b7d3fa32 | ||
|
|
f4f3034389 | ||
|
|
4b2ffb456f | ||
|
|
e17ff99c5b | ||
|
|
1cf036bbc6 | ||
|
|
da4c2ee60f | ||
|
|
fcf7c5ddd5 | ||
|
|
27926322e1 | ||
|
|
c735ff545e | ||
|
|
b4f048b028 | ||
|
|
54a57d217f | ||
|
|
cf28490acc | ||
|
|
019670d5d1 | ||
|
|
b07cc500e7 | ||
|
|
82c235d5af | ||
|
|
307e4a6990 | ||
|
|
ddb7af63a6 | ||
|
|
7a429ee5bb | ||
|
|
bb591604ac | ||
|
|
81f7a65dd5 | ||
|
|
4b313bb1c6 | ||
|
|
aba0b2f13c | ||
|
|
9a284e47da | ||
|
|
9f2fbc661a | ||
|
|
949407368e | ||
|
|
93c65f6a79 | ||
|
|
d9fe16a3ee | ||
|
|
adaca4d4e3 | ||
|
|
a6d5f3038c | ||
|
|
4d49132821 | ||
|
|
c287276d0e | ||
|
|
e89868c692 | ||
|
|
f93317cd2e | ||
|
|
8412802f4d | ||
|
|
53c20e1e99 | ||
|
|
3c8c8e20b1 | ||
|
|
49f8abcd79 | ||
|
|
4a4d73b87b | ||
|
|
046eab3776 | ||
|
|
17c0e91a0d | ||
|
|
fe4a0ae166 | ||
|
|
52c84f8d22 | ||
|
|
b22fecb615 | ||
|
|
23b7fc3c54 | ||
|
|
1efb1235b4 | ||
|
|
0924070a13 | ||
|
|
12fc5a8f91 | ||
|
|
9eba058cf7 | ||
|
|
fa4f5fea8c | ||
|
|
898563fe7c | ||
|
|
c418a17161 | ||
|
|
cd0da04ea2 | ||
|
|
01e942c6a0 | ||
|
|
bb9abafa82 | ||
|
|
d0cd926517 | ||
|
|
9baf0161c7 | ||
|
|
8ba18b2ce1 | ||
|
|
ab021ee535 | ||
|
|
c76a1b1ba5 | ||
|
|
6266a5e500 | ||
|
|
5d27e89bfa | ||
|
|
6da4e78374 | ||
|
|
be30651172 | ||
|
|
95764c2b76 | ||
|
|
92a75685b5 | ||
|
|
a1592373aa | ||
|
|
5747a87f66 | ||
|
|
9cda671aef | ||
|
|
2c9983046c | ||
|
|
11d33f328e | ||
|
|
f79c741d95 | ||
|
|
8a39a4469a | ||
|
|
42daae10c6 | ||
|
|
64a65e2018 | ||
|
|
16c71f3647 | ||
|
|
363f525ad1 | ||
|
|
f4fb519d55 | ||
|
|
b7786504b8 | ||
|
|
f0adf10e6a | ||
|
|
cc8c6c5d16 | ||
|
|
4782446f42 | ||
|
|
230155312f | ||
|
|
3ab4365fca | ||
|
|
7349068b95 | ||
|
|
da6cc151d1 | ||
|
|
50527cf0a3 | ||
|
|
26f490bb00 | ||
|
|
dc4f412227 | ||
|
|
ec4234e243 | ||
|
|
f47fcb01ce | ||
|
|
02f6673345 | ||
|
|
e6cd8702b5 | ||
|
|
fda4ea8cca | ||
|
|
655d004ce7 | ||
|
|
56981d134c | ||
|
|
b9d49d2951 | ||
|
|
0c1e7c499e | ||
|
|
32fead5753 | ||
|
|
e5e9faba35 | ||
|
|
2852630d6c | ||
|
|
a4cc406114 | ||
|
|
53b15a5762 | ||
|
|
929a4e6474 | ||
|
|
45b597bbab | ||
|
|
0d1a2aa5d1 | ||
|
|
b82353d5e2 | ||
|
|
b17c09f7a7 | ||
|
|
f6c3fe7888 | ||
|
|
2e855e030f | ||
|
|
49f86621f4 | ||
|
|
03d9f93397 | ||
|
|
c472042a94 | ||
|
|
9f4356f67d | ||
|
|
a50317cc76 | ||
|
|
8afa98a1ca | ||
|
|
f6737f21dd | ||
|
|
e4a51cc116 | ||
|
|
acd78ae196 | ||
|
|
953bcfb5bb | ||
|
|
dacfab8b29 | ||
|
|
48b3e99939 | ||
|
|
41ad67c7c9 | ||
|
|
b49725cb1c | ||
|
|
75e674a966 | ||
|
|
9d826d9fb4 | ||
|
|
0af221f9fc | ||
|
|
9066c9bf90 | ||
|
|
77e3208f00 | ||
|
|
db5ecf07bd | ||
|
|
7f28aa6985 | ||
|
|
9d53e04ce9 | ||
|
|
3db9a1dd6e | ||
|
|
38f9761b67 | ||
|
|
7117ecf634 | ||
|
|
522e20f10a | ||
|
|
ebbce2396c | ||
|
|
f9a2ff6d90 | ||
|
|
df1b9e7319 | ||
|
|
e7c0c26b32 | ||
|
|
0dbb8b4420 | ||
|
|
a4b44bacc1 | ||
|
|
1338e68b8c | ||
|
|
f6f4cdde24 | ||
|
|
3daadf13c6 | ||
|
|
cbd5fab2e7 | ||
|
|
48ccb508f9 | ||
|
|
67edce0612 | ||
|
|
e8a41d7e6e | ||
|
|
be1dad03bd | ||
|
|
31db1db636 | ||
|
|
dca332a688 | ||
|
|
35f19ed53f | ||
|
|
a5c45ffe90 | ||
|
|
b6c3a65d2d | ||
|
|
220c8211fd | ||
|
|
ffbd04df29 | ||
|
|
73c59be865 | ||
|
|
3966abaf80 | ||
|
|
759517316a | ||
|
|
3e3024d47e | ||
|
|
517cb77637 | ||
|
|
14bd89a991 | ||
|
|
304de29924 | ||
|
|
ab3055150f | ||
|
|
6b9c7aa9c5 | ||
|
|
040f47b59c | ||
|
|
eac7834083 | ||
|
|
135a298080 | ||
|
|
0065a86371 | ||
|
|
2a842a2f50 | ||
|
|
231c02e00e | ||
|
|
4de4587ea6 | ||
|
|
8675e1d13f | ||
|
|
6ceacc68cc | ||
|
|
4aacf134b7 | ||
|
|
0605772715 | ||
|
|
3fa53556f4 | ||
|
|
76510b8971 | ||
|
|
66162966b9 | ||
|
|
71e1571c39 | ||
|
|
806b761e74 | ||
|
|
0176b38958 | ||
|
|
7a180c7310 | ||
|
|
3e1120182c | ||
|
|
8e86ce671c | ||
|
|
f75a324030 | ||
|
|
3fabff93f6 | ||
|
|
e74efc4e76 | ||
|
|
472ed0753d | ||
|
|
67538ff60c | ||
|
|
ae8bd69106 | ||
|
|
2538890b52 | ||
|
|
87dd819ae4 | ||
|
|
7ec560d4a2 | ||
|
|
6f9cd6a16b | ||
|
|
923af88336 | ||
|
|
5b6667c461 | ||
|
|
6f00740f67 | ||
|
|
248863cf16 | ||
|
|
97d48823dd | ||
|
|
5eb41e1a15 | ||
|
|
4a4837d9f5 | ||
|
|
4ad72fab7b | ||
|
|
fe68e45609 | ||
|
|
291b9a84ef | ||
|
|
2f9b7b188a | ||
|
|
d04d41bc23 | ||
|
|
6cb3d7167f | ||
|
|
90b1659a18 | ||
|
|
1aaf44f9b0 | ||
|
|
d7cfb84351 | ||
|
|
d28cf0b76d | ||
|
|
b4a3236284 | ||
|
|
556168892d | ||
|
|
77667be570 | ||
|
|
f48a912287 | ||
|
|
af30d0831d | ||
|
|
5989eb8f6e | ||
|
|
2bb778834b | ||
|
|
a5ce191e4d | ||
|
|
7617756576 | ||
|
|
0dfd3a5b0e | ||
|
|
61a54f48c5 | ||
|
|
5ca0237e34 | ||
|
|
ab1207e461 | ||
|
|
75fea4f7c0 | ||
|
|
fb34eb5394 | ||
|
|
41101217c6 | ||
|
|
19b1f5004a | ||
|
|
75fcd88f73 | ||
|
|
c21ce45d70 | ||
|
|
9f10cb2899 | ||
|
|
aa0c621223 | ||
|
|
f1dfb9051c | ||
|
|
932e58531d | ||
|
|
35a75e1066 | ||
|
|
522713473d | ||
|
|
3d6e268d15 | ||
|
|
cf8129dcbb | ||
|
|
3985cca8cc | ||
|
|
86ab65ef66 | ||
|
|
9db9616a43 | ||
|
|
39fd6f054b | ||
|
|
9b5f6ceca8 | ||
|
|
746da1a76e | ||
|
|
0a2d0da36f | ||
|
|
0076455e6e | ||
|
|
b674a0ed88 | ||
|
|
90dba34ecc | ||
|
|
01b40b26f5 | ||
|
|
c0805a285e | ||
|
|
ac76870d67 | ||
|
|
7160f50322 | ||
|
|
ba39f2595c | ||
|
|
38688b7065 | ||
|
|
9ef3218bb5 | ||
|
|
6f14e127a3 | ||
|
|
39890b319a | ||
|
|
2d8f166e4a | ||
|
|
d62af76097 | ||
|
|
b39ca51d41 | ||
|
|
2414ddd360 | ||
|
|
bed959f1cd | ||
|
|
a3f3470137 | ||
|
|
b7ec1d7d65 | ||
|
|
7e37068fc0 | ||
|
|
d049acad70 | ||
|
|
07044680d4 | ||
|
|
847b3fe54f | ||
|
|
7e9f0cc07a | ||
|
|
392c1650db | ||
|
|
0432c9bef3 | ||
|
|
e5e10ade72 | ||
|
|
ee14d5caf5 | ||
|
|
5a3457c180 | ||
|
|
c26002426f | ||
|
|
d48af9cea4 | ||
|
|
b34ab8a128 | ||
|
|
fd74e07fc8 | ||
|
|
3ab38e69fc | ||
|
|
d15e1bcc7d | ||
|
|
701df4b1ad | ||
|
|
7712a9afac | ||
|
|
eb8f760dca | ||
|
|
9940aa68e7 | ||
|
|
878db64878 | ||
|
|
56161e8e0d | ||
|
|
9c33689c11 | ||
|
|
6eff24369b | ||
|
|
81f7dd9a1d | ||
|
|
3d432d025a | ||
|
|
61facbb871 | ||
|
|
aba86dffd7 | ||
|
|
316f855280 | ||
|
|
77dad42f97 | ||
|
|
c35c2fe1ee | ||
|
|
f8d7c5209e | ||
|
|
88b3005589 | ||
|
|
952d335789 | ||
|
|
f8506f9d20 | ||
|
|
e4279bf257 | ||
|
|
b941f35812 | ||
|
|
619d395331 | ||
|
|
82a01b4483 | ||
|
|
c762195c8a | ||
|
|
054bebb081 | ||
|
|
8a93f1fc0c | ||
|
|
833e45155d | ||
|
|
e60ec6c47e | ||
|
|
61864970c1 | ||
|
|
b7c9810461 | ||
|
|
f097b80c35 | ||
|
|
662b191dfd | ||
|
|
c26407fa7e | ||
|
|
d973b87f5f | ||
|
|
5675ec8c02 | ||
|
|
1cafa02ba5 | ||
|
|
c66f250cd9 | ||
|
|
fcb2e10097 | ||
|
|
e17f1935d2 | ||
|
|
d2a4dbf283 | ||
|
|
04622a9e3b | ||
|
|
a3353aac0c | ||
|
|
a378b5108e | ||
|
|
d18de24cf9 | ||
|
|
46909dca85 | ||
|
|
b4b1c671bd | ||
|
|
bd9a1dbaf3 | ||
|
|
f2228cec7b | ||
|
|
a8ee779b31 | ||
|
|
971d7f703d | ||
|
|
bfc20ef219 | ||
|
|
20e1cd6d6b | ||
|
|
a020bc872d | ||
|
|
0a040a0531 | ||
|
|
305bf18819 | ||
|
|
f6e888ecf9 | ||
|
|
2a7e7e978b | ||
|
|
ef7a41f896 | ||
|
|
b57b4f9bd3 | ||
|
|
469e404725 | ||
|
|
35d9e98e83 | ||
|
|
5b6406d09d | ||
|
|
6aa6f4c8a2 | ||
|
|
0d56d3a555 | ||
|
|
331b85c57e | ||
|
|
7f9afa9c46 | ||
|
|
f3a25939db | ||
|
|
adc56d5877 | ||
|
|
766fa85c4c | ||
|
|
4a20abcbfa | ||
|
|
7e4b1a8f8f | ||
|
|
e9bd1f88c0 | ||
|
|
9c393ff6bc | ||
|
|
d0a85713d2 | ||
|
|
c7fc00095c | ||
|
|
97e2a5d30b | ||
|
|
e7d019028a | ||
|
|
8d8dcb1a3e | ||
|
|
802ef03013 | ||
|
|
98d057a2ac | ||
|
|
9321cc3de8 | ||
|
|
fd89735521 | ||
|
|
8deeb59d5c | ||
|
|
5c5e1c8b3c | ||
|
|
ef065d480a | ||
|
|
6cd29ad7e4 | ||
|
|
341f64839b | ||
|
|
54df58d44b | ||
|
|
1de2c222b4 | ||
|
|
e2121177e2 | ||
|
|
e4849cc0ac | ||
|
|
8604364846 | ||
|
|
dbd09edb51 | ||
|
|
ae6cc21f10 | ||
|
|
62247f4601 | ||
|
|
7b8657367c | ||
|
|
329ab73e1b | ||
|
|
ce749dad30 | ||
|
|
87e0f79d02 | ||
|
|
8b128c1bbe | ||
|
|
2f613aed26 | ||
|
|
e9ba295a1e | ||
|
|
48ad40dea2 | ||
|
|
9f4342bd19 | ||
|
|
a96fb46296 | ||
|
|
c6b6e841b6 | ||
|
|
cbefbb7927 | ||
|
|
cac59e4873 | ||
|
|
e714e87ad6 | ||
|
|
f3f8a62a18 | ||
|
|
5bda8a426c | ||
|
|
d93fa7f752 | ||
|
|
a0b2868e95 | ||
|
|
3cc1731c12 | ||
|
|
4c88944286 | ||
|
|
6c955424cd | ||
|
|
5570cea6b1 | ||
|
|
7bfeb6c177 | ||
|
|
41e8683307 | ||
|
|
18d26904e2 | ||
|
|
712151aa80 | ||
|
|
f38a9f36a1 | ||
|
|
a6d9f2d636 | ||
|
|
4b6a1c1c5e | ||
|
|
112256ca01 | ||
|
|
49733c239b | ||
|
|
81fee02e8f | ||
|
|
ef0393bed6 | ||
|
|
9ab0a83eb3 | ||
|
|
a9e3b2f625 | ||
|
|
37d645c00e | ||
|
|
4f98cef7cb | ||
|
|
1342634907 | ||
|
|
d757134ced | ||
|
|
4fc3ada42e | ||
|
|
67eaf8c70d | ||
|
|
cf0dbef3b5 | ||
|
|
e4d3ecb997 | ||
|
|
48586d953b | ||
|
|
0487b2fcb3 | ||
|
|
c97efda49b | ||
|
|
c1a0fbc66f | ||
|
|
c2a8da4551 | ||
|
|
9d8251f1b9 | ||
|
|
63a275c6e5 | ||
|
|
cb0939acf3 | ||
|
|
a44b1544b4 | ||
|
|
4fc8821cba | ||
|
|
bfe55c12a7 | ||
|
|
9c5dce925e | ||
|
|
b10ce8ea2d | ||
|
|
55d5b1e8da | ||
|
|
b370826624 | ||
|
|
4daa10d38b | ||
|
|
fed35d9c13 | ||
|
|
80af200c9f | ||
|
|
ba18c589f0 | ||
|
|
47d37c6047 | ||
|
|
63accc552a | ||
|
|
f7ed98d6b7 | ||
|
|
019a1fd448 | ||
|
|
3293979381 | ||
|
|
0bade9add3 | ||
|
|
f973744ff6 | ||
|
|
a830e0f59c | ||
|
|
2165528640 | ||
|
|
c43b403820 | ||
|
|
78e7841479 | ||
|
|
9c97690edd | ||
|
|
2262b99fb4 | ||
|
|
bc89483d47 | ||
|
|
1dc3c77bc9 | ||
|
|
d74ba03cd9 | ||
|
|
41832dcb4f | ||
|
|
bf3d22a4c8 | ||
|
|
1f445965e7 | ||
|
|
f6d27d3ba7 | ||
|
|
3da73b9d0d | ||
|
|
563d9e221f | ||
|
|
3b8fb073f4 | ||
|
|
97231bb477 | ||
|
|
c44319251a | ||
|
|
13a08ebbe1 | ||
|
|
697bbe29bc | ||
|
|
3d8024b0d9 | ||
|
|
87fb4f9dd2 | ||
|
|
59ff3a794d | ||
|
|
6701e07335 | ||
|
|
756dc08387 | ||
|
|
1304631aad | ||
|
|
37646bcfa0 | ||
|
|
4488f98900 | ||
|
|
7ce1dc0d48 | ||
|
|
daec4ac9b6 | ||
|
|
ee211aba51 | ||
|
|
6aa7cbade0 | ||
|
|
d8adea734f | ||
|
|
fa2f34ba3c | ||
|
|
ad04bdc99c | ||
|
|
448fa200a4 | ||
|
|
3af354b343 | ||
|
|
33cae2c222 | ||
|
|
d5d671bac1 | ||
|
|
cfe9aed42a | ||
|
|
0d5c4b83bb | ||
|
|
6dad4ce38b | ||
|
|
9ab7af44cb | ||
|
|
d10a46f8dd | ||
|
|
7032fd782e | ||
|
|
12cd01783b | ||
|
|
6951aaaeb9 | ||
|
|
ce7ffc8c43 | ||
|
|
065e6c55b1 | ||
|
|
6aad65f55c | ||
|
|
7a954a288d | ||
|
|
c1c2afd47d | ||
|
|
1763e0341d | ||
|
|
d0af38fb14 | ||
|
|
cfc2405596 | ||
|
|
4a1378debd | ||
|
|
d4976c6eb6 | ||
|
|
43583d7777 | ||
|
|
5c797667bb | ||
|
|
1a9af245c1 | ||
|
|
3bdea4c931 | ||
|
|
9f0ca1cc2e | ||
|
|
f648ab49f7 | ||
|
|
de759290e5 | ||
|
|
8910d5a65d | ||
|
|
bb3a1d2f16 | ||
|
|
6d2b88c31f | ||
|
|
f7dd110a49 | ||
|
|
631968ee5b | ||
|
|
ace5866024 | ||
|
|
ad2287f9eb | ||
|
|
f6db5968eb | ||
|
|
6946ae9397 | ||
|
|
7299c6b81d | ||
|
|
42504f0936 | ||
|
|
b94a50e720 | ||
|
|
448182497f | ||
|
|
7bcbfc13b0 | ||
|
|
716c9aa860 | ||
|
|
c407306db5 | ||
|
|
897525657f | ||
|
|
5bab5bb8c1 | ||
|
|
66f612e6b4 | ||
|
|
9d6557bacb | ||
|
|
a4c5105d78 | ||
|
|
3d8e6f2215 | ||
|
|
9ea8eb29df | ||
|
|
8f3ef2b1c5 | ||
|
|
ab1ea30dec | ||
|
|
3f28d19e06 | ||
|
|
b948457115 | ||
|
|
66af4d98ce | ||
|
|
6067e5118f | ||
|
|
587072c135 | ||
|
|
c69f95dea1 | ||
|
|
45c8b331de | ||
|
|
a0e229a489 | ||
|
|
0123ae97a1 | ||
|
|
793cae1dfa | ||
|
|
3589b92ec9 | ||
|
|
7456fc1ac7 | ||
|
|
8daad8f8b8 | ||
|
|
c4c2e81701 | ||
|
|
2c3ddfd363 | ||
|
|
2b8467e6a1 | ||
|
|
66dc9f2560 | ||
|
|
05c9126184 | ||
|
|
ca9ac9f92b | ||
|
|
3b502005e9 | ||
|
|
f828cd813b | ||
|
|
c9c56c915c | ||
|
|
b68aabb2c9 | ||
|
|
c91a3f1efc | ||
|
|
36c30c3016 | ||
|
|
0fc8aa9698 | ||
|
|
58236e0840 | ||
|
|
719d3114da | ||
|
|
be27f7d403 | ||
|
|
42d12e36e5 | ||
|
|
0d45d9980c | ||
|
|
f46493e885 | ||
|
|
c580375af2 | ||
|
|
5211523af4 | ||
|
|
cbfc490d95 | ||
|
|
890f0819ad | ||
|
|
8dcb790d12 | ||
|
|
7b386119ee | ||
|
|
e91fcfb244 | ||
|
|
cdf6e600bd | ||
|
|
766a3d490a | ||
|
|
a489d95ea1 | ||
|
|
fc039361b6 | ||
|
|
01808d3e71 | ||
|
|
ea362131b7 | ||
|
|
624323151e | ||
|
|
1540493b29 | ||
|
|
38cb3fddd9 | ||
|
|
8b6598ea6b | ||
|
|
fe51f8fbf7 | ||
|
|
1f3a7758c4 | ||
|
|
aa3dbdfaa2 | ||
|
|
f79b3841c7 | ||
|
|
384ab2dd18 | ||
|
|
8719fbaff7 | ||
|
|
bcdab8da1c | ||
|
|
2c9c9a86f4 | ||
|
|
4698ab9722 | ||
|
|
8f0cd69cd3 | ||
|
|
767824f2f7 | ||
|
|
f43ca07f33 | ||
|
|
70fa57d8c2 | ||
|
|
4df928a660 | ||
|
|
7ce31c77c5 | ||
|
|
35b9ce4ff0 | ||
|
|
1584a48b11 | ||
|
|
1bbf79fe20 | ||
|
|
3f3036961e | ||
|
|
0d05623bd0 | ||
|
|
eca9f60d7d | ||
|
|
8dbe3cfe0c | ||
|
|
cafe9019c1 | ||
|
|
5252a085c3 | ||
|
|
dfe183d4fc | ||
|
|
ff4eed5342 | ||
|
|
eaf31eb946 | ||
|
|
87547d5622 | ||
|
|
ecd16de53f | ||
|
|
9e66e7a3e1 | ||
|
|
76bd4ae8ef | ||
|
|
c701fbad32 | ||
|
|
e4704fb7e6 | ||
|
|
a4177ca0ec | ||
|
|
0f7512a394 | ||
|
|
309ea0e4d2 | ||
|
|
bc79e142e5 | ||
|
|
6626795a99 | ||
|
|
169dc3c2d1 | ||
|
|
0496a32e1d | ||
|
|
07ada6867e | ||
|
|
f5e0d0e032 | ||
|
|
063945d9c5 | ||
|
|
1477e9c35c | ||
|
|
6077a1c448 | ||
|
|
118e4fd089 | ||
|
|
18fde01ed5 | ||
|
|
3e18f0f238 | ||
|
|
0c4a9c6fad | ||
|
|
ef920088b1 | ||
|
|
bae8098974 | ||
|
|
2ae67b8ca9 | ||
|
|
3da58ed2b8 | ||
|
|
d96cae2fff | ||
|
|
7ef4d8dccb | ||
|
|
fa1fca7dbd | ||
|
|
3476d6087d | ||
|
|
d80b0f77c5 | ||
|
|
19a83bb2c0 | ||
|
|
a97d22b81b | ||
|
|
b097842d01 | ||
|
|
127d42d873 | ||
|
|
9767ca47ff | ||
|
|
0add4155ab | ||
|
|
b3cdd7df3c | ||
|
|
63d268c77e | ||
|
|
d650f61845 | ||
|
|
f749b11751 | ||
|
|
815fa538da | ||
|
|
d01486ce42 | ||
|
|
b2c3c9069f | ||
|
|
88d1028950 | ||
|
|
e37260f809 | ||
|
|
d029029043 | ||
|
|
eb9c65d765 | ||
|
|
5286aa2b1d | ||
|
|
91fb7b09bf | ||
|
|
1d9b006db7 | ||
|
|
774b8d987b | ||
|
|
5abe308a97 | ||
|
|
d67d72987a | ||
|
|
1fa077fc8a | ||
|
|
881bae0a15 | ||
|
|
0f18fbc24c | ||
|
|
6e084db3d9 | ||
|
|
9c055f2149 | ||
|
|
e73d86255b | ||
|
|
bbcabc8e71 | ||
|
|
50bac2c056 | ||
|
|
2c3682cc26 | ||
|
|
5d864f5888 | ||
|
|
05ee63cc83 | ||
|
|
0fb8d74077 | ||
|
|
c2a5b6d7f6 | ||
|
|
e2a5b5706e | ||
|
|
82c786517e | ||
|
|
ca7219f3cd | ||
|
|
17d72089ce | ||
|
|
a22ed4132a | ||
|
|
f8a39803e6 | ||
|
|
d9e37b37bb | ||
|
|
5f5dde6565 | ||
|
|
b46410d705 | ||
|
|
f11a89aec7 | ||
|
|
2014183e88 | ||
|
|
63a2e69cc6 | ||
|
|
fe189da30f | ||
|
|
6a599c53d7 | ||
|
|
6b82a9ef11 | ||
|
|
b82648ef5b | ||
|
|
e5aad4d170 | ||
|
|
0f28acac00 | ||
|
|
77321172a2 | ||
|
|
39d37010a3 | ||
|
|
ea5c1b9e12 | ||
|
|
4a96b4b622 | ||
|
|
8946a8bde2 | ||
|
|
6e094eaf42 | ||
|
|
0aa816b4f2 | ||
|
|
c8f70a4e3b | ||
|
|
7138eb4b69 | ||
|
|
2d9db6d9cb | ||
|
|
cd625ba1e1 | ||
|
|
7341e2586d | ||
|
|
eae167f8bd | ||
|
|
4c40eedffb | ||
|
|
8991de2610 | ||
|
|
25870dadd8 | ||
|
|
5b3a3edcd0 | ||
|
|
327b7769f3 | ||
|
|
59f631e1d4 | ||
|
|
c953482ba9 | ||
|
|
232d2ccf79 | ||
|
|
40bab90946 | ||
|
|
3d4e985aa6 | ||
|
|
ca777384eb | ||
|
|
726d46c3c1 | ||
|
|
0f50d1accd | ||
|
|
6e72889294 | ||
|
|
108c790cf4 | ||
|
|
bdb19dae89 | ||
|
|
d103a01c53 | ||
|
|
c9ed94bcc5 | ||
|
|
84a5099dd6 | ||
|
|
820ae61c20 | ||
|
|
2f8ebecdb2 | ||
|
|
abdaeb10c5 | ||
|
|
950c23aa5b | ||
|
|
4ddc65d0c5 | ||
|
|
820e432f92 | ||
|
|
97b336bff4 | ||
|
|
b85a0c4d09 | ||
|
|
1d2c4fc553 | ||
|
|
0bd7d99dd9 | ||
|
|
e0227e7d1c | ||
|
|
e59cad8265 | ||
|
|
ac7a00bf3f | ||
|
|
86e59c8c41 | ||
|
|
d4f642ed2b | ||
|
|
948ec522b0 | ||
|
|
19cefd9718 | ||
|
|
aac5177495 | ||
|
|
6b2ff63255 | ||
|
|
e517afc306 | ||
|
|
2b5bf56153 | ||
|
|
0e26731b75 | ||
|
|
6e3cc9d1f9 | ||
|
|
bb0554c727 | ||
|
|
15af5e5b37 | ||
|
|
d066b2086b | ||
|
|
6192bd2cc4 | ||
|
|
89614fc047 | ||
|
|
8bf1b9e707 | ||
|
|
c5a40926e3 | ||
|
|
6f50f857c6 | ||
|
|
1a101bfda4 | ||
|
|
4eb89414bb | ||
|
|
afaab39d88 | ||
|
|
c81ec556db | ||
|
|
fde7790680 | ||
|
|
81de9ff39e | ||
|
|
c90171fd5c | ||
|
|
4e7ef94543 | ||
|
|
c97656d45e | ||
|
|
ac424ce529 | ||
|
|
e872dc8edb | ||
|
|
604f6832da | ||
|
|
b7dffc8594 | ||
|
|
d97edbcac3 | ||
|
|
abafef3ad8 | ||
|
|
ca4bd779f2 | ||
|
|
a672d4dbdd | ||
|
|
570fb8f4c3 | ||
|
|
236b35c93e | ||
|
|
a415c10394 | ||
|
|
4044f28167 | ||
|
|
353465cba7 | ||
|
|
aeefc34ae4 | ||
|
|
9e465f6a0d | ||
|
|
a4c5752a0c | ||
|
|
cf0cf13a68 | ||
|
|
80c63e5701 | ||
|
|
3ba9f399b8 | ||
|
|
18c69d731c | ||
|
|
af9240240d | ||
|
|
ec867335d5 | ||
|
|
f23a760aac | ||
|
|
45ea53b9ba | ||
|
|
e9a2310f62 | ||
|
|
62a214920d | ||
|
|
b9057d05a1 | ||
|
|
f93b643e43 | ||
|
|
f5da79b690 | ||
|
|
56abe0e5a5 | ||
|
|
2445c0e464 | ||
|
|
3d76be07b2 | ||
|
|
d2b0823cd0 | ||
|
|
ea2a13dff2 | ||
|
|
bbd6b68eae | ||
|
|
89ba10ab4e | ||
|
|
2834d7f342 | ||
|
|
acaacec82d | ||
|
|
971132c2ba | ||
|
|
af15b561ab | ||
|
|
302ebb0082 | ||
|
|
41d366d27e | ||
|
|
f766600fd8 | ||
|
|
5a1a33242c | ||
|
|
58c93d99a9 | ||
|
|
64101b3b06 | ||
|
|
167379b0e0 | ||
|
|
a0306f3951 | ||
|
|
a36ab6486d | ||
|
|
b07f2c486c | ||
|
|
838d6f1981 | ||
|
|
6e7ee0ca48 | ||
|
|
8677b1d85d | ||
|
|
aea6bced69 | ||
|
|
8e819485e4 | ||
|
|
c526e383b4 | ||
|
|
c89ea2a1f0 | ||
|
|
ee515ff940 | ||
|
|
c023fdae8b | ||
|
|
a044354294 | ||
|
|
1dbd1065f9 | ||
|
|
c1bafccac1 | ||
|
|
f5bf07a7c3 | ||
|
|
e00ccf65cf | ||
|
|
8f7fd4295e | ||
|
|
ecf0b30513 | ||
|
|
828ecc6803 | ||
|
|
13fda50aac | ||
|
|
c4397adfce | ||
|
|
5932ef09e3 | ||
|
|
cd5655bd3f | ||
|
|
203c19c01f | ||
|
|
d4c35dc6c8 | ||
|
|
b58a3bf209 | ||
|
|
f2e91f97ed | ||
|
|
c350018716 | ||
|
|
5f104c58f2 | ||
|
|
6c71d168c4 | ||
|
|
4e7bfc12aa | ||
|
|
14d7e9e7f9 | ||
|
|
8e5adb47a0 | ||
|
|
3b7456a561 | ||
|
|
70466bb9b5 | ||
|
|
62da6173e2 | ||
|
|
6f1f7bf0f3 | ||
|
|
5b59fb38a4 | ||
|
|
03d224b708 | ||
|
|
3b636ef022 | ||
|
|
5c069d3c3c | ||
|
|
ae144588cc | ||
|
|
3565dd9c56 | ||
|
|
339d94e300 | ||
|
|
4156ba8908 | ||
|
|
ab6d361e64 | ||
|
|
b14612aba5 | ||
|
|
38a45d9816 | ||
|
|
8651d02651 | ||
|
|
28cf89627d | ||
|
|
846e74b8b5 | ||
|
|
99be766e90 | ||
|
|
ec233162e9 | ||
|
|
9bbf25b9f4 | ||
|
|
dedcc80501 | ||
|
|
ce265e1f91 | ||
|
|
0072343e0f | ||
|
|
a275ac5f41 | ||
|
|
752c86d8f7 | ||
|
|
57c64d0b86 | ||
|
|
d93dcd2a6c | ||
|
|
c44d7de143 | ||
|
|
9cff545c99 | ||
|
|
e54369334b | ||
|
|
ea62d25fa3 | ||
|
|
5fce4a2e2a | ||
|
|
7fd224b44a | ||
|
|
e635dc4316 | ||
|
|
3575972c43 | ||
|
|
114647810b | ||
|
|
6910f9c5c7 | ||
|
|
43a4b1c9cb | ||
|
|
bdd4a24567 | ||
|
|
c47b0ddcc1 | ||
|
|
6671f11a91 | ||
|
|
ec3ae7f6de | ||
|
|
845a8e5076 | ||
|
|
86afa200cd | ||
|
|
35f8911b1b | ||
|
|
3ba4a5cfb5 | ||
|
|
25815988d9 | ||
|
|
ee084b0bd4 | ||
|
|
931664c0c0 | ||
|
|
d6dc540236 | ||
|
|
0731b1fe6e | ||
|
|
9404071c95 | ||
|
|
a1483a4111 | ||
|
|
19e1f60d69 | ||
|
|
5778152e95 | ||
|
|
4a5ee9342e | ||
|
|
7b9d1284aa | ||
|
|
4b8cdc03a9 | ||
|
|
2191e4cdcc | ||
|
|
ea7d147d79 | ||
|
|
70d032ff23 | ||
|
|
8e1c6d2bd2 | ||
|
|
3a448a6ffc | ||
|
|
9275b3edd7 | ||
|
|
27e867e6ad | ||
|
|
946758e219 | ||
|
|
fe6ecd465e | ||
|
|
775ee5d27a | ||
|
|
c41bc8dac2 | ||
|
|
c659683d92 | ||
|
|
e122ee827c | ||
|
|
b5b36a8eb1 | ||
|
|
d488df0827 | ||
|
|
d64ec4b45d | ||
|
|
a9617bba3d | ||
|
|
be6c415a4e | ||
|
|
7721bc6dce | ||
|
|
3e69b47d76 | ||
|
|
f63036454e | ||
|
|
48e2ce2ca4 | ||
|
|
3b43b7aa65 | ||
|
|
815657f0c6 | ||
|
|
c931226f23 | ||
|
|
112082b643 | ||
|
|
e69040c799 | ||
|
|
2ae57ae1f1 | ||
|
|
69e5e3f461 | ||
|
|
6a10b8c127 | ||
|
|
76bf601e1b | ||
|
|
19ad184cd6 | ||
|
|
db92dc3636 | ||
|
|
bba8b25b48 | ||
|
|
ec3fe284b6 | ||
|
|
d3bf1137d7 | ||
|
|
d77d32853f | ||
|
|
dfecf2cc60 | ||
|
|
a37f748639 | ||
|
|
f421bcb2c9 | ||
|
|
e6726b0690 | ||
|
|
5ed91c05bf | ||
|
|
982f5beaf5 | ||
|
|
117ba360ac | ||
|
|
631b61e11c | ||
|
|
80e915c015 | ||
|
|
26da4eadb0 | ||
|
|
a7ff6e157d | ||
|
|
cefb9e825d | ||
|
|
96b9f8213c | ||
|
|
dcc1c72882 | ||
|
|
7fcf75829a | ||
|
|
539f82eb08 | ||
|
|
23f58b8c13 | ||
|
|
1e98cddaa6 | ||
|
|
c4a4801414 | ||
|
|
d5b332fc59 | ||
|
|
ec0e560bac | ||
|
|
69811e26cd | ||
|
|
32b7a1ffcd | ||
|
|
b8b0d2243f | ||
|
|
135f9ab048 | ||
|
|
ac976d0f3a | ||
|
|
3156d97969 | ||
|
|
a0a03f386a | ||
|
|
34b162e5d3 | ||
|
|
186402168f | ||
|
|
c3b1c493a1 | ||
|
|
4a97b762be | ||
|
|
58596632b3 | ||
|
|
f2a70a0e86 | ||
|
|
5d5d856155 | ||
|
|
d5c1e1d3f4 | ||
|
|
0aff193e17 | ||
|
|
35d6881336 | ||
|
|
48df84fb19 | ||
|
|
abf778ce86 | ||
|
|
aeacd7cf34 | ||
|
|
612460ca16 | ||
|
|
906b4ce158 | ||
|
|
59d69587dd | ||
|
|
c420f89d48 | ||
|
|
34a935f757 | ||
|
|
4eb1c0412b | ||
|
|
c295a3e90b | ||
|
|
cc51b1d394 | ||
|
|
7c13a92d9a | ||
|
|
499101ae9c | ||
|
|
7fe50a9a1f | ||
|
|
b69410697d | ||
|
|
e174e2f68f | ||
|
|
1ad7d885b3 | ||
|
|
8bfdcdbf44 | ||
|
|
97572f158c | ||
|
|
581930d8c6 | ||
|
|
ad671cfcf1 | ||
|
|
1f5345dfdb | ||
|
|
3e475d87eb | ||
|
|
476abd30f4 | ||
|
|
48762724eb | ||
|
|
124e5d94ed | ||
|
|
6f3f31b872 | ||
|
|
c3cb5c7356 | ||
|
|
218e5ecb35 | ||
|
|
a3517d28fa | ||
|
|
a6fd3f102d | ||
|
|
abce04805f | ||
|
|
e20cdd83f8 | ||
|
|
fd02219c83 | ||
|
|
4403147485 | ||
|
|
22028e23e3 | ||
|
|
ac0c88f486 | ||
|
|
f6c7343b70 | ||
|
|
05bd6cb180 | ||
|
|
f50e850774 | ||
|
|
83392dd8dc | ||
|
|
aab29f9077 | ||
|
|
7c884797d1 | ||
|
|
79a850f3b9 | ||
|
|
ce9fb38055 | ||
|
|
85af1b4ffb | ||
|
|
00a8c847e9 | ||
|
|
e7763f3b73 | ||
|
|
f723b504a1 | ||
|
|
910e8279a0 | ||
|
|
58d51dd128 | ||
|
|
7fd93ab556 | ||
|
|
23cea9a130 | ||
|
|
53e2960ffd | ||
|
|
4706414e26 | ||
|
|
8868765ea5 | ||
|
|
a11dc20d71 | ||
|
|
1a9f360132 | ||
|
|
77c86400c0 | ||
|
|
4ebae8dcf6 | ||
|
|
7377f80bdd | ||
|
|
4c999c3ff2 | ||
|
|
906d77e0cc | ||
|
|
1d2b5d5b15 | ||
|
|
7ea73c3db2 | ||
|
|
c579dcfb53 | ||
|
|
758a24be5b | ||
|
|
4519070995 | ||
|
|
8df0be52bf | ||
|
|
45889b8b9f | ||
|
|
9f19e9161f | ||
|
|
19137ad705 | ||
|
|
3673117580 | ||
|
|
321087cb6d | ||
|
|
2fb8d928e5 | ||
|
|
ebba7f7ecb | ||
|
|
b733865cb5 | ||
|
|
bc6feed17f | ||
|
|
a7c4e06bc3 | ||
|
|
befc51beec | ||
|
|
8ab185369e | ||
|
|
0730a88e63 | ||
|
|
257af96b97 | ||
|
|
a623b18947 | ||
|
|
803b8f5292 | ||
|
|
c98d756e6b | ||
|
|
28601437e4 | ||
|
|
98983bb604 | ||
|
|
4435563f1e | ||
|
|
d51167d770 | ||
|
|
f5c1533309 | ||
|
|
d1b266a361 | ||
|
|
91ba7a5704 | ||
|
|
227eebc5f0 | ||
|
|
9e06058b59 | ||
|
|
8032dd08bc | ||
|
|
540edea866 | ||
|
|
f83688f804 | ||
|
|
c0c9dacac6 | ||
|
|
4d69094a1f | ||
|
|
9fd62b4437 | ||
|
|
908d3fbf98 | ||
|
|
f12ad10fce | ||
|
|
4ff73893cb | ||
|
|
fd7a01b923 | ||
|
|
30f597c563 | ||
|
|
c44a9afdeb | ||
|
|
2487dde69e | ||
|
|
2c68eed072 | ||
|
|
9f32730714 | ||
|
|
f4210e39f2 | ||
|
|
cc1c08786f | ||
|
|
3e9e1e94d6 | ||
|
|
9051214278 | ||
|
|
6135c139da | ||
|
|
dd51b002b8 | ||
|
|
18a2d0bd90 | ||
|
|
50316c9cf6 | ||
|
|
07c952d75b | ||
|
|
85a311b3ae | ||
|
|
71a264ba96 | ||
|
|
1e187560cd | ||
|
|
f3f9aa96ca | ||
|
|
706088b9b3 | ||
|
|
de533cbf16 | ||
|
|
f832f11589 | ||
|
|
caa005a6ed | ||
|
|
9719682199 | ||
|
|
570461a45f | ||
|
|
9d6c93768d | ||
|
|
97f0f34209 | ||
|
|
ffa8f40864 | ||
|
|
ab588d37c0 | ||
|
|
2e8b1134b9 | ||
|
|
f61a67279a | ||
|
|
d947175e4b | ||
|
|
c3f13b54c1 | ||
|
|
ec87b99121 | ||
|
|
d6c725ea83 | ||
|
|
dea324b512 | ||
|
|
46a543441f | ||
|
|
14919980c2 | ||
|
|
1ba57ef6c3 | ||
|
|
ff5ff7f310 | ||
|
|
06e00ffccb | ||
|
|
14be2c1ebf | ||
|
|
4ecf1edb25 | ||
|
|
4c3c546c2f | ||
|
|
9b26a2a859 | ||
|
|
568fec3708 | ||
|
|
310cac8233 | ||
|
|
bb694cbffd | ||
|
|
c8434acd0d | ||
|
|
09ab64105d | ||
|
|
2f91b42a7f | ||
|
|
f5741a2a89 | ||
|
|
1905ed0dc6 | ||
|
|
408236b6b1 | ||
|
|
38315492a2 | ||
|
|
6fd54561ca | ||
|
|
3327e6b584 | ||
|
|
9b59e88214 | ||
|
|
f79583a394 | ||
|
|
928ca44f7d | ||
|
|
514976e9e5 | ||
|
|
94b6c0c49e | ||
|
|
7954b892bc | ||
|
|
f784cdfc68 | ||
|
|
02edfaf54b | ||
|
|
01ec1ed563 | ||
|
|
174fc76fb8 | ||
|
|
040e5a5198 | ||
|
|
135eb7a11e | ||
|
|
b3657dfe2b | ||
|
|
e6f0059e5e | ||
|
|
dea1164b1d | ||
|
|
2d17c15b71 | ||
|
|
302f224bc0 | ||
|
|
91e4280f6b | ||
|
|
2a8d603f98 | ||
|
|
33572bd13b | ||
|
|
b68600582c | ||
|
|
ecfbdc4870 | ||
|
|
259f41bb8a | ||
|
|
a30c763e09 | ||
|
|
fdcd3c7515 | ||
|
|
27172740e9 | ||
|
|
13fbdc2340 | ||
|
|
940cb3c000 | ||
|
|
829a45f410 | ||
|
|
026f953961 | ||
|
|
a642cc2470 | ||
|
|
9f4a7384b2 | ||
|
|
cd31ec8db1 | ||
|
|
304087a24d | ||
|
|
8c94bcb27b | ||
|
|
f246d06629 | ||
|
|
c29115146d | ||
|
|
2d422326bd | ||
|
|
ff3d0b29e3 | ||
|
|
5d89df554a | ||
|
|
e129e8b347 | ||
|
|
2d25d3848c | ||
|
|
1da3f871fa | ||
|
|
bb435b082e | ||
|
|
e052047b04 | ||
|
|
47d3ac3ef5 | ||
|
|
00ae88b6b6 | ||
|
|
e63b15cb67 | ||
|
|
8cc5276c9d | ||
|
|
ae0bb7727b | ||
|
|
04626eb1ee | ||
|
|
a852a15dab | ||
|
|
da2deb85c4 | ||
|
|
00d9983655 | ||
|
|
12ef88b90f | ||
|
|
19ec042a0a | ||
|
|
8019dc1267 | ||
|
|
da2f657342 | ||
|
|
f05ab87c08 | ||
|
|
f8c9915800 | ||
|
|
7f3e8bdad9 | ||
|
|
eb6dc9615c | ||
|
|
b9ffe3b204 | ||
|
|
3b191fa73e | ||
|
|
e2f527fc7b | ||
|
|
90424d9847 | ||
|
|
6205598b45 | ||
|
|
037861321d | ||
|
|
55e45a668f | ||
|
|
e822b85541 | ||
|
|
31b653f235 | ||
|
|
e11f8837a4 | ||
|
|
d70f9f9a46 | ||
|
|
b0f062ff0c | ||
|
|
0aa91b977d | ||
|
|
8a12813e1a | ||
|
|
3b95e840db | ||
|
|
845ca5c6b3 | ||
|
|
7c9506c96d | ||
|
|
7e47fdf52f | ||
|
|
1c259fe12e | ||
|
|
78c4344583 | ||
|
|
9019d1484e | ||
|
|
8e0c1027bb | ||
|
|
593f1acf10 | ||
|
|
4df66ebf00 | ||
|
|
0b248e9be4 | ||
|
|
01301c99df | ||
|
|
54441ddfde | ||
|
|
46c2d311e9 | ||
|
|
f4daffaa89 | ||
|
|
f7dd65d1e1 | ||
|
|
8d530d5f6f | ||
|
|
79f3b7c4b8 | ||
|
|
434ca04e00 | ||
|
|
e7592949f7 | ||
|
|
50bc6b0a48 | ||
|
|
7eeaf37873 | ||
|
|
653efb6983 | ||
|
|
1c87146a50 | ||
|
|
ca0a3974e4 | ||
|
|
e604abf47a | ||
|
|
f57684b024 | ||
|
|
9e326d15b9 | ||
|
|
789b699556 | ||
|
|
f7c615c958 | ||
|
|
26cfcd31f8 | ||
|
|
e47d493776 | ||
|
|
436d22e385 | ||
|
|
c1866f84e4 | ||
|
|
0d3b493a76 | ||
|
|
29fb40bd16 | ||
|
|
34c9265aa9 | ||
|
|
cf121062a1 | ||
|
|
83a7ef21f3 | ||
|
|
d0d33da493 | ||
|
|
be351232d3 | ||
|
|
e74c464857 | ||
|
|
1bb3117b05 | ||
|
|
3c4e5a080b | ||
|
|
6524fade3e | ||
|
|
97cd5ed537 | ||
|
|
d7c7494007 | ||
|
|
9a7a992495 | ||
|
|
8de996a368 | ||
|
|
232e907363 | ||
|
|
2d6af39ed0 | ||
|
|
bfdae4339c | ||
|
|
a613e9f113 | ||
|
|
75326f6626 | ||
|
|
a0da981ec7 | ||
|
|
315d35ca10 | ||
|
|
5c22dd35cf | ||
|
|
fed15ba072 | ||
|
|
0b44e7d6c7 | ||
|
|
722d0cbadc | ||
|
|
edd1719431 | ||
|
|
e8088e2a70 | ||
|
|
c4d39aced2 | ||
|
|
b40a5adeb0 | ||
|
|
558a900620 | ||
|
|
6b5e5a504d | ||
|
|
e44dca2464 | ||
|
|
e1f84b277a | ||
|
|
2518f46b08 | ||
|
|
01e18a9496 | ||
|
|
564ca709d3 | ||
|
|
a54a36ae18 | ||
|
|
43603b0961 | ||
|
|
96cd99f904 | ||
|
|
3438d10e25 | ||
|
|
022ccb42a1 | ||
|
|
e6d72e9f87 | ||
|
|
06e8a6af23 | ||
|
|
ac188d137a | ||
|
|
cae466745a | ||
|
|
d61f16dab0 | ||
|
|
02ba277a86 | ||
|
|
470ff49a02 | ||
|
|
04d741581d | ||
|
|
038f210148 | ||
|
|
2adad3a7bd | ||
|
|
05fb26a49b | ||
|
|
1c237affb4 | ||
|
|
3e81d7e9cb | ||
|
|
edb66620c1 | ||
|
|
04f7e8e777 | ||
|
|
eee201013c | ||
|
|
1190cb4ea1 | ||
|
|
507100ea0b | ||
|
|
9b13912b6d | ||
|
|
ee65deebfd | ||
|
|
ba9fa442d1 | ||
|
|
87da27f9bf | ||
|
|
b5bc5fe2c6 | ||
|
|
d2329360d0 | ||
|
|
7ece0ae10a | ||
|
|
f931b47eb8 | ||
|
|
7f7eb12ded | ||
|
|
c0940f7a19 | ||
|
|
9dfde11e35 | ||
|
|
6f15cc2dbc | ||
|
|
120308638f | ||
|
|
1d04ef99bb | ||
|
|
9b00d177ef | ||
|
|
884524c448 | ||
|
|
3ae1e7e87d | ||
|
|
81f885311d | ||
|
|
d9362f09d8 | ||
|
|
906d181d1b | ||
|
|
44b8812a7b | ||
|
|
3308c45e88 | ||
|
|
e530ecf9f9 | ||
|
|
51b5edb04f | ||
|
|
f0d89f850e | ||
|
|
b777e08542 | ||
|
|
2e485df530 | ||
|
|
3c37d22a6e | ||
|
|
08ab7a504a | ||
|
|
06563ef921 | ||
|
|
34f6210bc0 | ||
|
|
0bbde0c605 | ||
|
|
a8f24fd1b7 | ||
|
|
c3e0237696 | ||
|
|
bb6925920f | ||
|
|
63ec2a33ae | ||
|
|
c89a959fe8 | ||
|
|
150b50e0ba | ||
|
|
4ef824f665 | ||
|
|
5a56cca0aa | ||
|
|
b9189d7647 | ||
|
|
20226c914b | ||
|
|
434e7f8a09 | ||
|
|
a29d733a02 | ||
|
|
9abe4b967b | ||
|
|
3b6a4ece0f | ||
|
|
28d2471b4d | ||
|
|
d122af9fed | ||
|
|
77271f3856 | ||
|
|
ededfb68a6 | ||
|
|
4a3affdd24 | ||
|
|
8f8ea120d3 | ||
|
|
0fa88009f8 | ||
|
|
4375a807df | ||
|
|
b2d97c5908 | ||
|
|
ec89dd606d | ||
|
|
198508a7c3 | ||
|
|
4845e986bb | ||
|
|
1da8a307fc | ||
|
|
b4886e604e | ||
|
|
e84544136e | ||
|
|
ce70252a69 | ||
|
|
5c56962ea1 | ||
|
|
d2ed53b946 | ||
|
|
a4da80b498 | ||
|
|
9bd01492b1 | ||
|
|
da032941b4 | ||
|
|
c138fcc2e2 | ||
|
|
cbb69b0350 | ||
|
|
a8aed3354d | ||
|
|
e8790a4d4c | ||
|
|
df6ef3aaa0 | ||
|
|
2820d99f7b | ||
|
|
077aa4445a | ||
|
|
23bfc119d9 | ||
|
|
ab712ac637 | ||
|
|
b056826e94 | ||
|
|
6311627899 | ||
|
|
37cea5fb61 | ||
|
|
655a8cd60d | ||
|
|
4c8babc96a | ||
|
|
612bacebed | ||
|
|
ade7c8566d | ||
|
|
19553ce5c8 | ||
|
|
18ed2527e8 | ||
|
|
b0652bc884 | ||
|
|
15c9ad23fe | ||
|
|
578bb12562 | ||
|
|
f82cfda07f | ||
|
|
9e52b2788d | ||
|
|
2e56a113d9 | ||
|
|
4722d777e6 | ||
|
|
2141d54ae0 | ||
|
|
e346225136 | ||
|
|
012d4dae56 | ||
|
|
b4d9fe70af | ||
|
|
85e83b5441 | ||
|
|
6b2a453b8f | ||
|
|
27021538d8 | ||
|
|
8b57a2b055 | ||
|
|
75dd894685 | ||
|
|
9101ef8774 | ||
|
|
5932540630 | ||
|
|
ec376b2e47 | ||
|
|
a176562ad0 | ||
|
|
becf37b676 | ||
|
|
9b5efab8f8 | ||
|
|
e98a8ba599 | ||
|
|
7ddac50008 | ||
|
|
9837ae359f | ||
|
|
710a829dcb | ||
|
|
ccd84fa454 | ||
|
|
335b36d3a9 | ||
|
|
2be30fae00 | ||
|
|
db5cd21884 | ||
|
|
bfd3020031 | ||
|
|
344c36997a | ||
|
|
dfd9272b70 | ||
|
|
359f4520f5 | ||
|
|
aecf014f4e | ||
|
|
d2a89ddf84 | ||
|
|
c01fe153ae | ||
|
|
4f4a838799 | ||
|
|
ac6f2567eb | ||
|
|
05a5816ac6 | ||
|
|
9c8f6e9195 | ||
|
|
2fd001f6d2 | ||
|
|
d641d32413 | ||
|
|
18064ef6a2 | ||
|
|
5cb9216add | ||
|
|
91c36dc810 | ||
|
|
6efb02fa32 | ||
|
|
97313e4180 | ||
|
|
568ab24fd9 | ||
|
|
5a745efcd3 | ||
|
|
c651570e62 | ||
|
|
8980598085 | ||
|
|
c07c742feb | ||
|
|
1053abb9a9 | ||
|
|
2c9e57cbb1 | ||
|
|
c6eaa2c8a6 | ||
|
|
5ab5e913ee | ||
|
|
cea53ca476 | ||
|
|
58af09114b | ||
|
|
c4c0417e2d | ||
|
|
74f90e6947 | ||
|
|
ad5c339780 | ||
|
|
305823db00 | ||
|
|
baf58b298f | ||
|
|
c37367d018 | ||
|
|
1c98796e64 | ||
|
|
e686d9a6ea | ||
|
|
a1936b9d59 | ||
|
|
834f9c9337 | ||
|
|
615f8cfd3b | ||
|
|
8ed134105f | ||
|
|
5d6169b270 | ||
|
|
e83de8b938 | ||
|
|
ee55e039b2 | ||
|
|
086dd89144 | ||
|
|
68e5d4dd2c | ||
|
|
55a35c6bec | ||
|
|
d09b4885fe | ||
|
|
a46773e6d8 | ||
|
|
a422d0220c | ||
|
|
e5eba8430a | ||
|
|
3d235dc316 | ||
|
|
80d3b4be8c | ||
|
|
fe8b7480df | ||
|
|
cebfc3aaa0 | ||
|
|
f778b5a12d | ||
|
|
2244050160 | ||
|
|
9284e42b62 | ||
|
|
ee40120496 | ||
|
|
30cd2149ea | ||
|
|
395df36d57 | ||
|
|
79597ea0e5 | ||
|
|
283f39270a | ||
|
|
7d892bb19d | ||
|
|
a025f124f3 | ||
|
|
84f7287bf8 | ||
|
|
a58544b502 | ||
|
|
4d26175ebe | ||
|
|
78f0e6ff6b | ||
|
|
3af97af634 | ||
|
|
2c2663c8a4 | ||
|
|
1122b8a2f7 | ||
|
|
5b9f38948b | ||
|
|
507eb3b424 | ||
|
|
56fbc0ed6c | ||
|
|
7aaad314e3 | ||
|
|
356949dd54 | ||
|
|
9878baca53 | ||
|
|
9cbc7c2939 | ||
|
|
4680b63911 | ||
|
|
ce4a2d95f2 | ||
|
|
b2e048de8d | ||
|
|
d25a9d7515 | ||
|
|
dc130d3705 | ||
|
|
2391850218 | ||
|
|
c8f7ca920e | ||
|
|
e3e39af6fb | ||
|
|
f38114f5a5 | ||
|
|
1ee9d041df | ||
|
|
9c6f412f04 | ||
|
|
4fa0f2d04a | ||
|
|
e566a66ea4 | ||
|
|
58a42abc67 | ||
|
|
5676bd9d0d | ||
|
|
9691010e7b | ||
|
|
d19be3ad52 | ||
|
|
ec3cbf788b | ||
|
|
1282fd0b76 | ||
|
|
93430e5607 | ||
|
|
14201f4052 | ||
|
|
47979bf16d | ||
|
|
29530f3b17 | ||
|
|
af548e6ef8 | ||
|
|
ed24a9c990 | ||
|
|
cb1d86d08b | ||
|
|
88f3f628ef | ||
|
|
295bea37bc | ||
|
|
bd7d756254 | ||
|
|
4261147fe8 | ||
|
|
a70adc5eb3 | ||
|
|
0d51b04d79 | ||
|
|
06d40b8a81 | ||
|
|
2358510cba | ||
|
|
e6d13cb7d7 | ||
|
|
39e21c3f36 | ||
|
|
8da900ee72 | ||
|
|
9f4e81a1a3 | ||
|
|
0b918c2f51 | ||
|
|
085cd2a314 | ||
|
|
98d2399568 | ||
|
|
515d9a0008 | ||
|
|
aece1fa7d3 | ||
|
|
abc614ecfd | ||
|
|
1180d3fdde | ||
|
|
1639d1725a | ||
|
|
5df1deecbc | ||
|
|
fe3c0cf76e | ||
|
|
cc0df0182c | ||
|
|
eb354f639f | ||
|
|
02c530dcbe | ||
|
|
379b1de64f | ||
|
|
f3ff324925 | ||
|
|
0f2160222f | ||
|
|
ce3750c51c | ||
|
|
72a7ea6e91 | ||
|
|
4ad7e1f8e6 | ||
|
|
2007ba0c3b | ||
|
|
2009dc11db | ||
|
|
62f2196a0c | ||
|
|
e63c65da4f | ||
|
|
570a082227 | ||
|
|
9b1ede3a59 | ||
|
|
c445fc0f8a | ||
|
|
699493cf24 | ||
|
|
6c89686f31 | ||
|
|
f55b861849 | ||
|
|
adf82c04ad | ||
|
|
1b80956fe8 | ||
|
|
de9da8caf9 | ||
|
|
967f42dd89 | ||
|
|
95e8b29fa2 | ||
|
|
2e3c815e53 | ||
|
|
132707caa7 | ||
|
|
0dad616c38 | ||
|
|
c0882dffde | ||
|
|
5e082c647c | ||
|
|
285c3c2f5d | ||
|
|
dcb29a80fe | ||
|
|
b45ad19732 | ||
|
|
f12d453b5f | ||
|
|
8a00b711be | ||
|
|
56204efc7a | ||
|
|
da638c270f | ||
|
|
ad4b974274 | ||
|
|
943a05edcc | ||
|
|
1a28e65e50 | ||
|
|
cd3af7fa39 | ||
|
|
8ccb0c88db | ||
|
|
127880cf8d | ||
|
|
2e56086661 | ||
|
|
a129be0dbd | ||
|
|
12c0760cb3 | ||
|
|
9d3ed85ffd | ||
|
|
850d57d0d2 | ||
|
|
7981bec1ed | ||
|
|
76373a8597 | ||
|
|
9913e7b70b | ||
|
|
a08bb25bfa | ||
|
|
28ec164bc2 | ||
|
|
3d5ea8629c | ||
|
|
4aaf59d034 | ||
|
|
14850476c7 | ||
|
|
bf5b6170fa | ||
|
|
6f91591448 | ||
|
|
3c723bcba2 | ||
|
|
e7dd13cffa | ||
|
|
ad91630faa | ||
|
|
57f746b584 | ||
|
|
a55720091c | ||
|
|
b461635834 | ||
|
|
1375580651 | ||
|
|
3d20433ad1 | ||
|
|
58447c6456 | ||
|
|
c6273e9177 | ||
|
|
ffdc158d44 | ||
|
|
876c81fad8 | ||
|
|
028ee6d7b1 | ||
|
|
ec00548f1b | ||
|
|
c4dc03e4a8 | ||
|
|
3a510a77ec | ||
|
|
98a785fced | ||
|
|
c48654160d | ||
|
|
55b80132c4 | ||
|
|
1f0c168936 | ||
|
|
6715bc750f | ||
|
|
04a48a626b | ||
|
|
2f9f0da7c6 | ||
|
|
513c4f9e29 | ||
|
|
3f078517a0 | ||
|
|
37036f0fca | ||
|
|
5789aadb5c | ||
|
|
a768ed718a | ||
|
|
9c6092f31f | ||
|
|
40d294a247 | ||
|
|
72844e4edc | ||
|
|
db0a71125a | ||
|
|
da244af39d | ||
|
|
067f502d3c | ||
|
|
fffc6b1e4e | ||
|
|
9121c6a078 | ||
|
|
9c4e581d8b | ||
|
|
dfadd31f46 | ||
|
|
0cfa6fff43 | ||
|
|
d61671c1a0 | ||
|
|
d4f10a9af3 | ||
|
|
03861af893 | ||
|
|
ae531c445d | ||
|
|
4b26aeef9a | ||
|
|
1e47b79b50 | ||
|
|
0c223dcec4 | ||
|
|
0f4536c3d3 | ||
|
|
f43c584463 | ||
|
|
91c558ec83 | ||
|
|
9d45ab3246 | ||
|
|
34ff6eb567 | ||
|
|
8793c00438 | ||
|
|
d7981d5c3e | ||
|
|
bcaae3b67b | ||
|
|
046d9f9597 | ||
|
|
81bd0301d2 | ||
|
|
530e7e494f | ||
|
|
d402fd5690 | ||
|
|
eebec3b92f | ||
|
|
211c6585fa | ||
|
|
e1b5c40ca0 | ||
|
|
747a9b521b | ||
|
|
c2d72ad309 | ||
|
|
596181b622 | ||
|
|
77c5270e1e | ||
|
|
a663c14df8 | ||
|
|
3bd9f00268 | ||
|
|
1aadda735d | ||
|
|
12035208e2 | ||
|
|
df8a9f673c | ||
|
|
aa5c8a2c56 | ||
|
|
a84540e6bb | ||
|
|
fb91b64063 | ||
|
|
94cc77ebca | ||
|
|
aac6981304 | ||
|
|
ca05828b68 | ||
|
|
8ec6b4c59c | ||
|
|
f1be5f5341 | ||
|
|
714c264002 | ||
|
|
eca58097ef | ||
|
|
281146e22b | ||
|
|
f3a19a5d02 | ||
|
|
9b9b6937f4 | ||
|
|
f54c0b7dff | ||
|
|
36c58ad286 | ||
|
|
a67f633259 | ||
|
|
f39a607c1a | ||
|
|
0cc67ed2e5 | ||
|
|
5f8402c645 | ||
|
|
3ab87cd11e | ||
|
|
d5620d305d | ||
|
|
35ebc5e842 | ||
|
|
66276be1d2 | ||
|
|
47c0d522db | ||
|
|
b654883d1a | ||
|
|
b4f9d29129 | ||
|
|
bec6b961f3 | ||
|
|
2ce8f34306 | ||
|
|
30d1ae59ec | ||
|
|
ac7d4e3645 | ||
|
|
868c4001f6 | ||
|
|
e99c44d967 | ||
|
|
48a877f160 | ||
|
|
cea894a8bd | ||
|
|
087e7b9311 | ||
|
|
39ba498293 | ||
|
|
fe7390bd4d | ||
|
|
75af551435 | ||
|
|
ae2d3ebb48 | ||
|
|
5ff6c53715 | ||
|
|
3c94723b23 | ||
|
|
c6a2e3e328 | ||
|
|
2dc5e10878 | ||
|
|
4086dfcf56 | ||
|
|
7937c2bab0 | ||
|
|
5ffa8e9936 | ||
|
|
c431cee517 | ||
|
|
375f17e728 | ||
|
|
d3f658c874 | ||
|
|
5e340a4cdd | ||
|
|
409a5b9f99 | ||
|
|
fba305020b | ||
|
|
bd4ce3ac45 | ||
|
|
733de60f7c | ||
|
|
c365a44e01 | ||
|
|
e94f450bf0 | ||
|
|
d5efc9ddde | ||
|
|
68895ba4a5 | ||
|
|
139aa7a0fc | ||
|
|
4955157e13 | ||
|
|
f2dd5cc75e | ||
|
|
2ad634dbc6 | ||
|
|
de13f65a24 | ||
|
|
8994dde8f0 | ||
|
|
b7303a0828 | ||
|
|
5bc330162a | ||
|
|
0ebc0217f3 | ||
|
|
95c810b80a | ||
|
|
82d7fb883d | ||
|
|
b96e710543 | ||
|
|
24e5e85225 | ||
|
|
7b8f81f1b2 | ||
|
|
62e60fc7ab | ||
|
|
ccd3d4aded | ||
|
|
f0cf155b5c | ||
|
|
b66f67d889 | ||
|
|
e5dc07bde1 | ||
|
|
a955eb0fec | ||
|
|
c070af9681 | ||
|
|
c15e060ef2 | ||
|
|
6d6f2454a7 | ||
|
|
9ff44ed46b | ||
|
|
adf3ef61b8 | ||
|
|
832107e0b8 | ||
|
|
0ea1e71808 | ||
|
|
3b9b3f8ffa | ||
|
|
fda8823050 | ||
|
|
b5756cb14f | ||
|
|
617d3dbe52 | ||
|
|
c18beb1c7c | ||
|
|
a6957b919c | ||
|
|
816a362534 | ||
|
|
7ce3ebde4e | ||
|
|
cc2f83c4d9 | ||
|
|
6ce492049e | ||
|
|
a7999de4b0 | ||
|
|
d4bdfabf19 | ||
|
|
85030ab804 | ||
|
|
2a9bd00a50 | ||
|
|
1c2d76e651 | ||
|
|
a97f7d225a | ||
|
|
2781848aac | ||
|
|
d179da2bee | ||
|
|
60e7922734 | ||
|
|
1ece37ec3c | ||
|
|
8dad865146 | ||
|
|
80d15e782b | ||
|
|
d24e4c6518 | ||
|
|
6def46544c | ||
|
|
d66bae32d3 | ||
|
|
1b753a4020 | ||
|
|
25e6a74a0a | ||
|
|
afde00a4be | ||
|
|
0022d380bb | ||
|
|
d80d2ab934 | ||
|
|
13c1734753 | ||
|
|
bf33d6c34e | ||
|
|
1b02f9bd5d | ||
|
|
9f63c645ff | ||
|
|
abf271fb68 | ||
|
|
363755c3bf | ||
|
|
ba2db666aa | ||
|
|
9f3677b694 | ||
|
|
e6024c997f | ||
|
|
3cb83e2286 | ||
|
|
780d03e5e1 | ||
|
|
214114e6ce | ||
|
|
274d3fe679 | ||
|
|
0ecf86d8a3 | ||
|
|
c2d4390a72 | ||
|
|
7627d59d43 | ||
|
|
71ce9a6b37 | ||
|
|
232018c925 | ||
|
|
9dfbbe58ff | ||
|
|
fa9738a2e0 | ||
|
|
94ecbc5921 | ||
|
|
3c68d317d7 | ||
|
|
56d4edfb9d | ||
|
|
c6c037ff17 | ||
|
|
44feba4d89 | ||
|
|
962f2c7380 | ||
|
|
22007426aa | ||
|
|
008e9a92d3 | ||
|
|
41139ee2ab | ||
|
|
845c40d23c | ||
|
|
a22f26c4c8 | ||
|
|
99ff020f56 | ||
|
|
f863b42b71 | ||
|
|
2e713b459e | ||
|
|
923241ce1e | ||
|
|
3a8929b9d7 | ||
|
|
eb92d39d40 | ||
|
|
bdc62a007e | ||
|
|
4b35db6291 | ||
|
|
c8282b215d | ||
|
|
c123669828 | ||
|
|
781fd0a1cd | ||
|
|
9bd99605fb | ||
|
|
dc626bd4f0 | ||
|
|
aa27aeafa1 | ||
|
|
cdb25cd0e9 | ||
|
|
dc2d15fd9c | ||
|
|
55cb788380 | ||
|
|
0f3b7fe643 | ||
|
|
4b812350a8 | ||
|
|
aec37164de | ||
|
|
dec02bd8db | ||
|
|
1bd6a8ed9e | ||
|
|
2030f714fa | ||
|
|
4416646954 | ||
|
|
52ba9dc02a | ||
|
|
dad3d42d14 | ||
|
|
0d12f3043b | ||
|
|
1225786fc0 | ||
|
|
71496d5229 | ||
|
|
eb0aa20fe1 | ||
|
|
c34de3d0a3 | ||
|
|
54e0a9fc28 | ||
|
|
4bcd034b3d | ||
|
|
111bd29cc8 | ||
|
|
e038865693 | ||
|
|
dfd29dc37a | ||
|
|
b0fcd23ca6 | ||
|
|
f80b1d31f5 | ||
|
|
811ea5b92a | ||
|
|
f9dfbd5800 | ||
|
|
88f1c36929 | ||
|
|
8bbe771f5b | ||
|
|
c578fa63e5 | ||
|
|
4448b86b93 | ||
|
|
17badf95dc | ||
|
|
a267ee40d2 | ||
|
|
8ef645b3c2 | ||
|
|
35625b22f5 | ||
|
|
221dcefd6c | ||
|
|
9c74a9c1db | ||
|
|
55fc3920fc | ||
|
|
5d60b5eb8b | ||
|
|
049d5166e8 | ||
|
|
f4019db3d1 | ||
|
|
9f3732d35b | ||
|
|
b4f17ac3c6 | ||
|
|
978e35d335 | ||
|
|
22cbbec960 | ||
|
|
21f3a70788 | ||
|
|
b4c6f80e1c | ||
|
|
e1198c42eb | ||
|
|
e09fdbcef0 | ||
|
|
b708e79929 | ||
|
|
cbaecff3b7 | ||
|
|
4f7d2630af | ||
|
|
92d3860240 | ||
|
|
3757d5da9f | ||
|
|
1d38a885bb | ||
|
|
dbd767e8f1 | ||
|
|
8b83c38127 | ||
|
|
f1ea01e709 | ||
|
|
12a1aeb0f8 | ||
|
|
413150012f | ||
|
|
8ef5604ce8 | ||
|
|
42e50c800b | ||
|
|
8fbd08003c | ||
|
|
877577efdb | ||
|
|
a6f457749b | ||
|
|
9afb713df1 | ||
|
|
8f660c0276 | ||
|
|
a7e86d9afd | ||
|
|
462eea90c0 | ||
|
|
79c30dfc91 | ||
|
|
410a78b366 | ||
|
|
065807a0bc | ||
|
|
1d93658e56 | ||
|
|
2b7865e6ea | ||
|
|
0cdba8c329 | ||
|
|
11b317b788 | ||
|
|
fb955e15f4 | ||
|
|
ae2d141f0d | ||
|
|
68c983923e | ||
|
|
bf252f7f20 | ||
|
|
324038486f | ||
|
|
bef5da49cf | ||
|
|
24e7f547fa | ||
|
|
3ee3ab0ad1 | ||
|
|
f734154da8 | ||
|
|
7a053ce697 | ||
|
|
25f250310e | ||
|
|
4eca05bbba | ||
|
|
45b0f791bb | ||
|
|
42415a81c1 | ||
|
|
6882e83d1e | ||
|
|
d4b7318413 | ||
|
|
a2b4d400af | ||
|
|
f07868d24e | ||
|
|
d46ee049f4 | ||
|
|
c62eda5627 | ||
|
|
7683164ed2 | ||
|
|
7090c16575 | ||
|
|
9e634fed13 | ||
|
|
9bb125cebd | ||
|
|
0c4850b91d | ||
|
|
0eb7688c4d | ||
|
|
f47cdb68d9 | ||
|
|
d3c3cded37 | ||
|
|
e91ea4ecbe | ||
|
|
680b20d199 | ||
|
|
ec97e04fd4 | ||
|
|
b0b2657fe0 | ||
|
|
d27426fd8f | ||
|
|
d8206c0e3e | ||
|
|
3f1841a188 | ||
|
|
cb478e0dc8 | ||
|
|
02c42a7e3a | ||
|
|
ef40f7349e | ||
|
|
86eebb35cb | ||
|
|
a901388887 | ||
|
|
6cd1c5de38 | ||
|
|
7489f172a1 | ||
|
|
702798c275 | ||
|
|
430d51866c | ||
|
|
9d08421f01 | ||
|
|
f4051874b2 | ||
|
|
bbe0690056 | ||
|
|
772c0d1e41 | ||
|
|
8eb9ca0260 | ||
|
|
bd27afe0da | ||
|
|
a3af21275a | ||
|
|
61eb155d13 | ||
|
|
7932c1c4a9 | ||
|
|
f776fb83e7 | ||
|
|
a97521aba2 | ||
|
|
d1c0fe503e | ||
|
|
ed02c1ae36 | ||
|
|
9a67cf7355 | ||
|
|
755eeda364 | ||
|
|
136dee7747 | ||
|
|
e4e8428855 | ||
|
|
de8dc021f9 | ||
|
|
991587f252 | ||
|
|
8dbcf257c4 | ||
|
|
0b067364a9 | ||
|
|
5367bd6134 | ||
|
|
92228c4379 | ||
|
|
fb2c7896b3 | ||
|
|
23265d9091 | ||
|
|
2c9bb0e767 | ||
|
|
f9e8400d83 | ||
|
|
927a13cd76 | ||
|
|
51b3293e69 | ||
|
|
3f76cadea9 | ||
|
|
6dbf53b558 | ||
|
|
22e937c798 | ||
|
|
ac5cc8b299 | ||
|
|
c588ab723b | ||
|
|
4b2dfc051d | ||
|
|
5238c83f3f | ||
|
|
90bb580e50 | ||
|
|
f40e142704 | ||
|
|
a67618675d | ||
|
|
4fe436e4d1 | ||
|
|
683b8c966f | ||
|
|
28377a156d | ||
|
|
3dcc4faabb | ||
|
|
60a033f93a | ||
|
|
436bd73786 | ||
|
|
5c69ff3339 | ||
|
|
2105b1e7c4 | ||
|
|
523004e5b2 | ||
|
|
5e02c386ec | ||
|
|
b4501fe52d | ||
|
|
3c29eaa1b1 | ||
|
|
ee67e163b1 | ||
|
|
9662bc29fb | ||
|
|
96f2660b98 | ||
|
|
20f594c66c | ||
|
|
2b8d59dca3 | ||
|
|
d44047d109 | ||
|
|
57c4d33bd3 | ||
|
|
7a5377efe0 | ||
|
|
91e7cffccc | ||
|
|
df31e47313 | ||
|
|
cb9586270c | ||
|
|
21dfa5227c | ||
|
|
9d15d2be77 | ||
|
|
929c02d31f | ||
|
|
846185dd42 | ||
|
|
7bc2299a8e | ||
|
|
d40e131bd8 | ||
|
|
552c7297bf | ||
|
|
3f5fd23955 | ||
|
|
8b8566251e | ||
|
|
6db47def8e | ||
|
|
1d0edc7b25 | ||
|
|
f9a417638a | ||
|
|
984fe01551 | ||
|
|
d0cb350687 | ||
|
|
5f51011ce1 | ||
|
|
9ca125ac55 | ||
|
|
360f4f8c27 | ||
|
|
6501f71bd6 | ||
|
|
bf6b799dba | ||
|
|
5f57279283 | ||
|
|
5ed3565520 | ||
|
|
513fa90b8a | ||
|
|
a4d9b9689b | ||
|
|
1c05c0dcbb | ||
|
|
a1b49a3a6b | ||
|
|
6f57298cbb | ||
|
|
d8ce673088 | ||
|
|
4cd7af7a74 | ||
|
|
49c61b5992 | ||
|
|
e44d0550d2 | ||
|
|
17f82109b6 | ||
|
|
2d8888ae9b | ||
|
|
4abe9c6fb2 | ||
|
|
f9d94fa660 | ||
|
|
eaa13f4990 | ||
|
|
01fd5901fe | ||
|
|
3d6adeffc4 | ||
|
|
9066952759 | ||
|
|
6dd7f6274a | ||
|
|
7a8fe6d152 | ||
|
|
be507be3a9 | ||
|
|
657b97f190 | ||
|
|
9d7745cd9b | ||
|
|
3668f83693 | ||
|
|
a2d5d99c1f | ||
|
|
f379ef6a3b | ||
|
|
510a748749 | ||
|
|
550150d685 | ||
|
|
011ea9659e | ||
|
|
6eca7d948e | ||
|
|
90e639f119 | ||
|
|
86ac6461d1 | ||
|
|
18a95bf9ab | ||
|
|
7949bbe66d | ||
|
|
4b603c452a | ||
|
|
837f0634b6 | ||
|
|
78076f7854 | ||
|
|
719350cee1 | ||
|
|
4f6be3e6f5 | ||
|
|
8e61e9fecb | ||
|
|
2083285d78 | ||
|
|
034e86e2cb | ||
|
|
f4a2d5c652 | ||
|
|
534ccd6bf6 | ||
|
|
c17064f853 | ||
|
|
1e1566082f | ||
|
|
449548654d | ||
|
|
6fc99524f0 | ||
|
|
051629fad3 | ||
|
|
f957008c1c | ||
|
|
98e1deec88 | ||
|
|
99127652af | ||
|
|
e9b9e9e82c | ||
|
|
2ed5c3746e | ||
|
|
8902056fdb | ||
|
|
defa6ff6e8 | ||
|
|
eed44e81be | ||
|
|
1951aec5ec | ||
|
|
9c4e0b4107 | ||
|
|
c8deac660d | ||
|
|
4cc5ec9bd0 | ||
|
|
c41bef2e81 | ||
|
|
5b735cf960 | ||
|
|
604e960aa9 | ||
|
|
6c465aa1f2 | ||
|
|
c266832fdc | ||
|
|
906d8d0413 | ||
|
|
cb05fd4a3c | ||
|
|
2eda24799b | ||
|
|
41e221f0cb | ||
|
|
f75af035bb | ||
|
|
e9e6449edf | ||
|
|
f09d76da35 | ||
|
|
40dfe0919b | ||
|
|
85990dd074 | ||
|
|
38acc16e1c | ||
|
|
b7cc4c1e92 | ||
|
|
1f232d96d8 | ||
|
|
83508f165d | ||
|
|
7cc58e7e84 | ||
|
|
31d9740aac | ||
|
|
69891a64a0 | ||
|
|
0940309600 | ||
|
|
a762b1ed60 | ||
|
|
1b9d9d3a8b | ||
|
|
d9908b3d61 | ||
|
|
c40b80436a | ||
|
|
8f1e352bcc | ||
|
|
18e769b5e5 | ||
|
|
27af6459b3 | ||
|
|
2c4bfab01a | ||
|
|
e689be552b | ||
|
|
ad80e7f48b | ||
|
|
d81b75b084 | ||
|
|
90f1431047 | ||
|
|
61ea7dabae | ||
|
|
5d9f5f4a7d | ||
|
|
f956f612d3 | ||
|
|
3f5108268d | ||
|
|
4c0dfc3f30 | ||
|
|
1670fe9b1c | ||
|
|
300b28c0f2 | ||
|
|
e7038961ef | ||
|
|
24e77a5211 | ||
|
|
9df039fbc2 | ||
|
|
143cd46a81 | ||
|
|
680e9871ed | ||
|
|
d5ece58f71 | ||
|
|
d7bbb5c4b7 | ||
|
|
cf9c991c79 | ||
|
|
0f0d96195d | ||
|
|
3a562bb714 | ||
|
|
6381ba8478 | ||
|
|
9e3c14841a | ||
|
|
1917091338 | ||
|
|
b1bb508554 | ||
|
|
4040b334f5 | ||
|
|
d7e72519ef | ||
|
|
c7752f0be9 | ||
|
|
0ffe28a733 | ||
|
|
56f24fe317 | ||
|
|
341cde2781 | ||
|
|
33bb8d434d | ||
|
|
9f813b7385 | ||
|
|
02a336a25d | ||
|
|
7df532fa72 | ||
|
|
1f40c2ccf8 | ||
|
|
4a8fd309c5 | ||
|
|
b416849d9c | ||
|
|
bc321d8ced | ||
|
|
45919fc0cf | ||
|
|
dd6f4c4844 | ||
|
|
bb47db033f | ||
|
|
111ea78693 | ||
|
|
c17253589a | ||
|
|
7e6156f5dd | ||
|
|
d5cfb63f52 | ||
|
|
cab15055e7 | ||
|
|
9185910171 | ||
|
|
b4892e0caf | ||
|
|
83e0cafef9 | ||
|
|
7cb75506c3 | ||
|
|
ac6970ad40 | ||
|
|
5a95cc236c | ||
|
|
95c942f477 |
8
.coolify-logo
Normal file
8
.coolify-logo
Normal file
@@ -0,0 +1,8 @@
|
||||
_____ _ _ __
|
||||
/ ____| | (_)/ _|
|
||||
| | ___ ___ | |_| |_ _ _
|
||||
| | / _ \ / _ \| | | _| | | |
|
||||
| |___| (_) | (_) | | | | | |_| |
|
||||
\_____\___/ \___/|_|_|_| \__, |
|
||||
__/ |
|
||||
|___/
|
||||
@@ -1,16 +0,0 @@
|
||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile
|
||||
|
||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||
ARG VARIANT="16-bullseye"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment if you want to install an additional version of node using nvm
|
||||
# ARG EXTRA_NODE_VERSION=10
|
||||
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
||||
|
||||
# [Optional] Uncomment if you want to install more global node modules
|
||||
RUN su node -c "npm install -g pnpm"
|
||||
@@ -1,36 +0,0 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node
|
||||
{
|
||||
"name": "Node.js",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
|
||||
// Append -bullseye or -buster to pin to an OS version.
|
||||
// Use -bullseye variants on local arm64/Apple Silicon.
|
||||
"args": {
|
||||
"VARIANT": "18-bullseye"
|
||||
}
|
||||
},
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"ms-azuretools.vscode-docker",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"svelte.svelte-vscode",
|
||||
"ardenivanov.svelte-intellisense",
|
||||
"Prisma.prisma",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"waderyan.gitblame"
|
||||
],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [3000, 3001],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "cp apps/api/.env.example apps/api/.env && pnpm install && pnpm db:push && pnpm db:seed",
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node",
|
||||
"features": {
|
||||
"docker-in-docker": "20.10",
|
||||
"github-cli": "latest"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,24 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
build
|
||||
.svelte-kit
|
||||
package
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
local-serve
|
||||
.env.backup
|
||||
.env.secrets
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
||||
/.npm
|
||||
/.bash_history
|
||||
/_data
|
||||
.rnd
|
||||
/.ssh
|
||||
|
||||
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
||||
16
.env.development.example
Normal file
16
.env.development.example
Normal file
@@ -0,0 +1,16 @@
|
||||
APP_NAME=Coolify-localhost
|
||||
APP_ID=development
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
APP_PORT=8000
|
||||
MUX_ENABLED=false
|
||||
|
||||
DUSK_DRIVER_URL=http://selenium:4444
|
||||
|
||||
## For Andras only
|
||||
# To purge cache
|
||||
BUNNY_API_KEY=
|
||||
# To upload assets
|
||||
BUNNY_STORAGE_API_KEY=
|
||||
6
.env.production
Normal file
6
.env.production
Normal file
@@ -0,0 +1,6 @@
|
||||
APP_ID=
|
||||
APP_NAME=Coolify
|
||||
APP_KEY=
|
||||
|
||||
DB_PASSWORD=
|
||||
REDIS_PASSWORD=
|
||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
2
.github/FUNDING.yml → .github/FUNDING.yaml
vendored
2
.github/FUNDING.yml → .github/FUNDING.yaml
vendored
@@ -1,2 +1,2 @@
|
||||
open_collective: coollabsio
|
||||
github: coollabsio
|
||||
github: coollabsio
|
||||
47
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
47
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
@@ -1,47 +0,0 @@
|
||||
name: 🐞 Bug report
|
||||
description: Create a bug report to help us improve coolify
|
||||
title: "[Bug]: "
|
||||
labels: [Bug]
|
||||
assignees:
|
||||
- andrasbacsai
|
||||
- vasani-arpit
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! Please fill the form in English
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A concise description of what you're experiencing and what you expect.
|
||||
placeholder: |
|
||||
When I do <X>, <Y> happens and I see the error message attached below:
|
||||
```...```
|
||||
What I expect is <Z>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Add steps to reproduce this behaviour, include console / network logs & videos
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: "The version of your coolify Instance"
|
||||
placeholder: "2.5.2"
|
||||
validations:
|
||||
required: true
|
||||
31
.github/ISSUE_TEMPLATE/--feature-request.yaml
vendored
31
.github/ISSUE_TEMPLATE/--feature-request.yaml
vendored
@@ -1,31 +0,0 @@
|
||||
name: 🛠️ Feature request
|
||||
description: Suggest an idea to improve coolify
|
||||
title: '[Feature]: '
|
||||
labels: [Enhancement]
|
||||
assignees:
|
||||
- andrasbacsai
|
||||
- vasani-arpit
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to request a feature for coolify! Please also add your request here to get feedback from the community: https://feedback.coolify.io/!
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue related to this feature request already exists.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: One paragraph description of the feature.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Why should this be worked on?
|
||||
description: A concise description of the problems or use cases for this feature request.
|
||||
validations:
|
||||
required: true
|
||||
20
.github/ISSUE_TEMPLATE/--task.yaml
vendored
20
.github/ISSUE_TEMPLATE/--task.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
name: 📝 Task
|
||||
description: Create a task for the team to work on
|
||||
title: "[Task]: "
|
||||
labels: [Task]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue related to this already exists.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: SubTasks
|
||||
placeholder: |
|
||||
- Sub Task 1
|
||||
- Sub Task 2
|
||||
validations:
|
||||
required: false
|
||||
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Bug report
|
||||
description: Create a new bug report
|
||||
title: '[Bug]: '
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Minimal Reproduction (if possible, example repository)
|
||||
description: Please provide a step by step guide to reproduce the issue
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Exception or Error
|
||||
description: Please provide error logs if possible.
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: Coolify's version (see bottom left corner).
|
||||
validations:
|
||||
required: true
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🤔 Questions and Help
|
||||
url: https://discord.com/invite/6rDM4fkymF
|
||||
about: Reach out to us on discord or our github discussions page.
|
||||
- name: 🙋♂️ Service request
|
||||
url: https://feedback.coolify.io/
|
||||
about: Want to request a new service or build pack? For example Wordpress, Hasura, Appwrite, Angular etc...
|
||||
- name: 🤔 Community Support (Chat)
|
||||
url: https://coollabs.io/discord
|
||||
about: Reach out to us on Discord.
|
||||
- name: 🙋♂️ Feature Requests
|
||||
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
|
||||
about: All feature requests will be discussed here.
|
||||
|
||||
84
.github/workflows/coolify-helper-next.yml
vendored
Normal file
84
.github/workflows/coolify-helper-next.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: Coolify Helper Image Development (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "next" ]
|
||||
paths:
|
||||
- .github/workflows/coolify-helper-next.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify-helper"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/coolify-helper/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
aarch64:
|
||||
runs-on: [ self-hosted, arm64 ]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/coolify-helper/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [ amd64, aarch64 ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
84
.github/workflows/coolify-helper.yml
vendored
Normal file
84
.github/workflows/coolify-helper.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: Coolify Helper Image (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify-helper"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/coolify-helper/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
aarch64:
|
||||
runs-on: [ self-hosted, arm64 ]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/coolify-helper/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [ amd64, aarch64 ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||
79
.github/workflows/development-build.yml
vendored
Normal file
79
.github/workflows/development-build.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: Development Build (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
paths-ignore:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: [self-hosted, x64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
44
.github/workflows/docker-image.yml
vendored
Normal file
44
.github/workflows/docker-image.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches: [ "main" ]
|
||||
# pull_request:
|
||||
# branches: [ "*" ]
|
||||
push:
|
||||
branches: ["this-does-not-exist"]
|
||||
pull_request:
|
||||
branches: ["this-does-not-exist"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
/usr/local/share/ca-certificates
|
||||
/var/cache/apt/archives
|
||||
/var/lib/apt/lists
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-docker-
|
||||
- name: Build the Docker image
|
||||
run: |
|
||||
cp .env.example .env
|
||||
docker run --rm -u "$(id -u):$(id -g)" \
|
||||
-v "$(pwd):/app" \
|
||||
-w /app composer:2 \
|
||||
composer install --ignore-platform-reqs
|
||||
./vendor/bin/spin build
|
||||
- name: Start the stack
|
||||
run: |
|
||||
./vendor/bin/spin up -d
|
||||
./vendor/bin/spin exec coolify php artisan key:generate
|
||||
./vendor/bin/spin exec coolify php artisan migrate:fresh --seed
|
||||
- name: Test (missing E2E tests)
|
||||
run: |
|
||||
./vendor/bin/spin exec coolify php artisan test
|
||||
85
.github/workflows/production-build.yml
vendored
Normal file
85
.github/workflows/production-build.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Production Build (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: [self-hosted, x64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||
85
.github/workflows/production-release.yml
vendored
85
.github/workflows/production-release.yml
vendored
@@ -1,85 +0,0 @@
|
||||
name: production-release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
arm64-build:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Get current package version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
id: package-version
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-arm64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-arm64,mode=max
|
||||
amd64-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Get current package version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
id: package-version
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [amd64-build, arm64-build]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Get current package version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
id: package-version
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker manifest create coollabsio/coolify:${{steps.package-version.outputs.current-version}} --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
|
||||
docker manifest push coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||
90
.github/workflows/release-candidate.yml
vendored
90
.github/workflows/release-candidate.yml
vendored
@@ -1,90 +0,0 @@
|
||||
name: release-candidate
|
||||
on:
|
||||
release:
|
||||
types: [prereleased]
|
||||
|
||||
jobs:
|
||||
arm64-making-something-cool:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "next"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Get current package version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
id: package-version
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
tags: coollabsio/coolify:${{github.event.release.name}}-arm64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64,mode=max
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
amd64-making-something-cool:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "next"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Get current package version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
id: package-version
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/coolify:${{github.event.release.name}}-amd64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64,mode=max
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
merge-manifest-to-be-cool:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [arm64-making-something-cool, amd64-making-something-cool]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker manifest create coollabsio/coolify:${{github.event.release.name}} --amend coollabsio/coolify:${{github.event.release.name}}-amd64 --amend coollabsio/coolify:${{github.event.release.name}}-arm64
|
||||
docker manifest push coollabsio/coolify:${{github.event.release.name}}
|
||||
|
||||
87
.github/workflows/staging-release.yml
vendored
87
.github/workflows/staging-release.yml
vendored
@@ -1,87 +0,0 @@
|
||||
name: staging-release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
|
||||
jobs:
|
||||
arm64-making-something-cool:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "next"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Get current package version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
id: package-version
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
tags: coollabsio/coolify:next-arm64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-arm64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-arm64,mode=max
|
||||
amd64-making-something-cool:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "next"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Get current package version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||
id: package-version
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/coolify:next-amd64,coollabsio/coolify:next-test
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
||||
merge-manifest-to-be-cool:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [arm64-making-something-cool, amd64-making-something-cool]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker manifest create coollabsio/coolify:next --amend coollabsio/coolify:next-amd64 --amend coollabsio/coolify:next-arm64
|
||||
docker manifest push coollabsio/coolify:next
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
47
.gitignore
vendored
47
.gitignore
vendored
@@ -1,15 +1,34 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
.pnpm-store
|
||||
build
|
||||
.svelte-kit
|
||||
package
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
local-serve
|
||||
apps/api/db/migration.db-journal
|
||||
apps/api/core*
|
||||
.env.backup
|
||||
.env.secrets
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
||||
/.npm
|
||||
/.bash_history
|
||||
/_data
|
||||
_testing_hosts/
|
||||
_volumes/
|
||||
.lesshst
|
||||
psysh_history
|
||||
.psql_history
|
||||
_ide_helper.php
|
||||
.gitignore
|
||||
.phpstorm.meta.php
|
||||
_ide_helper_models.php
|
||||
.rnd
|
||||
/.ssh
|
||||
scripts/load-test/*
|
||||
|
||||
2
.gitpod.Dockerfile
vendored
2
.gitpod.Dockerfile
vendored
@@ -1,2 +0,0 @@
|
||||
FROM gitpod/workspace-full:2022-08-17-18-37-55
|
||||
RUN brew install buildpacks/tap/pack
|
||||
71
.gitpod.yml
71
.gitpod.yml
@@ -1,14 +1,65 @@
|
||||
# This configuration file was automatically generated by Gitpod.
|
||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||
# and commit this file to your remote git repository to share the goodness with others.
|
||||
#image:
|
||||
# file: .gitpod.Dockerfile
|
||||
#tasks:
|
||||
# - init: pnpm install && pnpm db:push && pnpm db:seed
|
||||
# command: pnpm dev
|
||||
tasks:
|
||||
- name: Setup Spin environment and Composer dependencies
|
||||
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614
|
||||
before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
|
||||
init: |
|
||||
cp .env.example .env &&
|
||||
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
|
||||
sed -i "s#USERID=#USERID=33333#g" .env
|
||||
sed -i "s#GROUPID=#GROUPID=33333#g" .env
|
||||
composer install --ignore-platform-reqs
|
||||
./vendor/bin/spin up -d
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan key:generate
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan storage:link
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed
|
||||
cat .coolify-logo
|
||||
gp sync-done spin-is-ready
|
||||
|
||||
- name: Install Node dependencies and run Vite
|
||||
command: |
|
||||
echo "Waiting for Sail environment to boot up."
|
||||
gp sync-await spin-is-ready
|
||||
./vendor/bin/spin exec vite npm install
|
||||
./vendor/bin/spin exec vite npm run dev
|
||||
|
||||
- name: Laravel Queue Worker, listening to code changes
|
||||
command: |
|
||||
echo "Waiting for Sail environment to boot up."
|
||||
gp sync-await spin-is-ready
|
||||
./vendor/bin/spin exec -u webuser coolify php artisan queue:listen
|
||||
|
||||
ports:
|
||||
- port: 3001
|
||||
- port: 5432
|
||||
onOpen: ignore
|
||||
name: PostgreSQL
|
||||
visibility: public
|
||||
- port: 3000
|
||||
- port: 5173
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
name: Node Server for Vite
|
||||
- port: 8000
|
||||
onOpen: ignore
|
||||
visibility: public
|
||||
name: Coolify
|
||||
|
||||
# Configure vscode
|
||||
vscode:
|
||||
extensions:
|
||||
- bmewburn.vscode-intelephense-client
|
||||
- ikappas.composer
|
||||
- ms-azuretools.vscode-docker
|
||||
- ecmel.vscode-html-css
|
||||
- MehediDracula.php-namespace-resolver
|
||||
- wmaurer.change-case
|
||||
- Equinusocio.vsc-community-material-theme
|
||||
- EditorConfig.EditorConfig
|
||||
- streetsidesoftware.code-spell-checker
|
||||
- rangav.vscode-thunder-client
|
||||
- PKief.material-icon-theme
|
||||
- cierra.livewire-vscode
|
||||
- lennardv.livewire-goto-updated
|
||||
- bradlc.vscode-tailwindcss
|
||||
- heybourn.headwind
|
||||
- adrianwilczynski.alpine-js-intellisense
|
||||
- amiralizadeh9480.laravel-extra-intellisense
|
||||
- shufo.vscode-blade-formatter
|
||||
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"i18n-ally.localesPaths": ["src/lib/locales"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.extract.ignoredByFiles": {
|
||||
"src\\routes\\__layout.svelte": ["Coolify", "coolLabs logo"]
|
||||
},
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.enabledFrameworks": ["svelte"],
|
||||
"i18n-ally.enabledParsers": ["js", "ts", "json"],
|
||||
"i18n-ally.extract.autoDetect": true
|
||||
}
|
||||
256
CONTRIBUTING.md
256
CONTRIBUTING.md
@@ -1,256 +0,0 @@
|
||||
# 👋 Welcome
|
||||
|
||||
First of all, thank you for considering contributing to my project! It means a lot 💜.
|
||||
|
||||
|
||||
## 🙋 Want to help?
|
||||
|
||||
If you begin in GitHub contribution, you can find the [first contribution](https://github.com/firstcontributions/first-contributions) and follow this guide.
|
||||
|
||||
Follow the [introduction](#introduction) to get started then start contributing!
|
||||
|
||||
This is a little list of what you can do to help the project:
|
||||
|
||||
- [🧑💻 Develop your own ideas](#developer-contribution)
|
||||
- [🌐 Translate the project](#translation)
|
||||
|
||||
## 👋 Introduction
|
||||
|
||||
### Setup with Github codespaces
|
||||
|
||||
If you have github codespaces enabled then you can just create a codespace and run `pnpm dev` to run your the dev environment. All the required dependencies and packages has been configured for you already.
|
||||
|
||||
### Setup with Gitpod
|
||||
|
||||
If you have a [Gitpod](https://gitpod.io), you can just create a workspace from this repository, run `pnpm install && pnpm db:push && pnpm db:seed` and then `pnpm dev`. All the required dependencies and packages has been configured for you already.
|
||||
|
||||
### Setup locally in your machine
|
||||
|
||||
> 🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS. Consider using Gitpod or Github Codespaces.
|
||||
|
||||
#### Recommended Pull Request Guideline
|
||||
|
||||
- Fork the project
|
||||
- Clone your fork repo to local
|
||||
- Create a new branch
|
||||
- Push to your fork repo
|
||||
- Create a pull request: https://github.com/coollabsio/coolify/compare
|
||||
- Write a proper description
|
||||
- Open the pull request to review against `next` branch
|
||||
|
||||
---
|
||||
|
||||
# 🧑💻 Developer contribution
|
||||
## Technical skills required
|
||||
|
||||
- **Languages**: Node.js / Javascript / Typescript
|
||||
- **Framework JS/TS**: [SvelteKit](https://kit.svelte.dev/) & [Fastify](https://www.fastify.io/)
|
||||
- **Database ORM**: [Prisma.io](https://www.prisma.io/)
|
||||
- **Docker Engine API**
|
||||
|
||||
---
|
||||
|
||||
## How to start after you set up your local fork?
|
||||
|
||||
### Prerequisites
|
||||
1. Due to the lock file, this repository is best with [pnpm](https://pnpm.io). I recommend you try and use `pnpm` because it is cool and efficient!
|
||||
|
||||
2. You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
|
||||
3. You need to have [Docker Compose Plugin](https://docs.docker.com/compose/install/compose-plugin/) installed locally.
|
||||
4. You need to have [GIT LFS Support](https://git-lfs.github.com/) installed locally.
|
||||
|
||||
Optional:
|
||||
|
||||
4. To test Heroku buildpacks, you need [pack](https://github.com/buildpacks/pack) binary installed locally.
|
||||
|
||||
### Steps for local setup
|
||||
|
||||
1. Copy `apps/api/.env.template` to `apps/api/.env.template` and set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||
2. Install dependencies with `pnpm install`.
|
||||
3. Need to create a local SQlite database with `pnpm db:push`.
|
||||
|
||||
This will apply all migrations at `db/dev.db`.
|
||||
|
||||
4. Seed the database with base entities with `pnpm db:seed`
|
||||
5. You can start coding after starting `pnpm dev`.
|
||||
|
||||
---
|
||||
|
||||
## Database migrations
|
||||
|
||||
During development, if you change the database layout, you need to run `pnpm db:push` to migrate the database and create types for Prisma. You also need to restart the development process.
|
||||
|
||||
If the schema is finalized, you need to create a migration file with `pnpm db:migrate <nameOfMigration>` where `nameOfMigration` is given by you. Make it sense. :)
|
||||
|
||||
---
|
||||
|
||||
## How to add new services
|
||||
|
||||
You can add any open-source and self-hostable software (service/application) to Coolify if the following statements are true:
|
||||
|
||||
- Self-hostable (obviously)
|
||||
- Open-source
|
||||
- Maintained (I do not want to add software full of bugs)
|
||||
|
||||
## Backend
|
||||
|
||||
There are 5 steps you should make on the backend side.
|
||||
|
||||
1. Create Prisma / database schema for the new service.
|
||||
2. Add supported versions of the service.
|
||||
3. Update global functions.
|
||||
4. Create API endpoints.
|
||||
5. Define automatically generated variables.
|
||||
|
||||
> I will use [Umami](https://umami.is/) as an example service.
|
||||
|
||||
### Create Prisma / Database schema for the new service.
|
||||
|
||||
You only need to do this if you store passwords or any persistent configuration. Mostly it is required by all services, but there are some exceptions, like NocoDB.
|
||||
|
||||
Update Prisma schema in [prisma/schema.prisma](prisma/schema.prisma).
|
||||
|
||||
- Add new model with the new service name.
|
||||
- Make a relationship with `Service` model.
|
||||
- In the `Service` model, the name of the new field should be with low-capital.
|
||||
- If the service needs a database, define a `publicPort` field to be able to make it's database public, example field name in case of PostgreSQL: `postgresqlPublicPort`. It should be a optional field.
|
||||
|
||||
If you are finished with the Prisma schema, you should update the database schema with `pnpm db:push` command.
|
||||
|
||||
> You must restart the running development environment to be able to use the new model
|
||||
|
||||
> If you use VSCode/TLS, you probably need to restart the `Typescript Language Server` to get the new types loaded in the running environment.
|
||||
|
||||
### Add supported versions
|
||||
|
||||
Supported versions are hardcoded into Coolify (for now).
|
||||
|
||||
You need to update `supportedServiceTypesAndVersions` function at [apps/api/src/lib/services/supportedVersions.ts](apps/api/src/lib/services/supportedVersions.ts). Example JSON:
|
||||
|
||||
```js
|
||||
{
|
||||
// Name used to identify the service internally
|
||||
name: 'umami',
|
||||
// Fancier name to show to the user
|
||||
fancyName: 'Umami',
|
||||
// Docker base image for the service
|
||||
baseImage: 'ghcr.io/mikecao/umami',
|
||||
// Optional: If there is any dependent image, you should list it here
|
||||
images: [],
|
||||
// Usable tags
|
||||
versions: ['postgresql-latest'],
|
||||
// Which tag is the recommended
|
||||
recommendedVersion: 'postgresql-latest',
|
||||
// Application's default port, Umami listens on 3000
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add required functions/properties
|
||||
|
||||
1. Add the new service to the `includeServices` variable in [apps/api/src/lib/services/common.ts](apps/api/src/lib/services/common.ts), so it will be included in all places in the database queries where it is required.
|
||||
|
||||
```js
|
||||
const include: any = {
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
serviceSecret: true,
|
||||
minio: true,
|
||||
plausibleAnalytics: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
meiliSearch: true,
|
||||
umami: true // This line!
|
||||
};
|
||||
```
|
||||
|
||||
2. Update the database update query with the new service type to `configureServiceType` function in [apps/api/src/lib/services/common.ts](apps/api/src/lib/services/common.ts). This function defines the automatically generated variables (passwords, users, etc.) and it's encryption process (if applicable).
|
||||
|
||||
```js
|
||||
[...]
|
||||
else if (type === 'umami') {
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword());
|
||||
const postgresqlDatabase = 'umami';
|
||||
const hashSalt = encrypt(generatePassword(64));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
umami: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlPassword,
|
||||
postgresqlUser,
|
||||
hashSalt,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
3. Add field details to [apps/api/src/lib/services/serviceFields.ts](apps/api/src/lib/services/serviceFields.ts), so every component will know what to do with the values (decrypt/show it by default/readonly)
|
||||
|
||||
```js
|
||||
export const umami = [{
|
||||
name: 'postgresqlUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
}]
|
||||
```
|
||||
|
||||
4. Add service deletion query to `removeService` function in [apps/api/src/lib/services/common.ts](apps/api/src/lib/services/common.ts)
|
||||
|
||||
5. Add start process for the new service in [apps/api/src/lib/services/handlers.ts](apps/api/src/lib/services/handlers.ts)
|
||||
|
||||
> See startUmamiService() function as example.
|
||||
|
||||
6. Add the newly added start process to `startService` in [apps/api/src/routes/api/v1/services/handlers.ts](apps/api/src/routes/api/v1/services/handlers.ts)
|
||||
|
||||
7. You need to add a custom logo at [apps/ui/src/lib/components/svg/services](apps/ui/src/lib/components/svg/services) as a svelte component and export it in [apps/ui/src/lib/components/svg/services/index.ts](apps/ui/src/lib/components/svg/services/index.ts)
|
||||
|
||||
SVG is recommended, but you can use PNG as well. It should have the `isAbsolute` variable with the suitable CSS classes, primarily for sizing and positioning.
|
||||
|
||||
8. You need to include it the logo at:
|
||||
|
||||
- [apps/ui/src/lib/components/svg/services/ServiceIcons.svelte](apps/ui/src/lib/components/svg/services/ServiceIcons.svelte) with `isAbsolute`.
|
||||
- [apps/ui/src/routes/services/[id]/_ServiceLinks.svelte](apps/ui/src/routes/services/[id]/_ServiceLinks.svelte) with the link to the docs/main site of the service
|
||||
|
||||
9. By default the URL and the name frontend forms are included in [apps/ui/src/routes/services/[id]/_Services/_Services.svelte](apps/ui/src/routes/services/[id]/_Services/_Services.svelte).
|
||||
|
||||
If you need to show more details on the frontend, such as users/passwords, you need to add Svelte component to [apps/ui/src/routes/services/[id]/_Services](apps/ui/src/routes/services/[id]/_Services) with an underscore.
|
||||
|
||||
> For example, see other [here](apps/ui/src/routes/services/[id]/_Services/_Umami.svelte).
|
||||
|
||||
|
||||
Good job! 👏
|
||||
|
||||
<!-- # 🌐 Translate the project
|
||||
|
||||
The project use [sveltekit-i18n](https://github.com/sveltekit-i18n/lib) to translate the project.
|
||||
It follows the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to name languages.
|
||||
|
||||
### Installation
|
||||
|
||||
You must have gone throw all the [intro](#introduction) steps before you can start translating.
|
||||
|
||||
It's only an advice, but I recommend you to use:
|
||||
|
||||
- Visual Studio Code
|
||||
- [i18n Ally for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally): ideal to see the progress of the translation.
|
||||
- [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode): to get the syntax color for the project
|
||||
|
||||
### Adding a language
|
||||
|
||||
If your language doesn't appear in the [locales folder list](src/lib/locales/), follow the step below:
|
||||
|
||||
1. In `src/lib/locales/`, Copy paste `en.json` and rename it with your language (eg: `cz.json`).
|
||||
2. In the [lang.json](src/lib/lang.json) file, add a line after the first bracket (`{`) with `"ISO of your language": "Language",` (eg: `"cz": "Czech",`).
|
||||
3. Have fun translating! -->
|
||||
29
CONTRIBUTION.md
Normal file
29
CONTRIBUTION.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Contributing
|
||||
|
||||
> "First, thanks for considering to contribute to my project.
|
||||
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
|
||||
|
||||
You can ask for guidance anytime on our
|
||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
||||
|
||||
|
||||
## 1) Setup your development environment
|
||||
|
||||
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
||||
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
||||
|
||||
## 2) Set your environment variables
|
||||
|
||||
- Copy [.env.development.example](./.env.development.example) to .env.
|
||||
|
||||
## 3) Start & setup Coolify
|
||||
|
||||
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
|
||||
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
|
||||
|
||||
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
|
||||
|
||||
## 4) Start development
|
||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
||||
|
||||
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
||||
@@ -1,108 +0,0 @@
|
||||
---
|
||||
head:
|
||||
- - meta
|
||||
- name: description
|
||||
content: Coolify - Databases
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: databases coollabs coolify
|
||||
- - meta
|
||||
- name: twitter:card
|
||||
content: summary_large_image
|
||||
- - meta
|
||||
- name: twitter:site
|
||||
content: '@andrasbacsai'
|
||||
- - meta
|
||||
- name: twitter:title
|
||||
content: Coolify
|
||||
- - meta
|
||||
- name: twitter:description
|
||||
content: An open-source & self-hostable Heroku / Netlify alternative.
|
||||
- - meta
|
||||
- name: twitter:image
|
||||
content: https://cdn.coollabs.io/assets/coollabs/og-image-databases.png
|
||||
- - meta
|
||||
- property: og:type
|
||||
content: website
|
||||
- - meta
|
||||
- property: og:url
|
||||
content: https://coolify.io
|
||||
- - meta
|
||||
- property: og:title
|
||||
content: Coolify
|
||||
- - meta
|
||||
- property: og:description
|
||||
content: An open-source & self-hostable Heroku / Netlify alternative.
|
||||
- - meta
|
||||
- property: og:site_name
|
||||
content: Coolify
|
||||
- - meta
|
||||
- property: og:image
|
||||
content: https://cdn.coollabs.io/assets/coollabs/og-image-databases.png
|
||||
---
|
||||
# Contribution
|
||||
|
||||
First, thanks for considering to contribute to my project. It really means a lot! :)
|
||||
|
||||
You can ask for guidance anytime on our Discord server in the #contribution channel.
|
||||
|
||||
## Setup your development environment
|
||||
### Github codespaces
|
||||
|
||||
If you have github codespaces enabled then you can just create a codespace and run `pnpm dev` to run your the dev environment. All the required dependencies and packages has been configured for you already.
|
||||
|
||||
### Gitpod
|
||||
|
||||
If you have a [Gitpod](https://gitpod.io), you can just create a workspace from this repository, run `pnpm install && pnpm db:push && pnpm db:seed` and then `pnpm dev`. All the required dependencies and packages has been configured for you already.
|
||||
|
||||
### Local Machine
|
||||
> At the moment, Coolify `doesn't support Windows`. You must use `Linux` or `MacOS` or consider using Gitpod or Github Codespaces.
|
||||
|
||||
- Due to the lock file, this repository is best with [pnpm](https://pnpm.io). I recommend you try and use `pnpm` because it is cool and efficient!
|
||||
|
||||
- You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
|
||||
- You need to have [Docker Compose Plugin](https://docs.docker.com/compose/install/compose-plugin/) installed locally.
|
||||
- You need to have [GIT LFS Support](https://git-lfs.github.com/) installed locally.
|
||||
|
||||
Optional:
|
||||
- To test Heroku buildpacks, you need [pack](https://github.com/buildpacks/pack) binary installed locally.
|
||||
|
||||
### Inside a Docker container
|
||||
`WIP`
|
||||
|
||||
## Setup Coolify
|
||||
- Copy `apps/api/.env.template` to `apps/api/.env.template` and set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||
- `pnpm install` to install dependencies.
|
||||
- `pnpm db:push` to o create a local SQlite database.
|
||||
|
||||
This will apply all migrations at `db/dev.db`.
|
||||
|
||||
- `pnpm db:seed` seed the database.
|
||||
- `pnpm dev` start coding.
|
||||
|
||||
## Technical skills required
|
||||
|
||||
- **Languages**: Node.js / Javascript / Typescript
|
||||
- **Framework JS/TS**: [SvelteKit](https://kit.svelte.dev/) & [Fastify](https://www.fastify.io/)
|
||||
- **Database ORM**: [Prisma.io](https://www.prisma.io/)
|
||||
- **Docker Engine API**
|
||||
|
||||
## Add a new service
|
||||
### Which service is eligable to add to Coolify?
|
||||
The following statements needs to be true:
|
||||
|
||||
- Self-hostable
|
||||
- Open-source
|
||||
- Maintained (I do not want to add software full of bugs)
|
||||
|
||||
### Create Prisma / Database schema for the new service.
|
||||
All data that needs to be persist for a service should be saved to the database in `cleartext` or `encrypted`.
|
||||
|
||||
very password/api key/passphrase needs to be encrypted. If you are not sure, whether it should be encrypted or not, just encrypt it.
|
||||
|
||||
Update Prisma schema in [src/api/prisma/schema.prisma](https://github.com/coollabsio/coolify/blob/main/apps/api/prisma/schema.prisma).
|
||||
|
||||
- Add new model with the new service name.
|
||||
- Make a relationship with `Service` model.
|
||||
- In the `Service` model, the name of the new field should be with low-capital.
|
||||
- If the service needs a database, define a `publicPort` field to be able to make it's database public, example field name in case of PostgreSQL: `postgresqlPublicPort`. It should be a optional field.
|
||||
45
Dockerfile
45
Dockerfile
@@ -1,45 +0,0 @@
|
||||
ARG PNPM_VERSION=7.11.0
|
||||
ARG NPM_VERSION=8.19.1
|
||||
|
||||
FROM node:18-slim as build
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt update && apt -y install curl
|
||||
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||
|
||||
COPY . .
|
||||
RUN pnpm install
|
||||
RUN pnpm build
|
||||
|
||||
# Production build
|
||||
FROM node:18-slim
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV production
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3
|
||||
RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/
|
||||
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||
RUN npm install -g npm@${PNPM_VERSION}
|
||||
|
||||
RUN mkdir -p ~/.docker/cli-plugins/
|
||||
# https://download.docker.com/linux/static/stable/
|
||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
|
||||
# https://github.com/docker/compose/releases
|
||||
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.6.1 -o ~/.docker/cli-plugins/docker-compose
|
||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
||||
|
||||
RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack)
|
||||
|
||||
COPY --from=build /app/apps/api/build/ .
|
||||
COPY --from=build /app/apps/ui/build/ ./public
|
||||
COPY --from=build /app/apps/api/prisma/ ./prisma
|
||||
COPY --from=build /app/apps/api/package.json .
|
||||
COPY --from=build /app/docker-compose.yaml .
|
||||
|
||||
RUN pnpm install -p
|
||||
|
||||
EXPOSE 3000
|
||||
ENV CHECKPOINT_DISABLE=1
|
||||
CMD pnpm start
|
||||
146
README.md
146
README.md
@@ -1,121 +1,67 @@
|
||||
# Coolify
|
||||
# About the Project
|
||||
|
||||
An open-source & self-hostable Heroku / Netlify alternative.
|
||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||
|
||||
## Live Demo
|
||||
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.
|
||||
|
||||
https://demo.coolify.io/
|
||||
Image if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
||||
|
||||
(If it is unresponsive, that means someone overloaded the server. 😄)
|
||||
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. 🪄️
|
||||
|
||||
## Feedback
|
||||
For more information, take a look at our landing page [here](https://coolify.io).
|
||||
|
||||
If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
|
||||
> If you are looking for previous (v3) version, it is [here](https://github.com/coollabsio/coolify/tree/v3).
|
||||
|
||||
---
|
||||
# Cloud
|
||||
|
||||
## How to install
|
||||
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
||||
|
||||
For more details goto the [docs](https://docs.coollabs.io/coolify/installation).
|
||||
You can easily attach your own servers, get all the automations, free email notifications, etc.
|
||||
|
||||
Installation is automated with the following command:
|
||||
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
||||
|
||||
# Beta
|
||||
|
||||
The latest version (v4) is still in beta. That does not mean it is unstable. All the features that are available are stable enough be usable in real-life.
|
||||
|
||||
There are hundreds of people using it for managing their client's applications, freelancers, hobbyists, businesses.
|
||||
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
```
|
||||
|
||||
If you would like no questions during installation:
|
||||
|
||||
```bash
|
||||
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Git Sources
|
||||
|
||||
Self-hosted versions also!
|
||||
|
||||
<a href="https://github.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/github.com"></a>
|
||||
<a href="https://gitlab.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/gitlab.com"></a>
|
||||
|
||||
### Destinations
|
||||
|
||||
Deploy your resource to:
|
||||
|
||||
- Local Docker Engine
|
||||
- Remote Docker Engine
|
||||
|
||||
### Applications
|
||||
|
||||
<a href="https://heroku.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/heroku.com"></a>
|
||||
<a href="https://html5.org/">
|
||||
<svg style="width:40px;height:40px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" ><g clip-path="url(#HTML5_Clip0_4)" ><path d="M30.216 0L27.6454 28.7967L16.0907 32L4.56783 28.8012L2 0H30.216Z" fill="#E44D26" /><path d="M16.108 29.5515L25.4447 26.963L27.6415 2.35497H16.108V29.5515Z" fill="#F16529" /><path d="M11.1109 9.4197H16.108V5.88731H7.25053L7.33509 6.83499L8.20327 16.5692H16.108V13.0369H11.4338L11.1109 9.4197Z" fill="#EBEBEB" /><path d="M11.907 18.3354H8.36111L8.856 23.8818L16.0917 25.8904L16.108 25.8859V22.2108L16.0925 22.2149L12.1585 21.1527L11.907 18.3354Z" fill="#EBEBEB" /><path d="M16.0958 16.5692H20.4455L20.0354 21.1504L16.0958 22.2138V25.8887L23.3373 23.8817L23.3904 23.285L24.2205 13.9855L24.3067 13.0369H16.0958V16.5692Z" fill="white" /><path d="M16.0958 9.41105V9.41969H24.6281L24.6989 8.62572L24.8599 6.83499L24.9444 5.88731H16.0958V9.41105Z" fill="white" /></g><defs><clipPath id="HTML5_Clip0_4"><rect width="32" height="32" fill="white" /></clipPath></defs></svg></a>
|
||||
<a href="https://nodejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nodejs.org"></a>
|
||||
<a href="https://vuejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/vuejs.org"></a>
|
||||
<a href="https://nuxtjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nuxtjs.org"></a>
|
||||
<a href="https://nextjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nextjs.org"></a>
|
||||
<a href="https://reactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/reactjs.org"></a>
|
||||
<a href="https://preactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/preactjs.org"></a>
|
||||
<a href="https://gatsbyjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/gatsbyjs.org"></a>
|
||||
<a href="https://svelte.dev"><img style="width:40px;height:40px" src="https://icon.horse/icon/svelte.dev"></a>
|
||||
<a href="https://php.net"><img style="width:40px;height:40px" src="https://icon.horse/icon/php.net"></a>
|
||||
<a href="https://laravel.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/laravel.com"></a>
|
||||
<a href="https://python.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/python.org"></a>
|
||||
<a href="https://deno.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/deno.com"></a>
|
||||
<a href="https://docker.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/docker.com"></a>
|
||||
|
||||
### Databases
|
||||
|
||||
<a href="https://mongodb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mongodb.org"></a>
|
||||
<a href="https://mariadb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mariadb.org"></a>
|
||||
<a href="https://mysql.com"><svg style="width:40px;height:40px" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 25.6 25.6" ><path d="M179.076 94.886c-3.568-.1-6.336.268-8.656 1.25-.668.27-1.74.27-1.828 1.116.357.355.4.936.713 1.428.535.893 1.473 2.096 2.32 2.72l2.855 2.053c1.74 1.07 3.703 1.695 5.398 2.766.982.625 1.963 1.428 2.945 2.098.5.357.803.938 1.428 1.16v-.135c-.312-.4-.402-.98-.713-1.428l-1.34-1.293c-1.293-1.74-2.9-3.258-4.64-4.506-1.428-.982-4.55-2.32-5.13-3.97l-.088-.1c.98-.1 2.14-.447 3.078-.715 1.518-.4 2.9-.312 4.46-.713l2.143-.625v-.4c-.803-.803-1.383-1.874-2.23-2.632-2.275-1.963-4.775-3.882-7.363-5.488-1.383-.892-3.168-1.473-4.64-2.23-.537-.268-1.428-.402-1.74-.848-.805-.98-1.25-2.275-1.83-3.436l-3.658-7.763c-.803-1.74-1.295-3.48-2.275-5.086-4.596-7.585-9.594-12.18-17.268-16.687-1.65-.937-3.613-1.34-5.7-1.83l-3.346-.18c-.715-.312-1.428-1.16-2.053-1.562-2.543-1.606-9.102-5.086-10.977-.5-1.205 2.9 1.785 5.755 2.8 7.228.76 1.026 1.74 2.186 2.277 3.346.3.758.4 1.562.713 2.365.713 1.963 1.383 4.15 2.32 5.98.5.937 1.025 1.92 1.65 2.767.357.5.982.714 1.115 1.517-.625.893-.668 2.23-1.025 3.347-1.607 5.042-.982 11.288 1.293 15 .715 1.115 2.4 3.57 4.686 2.632 2.008-.803 1.56-3.346 2.14-5.577.135-.535.045-.892.312-1.25v.1l1.83 3.703c1.383 2.186 3.793 4.462 5.8 5.98 1.07.803 1.918 2.187 3.256 2.677v-.135h-.088c-.268-.4-.67-.58-1.027-.892-.803-.803-1.695-1.785-2.32-2.677-1.873-2.498-3.523-5.265-4.996-8.12-.715-1.383-1.34-2.9-1.918-4.283-.27-.536-.27-1.34-.715-1.606-.67.98-1.65 1.83-2.143 3.034-.848 1.918-.936 4.283-1.248 6.737-.18.045-.1 0-.18.1-1.426-.356-1.918-1.83-2.453-3.078-1.338-3.168-1.562-8.254-.402-11.913.312-.937 1.652-3.882 1.117-4.774-.27-.848-1.16-1.338-1.652-2.008-.58-.848-1.203-1.918-1.605-2.855-1.07-2.5-1.605-5.265-2.766-7.764-.537-1.16-1.473-2.365-2.232-3.435-.848-1.205-1.783-2.053-2.453-3.48-.223-.5-.535-1.294-.178-1.83.088-.357.268-.5.623-.58.58-.5 2.232.134 2.812.4 1.65.67 3.033 1.294 4.416 2.23.625.446 1.295 1.294 2.098 1.518h.938c1.428.312 3.033.1 4.37.5 2.365.76 4.506 1.874 6.426 3.08 5.844 3.703 10.664 8.968 13.92 15.26.535 1.026.758 1.963 1.25 3.034.938 2.187 2.098 4.417 3.033 6.56.938 2.097 1.83 4.24 3.168 5.98.67.937 3.346 1.427 4.55 1.918.893.4 2.275.76 3.08 1.25 1.516.937 3.033 2.008 4.46 3.034.713.534 2.945 1.65 3.078 2.54zm-45.5-38.772a7.09 7.09 0 0 0-1.828.223v.1h.088c.357.714.982 1.205 1.428 1.83l1.027 2.142.088-.1c.625-.446.938-1.16.938-2.23-.268-.312-.312-.625-.535-.937-.268-.446-.848-.67-1.206-1.026z" transform="matrix(.390229 0 0 .38781 -46.300037 -16.856717)" fill-rule="evenodd" fill="#00678c" /></svg></a>
|
||||
<a href="https://postgresql.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/postgresql.org"></a>
|
||||
<a href="https://couchdb.apache.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/couchdb.apache.org"></a>
|
||||
<a href="https://redis.io"><svg style="width:40px;height:40px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><defs ><path id="a" d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" /><path id="b" d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z" /></defs ><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)" ><use fill="#a41e11" xlink:href="#a" /><path d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z" fill="#d82c20" /><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" fill="#a41e11" /><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff" ><path d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z" /><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g ><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z" fill="#ad2115" /></g ></svg ></a>
|
||||
|
||||
### Services
|
||||
- [Appwrite](https://appwrite.io)
|
||||
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
||||
- [Ghost](https://ghost.org)
|
||||
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
||||
- [NocoDB](https://nocodb.com)
|
||||
- [VSCode Server](https://github.com/cdr/code-server)
|
||||
- [MinIO](https://min.io)
|
||||
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
|
||||
- [LanguageTool](https://languagetool.org)
|
||||
- [n8n](https://n8n.io)
|
||||
- [Uptime Kuma](https://github.com/louislam/uptime-kuma)
|
||||
- [MeiliSearch](https://github.com/meilisearch/meilisearch)
|
||||
- [Umami](https://github.com/mikecao/umami)
|
||||
- [Fider](https://fider.io)
|
||||
- [Hasura](https://hasura.io)
|
||||
- [GlitchTip](https://glitchtip.com)
|
||||
|
||||
## Migration from v1
|
||||
|
||||
A fresh installation is necessary. v2 and v3 are not compatible with v1.
|
||||
You can find the installation script [here](./scripts/install.sh).
|
||||
|
||||
## Support
|
||||
|
||||
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
||||
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
|
||||
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
||||
- Discord: [Invitation](https://coollabs.io/discord)
|
||||
Contact us [here](https://coolify.io/contact).
|
||||
|
||||
## Financial Contributors
|
||||
## Recognitions
|
||||
|
||||
<a href="https://news.ycombinator.com/item?id=26624341">
|
||||
<img
|
||||
style="width: 250px; height: 54px;" width="250" height="54"
|
||||
alt="Featured on Hacker News"
|
||||
src="https://hackernews-badge.vercel.app/api?id=26624341"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## 💰 Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
||||
|
||||
### Individuals
|
||||
|
||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||
|
||||
### Organizations
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website.
|
||||
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
|
||||
|
||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="appwrite logo" width="200"/></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website.
|
||||
|
||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
|
||||
@@ -126,4 +72,12 @@ Support this project with your organization. Your logo will show up here with a
|
||||
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||
|
||||
### Individuals
|
||||
|
||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#coollabsio/coolify&Date)
|
||||
|
||||
47
app/Actions/CoolifyTask/PrepareCoolifyTask.php
Normal file
47
app/Actions/CoolifyTask/PrepareCoolifyTask.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CoolifyTask;
|
||||
|
||||
use App\Data\CoolifyTaskArgs;
|
||||
use App\Jobs\CoolifyTask;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
/**
|
||||
* The initial step to run a `CoolifyTask`: a remote SSH process
|
||||
* with monitoring/tracking/trace feature. Such thing is made
|
||||
* possible using an Activity model and some attributes.
|
||||
*/
|
||||
class PrepareCoolifyTask
|
||||
{
|
||||
protected Activity $activity;
|
||||
protected CoolifyTaskArgs $remoteProcessArgs;
|
||||
|
||||
public function __construct(CoolifyTaskArgs $remoteProcessArgs)
|
||||
{
|
||||
$this->remoteProcessArgs = $remoteProcessArgs;
|
||||
|
||||
if ($remoteProcessArgs->model) {
|
||||
$properties = $remoteProcessArgs->toArray();
|
||||
unset($properties['model']);
|
||||
|
||||
$this->activity = activity()
|
||||
->withProperties($properties)
|
||||
->performedOn($remoteProcessArgs->model)
|
||||
->event($remoteProcessArgs->type)
|
||||
->log("[]");
|
||||
} else {
|
||||
$this->activity = activity()
|
||||
->withProperties($remoteProcessArgs->toArray())
|
||||
->event($remoteProcessArgs->type)
|
||||
->log("[]");
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(): Activity
|
||||
{
|
||||
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors);
|
||||
dispatch($job);
|
||||
$this->activity->refresh();
|
||||
return $this->activity;
|
||||
}
|
||||
}
|
||||
175
app/Actions/CoolifyTask/RunRemoteProcess.php
Normal file
175
app/Actions/CoolifyTask/RunRemoteProcess.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CoolifyTask;
|
||||
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Process\ProcessResult;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class RunRemoteProcess
|
||||
{
|
||||
public Activity $activity;
|
||||
|
||||
public bool $hide_from_output;
|
||||
|
||||
public bool $is_finished;
|
||||
|
||||
public bool $ignore_errors;
|
||||
|
||||
protected $time_start;
|
||||
|
||||
protected $current_time;
|
||||
|
||||
protected $last_write_at = 0;
|
||||
|
||||
protected $throttle_interval_ms = 500;
|
||||
|
||||
protected int $counter = 1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(Activity $activity, bool $hide_from_output = false, bool $is_finished = false, bool $ignore_errors = false)
|
||||
{
|
||||
|
||||
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
|
||||
throw new \RuntimeException('Incompatible Activity to run a remote command.');
|
||||
}
|
||||
|
||||
$this->activity = $activity;
|
||||
$this->hide_from_output = $hide_from_output;
|
||||
$this->is_finished = $is_finished;
|
||||
$this->ignore_errors = $ignore_errors;
|
||||
}
|
||||
|
||||
public static function decodeOutput(?Activity $activity = null): string
|
||||
{
|
||||
if (is_null($activity)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
$decoded = json_decode(
|
||||
data_get($activity, 'description'),
|
||||
associative: true,
|
||||
flags: JSON_THROW_ON_ERROR
|
||||
);
|
||||
} catch (\JsonException $exception) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return collect($decoded)
|
||||
->sortBy(fn ($i) => $i['order'])
|
||||
->map(fn ($i) => $i['output'])
|
||||
->implode("");
|
||||
}
|
||||
|
||||
public function __invoke(): ProcessResult
|
||||
{
|
||||
$this->time_start = hrtime(true);
|
||||
|
||||
$status = ProcessStatus::IN_PROGRESS;
|
||||
$processResult = Process::forever()->run($this->getCommand(), $this->handleOutput(...));
|
||||
|
||||
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
|
||||
$status = ProcessStatus::ERROR;
|
||||
} else {
|
||||
if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
|
||||
$status = ProcessStatus::FINISHED;
|
||||
}
|
||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
||||
$status = ProcessStatus::ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
$this->activity->properties = $this->activity->properties->merge([
|
||||
'exitCode' => $processResult->exitCode(),
|
||||
'stdout' => $processResult->output(),
|
||||
'stderr' => $processResult->errorOutput(),
|
||||
'status' => $status->value,
|
||||
]);
|
||||
$this->activity->save();
|
||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
||||
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
|
||||
}
|
||||
|
||||
return $processResult;
|
||||
}
|
||||
|
||||
protected function getCommand(): string
|
||||
{
|
||||
$server_uuid = $this->activity->getExtraProperty('server_uuid');
|
||||
$command = $this->activity->getExtraProperty('command');
|
||||
$server = Server::whereUuid($server_uuid)->firstOrFail();
|
||||
|
||||
return generateSshCommand($server, $command);
|
||||
}
|
||||
|
||||
protected function handleOutput(string $type, string $output)
|
||||
{
|
||||
if ($this->hide_from_output) {
|
||||
return;
|
||||
}
|
||||
$this->current_time = $this->elapsedTime();
|
||||
$this->activity->description = $this->encodeOutput($type, $output);
|
||||
|
||||
if ($this->isAfterLastThrottle()) {
|
||||
// Let's write to database.
|
||||
DB::transaction(function () {
|
||||
$this->activity->save();
|
||||
$this->last_write_at = $this->current_time;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected function elapsedTime(): int
|
||||
{
|
||||
$timeMs = (hrtime(true) - $this->time_start) / 1_000_000;
|
||||
|
||||
return intval($timeMs);
|
||||
}
|
||||
|
||||
public function encodeOutput($type, $output)
|
||||
{
|
||||
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
$outputStack[] = [
|
||||
'type' => $type,
|
||||
'output' => $output,
|
||||
'timestamp' => hrtime(true),
|
||||
'batch' => ApplicationDeploymentJob::$batch_counter,
|
||||
'order' => $this->getLatestCounter(),
|
||||
];
|
||||
|
||||
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
protected function getLatestCounter(): int
|
||||
{
|
||||
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
if ($description === null || count($description) === 0) {
|
||||
return 1;
|
||||
}
|
||||
return end($description)['order'] + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if it's time to write again to database.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAfterLastThrottle()
|
||||
{
|
||||
// If DB was never written, then we immediately decide we have to write.
|
||||
if ($this->last_write_at === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ($this->current_time - $this->throttle_interval_ms) > $this->last_write_at;
|
||||
}
|
||||
}
|
||||
166
app/Actions/Database/StartPostgresql.php
Normal file
166
app/Actions/Database/StartPostgresql.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class StartPostgresql
|
||||
{
|
||||
public StandalonePostgresql $database;
|
||||
public array $commands = [];
|
||||
public array $init_scripts = [];
|
||||
public string $configuration_dir;
|
||||
|
||||
public function __invoke(Server $server, StandalonePostgresql $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo '####### Starting {$database->name}.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->generate_init_scripts();
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
'container_name' => $container_name,
|
||||
'environment' => $environment_variables,
|
||||
'restart' => RESTART_MODE,
|
||||
'networks' => [
|
||||
$this->database->destination->network,
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
'pg_isready',
|
||||
'-d',
|
||||
$this->database->postgres_db,
|
||||
'-U',
|
||||
$this->database->postgres_user,
|
||||
],
|
||||
'interval' => '5s',
|
||||
'timeout' => '5s',
|
||||
'retries' => 10,
|
||||
'start_period' => '5s'
|
||||
],
|
||||
'mem_limit' => $this->database->limits_memory,
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => $this->database->limits_cpus,
|
||||
'cpuset' => $this->database->limits_cpuset,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$this->database->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $this->database->destination->network,
|
||||
'attachable' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
if (count($this->database->ports_mappings_array) > 0) {
|
||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||
}
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
if (count($this->init_scripts) > 0) {
|
||||
foreach ($this->init_scripts as $init_script) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $init_script,
|
||||
'target' => '/docker-entrypoint-initdb.d/' . basename($init_script),
|
||||
'read_only' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||
$readme = generate_readme_file($this->database->name, now());
|
||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||
return remote_process($this->commands, $server);
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes_only_volume_names()
|
||||
{
|
||||
$local_persistent_volumes_names = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path) {
|
||||
continue;
|
||||
}
|
||||
$name = $persistentStorage->name;
|
||||
$local_persistent_volumes_names[$name] = [
|
||||
'name' => $name,
|
||||
'external' => false,
|
||||
];
|
||||
}
|
||||
return $local_persistent_volumes_names;
|
||||
}
|
||||
|
||||
private function generate_environment_variables()
|
||||
{
|
||||
$environment_variables = collect();
|
||||
ray('Generate Environment Variables')->green();
|
||||
ray($this->database->runtime_environment_variables)->green();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
||||
}
|
||||
return $environment_variables->all();
|
||||
}
|
||||
|
||||
private function generate_init_scripts()
|
||||
{
|
||||
if (is_null($this->database->init_scripts) || count($this->database->init_scripts) === 0) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->database->init_scripts as $init_script) {
|
||||
$filename = data_get($init_script, 'filename');
|
||||
$content = data_get($init_script, 'content');
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
||||
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
||||
}
|
||||
}
|
||||
}
|
||||
71
app/Actions/Fortify/CreateNewUser.php
Normal file
71
app/Actions/Fortify/CreateNewUser.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||
|
||||
class CreateNewUser implements CreatesNewUsers
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and create a newly registered user.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function create(array $input): User
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
if (!$settings->is_registration_enabled) {
|
||||
abort(403);
|
||||
}
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique(User::class),
|
||||
],
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
if (User::count() == 0) {
|
||||
// If this is the first user, make them the root user
|
||||
// Team is already created in the database/seeders/ProductionSeeder.php
|
||||
$user = User::create([
|
||||
'id' => 0,
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => Hash::make($input['password']),
|
||||
]);
|
||||
$team = $user->teams()->first();
|
||||
|
||||
// Disable registration after first user is created
|
||||
$settings = InstanceSettings::get();
|
||||
$settings->is_registration_enabled = false;
|
||||
$settings->save();
|
||||
} else {
|
||||
$user = User::create([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => Hash::make($input['password']),
|
||||
]);
|
||||
$team = $user->teams()->first();
|
||||
if (isCloud()) {
|
||||
$user->sendVerificationEmail();
|
||||
} else {
|
||||
$user->markEmailAsVerified();
|
||||
}
|
||||
}
|
||||
// Set session variable
|
||||
session(['currentTeam' => $user->currentTeam = $team]);
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
18
app/Actions/Fortify/PasswordValidationRules.php
Normal file
18
app/Actions/Fortify/PasswordValidationRules.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use Laravel\Fortify\Rules\Password;
|
||||
|
||||
trait PasswordValidationRules
|
||||
{
|
||||
/**
|
||||
* Get the validation rules used to validate passwords.
|
||||
*
|
||||
* @return array<int, \Illuminate\Contracts\Validation\Rule|array|string>
|
||||
*/
|
||||
protected function passwordRules(): array
|
||||
{
|
||||
return ['required', 'string', new Password, 'confirmed'];
|
||||
}
|
||||
}
|
||||
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||
|
||||
class ResetUserPassword implements ResetsUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and reset the user's forgotten password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function reset(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
32
app/Actions/Fortify/UpdateUserPassword.php
Normal file
32
app/Actions/Fortify/UpdateUserPassword.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
|
||||
|
||||
class UpdateUserPassword implements UpdatesUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and update the user's password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'current_password' => ['required', 'string', 'current_password:web'],
|
||||
'password' => $this->passwordRules(),
|
||||
], [
|
||||
'current_password.current_password' => __('The provided password does not match your current password.'),
|
||||
])->validateWithBag('updatePassword');
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
60
app/Actions/Fortify/UpdateUserProfileInformation.php
Normal file
60
app/Actions/Fortify/UpdateUserProfileInformation.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
|
||||
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
{
|
||||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users')->ignore($user->id),
|
||||
],
|
||||
])->validateWithBag('updateProfileInformation');
|
||||
|
||||
if (
|
||||
$input['email'] !== $user->email &&
|
||||
$user instanceof MustVerifyEmail
|
||||
) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given verified user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
protected function updateVerifiedUser(User $user, array $input): void
|
||||
{
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'email_verified_at' => null,
|
||||
])->save();
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
}
|
||||
}
|
||||
69
app/Actions/License/CheckResaleLicense.php
Normal file
69
app/Actions/License/CheckResaleLicense.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\License;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class CheckResaleLicense
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
$settings->update([
|
||||
'is_resale_license_active' => false,
|
||||
]);
|
||||
if (isDev()) {
|
||||
return;
|
||||
}
|
||||
if (!$settings->resale_license) {
|
||||
return;
|
||||
}
|
||||
$base_url = config('coolify.license_url');
|
||||
if (isDev()) {
|
||||
$base_url = 'http://host.docker.internal:8787';
|
||||
}
|
||||
$instance_id = config('app.id');
|
||||
|
||||
ray("Checking license key against $base_url/lemon/validate");
|
||||
$data = Http::withHeaders([
|
||||
'Accept' => 'application/json',
|
||||
])->get("$base_url/lemon/validate", [
|
||||
'license_key' => $settings->resale_license,
|
||||
'instance_id' => $instance_id,
|
||||
])->json();
|
||||
if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') {
|
||||
ray('Valid & active license key');
|
||||
$settings->update([
|
||||
'is_resale_license_active' => true,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
$data = Http::withHeaders([
|
||||
'Accept' => 'application/json',
|
||||
])->get("$base_url/lemon/activate", [
|
||||
'license_key' => $settings->resale_license,
|
||||
'instance_id' => $instance_id,
|
||||
])->json();
|
||||
if (data_get($data, 'activated') === true) {
|
||||
ray('Activated license key');
|
||||
$settings->update([
|
||||
'is_resale_license_active' => true,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
if (data_get($data, 'license_key.status') === 'active') {
|
||||
throw new \Exception('Invalid license key.');
|
||||
}
|
||||
throw new \Exception('Cannot activate license key.');
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
$settings->update([
|
||||
'resale_license' => null,
|
||||
'is_resale_license_active' => false,
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
app/Actions/Proxy/CheckConfiguration.php
Normal file
28
app/Actions/Proxy/CheckConfiguration.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CheckConfiguration
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server, bool $reset = false)
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_configuration = instant_remote_process([
|
||||
"mkdir -p $proxy_path",
|
||||
"cat $proxy_path/docker-compose.yml",
|
||||
], $server, false);
|
||||
|
||||
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
|
||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
||||
}
|
||||
if (!$proxy_configuration || is_null($proxy_configuration)) {
|
||||
throw new \Exception("Could not generate proxy configuration");
|
||||
}
|
||||
return $proxy_configuration;
|
||||
}
|
||||
}
|
||||
29
app/Actions/Proxy/SaveConfiguration.php
Normal file
29
app/Actions/Proxy/SaveConfiguration.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class SaveConfiguration
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server, ?string $proxy_settings = null)
|
||||
{
|
||||
if (is_null($proxy_settings)) {
|
||||
$proxy_settings = CheckConfiguration::run($server, true);
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||
|
||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
|
||||
return instant_remote_process([
|
||||
"mkdir -p $proxy_path",
|
||||
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
|
||||
], $server);
|
||||
}
|
||||
}
|
||||
65
app/Actions/Proxy/StartProxy.php
Normal file
65
app/Actions/Proxy/StartProxy.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class StartProxy
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server, bool $async = true): Activity|string
|
||||
{
|
||||
$commands = collect([]);
|
||||
$proxyType = $server->proxyType();
|
||||
if ($proxyType === 'none') {
|
||||
return 'OK';
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$configuration = CheckConfiguration::run($server);
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
}
|
||||
SaveConfiguration::run($server, $configuration);
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
|
||||
$commands = $commands->merge([
|
||||
"apt-get update > /dev/null 2>&1 || true",
|
||||
"command -v lsof >/dev/null || echo '####### Installing lsof.'",
|
||||
"command -v lsof >/dev/null || apt install -y lsof",
|
||||
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo '####### Creating Docker Compose file.'",
|
||||
"echo '####### Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
"echo '####### Stopping existing coolify-proxy.'",
|
||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||
"command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
|
||||
"command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
|
||||
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
|
||||
"command -v fuser >/dev/null && fuser -k 80/tcp || true",
|
||||
"command -v fuser >/dev/null && fuser -k 443/tcp || true",
|
||||
"systemctl disable nginx > /dev/null 2>&1 || true",
|
||||
"systemctl disable apache2 > /dev/null 2>&1 || true",
|
||||
"systemctl disable apache > /dev/null 2>&1 || true",
|
||||
"echo '####### Starting coolify-proxy.'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo '####### Proxy installed successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
} else {
|
||||
instant_remote_process($commands, $server);
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->proxy->set('type', $proxyType);
|
||||
$server->save();
|
||||
return 'OK';
|
||||
}
|
||||
}
|
||||
}
|
||||
62
app/Actions/Server/InstallDocker.php
Normal file
62
app/Actions/Server/InstallDocker.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
|
||||
class InstallDocker
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server)
|
||||
{
|
||||
$dockerVersion = '24.0';
|
||||
$config = base64_encode('{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "10m",
|
||||
"max-file": "3"
|
||||
}
|
||||
}');
|
||||
$found = StandaloneDocker::where('server_id', $server->id);
|
||||
if ($found->count() == 0 && $server->id) {
|
||||
StandaloneDocker::create([
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
}
|
||||
|
||||
if (isDev() && $server->id === 0) {
|
||||
$command = [
|
||||
"echo '####### Installing Prerequisites...'",
|
||||
"sleep 1",
|
||||
"echo '####### Installing/updating Docker Engine...'",
|
||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||
"sleep 4",
|
||||
"echo '####### Restarting Docker Engine...'",
|
||||
"ls -l /tmp"
|
||||
];
|
||||
} else {
|
||||
$command = [
|
||||
"echo '####### Installing Prerequisites...'",
|
||||
"command -v jq >/dev/null || apt-get update",
|
||||
"command -v jq >/dev/null || apt install -y jq",
|
||||
"echo '####### Installing/updating Docker Engine...'",
|
||||
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh",
|
||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
||||
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
||||
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
|
||||
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
|
||||
"echo '####### Restarting Docker Engine...'",
|
||||
"systemctl restart docker",
|
||||
"echo '####### Creating default Docker network (coolify)...'",
|
||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||
"echo '####### Done!'"
|
||||
];
|
||||
}
|
||||
return remote_process($command, $server);
|
||||
}
|
||||
}
|
||||
71
app/Actions/Server/UpdateCoolify.php
Normal file
71
app/Actions/Server/UpdateCoolify.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
|
||||
class UpdateCoolify
|
||||
{
|
||||
public ?Server $server = null;
|
||||
public ?string $latestVersion = null;
|
||||
public ?string $currentVersion = null;
|
||||
|
||||
public function __invoke(bool $force)
|
||||
{
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
ray('Running InstanceAutoUpdateJob');
|
||||
$this->server = Server::find(0)->first();
|
||||
if (!$this->server) {
|
||||
return;
|
||||
}
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
|
||||
if ($settings->next_channel) {
|
||||
ray('next channel enabled');
|
||||
$this->latestVersion = 'next';
|
||||
}
|
||||
if ($force) {
|
||||
$this->update();
|
||||
} else {
|
||||
if (!$settings->is_auto_update_enabled) {
|
||||
return 'Auto update is disabled';
|
||||
}
|
||||
if ($this->latestVersion === $this->currentVersion) {
|
||||
return 'Already on latest version';
|
||||
}
|
||||
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
|
||||
return 'Latest version is lower than current version?!';
|
||||
}
|
||||
$this->update();
|
||||
}
|
||||
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
|
||||
} catch (\Throwable $e) {
|
||||
ray('InstanceAutoUpdateJob failed');
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function update()
|
||||
{
|
||||
if (isDev()) {
|
||||
ray("Running update on local docker container. Updating to $this->latestVersion");
|
||||
remote_process([
|
||||
"sleep 10"
|
||||
], $this->server);
|
||||
ray('Update done');
|
||||
return;
|
||||
} else {
|
||||
ray('Running update on production server');
|
||||
remote_process([
|
||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
||||
], $this->server);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
app/Actions/Service/StartService.php
Normal file
34
app/Actions/Service/StartService.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class StartService
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$network = $service->destination->network;
|
||||
$service->saveComposeConfigs();
|
||||
$commands[] = "cd " . $service->workdir();
|
||||
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
|
||||
$commands[] = "echo '####### Creating Docker network.'";
|
||||
$commands[] = "docker network create --attachable {$service->uuid} >/dev/null 2>/dev/null || true";
|
||||
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
|
||||
$commands[] = "echo '####### Pulling images.'";
|
||||
$commands[] = "docker compose pull";
|
||||
$commands[] = "echo '####### Starting containers.'";
|
||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate";
|
||||
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
|
||||
$compose = data_get($service,'docker_compose',[]);
|
||||
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
|
||||
foreach($serviceNames as $serviceName => $serviceConfig){
|
||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} 2>/dev/null || true";
|
||||
}
|
||||
$activity = remote_process($commands, $service->server);
|
||||
return $activity;
|
||||
}
|
||||
}
|
||||
26
app/Actions/Service/StopService.php
Normal file
26
app/Actions/Service/StopService.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
|
||||
class StopService
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/Cloud.php
Normal file
33
app/Console/Commands/Cloud.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Cloud extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'cloud:unused-servers';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Get Unused Servers from Cloud';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
|
||||
$this->info($server->name);
|
||||
});
|
||||
}
|
||||
}
|
||||
280
app/Console/Commands/Emails.php
Normal file
280
app/Console/Commands/Emails.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use App\Notifications\Application\DeploymentFailed;
|
||||
use App\Notifications\Application\DeploymentSuccess;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use App\Notifications\Database\BackupFailed;
|
||||
use App\Notifications\Database\BackupSuccess;
|
||||
use App\Notifications\Test;
|
||||
use App\Notifications\TransactionalEmails\InvitationLink;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Mail;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\select;
|
||||
use function Laravel\Prompts\text;
|
||||
|
||||
class Emails extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'emails';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send out test / prod emails';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
private ?MailMessage $mail = null;
|
||||
private ?string $email = null;
|
||||
public function handle()
|
||||
{
|
||||
$type = select(
|
||||
'Which Email should be sent?',
|
||||
options: [
|
||||
'updates' => 'Send Update Email to all users',
|
||||
'emails-test' => 'Test',
|
||||
'application-deployment-success' => 'Application - Deployment Success',
|
||||
'application-deployment-failed' => 'Application - Deployment Failed',
|
||||
'application-status-changed' => 'Application - Status Changed',
|
||||
'backup-success' => 'Database - Backup Success',
|
||||
'backup-failed' => 'Database - Backup Failed',
|
||||
// 'invitation-link' => 'Invitation Link',
|
||||
'waitlist-invitation-link' => 'Waitlist Invitation Link',
|
||||
'waitlist-confirmation' => 'Waitlist Confirmation',
|
||||
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
|
||||
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
|
||||
],
|
||||
);
|
||||
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
|
||||
if (!in_array($type, $emailsGathered)) {
|
||||
$this->email = text('Email Address to send to');
|
||||
}
|
||||
set_transanctional_email_settings();
|
||||
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->subject("Test Email");
|
||||
switch ($type) {
|
||||
case 'updates':
|
||||
$teams = Team::all();
|
||||
if (!$teams || $teams->isEmpty()) {
|
||||
echo 'No teams found.' . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
$emails = [];
|
||||
foreach ($teams as $team) {
|
||||
foreach ($team->members as $member) {
|
||||
if ($member->email && $member->marketing_emails) {
|
||||
$emails[] = $member->email;
|
||||
}
|
||||
}
|
||||
}
|
||||
$emails = array_unique($emails);
|
||||
$this->info("Sending to " . count($emails) . " emails.");
|
||||
foreach ($emails as $email) {
|
||||
$this->info($email);
|
||||
}
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if ($confirmed) {
|
||||
foreach ($emails as $email) {
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->subject('One-click Services, Docker Compose support');
|
||||
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
||||
'token' => encrypt($email),
|
||||
]);
|
||||
$this->mail->view('emails.updates',["unsubscribeUrl" => $unsubscribeUrl]);
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'emails-test':
|
||||
$this->mail = (new Test())->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-deployment-success':
|
||||
$application = Application::all()->first();
|
||||
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-deployment-failed':
|
||||
$application = Application::all()->first();
|
||||
$preview = ApplicationPreview::all()->first();
|
||||
if (!$preview) {
|
||||
$preview = ApplicationPreview::create([
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => 1,
|
||||
'pull_request_html_url' => 'http://example.com',
|
||||
'fqdn' => $application->fqdn,
|
||||
]);
|
||||
}
|
||||
$this->mail = (new DeploymentFailed($application, 'test'))->toMail();
|
||||
$this->sendEmail();
|
||||
$this->mail = (new DeploymentFailed($application, 'test', $preview))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-status-changed':
|
||||
$application = Application::all()->first();
|
||||
$this->mail = (new StatusChanged($application))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'backup-failed':
|
||||
$backup = ScheduledDatabaseBackup::all()->first();
|
||||
$db = StandalonePostgresql::all()->first();
|
||||
if (!$backup) {
|
||||
$backup = ScheduledDatabaseBackup::create([
|
||||
'enabled' => true,
|
||||
'frequency' => 'daily',
|
||||
'save_s3' => false,
|
||||
'database_id' => $db->id,
|
||||
'database_type' => $db->getMorphClass(),
|
||||
'team_id' => 0,
|
||||
]);
|
||||
}
|
||||
$output = 'Because of an error, the backup of the database ' . $db->name . ' failed.';
|
||||
$this->mail = (new BackupFailed($backup, $db, $output))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'backup-success':
|
||||
$backup = ScheduledDatabaseBackup::all()->first();
|
||||
$db = StandalonePostgresql::all()->first();
|
||||
if (!$backup) {
|
||||
$backup = ScheduledDatabaseBackup::create([
|
||||
'enabled' => true,
|
||||
'frequency' => 'daily',
|
||||
'save_s3' => false,
|
||||
'database_id' => $db->id,
|
||||
'database_type' => $db->getMorphClass(),
|
||||
'team_id' => 0,
|
||||
]);
|
||||
}
|
||||
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
// case 'invitation-link':
|
||||
// $user = User::all()->first();
|
||||
// $invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||
// if (!$invitation) {
|
||||
// $invitation = TeamInvitation::create([
|
||||
// 'uuid' => Str::uuid(),
|
||||
// 'email' => $user->email,
|
||||
// 'team_id' => 1,
|
||||
// 'link' => 'http://example.com',
|
||||
// ]);
|
||||
// }
|
||||
// $this->mail = (new InvitationLink($user))->toMail();
|
||||
// $this->sendEmail();
|
||||
// break;
|
||||
case 'waitlist-invitation-link':
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.waitlist-invitation', [
|
||||
'loginLink' => 'https://coolify.io',
|
||||
]);
|
||||
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'waitlist-confirmation':
|
||||
$found = Waitlist::where('email', $this->email)->first();
|
||||
if ($found) {
|
||||
SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid);
|
||||
} else {
|
||||
throw new Exception('Waitlist not found');
|
||||
}
|
||||
|
||||
break;
|
||||
case 'realusers-before-trial':
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.before-trial-conversion');
|
||||
$this->mail->subject('Trial period has been added for all subscription plans.');
|
||||
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
||||
if (!$teams || $teams->isEmpty()) {
|
||||
echo 'No teams found.' . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
$emails = [];
|
||||
foreach ($teams as $team) {
|
||||
foreach ($team->members as $member) {
|
||||
if ($member->email) {
|
||||
$emails[] = $member->email;
|
||||
}
|
||||
}
|
||||
}
|
||||
$emails = array_unique($emails);
|
||||
$this->info("Sending to " . count($emails) . " emails.");
|
||||
foreach ($emails as $email) {
|
||||
$this->info($email);
|
||||
}
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if ($confirmed) {
|
||||
foreach ($emails as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'realusers-server-lost-connection':
|
||||
$serverId = text('Server Id');
|
||||
$server = Server::find($serverId);
|
||||
if (!$server) {
|
||||
throw new Exception('Server not found');
|
||||
}
|
||||
$admins = [];
|
||||
$members = $server->team->members;
|
||||
foreach ($members as $member) {
|
||||
if ($member->isAdmin()) {
|
||||
$admins[] = $member->email;
|
||||
}
|
||||
}
|
||||
$this->info('Sending to ' . count($admins) . ' admins.');
|
||||
foreach ($admins as $admin) {
|
||||
$this->info($admin);
|
||||
}
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.server-lost-connection', [
|
||||
'name' => $server->name,
|
||||
]);
|
||||
$this->mail->subject('Action required: Server ' . $server->name . ' lost connection.');
|
||||
foreach ($admins as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
private function sendEmail(string $email = null)
|
||||
{
|
||||
if ($email) {
|
||||
$this->email = $email;
|
||||
}
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->to($this->email)
|
||||
->subject($this->mail->subject)
|
||||
->html((string)$this->mail->render())
|
||||
);
|
||||
$this->info("Email sent to $this->email successfully. 📧");
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/Init.php
Normal file
33
app/Console/Commands/Init.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Init extends Command
|
||||
{
|
||||
protected $signature = 'app:init';
|
||||
protected $description = 'Cleanup instance related stuffs';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
}
|
||||
|
||||
private function cleanup_in_progress_application_deployments()
|
||||
{
|
||||
// Cleanup any failed deployments
|
||||
|
||||
try {
|
||||
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get();
|
||||
foreach ($halted_deployments as $deployment) {
|
||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$deployment->save();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
77
app/Console/Commands/NotifyDemo.php
Normal file
77
app/Console/Commands/NotifyDemo.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use function Termwind\ask;
|
||||
use function Termwind\render;
|
||||
use function Termwind\style;
|
||||
|
||||
class NotifyDemo extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:demo-notify {channel?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send a demo notification, to a given channel. Run to see options.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$channel = $this->argument('channel');
|
||||
|
||||
if (blank($channel)) {
|
||||
$this->showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
ray($channel);
|
||||
}
|
||||
|
||||
private function showHelp()
|
||||
{
|
||||
style('coolify')->color('#9333EA');
|
||||
style('title-box')->apply('mt-1 px-2 py-1 bg-coolify');
|
||||
|
||||
render(
|
||||
<<<'HTML'
|
||||
<div>
|
||||
<div class="title-box">
|
||||
Coolify
|
||||
</div>
|
||||
<p class="mt-1 ml-1 ">
|
||||
Demo Notify <strong class="text-coolify">=></strong> Send a demo notification to a given channel.
|
||||
</p>
|
||||
<p class="px-1 mt-1 ml-1 bg-coolify">
|
||||
php artisan app:demo-notify {channel}
|
||||
</p>
|
||||
<div class="my-1">
|
||||
<div class="text-yellow-500"> Channels: </div>
|
||||
<ul class="text-coolify">
|
||||
<li>email</li>
|
||||
<li>slack</li>
|
||||
<li>discord</li>
|
||||
<li>telegram</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
HTML
|
||||
);
|
||||
|
||||
ask(<<<'HTML'
|
||||
<div class="mr-1">
|
||||
In which manner you wish a <strong class="text-coolify">coolified</strong> notification?
|
||||
</div>
|
||||
HTML, ['email', 'slack', 'discord', 'telegram']);
|
||||
}
|
||||
}
|
||||
108
app/Console/Commands/ResourcesDelete.php
Normal file
108
app/Console/Commands/ResourcesDelete.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\multiselect;
|
||||
use function Laravel\Prompts\select;
|
||||
|
||||
class ResourcesDelete extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'resources:delete';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Delete a resource from the database';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$resource = select(
|
||||
'What resource do you want to delete?',
|
||||
['Application', 'Database', 'Service'],
|
||||
);
|
||||
if ($resource === 'Application') {
|
||||
$this->deleteApplication();
|
||||
} elseif ($resource === 'Database') {
|
||||
$this->deleteDatabase();
|
||||
} elseif ($resource === 'Service') {
|
||||
$this->deleteService();
|
||||
}
|
||||
}
|
||||
private function deleteApplication()
|
||||
{
|
||||
$applications = Application::all();
|
||||
if ($applications->count() === 0) {
|
||||
$this->error('There are no applications to delete.');
|
||||
return;
|
||||
}
|
||||
$applicationsToDelete = multiselect(
|
||||
'What application do you want to delete?',
|
||||
$applications->pluck('name')->toArray(),
|
||||
);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
foreach ($applicationsToDelete as $application) {
|
||||
$toDelete = $applications->where('name', $application)->first();
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
private function deleteDatabase()
|
||||
{
|
||||
$databases = StandalonePostgresql::all();
|
||||
if ($databases->count() === 0) {
|
||||
$this->error('There are no databases to delete.');
|
||||
return;
|
||||
}
|
||||
$databasesToDelete = multiselect(
|
||||
'What database do you want to delete?',
|
||||
$databases->pluck('name')->toArray(),
|
||||
);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
foreach ($databasesToDelete as $database) {
|
||||
$toDelete = $databases->where('name', $database)->first();
|
||||
$toDelete->delete();
|
||||
}
|
||||
|
||||
}
|
||||
private function deleteService()
|
||||
{
|
||||
$services = Service::all();
|
||||
if ($services->count() === 0) {
|
||||
$this->error('There are no services to delete.');
|
||||
return;
|
||||
}
|
||||
$servicesToDelete = multiselect(
|
||||
'What service do you want to delete?',
|
||||
$services->pluck('name')->toArray(),
|
||||
);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
foreach ($servicesToDelete as $service) {
|
||||
$toDelete = $services->where('name', $service)->first();
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
114
app/Console/Commands/SyncBunny.php
Normal file
114
app/Console/Commands/SyncBunny.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Http\Client\Pool;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
|
||||
class SyncBunny extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sync:bunny {--only-template} {--only-version}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sync files to BunnyCDN';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$that = $this;
|
||||
$only_template = $this->option('only-template');
|
||||
$only_version = $this->option('only-version');
|
||||
$bunny_cdn = "https://cdn.coollabs.io";
|
||||
$bunny_cdn_path = "coolify";
|
||||
$bunny_cdn_storage_name = "coolcdn";
|
||||
|
||||
$parent_dir = realpath(dirname(__FILE__) . '/../../..');
|
||||
|
||||
$compose_file = "docker-compose.yml";
|
||||
$compose_file_prod = "docker-compose.prod.yml";
|
||||
$install_script = "install.sh";
|
||||
$upgrade_script = "upgrade.sh";
|
||||
$production_env = ".env.production";
|
||||
$service_template = "service-templates.json";
|
||||
|
||||
$versions = "versions.json";
|
||||
|
||||
PendingRequest::macro('storage', function ($fileName) use($that) {
|
||||
$headers = [
|
||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/octet-stream'
|
||||
];
|
||||
$fileStream = fopen($fileName, "r");
|
||||
$file = fread($fileStream, filesize($fileName));
|
||||
$that->info('Uploading: ' . $fileName);
|
||||
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
|
||||
});
|
||||
PendingRequest::macro('purge', function ($url) use ($that) {
|
||||
$headers = [
|
||||
'AccessKey' => env('BUNNY_API_KEY'),
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
$that->info('Purging: ' . $url);
|
||||
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
|
||||
"url" => $url,
|
||||
"async" => false
|
||||
]);
|
||||
});
|
||||
try {
|
||||
$confirmed = confirm('Are you sure you want to sync?');
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
if ($only_template) {
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
||||
]);
|
||||
$this->info('Service template uploaded & purged...');
|
||||
return;
|
||||
}
|
||||
if ($only_version) {
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||
]);
|
||||
$this->info('versions.json uploaded & purged...');
|
||||
return;
|
||||
}
|
||||
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||
$pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
|
||||
$pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
|
||||
$pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
|
||||
]);
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file_prod"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$production_env"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
|
||||
]);
|
||||
$this->info("All files uploaded & purged...");
|
||||
} catch (\Throwable $e) {
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
49
app/Console/Commands/UsersResetRoot.php
Normal file
49
app/Console/Commands/UsersResetRoot.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
use function Laravel\Prompts\password;
|
||||
|
||||
class UsersResetRoot extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'users:reset-root';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Reset Root Password';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
$this->info('You are about to reset the root password.');
|
||||
$password = password('Give me a new password for root user: ');
|
||||
$passwordAgain = password('Again');
|
||||
if ($password != $passwordAgain) {
|
||||
$this->error('Passwords do not match.');
|
||||
return;
|
||||
}
|
||||
$this->info('Updating root password...');
|
||||
try {
|
||||
User::find(0)->update(['password' => Hash::make($password)]);
|
||||
$this->info('Root password updated successfully.');
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Failed to update root password.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
app/Console/Commands/WaitlistInvite.php
Normal file
105
app/Console/Commands/WaitlistInvite.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class WaitlistInvite extends Command
|
||||
{
|
||||
public Waitlist|User|null $next_patient = null;
|
||||
public string|null $password = null;
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'waitlist:invite {--people=1} {--only-email} {email?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send invitation to the next user (or by email) in the waitlist';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$people = $this->option('people');
|
||||
for ($i = 0; $i < $people; $i++) {
|
||||
$this->main();
|
||||
}
|
||||
}
|
||||
private function main() {
|
||||
if ($this->argument('email')) {
|
||||
if ($this->option('only-email')) {
|
||||
$this->next_patient = User::whereEmail($this->argument('email'))->first();
|
||||
$this->password = Str::password();
|
||||
$this->next_patient->update([
|
||||
'password' => Hash::make($this->password),
|
||||
'force_password_reset' => true,
|
||||
]);
|
||||
} else {
|
||||
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
||||
}
|
||||
if (!$this->next_patient) {
|
||||
$this->error("{$this->argument('email')} not found in the waitlist.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
||||
}
|
||||
if ($this->next_patient) {
|
||||
if ($this->option('only-email')) {
|
||||
$this->send_email();
|
||||
return;
|
||||
}
|
||||
$this->register_user();
|
||||
$this->remove_from_waitlist();
|
||||
$this->send_email();
|
||||
} else {
|
||||
$this->info('No verified user found in the waitlist. 👀');
|
||||
}
|
||||
}
|
||||
private function register_user()
|
||||
{
|
||||
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
||||
if (!$already_registered) {
|
||||
$this->password = Str::password();
|
||||
User::create([
|
||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
||||
'email' => $this->next_patient->email,
|
||||
'password' => Hash::make($this->password),
|
||||
'force_password_reset' => true,
|
||||
]);
|
||||
$this->info("User registered ({$this->next_patient->email}) successfully. 🎉");
|
||||
} else {
|
||||
throw new \Exception('User already registered');
|
||||
}
|
||||
}
|
||||
private function remove_from_waitlist()
|
||||
{
|
||||
$this->next_patient->delete();
|
||||
$this->info("User removed from waitlist successfully.");
|
||||
}
|
||||
private function send_email()
|
||||
{
|
||||
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||
$loginLink = route('auth.link', ['token' => $token]);
|
||||
$mail = new MailMessage();
|
||||
$mail->view('emails.waitlist-invitation', [
|
||||
'loginLink' => $loginLink,
|
||||
]);
|
||||
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
send_user_an_email($mail, $this->next_patient->email);
|
||||
$this->info("Email sent successfully. 📧");
|
||||
}
|
||||
}
|
||||
103
app/Console/Kernel.php
Normal file
103
app/Console/Kernel.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\CheckResaleLicenseJob;
|
||||
use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\DockerCleanupJob;
|
||||
use App\Jobs\InstanceAutoUpdateJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
if (isDev()) {
|
||||
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
|
||||
// $schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
||||
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
||||
// $this->instance_auto_update($schedule);
|
||||
// $this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->cleanup_servers($schedule);
|
||||
} else {
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
// $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
||||
$this->instance_auto_update($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->cleanup_servers($schedule);
|
||||
}
|
||||
}
|
||||
private function cleanup_servers($schedule)
|
||||
{
|
||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function check_resources($schedule)
|
||||
{
|
||||
if (isCloud()) {
|
||||
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
|
||||
} else {
|
||||
$servers = Server::all();
|
||||
}
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function instance_auto_update($schedule)
|
||||
{
|
||||
if (isDev()) {
|
||||
return;
|
||||
}
|
||||
$settings = InstanceSettings::get();
|
||||
if ($settings->is_auto_update_enabled) {
|
||||
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function check_scheduled_backups($schedule)
|
||||
{
|
||||
ray('check_scheduled_backups');
|
||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||
if ($scheduled_backups->isEmpty()) {
|
||||
ray('no scheduled backups');
|
||||
return;
|
||||
}
|
||||
foreach ($scheduled_backups as $scheduled_backup) {
|
||||
if (!$scheduled_backup->enabled) {
|
||||
continue;
|
||||
}
|
||||
if (is_null(data_get($scheduled_backup, 'database'))) {
|
||||
ray('database not found');
|
||||
$scheduled_backup->delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||
}
|
||||
$schedule->job(new DatabaseBackupJob(
|
||||
backup: $scheduled_backup
|
||||
))->cron($scheduled_backup->frequency)->onOneServer();
|
||||
}
|
||||
}
|
||||
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
||||
27
app/Data/CoolifyTaskArgs.php
Normal file
27
app/Data/CoolifyTaskArgs.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use App\Enums\ProcessStatus;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
/**
|
||||
* The parameters to execute a CoolifyTask, organized in a DTO.
|
||||
*/
|
||||
class CoolifyTaskArgs extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $server_uuid,
|
||||
public string $command,
|
||||
public string $type,
|
||||
public ?string $type_uuid = null,
|
||||
public ?Model $model = null,
|
||||
public ?string $status = null ,
|
||||
public bool $ignore_errors = false,
|
||||
) {
|
||||
if(is_null($status)){
|
||||
$this->status = ProcessStatus::QUEUED->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
app/Data/ServerMetadata.php
Normal file
16
app/Data/ServerMetadata.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class ServerMetadata extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public ?ProxyTypes $type,
|
||||
public ?ProxyStatus $status
|
||||
) {
|
||||
}
|
||||
}
|
||||
8
app/Enums/ActivityTypes.php
Normal file
8
app/Enums/ActivityTypes.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ActivityTypes: string
|
||||
{
|
||||
case INLINE = 'inline';
|
||||
}
|
||||
12
app/Enums/ApplicationDeploymentStatus.php
Normal file
12
app/Enums/ApplicationDeploymentStatus.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ApplicationDeploymentStatus: string
|
||||
{
|
||||
case QUEUED = 'queued';
|
||||
case IN_PROGRESS = 'in_progress';
|
||||
case FINISHED = 'finished';
|
||||
case FAILED = 'failed';
|
||||
case CANCELLED_BY_USER = 'cancelled-by-user';
|
||||
}
|
||||
12
app/Enums/ProcessStatus.php
Normal file
12
app/Enums/ProcessStatus.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ProcessStatus: string
|
||||
{
|
||||
case QUEUED = 'queued';
|
||||
case IN_PROGRESS = 'in_progress';
|
||||
case FINISHED = 'finished';
|
||||
case ERROR = 'error';
|
||||
case CANCELLED = 'cancelled';
|
||||
}
|
||||
17
app/Enums/ProxyTypes.php
Normal file
17
app/Enums/ProxyTypes.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ProxyTypes: string
|
||||
{
|
||||
case NONE = 'NONE';
|
||||
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
||||
case NGINX = 'NGINX';
|
||||
case CADDY = 'CADDY';
|
||||
}
|
||||
|
||||
enum ProxyStatus: string
|
||||
{
|
||||
case EXITED = 'exited';
|
||||
case RUNNING = 'running';
|
||||
}
|
||||
71
app/Exceptions/Handler.php
Normal file
71
app/Exceptions/Handler.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Sentry\Laravel\Integration;
|
||||
use Sentry\State\Scope;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* A list of exception types with their corresponding custom log levels.
|
||||
*
|
||||
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
|
||||
*/
|
||||
protected $levels = [
|
||||
//
|
||||
];
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array<int, class-string<\Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
ProcessException::class
|
||||
];
|
||||
/**
|
||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
private InstanceSettings $settings;
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
if (isDev()) {
|
||||
return;
|
||||
}
|
||||
$this->settings = InstanceSettings::get();
|
||||
if ($this->settings->do_not_track) {
|
||||
return;
|
||||
}
|
||||
app('sentry')->configureScope(
|
||||
function (Scope $scope) {
|
||||
$email = auth()?->user() ? auth()->user()->email : 'guest';
|
||||
$instanceAdmin = User::find(0)->email ?? 'admin@localhost';
|
||||
$scope->setUser(
|
||||
[
|
||||
'email' => $email,
|
||||
'instanceAdmin' => $instanceAdmin
|
||||
]
|
||||
);
|
||||
}
|
||||
);
|
||||
Integration::captureUnhandledException($e);
|
||||
});
|
||||
}
|
||||
}
|
||||
10
app/Exceptions/ProcessException.php
Normal file
10
app/Exceptions/ProcessException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ProcessException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
87
app/Http/Controllers/ApplicationController.php
Normal file
87
app/Http/Controllers/ApplicationController.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
|
||||
class ApplicationController extends Controller
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
public function configuration()
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
||||
if (!$application) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
return view('project.application.configuration', ['application' => $application]);
|
||||
}
|
||||
|
||||
public function deployments()
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
||||
if (!$application) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 8);
|
||||
return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]);
|
||||
}
|
||||
|
||||
public function deployment()
|
||||
{
|
||||
$deploymentUuid = request()->route('deployment_uuid');
|
||||
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
||||
if (!$application) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
// $activity = Activity::where('properties->type_uuid', '=', $deploymentUuid)->first();
|
||||
// if (!$activity) {
|
||||
// return redirect()->route('project.application.deployments', [
|
||||
// 'project_uuid' => $project->uuid,
|
||||
// 'environment_name' => $environment->name,
|
||||
// 'application_uuid' => $application->uuid,
|
||||
// ]);
|
||||
// }
|
||||
$application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first();
|
||||
if (!$application_deployment_queue) {
|
||||
return redirect()->route('project.application.deployments', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'application_uuid' => $application->uuid,
|
||||
]);
|
||||
}
|
||||
return view('project.application.deployment', [
|
||||
'application' => $application,
|
||||
// 'activity' => $activity,
|
||||
'application_deployment_queue' => $application_deployment_queue,
|
||||
'deployment_uuid' => $deploymentUuid,
|
||||
]);
|
||||
}
|
||||
}
|
||||
178
app/Http/Controllers/Controller.php
Normal file
178
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
public function link()
|
||||
{
|
||||
$token = request()->get('token');
|
||||
if ($token) {
|
||||
$decrypted = Crypt::decryptString($token);
|
||||
$email = Str::of($decrypted)->before('@@@');
|
||||
$password = Str::of($decrypted)->after('@@@');
|
||||
$user = User::whereEmail($email)->first();
|
||||
if (!$user) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
if (Hash::check($password, $user->password)) {
|
||||
$invitation = TeamInvitation::whereEmail($email);
|
||||
if ($invitation->exists()) {
|
||||
$team = $invitation->first()->team;
|
||||
$user->teams()->attach($team->id, ['role' => $invitation->first()->role]);
|
||||
$invitation->delete();
|
||||
} else {
|
||||
$team = $user->teams()->first();
|
||||
}
|
||||
Auth::login($user);
|
||||
session(['currentTeam' => $team]);
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||
}
|
||||
public function subscription()
|
||||
{
|
||||
if (!isCloud()) {
|
||||
abort(404);
|
||||
}
|
||||
return view('subscription.index', [
|
||||
'settings' => InstanceSettings::get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function license()
|
||||
{
|
||||
if (!isCloud()) {
|
||||
abort(404);
|
||||
}
|
||||
return view('settings.license', [
|
||||
'settings' => InstanceSettings::get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function force_passoword_reset()
|
||||
{
|
||||
return view('auth.force-password-reset');
|
||||
}
|
||||
public function boarding()
|
||||
{
|
||||
if (currentTeam()->boarding || isDev()) {
|
||||
return view('boarding');
|
||||
} else {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
public function settings()
|
||||
{
|
||||
if (isInstanceAdmin()) {
|
||||
$settings = InstanceSettings::get();
|
||||
$database = StandalonePostgresql::whereName('coolify-db')->first();
|
||||
if ($database) {
|
||||
$s3s = S3Storage::whereTeamId(0)->get();
|
||||
}
|
||||
return view('settings.configuration', [
|
||||
'settings' => $settings,
|
||||
'database' => $database,
|
||||
's3s' => $s3s ?? [],
|
||||
]);
|
||||
} else {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
public function team()
|
||||
{
|
||||
$invitations = [];
|
||||
if (auth()->user()->isAdminFromSession()) {
|
||||
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
|
||||
}
|
||||
return view('team.index', [
|
||||
'invitations' => $invitations,
|
||||
]);
|
||||
}
|
||||
|
||||
public function storages()
|
||||
{
|
||||
$s3 = S3Storage::ownedByCurrentTeam()->get();
|
||||
return view('team.storages.all', [
|
||||
's3' => $s3,
|
||||
]);
|
||||
}
|
||||
|
||||
public function storages_show()
|
||||
{
|
||||
$storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->firstOrFail();
|
||||
return view('team.storages.show', [
|
||||
'storage' => $storage,
|
||||
]);
|
||||
}
|
||||
|
||||
public function members()
|
||||
{
|
||||
$invitations = [];
|
||||
if (auth()->user()->isAdminFromSession()) {
|
||||
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
|
||||
}
|
||||
return view('team.members', [
|
||||
'invitations' => $invitations,
|
||||
]);
|
||||
}
|
||||
|
||||
public function acceptInvitation()
|
||||
{
|
||||
try {
|
||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||
if (auth()->user()->id !== $user->id) {
|
||||
abort(401);
|
||||
}
|
||||
$invitationValid = $invitation->isValid();
|
||||
if ($invitationValid) {
|
||||
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
||||
refreshSession($invitation->team);
|
||||
$invitation->delete();
|
||||
return redirect()->route('team.index');
|
||||
} else {
|
||||
abort(401);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function revokeInvitation()
|
||||
{
|
||||
try {
|
||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||
if (is_null(auth()->user())) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
if (auth()->user()->id !== $user->id) {
|
||||
abort(401);
|
||||
}
|
||||
$invitation->delete();
|
||||
return redirect()->route('team.index');
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
76
app/Http/Controllers/DatabaseController.php
Normal file
76
app/Http/Controllers/DatabaseController.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
|
||||
class DatabaseController extends Controller
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
public function configuration()
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
return view('project.database.configuration', ['database' => $database]);
|
||||
}
|
||||
|
||||
public function executions()
|
||||
{
|
||||
$backup_uuid = request()->route('backup_uuid');
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$backup = $database->scheduledBackups->where('uuid', $backup_uuid)->first();
|
||||
if (!$backup) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$executions = collect($backup->executions)->sortByDesc('created_at');
|
||||
return view('project.database.backups.executions', [
|
||||
'database' => $database,
|
||||
'backup' => $backup,
|
||||
'executions' => $executions,
|
||||
's3s' => currentTeam()->s3s,
|
||||
]);
|
||||
}
|
||||
|
||||
public function backups()
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
return view('project.database.backups.all', [
|
||||
'database' => $database,
|
||||
's3s' => currentTeam()->s3s,
|
||||
]);
|
||||
}
|
||||
}
|
||||
74
app/Http/Controllers/MagicController.php
Normal file
74
app/Http/Controllers/MagicController.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
|
||||
class MagicController extends Controller
|
||||
{
|
||||
public function servers()
|
||||
{
|
||||
return response()->json([
|
||||
'servers' => Server::isUsable()->get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function destinations()
|
||||
{
|
||||
return response()->json([
|
||||
'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name')
|
||||
]);
|
||||
}
|
||||
|
||||
public function projects()
|
||||
{
|
||||
return response()->json([
|
||||
'projects' => Project::ownedByCurrentTeam()->get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function environments()
|
||||
{
|
||||
return response()->json([
|
||||
'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments
|
||||
]);
|
||||
}
|
||||
|
||||
public function newProject()
|
||||
{
|
||||
$project = Project::firstOrCreate(
|
||||
['name' => request()->query('name') ?? generate_random_name()],
|
||||
['team_id' => currentTeam()->id]
|
||||
);
|
||||
return response()->json([
|
||||
'project_uuid' => $project->uuid
|
||||
]);
|
||||
}
|
||||
|
||||
public function newEnvironment()
|
||||
{
|
||||
$environment = Environment::firstOrCreate(
|
||||
['name' => request()->query('name') ?? generate_random_name()],
|
||||
['project_id' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->firstOrFail()->id]
|
||||
);
|
||||
return response()->json([
|
||||
'environment_name' => $environment->name,
|
||||
]);
|
||||
}
|
||||
|
||||
public function newTeam()
|
||||
{
|
||||
$team = Team::create(
|
||||
[
|
||||
'name' => request()->query('name') ?? generate_random_name(),
|
||||
'personal_team' => false,
|
||||
],
|
||||
);
|
||||
auth()->user()->teams()->attach($team, ['role' => 'admin']);
|
||||
refreshSession();
|
||||
return redirect(request()->header('Referer'));
|
||||
}
|
||||
}
|
||||
155
app/Http/Controllers/ProjectController.php
Normal file
155
app/Http/Controllers/ProjectController.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneDocker;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
public function all()
|
||||
{
|
||||
return view('projects', [
|
||||
'projects' => Project::ownedByCurrentTeam()->get(),
|
||||
'servers' => Server::ownedByCurrentTeam()->count(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$projectUuid = request()->route('project_uuid');
|
||||
$teamId = currentTeam()->id;
|
||||
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
return view('project.edit', ['project' => $project]);
|
||||
}
|
||||
|
||||
public function show()
|
||||
{
|
||||
$projectUuid = request()->route('project_uuid');
|
||||
$teamId = currentTeam()->id;
|
||||
|
||||
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$project->load(['environments']);
|
||||
return view('project.show', ['project' => $project]);
|
||||
}
|
||||
|
||||
public function new()
|
||||
{
|
||||
$services = getServiceTemplates();
|
||||
$type = Str::of(request()->query('type'));
|
||||
$destination_uuid = request()->query('destination');
|
||||
$server_id = request()->query('server_id');
|
||||
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
if (in_array($type, DATABASE_TYPES)) {
|
||||
$standalone_postgresql = create_standalone_postgresql($environment->id, $destination_uuid);
|
||||
return redirect()->route('project.database.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'database_uuid' => $standalone_postgresql->uuid,
|
||||
]);
|
||||
}
|
||||
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
|
||||
$oneClickServiceName = $type->after('one-click-service-')->value();
|
||||
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
||||
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
||||
if ($oneClickDotEnvs) {
|
||||
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
|
||||
}
|
||||
if ($oneClickService) {
|
||||
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
|
||||
$service = Service::create([
|
||||
'name' => "$oneClickServiceName-" . Str::random(10),
|
||||
'docker_compose_raw' => base64_decode($oneClickService),
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
$service->name = "$oneClickServiceName-" . $service->uuid;
|
||||
$service->save();
|
||||
if ($oneClickDotEnvs?->count() > 0) {
|
||||
$oneClickDotEnvs->each(function ($value) use ($service) {
|
||||
$key = Str::before($value, '=');
|
||||
$value = Str::of(Str::after($value, '='));
|
||||
$generatedValue = $value;
|
||||
if ($value->contains('SERVICE_')) {
|
||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||
// TODO: make it shared with Service.php
|
||||
switch ($command->value()) {
|
||||
case 'PASSWORD':
|
||||
$generatedValue = Str::password(symbols: false);
|
||||
break;
|
||||
case 'PASSWORD_64':
|
||||
$generatedValue = Str::password(length: 64, symbols: false);
|
||||
break;
|
||||
case 'BASE64_64':
|
||||
$generatedValue = Str::random(64);
|
||||
break;
|
||||
case 'BASE64_128':
|
||||
$generatedValue = Str::random(128);
|
||||
break;
|
||||
case 'BASE64':
|
||||
$generatedValue = Str::random(32);
|
||||
break;
|
||||
case 'USER':
|
||||
$generatedValue = Str::random(16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
EnvironmentVariable::create([
|
||||
'key' => $key,
|
||||
'value' => $generatedValue,
|
||||
'service_id' => $service->id,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
});
|
||||
}
|
||||
$service->parse(isNew: true);
|
||||
|
||||
return redirect()->route('project.service', [
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
}
|
||||
}
|
||||
return view('project.new', [
|
||||
'type' => $type->value()
|
||||
]);
|
||||
}
|
||||
|
||||
public function resources()
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
return view('project.resources', [
|
||||
'project' => $project,
|
||||
'environment' => $environment
|
||||
]);
|
||||
}
|
||||
}
|
||||
71
app/Http/Kernel.php
Normal file
71
app/Http/Kernel.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array<int, class-string|string>
|
||||
*/
|
||||
protected $middleware = [
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array<string, array<int, class-string|string>>
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\CheckForcePasswordReset::class,
|
||||
\App\Http\Middleware\DecideWhatToDoWithUser::class,
|
||||
|
||||
],
|
||||
|
||||
'api' => [
|
||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's middleware aliases.
|
||||
*
|
||||
* Aliases may be used to conveniently assign middleware to routes and groups.
|
||||
*
|
||||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected $middlewareAliases = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
];
|
||||
}
|
||||
56
app/Http/Livewire/ActivityMonitor.php
Normal file
56
app/Http/Livewire/ActivityMonitor.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Enums\ProcessStatus;
|
||||
use Livewire\Component;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class ActivityMonitor extends Component
|
||||
{
|
||||
public string|null $header = null;
|
||||
public $activityId;
|
||||
public $isPollingActive = false;
|
||||
|
||||
protected $activity;
|
||||
protected $listeners = ['newMonitorActivity'];
|
||||
|
||||
public function newMonitorActivity($activityId)
|
||||
{
|
||||
$this->activityId = $activityId;
|
||||
|
||||
$this->hydrateActivity();
|
||||
|
||||
$this->isPollingActive = true;
|
||||
}
|
||||
|
||||
public function hydrateActivity()
|
||||
{
|
||||
$this->activity = Activity::query()
|
||||
->find($this->activityId);
|
||||
}
|
||||
|
||||
public function polling()
|
||||
{
|
||||
$this->hydrateActivity();
|
||||
$this->setStatus(ProcessStatus::IN_PROGRESS);
|
||||
$exit_code = data_get($this->activity, 'properties.exitCode');
|
||||
if ($exit_code !== null) {
|
||||
if ($exit_code === 0) {
|
||||
$this->setStatus(ProcessStatus::FINISHED);
|
||||
} else {
|
||||
$this->setStatus(ProcessStatus::ERROR);
|
||||
}
|
||||
$this->isPollingActive = false;
|
||||
$this->emit('activityFinished');
|
||||
}
|
||||
}
|
||||
|
||||
protected function setStatus($status)
|
||||
{
|
||||
$this->activity->properties = $this->activity->properties->merge([
|
||||
'status' => $status,
|
||||
]);
|
||||
$this->activity->save();
|
||||
}
|
||||
}
|
||||
284
app/Http/Livewire/Boarding/Index.php
Normal file
284
app/Http/Livewire/Boarding/Index.php
Normal file
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Boarding;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public string $currentState = 'welcome';
|
||||
|
||||
public ?string $selectedServerType = null;
|
||||
public ?Collection $privateKeys = null;
|
||||
public ?int $selectedExistingPrivateKey = null;
|
||||
public ?string $privateKeyType = null;
|
||||
public ?string $privateKey = null;
|
||||
public ?string $publicKey = null;
|
||||
public ?string $privateKeyName = null;
|
||||
public ?string $privateKeyDescription = null;
|
||||
public ?PrivateKey $createdPrivateKey = null;
|
||||
|
||||
public ?Collection $servers = null;
|
||||
public ?int $selectedExistingServer = null;
|
||||
public ?string $remoteServerName = null;
|
||||
public ?string $remoteServerDescription = null;
|
||||
public ?string $remoteServerHost = null;
|
||||
public ?int $remoteServerPort = 22;
|
||||
public ?string $remoteServerUser = 'root';
|
||||
public ?Server $createdServer = null;
|
||||
|
||||
public Collection|array $projects = [];
|
||||
public ?int $selectedExistingProject = null;
|
||||
public ?Project $createdProject = null;
|
||||
|
||||
public bool $dockerInstallationStarted = false;
|
||||
|
||||
public string $serverPublicKey;
|
||||
public bool $serverReachable = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->privateKeyName = generate_random_name();
|
||||
$this->remoteServerName = generate_random_name();
|
||||
if (isDev()) {
|
||||
$this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
|
||||
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
|
||||
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
|
||||
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
-----END OPENSSH PRIVATE KEY-----';
|
||||
$this->privateKeyDescription = 'Created by Coolify';
|
||||
$this->remoteServerDescription = 'Created by Coolify';
|
||||
$this->remoteServerHost = 'coolify-testing-host';
|
||||
}
|
||||
}
|
||||
public function explanation()
|
||||
{
|
||||
if (isCloud()) {
|
||||
return $this->setServerType('remote');
|
||||
}
|
||||
$this->currentState = 'select-server-type';
|
||||
}
|
||||
|
||||
public function restartBoarding()
|
||||
{
|
||||
return redirect()->route('boarding');
|
||||
}
|
||||
public function skipBoarding()
|
||||
{
|
||||
Team::find(currentTeam()->id)->update([
|
||||
'show_boarding' => false
|
||||
]);
|
||||
refreshSession();
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
public function setServerType(string $type)
|
||||
{
|
||||
$this->selectedServerType = $type;
|
||||
if ($this->selectedServerType === 'localhost') {
|
||||
$this->createdServer = Server::find(0);
|
||||
if (!$this->createdServer) {
|
||||
return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
|
||||
}
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
return $this->validateServer('localhost');
|
||||
} elseif ($this->selectedServerType === 'remote') {
|
||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||
if ($this->privateKeys->count() > 0) {
|
||||
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
|
||||
}
|
||||
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||
if ($this->servers->count() > 0) {
|
||||
$this->selectedExistingServer = $this->servers->first()->id;
|
||||
$this->currentState = 'select-existing-server';
|
||||
return;
|
||||
}
|
||||
$this->currentState = 'private-key';
|
||||
}
|
||||
}
|
||||
public function selectExistingServer()
|
||||
{
|
||||
$this->createdServer = Server::find($this->selectedExistingServer);
|
||||
if (!$this->createdServer) {
|
||||
$this->emit('error', 'Server is not found.');
|
||||
$this->currentState = 'private-key';
|
||||
return;
|
||||
}
|
||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
$this->validateServer();
|
||||
}
|
||||
public function getProxyType()
|
||||
{
|
||||
$proxyTypeSet = $this->createdServer->proxy->type;
|
||||
if (!$proxyTypeSet) {
|
||||
$this->currentState = 'select-proxy';
|
||||
return;
|
||||
}
|
||||
$this->getProjects();
|
||||
}
|
||||
public function selectExistingPrivateKey()
|
||||
{
|
||||
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
|
||||
$this->privateKey = $this->createdPrivateKey->private_key;
|
||||
$this->currentState = 'create-server';
|
||||
}
|
||||
public function createNewServer()
|
||||
{
|
||||
$this->selectedExistingServer = null;
|
||||
$this->currentState = 'private-key';
|
||||
}
|
||||
public function setPrivateKey(string $type)
|
||||
{
|
||||
$this->selectedExistingPrivateKey = null;
|
||||
$this->privateKeyType = $type;
|
||||
if ($type === 'create') {
|
||||
$this->createNewPrivateKey();
|
||||
}
|
||||
$this->currentState = 'create-private-key';
|
||||
}
|
||||
public function savePrivateKey()
|
||||
{
|
||||
$this->validate([
|
||||
'privateKeyName' => 'required',
|
||||
'privateKey' => 'required',
|
||||
]);
|
||||
$this->createdPrivateKey = PrivateKey::create([
|
||||
'name' => $this->privateKeyName,
|
||||
'description' => $this->privateKeyDescription,
|
||||
'private_key' => $this->privateKey,
|
||||
'team_id' => currentTeam()->id
|
||||
]);
|
||||
$this->createdPrivateKey->save();
|
||||
$this->currentState = 'create-server';
|
||||
}
|
||||
public function saveServer()
|
||||
{
|
||||
$this->validate([
|
||||
'remoteServerName' => 'required',
|
||||
'remoteServerHost' => 'required',
|
||||
'remoteServerPort' => 'required|integer',
|
||||
'remoteServerUser' => 'required',
|
||||
]);
|
||||
$this->privateKey = formatPrivateKey($this->privateKey);
|
||||
$foundServer = Server::whereIp($this->remoteServerHost)->first();
|
||||
if ($foundServer) {
|
||||
return $this->emit('error', 'IP address is already in use by another team.');
|
||||
}
|
||||
$this->createdServer = Server::create([
|
||||
'name' => $this->remoteServerName,
|
||||
'ip' => $this->remoteServerHost,
|
||||
'port' => $this->remoteServerPort,
|
||||
'user' => $this->remoteServerUser,
|
||||
'description' => $this->remoteServerDescription,
|
||||
'private_key_id' => $this->createdPrivateKey->id,
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->createdServer->save();
|
||||
$this->validateServer();
|
||||
}
|
||||
public function validateServer()
|
||||
{
|
||||
try {
|
||||
$customErrorMessage = "Server is not reachable:";
|
||||
config()->set('coolify.mux_enabled', false);
|
||||
|
||||
instant_remote_process(['uptime'], $this->createdServer, true);
|
||||
|
||||
$this->createdServer->settings()->update([
|
||||
'is_reachable' => true,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->serverReachable = false;
|
||||
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
|
||||
}
|
||||
|
||||
try {
|
||||
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
|
||||
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||
if (is_null($dockerVersion)) {
|
||||
$this->currentState = 'install-docker';
|
||||
throw new \Exception('Docker version is not supported or not installed.');
|
||||
}
|
||||
$this->createdServer->settings()->update([
|
||||
'is_usable' => true,
|
||||
]);
|
||||
$this->getProxyType();
|
||||
} catch (\Throwable $e) {
|
||||
$this->dockerInstallationStarted = false;
|
||||
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
|
||||
}
|
||||
}
|
||||
public function installDocker()
|
||||
{
|
||||
$this->dockerInstallationStarted = true;
|
||||
$activity = InstallDocker::run($this->createdServer);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
public function dockerInstalledOrSkipped()
|
||||
{
|
||||
$this->validateServer();
|
||||
}
|
||||
public function selectProxy(string|null $proxyType = null)
|
||||
{
|
||||
if (!$proxyType) {
|
||||
return $this->getProjects();
|
||||
}
|
||||
$this->createdServer->proxy->type = $proxyType;
|
||||
$this->createdServer->proxy->status = 'exited';
|
||||
$this->createdServer->save();
|
||||
$this->getProjects();
|
||||
}
|
||||
|
||||
public function getProjects()
|
||||
{
|
||||
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
|
||||
if ($this->projects->count() > 0) {
|
||||
$this->selectedExistingProject = $this->projects->first()->id;
|
||||
}
|
||||
$this->currentState = 'create-project';
|
||||
}
|
||||
public function selectExistingProject()
|
||||
{
|
||||
$this->createdProject = Project::find($this->selectedExistingProject);
|
||||
$this->currentState = 'create-resource';
|
||||
}
|
||||
public function createNewProject()
|
||||
{
|
||||
$this->createdProject = Project::create([
|
||||
'name' => "My first project",
|
||||
'team_id' => currentTeam()->id
|
||||
]);
|
||||
$this->currentState = 'create-resource';
|
||||
}
|
||||
public function showNewResource()
|
||||
{
|
||||
$this->skipBoarding();
|
||||
return redirect()->route(
|
||||
'project.resources.new',
|
||||
[
|
||||
'project_uuid' => $this->createdProject->uuid,
|
||||
'environment_name' => 'production',
|
||||
'server' => $this->createdServer->id,
|
||||
]
|
||||
);
|
||||
}
|
||||
private function createNewPrivateKey()
|
||||
{
|
||||
$this->privateKeyName = generate_random_name();
|
||||
$this->privateKeyDescription = 'Created by Coolify';
|
||||
['private' => $this->privateKey, 'public' => $this->publicKey] = generateSSHKey();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.boarding.index')->layout('layouts.boarding');
|
||||
}
|
||||
}
|
||||
44
app/Http/Livewire/CheckLicense.php
Normal file
44
app/Http/Livewire/CheckLicense.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Actions\License\CheckResaleLicense;
|
||||
use App\Models\InstanceSettings;
|
||||
use Livewire\Component;
|
||||
|
||||
class CheckLicense extends Component
|
||||
{
|
||||
public InstanceSettings|null $settings = null;
|
||||
public string|null $instance_id = null;
|
||||
protected $rules = [
|
||||
'settings.resale_license' => 'nullable',
|
||||
'settings.is_resale_license_active' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'settings.resale_license' => 'License',
|
||||
'instance_id' => 'Instance Id (Do not change this)',
|
||||
'settings.is_resale_license_active' => 'Is License Active',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->instance_id = config('app.id');
|
||||
$this->settings = InstanceSettings::get();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$this->settings->save();
|
||||
if ($this->settings->resale_license) {
|
||||
try {
|
||||
resolve(CheckResaleLicense::class)();
|
||||
$this->emit('reloadWindow');
|
||||
} catch (\Throwable $e) {
|
||||
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
return redirect()->to('/settings/license');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
app/Http/Livewire/Dashboard.php
Normal file
41
app/Http/Livewire/Dashboard.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class Dashboard extends Component
|
||||
{
|
||||
public $projects = [];
|
||||
public $servers = [];
|
||||
public int $s3s = 0;
|
||||
public int $resources = 0;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||
$this->s3s = S3Storage::ownedByCurrentTeam()->get()->count();
|
||||
$projects = Project::ownedByCurrentTeam()->get();
|
||||
foreach ($projects as $project) {
|
||||
$this->resources += $project->applications->count();
|
||||
$this->resources += $project->postgresqls->count();
|
||||
}
|
||||
$this->projects = $projects;
|
||||
}
|
||||
// public function getIptables()
|
||||
// {
|
||||
// $servers = Server::ownedByCurrentTeam()->get();
|
||||
// foreach ($servers as $server) {
|
||||
// checkRequiredCommands($server);
|
||||
// $iptables = instant_remote_process(['docker run --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c "iptables -L -n | jc --iptables"'], $server);
|
||||
// ray($iptables);
|
||||
// }
|
||||
// }
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.dashboard');
|
||||
}
|
||||
}
|
||||
44
app/Http/Livewire/Destination/Form.php
Normal file
44
app/Http/Livewire/Destination/Form.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Destination;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
public mixed $destination;
|
||||
|
||||
protected $rules = [
|
||||
'destination.name' => 'required',
|
||||
'destination.network' => 'required',
|
||||
'destination.server.ip' => 'required',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'destination.name' => 'name',
|
||||
'destination.network' => 'network',
|
||||
'destination.server.ip' => 'IP Address',
|
||||
];
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$this->destination->save();
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
if ($this->destination->getMorphClass() === 'App\Models\StandaloneDocker') {
|
||||
if ($this->destination->attachedTo()) {
|
||||
return $this->emit('error', 'You must delete all resources before deleting this destination.');
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$this->destination->network} coolify-proxy"], $this->destination->server, throwError: false);
|
||||
instant_remote_process(['docker network rm -f ' . $this->destination->network], $this->destination->server);
|
||||
}
|
||||
$this->destination->delete();
|
||||
return redirect()->route('dashboard');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
app/Http/Livewire/Destination/New/StandaloneDocker.php
Normal file
84
app/Http/Livewire/Destination/New/StandaloneDocker.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Destination\New;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker as ModelsStandaloneDocker;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class StandaloneDocker extends Component
|
||||
{
|
||||
public string $name;
|
||||
public string $network;
|
||||
|
||||
public Collection $servers;
|
||||
public Server $server;
|
||||
public int|null $server_id = null;
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'network' => 'required|string',
|
||||
'server_id' => 'required|integer'
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'name',
|
||||
'network' => 'network',
|
||||
'server_id' => 'server'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (request()->query('server_id')) {
|
||||
$this->server_id = request()->query('server_id');
|
||||
} else {
|
||||
if ($this->servers->count() > 0) {
|
||||
$this->server_id = $this->servers->first()->id;
|
||||
}
|
||||
}
|
||||
if (request()->query('network_name')) {
|
||||
$this->network = request()->query('network_name');
|
||||
} else {
|
||||
$this->network = new Cuid2(7);
|
||||
}
|
||||
$this->name = Str::kebab("{$this->servers->first()->name}-{$this->network}");
|
||||
}
|
||||
|
||||
public function generate_name()
|
||||
{
|
||||
$this->server = Server::find($this->server_id);
|
||||
$this->name = Str::kebab("{$this->server->name}-{$this->network}");
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
try {
|
||||
$this->server = Server::find($this->server_id);
|
||||
$found = $this->server->standaloneDockers()->where('network', $this->network)->first();
|
||||
if ($found) {
|
||||
$this->createNetworkAndAttachToProxy();
|
||||
$this->emit('error', 'Network already added to this server.');
|
||||
return;
|
||||
} else {
|
||||
$docker = ModelsStandaloneDocker::create([
|
||||
'name' => $this->name,
|
||||
'network' => $this->network,
|
||||
'server_id' => $this->server_id,
|
||||
]);
|
||||
}
|
||||
$this->createNetworkAndAttachToProxy();
|
||||
return redirect()->route('destination.show', $docker->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
private function createNetworkAndAttachToProxy()
|
||||
{
|
||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||
}
|
||||
}
|
||||
27
app/Http/Livewire/Destination/Show.php
Normal file
27
app/Http/Livewire/Destination/Show.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Destination;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public Collection|array $networks = [];
|
||||
|
||||
public function scan()
|
||||
{
|
||||
$alreadyAddedNetworks = $this->server->standaloneDockers;
|
||||
$networks = instant_remote_process(['docker network ls --format "{{json .}}"'], $this->server, false);
|
||||
$this->networks = format_docker_command_output_to_json($networks)->filter(function ($network) {
|
||||
return $network['Name'] !== 'bridge' && $network['Name'] !== 'host' && $network['Name'] !== 'none';
|
||||
})->filter(function ($network) use ($alreadyAddedNetworks) {
|
||||
return !$alreadyAddedNetworks->contains('network', $network['Name']);
|
||||
});
|
||||
if ($this->networks->count() === 0) {
|
||||
$this->emit('success', 'No new networks found.');
|
||||
}
|
||||
}
|
||||
}
|
||||
28
app/Http/Livewire/Dev/Compose.php
Normal file
28
app/Http/Livewire/Dev/Compose.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Dev;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class Compose extends Component
|
||||
{
|
||||
public string $compose = '';
|
||||
public string $base64 = '';
|
||||
public $services;
|
||||
public function mount() {
|
||||
$this->services = getServiceTemplates();
|
||||
}
|
||||
public function setService(string $selected) {
|
||||
$this->base64 = data_get($this->services, $selected . '.compose');
|
||||
if ($this->base64) {
|
||||
$this->compose = base64_decode($this->base64);
|
||||
}
|
||||
}
|
||||
public function updatedCompose($value) {
|
||||
$this->base64 = base64_encode($value);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.dev.compose');
|
||||
}
|
||||
}
|
||||
43
app/Http/Livewire/ForcePasswordReset.php
Normal file
43
app/Http/Livewire/ForcePasswordReset.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
use Livewire\Component;
|
||||
|
||||
class ForcePasswordReset extends Component
|
||||
{
|
||||
use WithRateLimiting;
|
||||
public string $email;
|
||||
public string $password;
|
||||
public string $password_confirmation;
|
||||
|
||||
protected $rules = [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|min:8',
|
||||
'password_confirmation' => 'required|same:password',
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->email = auth()->user()->email;
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(10);
|
||||
$this->validate();
|
||||
$firstLogin = auth()->user()->created_at == auth()->user()->updated_at;
|
||||
auth()->user()->forceFill([
|
||||
'password' => Hash::make($this->password),
|
||||
'force_password_reset' => false,
|
||||
])->save();
|
||||
if ($firstLogin) {
|
||||
send_internal_notification('First login for ' . auth()->user()->email);
|
||||
}
|
||||
return redirect()->route('dashboard');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
app/Http/Livewire/Help.php
Normal file
54
app/Http/Livewire/Help.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Livewire\Component;
|
||||
|
||||
class Help extends Component
|
||||
{
|
||||
use WithRateLimiting;
|
||||
public string $description;
|
||||
public string $subject;
|
||||
public ?string $path = null;
|
||||
protected $rules = [
|
||||
'description' => 'required|min:10',
|
||||
'subject' => 'required|min:3'
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->path = Route::current()?->uri() ?? null;
|
||||
if (isDev()) {
|
||||
$this->description = "I'm having trouble with {$this->path}";
|
||||
$this->subject = "Help with {$this->path}";
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(3, 60);
|
||||
$this->validate();
|
||||
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
|
||||
$debug = "Route: {$this->path}";
|
||||
$mail = new MailMessage();
|
||||
$mail->view(
|
||||
'emails.help',
|
||||
[
|
||||
'description' => $this->description,
|
||||
'debug' => $debug
|
||||
]
|
||||
);
|
||||
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
|
||||
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
|
||||
$this->emit('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.help')->layout('layouts.app');
|
||||
}
|
||||
}
|
||||
58
app/Http/Livewire/Notifications/DiscordSettings.php
Normal file
58
app/Http/Livewire/Notifications/DiscordSettings.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Notifications;
|
||||
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Component;
|
||||
|
||||
class DiscordSettings extends Component
|
||||
{
|
||||
public Team $team;
|
||||
protected $rules = [
|
||||
'team.discord_enabled' => 'nullable|boolean',
|
||||
'team.discord_webhook_url' => 'required|url',
|
||||
'team.discord_notifications_test' => 'nullable|boolean',
|
||||
'team.discord_notifications_deployments' => 'nullable|boolean',
|
||||
'team.discord_notifications_status_changes' => 'nullable|boolean',
|
||||
'team.discord_notifications_database_backups' => 'nullable|boolean',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'team.discord_webhook_url' => 'Discord Webhook',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->submit();
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
$this->team->discord_enabled = false;
|
||||
$this->validate();
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
$this->validate();
|
||||
$this->saveModel();
|
||||
}
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->team->notify(new Test());
|
||||
$this->emit('success', 'Test notification sent.');
|
||||
}
|
||||
}
|
||||
192
app/Http/Livewire/Notifications/EmailSettings.php
Normal file
192
app/Http/Livewire/Notifications/EmailSettings.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Notifications;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Component;
|
||||
use Log;
|
||||
|
||||
class EmailSettings extends Component
|
||||
{
|
||||
public Team $team;
|
||||
public string $emails;
|
||||
public bool $sharedEmailEnabled = false;
|
||||
|
||||
protected $rules = [
|
||||
'team.smtp_enabled' => 'nullable|boolean',
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
'team.smtp_recipients' => 'nullable',
|
||||
'team.smtp_host' => 'required',
|
||||
'team.smtp_port' => 'required',
|
||||
'team.smtp_encryption' => 'nullable',
|
||||
'team.smtp_username' => 'nullable',
|
||||
'team.smtp_password' => 'nullable',
|
||||
'team.smtp_timeout' => 'nullable',
|
||||
'team.smtp_notifications_test' => 'nullable|boolean',
|
||||
'team.smtp_notifications_deployments' => 'nullable|boolean',
|
||||
'team.smtp_notifications_status_changes' => 'nullable|boolean',
|
||||
'team.smtp_notifications_database_backups' => 'nullable|boolean',
|
||||
'team.use_instance_email_settings' => 'boolean',
|
||||
'team.resend_enabled' => 'nullable|boolean',
|
||||
'team.resend_api_key' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'team.smtp_from_address' => 'From Address',
|
||||
'team.smtp_from_name' => 'From Name',
|
||||
'team.smtp_recipients' => 'Recipients',
|
||||
'team.smtp_host' => 'Host',
|
||||
'team.smtp_port' => 'Port',
|
||||
'team.smtp_encryption' => 'Encryption',
|
||||
'team.smtp_username' => 'Username',
|
||||
'team.smtp_password' => 'Password',
|
||||
'team.smtp_timeout' => 'Timeout',
|
||||
'team.resend_enabled' => 'Resend Enabled',
|
||||
'team.resend_api_key' => 'Resend API Key',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits;
|
||||
$this->emails = auth()->user()->email;
|
||||
}
|
||||
public function submitFromFields()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->team->notify(new Test($this->emails));
|
||||
$this->emit('success', 'Test Email sent successfully.');
|
||||
}
|
||||
public function instantSaveInstance()
|
||||
{
|
||||
try {
|
||||
if (!$this->sharedEmailEnabled) {
|
||||
throw new \Exception('Not allowed to change settings. Please upgrade your subscription.');
|
||||
}
|
||||
$this->team->smtp_enabled = false;
|
||||
$this->team->resend_enabled = false;
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveResend()
|
||||
{
|
||||
try {
|
||||
$this->team->smtp_enabled = false;
|
||||
$this->submitResend();
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->team->resend_enabled = false;
|
||||
$this->submit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
'team.smtp_host' => 'required',
|
||||
'team.smtp_port' => 'required|numeric',
|
||||
'team.smtp_encryption' => 'nullable',
|
||||
'team.smtp_username' => 'nullable',
|
||||
'team.smtp_password' => 'nullable',
|
||||
'team.smtp_timeout' => 'nullable',
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submitResend()
|
||||
{
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
'team.resend_api_key' => 'required'
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->resend_enabled = false;
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function copyFromInstanceSettings()
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
if ($settings->smtp_enabled) {
|
||||
$team = currentTeam();
|
||||
$team->update([
|
||||
'smtp_enabled' => $settings->smtp_enabled,
|
||||
'smtp_from_address' => $settings->smtp_from_address,
|
||||
'smtp_from_name' => $settings->smtp_from_name,
|
||||
'smtp_recipients' => $settings->smtp_recipients,
|
||||
'smtp_host' => $settings->smtp_host,
|
||||
'smtp_port' => $settings->smtp_port,
|
||||
'smtp_encryption' => $settings->smtp_encryption,
|
||||
'smtp_username' => $settings->smtp_username,
|
||||
'smtp_password' => $settings->smtp_password,
|
||||
'smtp_timeout' => $settings->smtp_timeout,
|
||||
]);
|
||||
refreshSession();
|
||||
$this->team = $team;
|
||||
$this->emit('success', 'Settings saved.');
|
||||
return;
|
||||
}
|
||||
if ($settings->resend_enabled) {
|
||||
$team = currentTeam();
|
||||
$team->update([
|
||||
'resend_enabled' => $settings->resend_enabled,
|
||||
'resend_api_key' => $settings->resend_api_key,
|
||||
]);
|
||||
refreshSession();
|
||||
$this->team = $team;
|
||||
$this->emit('success', 'Settings saved.');
|
||||
return;
|
||||
}
|
||||
$this->emit('error', 'Instance SMTP/Resend settings are not enabled.');
|
||||
}
|
||||
}
|
||||
64
app/Http/Livewire/Notifications/TelegramSettings.php
Normal file
64
app/Http/Livewire/Notifications/TelegramSettings.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Notifications;
|
||||
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Component;
|
||||
|
||||
class TelegramSettings extends Component
|
||||
{
|
||||
public Team $team;
|
||||
protected $rules = [
|
||||
'team.telegram_enabled' => 'nullable|boolean',
|
||||
'team.telegram_token' => 'required|string',
|
||||
'team.telegram_chat_id' => 'required|string',
|
||||
'team.telegram_notifications_test' => 'nullable|boolean',
|
||||
'team.telegram_notifications_deployments' => 'nullable|boolean',
|
||||
'team.telegram_notifications_status_changes' => 'nullable|boolean',
|
||||
'team.telegram_notifications_database_backups' => 'nullable|boolean',
|
||||
'team.telegram_notifications_test_message_thread_id' => 'nullable|string',
|
||||
'team.telegram_notifications_deployments_message_thread_id' => 'nullable|string',
|
||||
'team.telegram_notifications_status_changes_message_thread_id' => 'nullable|string',
|
||||
'team.telegram_notifications_database_backups_message_thread_id' => 'nullable|string',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'team.telegram_token' => 'Token',
|
||||
'team.telegram_chat_id' => 'Chat ID',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->team = auth()->user()->currentTeam();
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->submit();
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
$this->team->telegram_enabled = false;
|
||||
$this->validate();
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
$this->validate();
|
||||
$this->saveModel();
|
||||
}
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->team->notify(new Test());
|
||||
$this->emit('success', 'Test notification sent.');
|
||||
}
|
||||
}
|
||||
56
app/Http/Livewire/PrivateKey/Change.php
Normal file
56
app/Http/Livewire/PrivateKey/Change.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\PrivateKey;
|
||||
|
||||
use App\Models\PrivateKey;
|
||||
use Livewire\Component;
|
||||
|
||||
class Change extends Component
|
||||
{
|
||||
public PrivateKey $private_key;
|
||||
public $public_key;
|
||||
protected $rules = [
|
||||
'private_key.name' => 'required|string',
|
||||
'private_key.description' => 'nullable|string',
|
||||
'private_key.private_key' => 'required|string',
|
||||
'private_key.is_git_related' => 'nullable|boolean'
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'private_key.name' => 'name',
|
||||
'private_key.description' => 'description',
|
||||
'private_key.private_key' => 'private key'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->public_key = $this->private_key->publicKey();
|
||||
}catch(\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
if ($this->private_key->isEmpty()) {
|
||||
$this->private_key->delete();
|
||||
currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
|
||||
return redirect()->route('security.private-key.index');
|
||||
}
|
||||
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function changePrivateKey()
|
||||
{
|
||||
try {
|
||||
$this->private_key->private_key = formatPrivateKey($this->private_key->private_key);
|
||||
$this->private_key->save();
|
||||
refresh_server_connection($this->private_key);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
app/Http/Livewire/PrivateKey/Create.php
Normal file
77
app/Http/Livewire/PrivateKey/Create.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\PrivateKey;
|
||||
|
||||
use App\Models\PrivateKey;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
use Livewire\Component;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
|
||||
class Create extends Component
|
||||
{
|
||||
use WithRateLimiting;
|
||||
public string $name;
|
||||
public string $value;
|
||||
|
||||
public ?string $from = null;
|
||||
public ?string $description = null;
|
||||
public ?string $publicKey = null;
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'value' => 'required|string',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'name',
|
||||
'value' => 'private Key',
|
||||
];
|
||||
|
||||
public function generateNewKey()
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(10);
|
||||
$this->name = generate_random_name();
|
||||
$this->description = 'Created by Coolify';
|
||||
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey();
|
||||
} catch(\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function updated($updateProperty)
|
||||
{
|
||||
if ($updateProperty === 'value') {
|
||||
try {
|
||||
$this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH',['comment' => '']);
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->$updateProperty === "") {
|
||||
$this->publicKey = "";
|
||||
} else {
|
||||
$this->publicKey = "Invalid private key";
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->validateOnly($updateProperty);
|
||||
}
|
||||
public function createPrivateKey()
|
||||
{
|
||||
$this->validate();
|
||||
try {
|
||||
$this->value = trim($this->value);
|
||||
if (!str_ends_with($this->value, "\n")) {
|
||||
$this->value .= "\n";
|
||||
}
|
||||
$private_key = PrivateKey::create([
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'private_key' => $this->value,
|
||||
'team_id' => currentTeam()->id
|
||||
]);
|
||||
if ($this->from === 'server') {
|
||||
return redirect()->route('server.create');
|
||||
}
|
||||
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/Http/Livewire/Profile/Form.php
Normal file
40
app/Http/Livewire/Profile/Form.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Profile;
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
public int $userId;
|
||||
public string $name;
|
||||
public string $email;
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'name',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->userId = auth()->user()->id;
|
||||
$this->name = auth()->user()->name;
|
||||
$this->email = auth()->user()->email;
|
||||
}
|
||||
|
||||
public function submit()
|
||||
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
User::where('id', $this->userId)->update([
|
||||
'name' => $this->name,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
app/Http/Livewire/Project/AddEmpty.php
Normal file
37
app/Http/Livewire/Project/AddEmpty.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project;
|
||||
|
||||
use App\Models\Project;
|
||||
use Livewire\Component;
|
||||
|
||||
class AddEmpty extends Component
|
||||
{
|
||||
public string $name = '';
|
||||
public string $description = '';
|
||||
protected $rules = [
|
||||
'name' => 'required|string|min:3',
|
||||
'description' => 'nullable|string',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'Project Name',
|
||||
'description' => 'Project Description',
|
||||
];
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$project = Project::create([
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
return redirect()->route('project.show', $project->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->name = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/Http/Livewire/Project/AddEnvironment.php
Normal file
40
app/Http/Livewire/Project/AddEnvironment.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project;
|
||||
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use Livewire\Component;
|
||||
|
||||
class AddEnvironment extends Component
|
||||
{
|
||||
public Project $project;
|
||||
public string $name = '';
|
||||
public string $description = '';
|
||||
protected $rules = [
|
||||
'name' => 'required|string|min:3',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'Environment Name',
|
||||
];
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$environment = Environment::create([
|
||||
'name' => $this->name,
|
||||
'project_id' => $this->project->id,
|
||||
]);
|
||||
|
||||
return redirect()->route('project.resources', [
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
} finally {
|
||||
$this->name = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
27
app/Http/Livewire/Project/Application/DeploymentLogs.php
Normal file
27
app/Http/Livewire/Project/Application/DeploymentLogs.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeploymentLogs extends Component
|
||||
{
|
||||
public ApplicationDeploymentQueue $application_deployment_queue;
|
||||
public $isKeepAliveOn = true;
|
||||
protected $listeners = ['refreshQueue'];
|
||||
|
||||
public function refreshQueue()
|
||||
{
|
||||
$this->application_deployment_queue->refresh();
|
||||
}
|
||||
|
||||
public function polling()
|
||||
{
|
||||
$this->emit('deploymentFinished');
|
||||
$this->application_deployment_queue->refresh();
|
||||
if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') {
|
||||
$this->isKeepAliveOn = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
app/Http/Livewire/Project/Application/DeploymentNavbar.php
Normal file
71
app/Http/Livewire/Project/Application/DeploymentNavbar.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeploymentNavbar extends Component
|
||||
{
|
||||
public ApplicationDeploymentQueue $application_deployment_queue;
|
||||
public Application $application;
|
||||
public Server $server;
|
||||
public bool $is_debug_enabled = false;
|
||||
protected $listeners = ['deploymentFinished'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
||||
$this->server = $this->application->destination->server;
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
}
|
||||
|
||||
public function deploymentFinished()
|
||||
{
|
||||
$this->application_deployment_queue->refresh();
|
||||
}
|
||||
|
||||
public function show_debug()
|
||||
{
|
||||
$this->application->settings->is_debug_enabled = !$this->application->settings->is_debug_enabled;
|
||||
$this->application->settings->save();
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->emit('refreshQueue');
|
||||
}
|
||||
|
||||
public function cancel()
|
||||
{
|
||||
try {
|
||||
$kill_command = "kill -9 {$this->application_deployment_queue->current_process_id}";
|
||||
if ($this->application_deployment_queue->current_process_id) {
|
||||
$process = Process::run("ps -p {$this->application_deployment_queue->current_process_id} -o command --no-headers");
|
||||
if (Str::of($process->output())->contains([$this->server->ip, 'EOF-COOLIFY-SSH'])) {
|
||||
Process::run($kill_command);
|
||||
}
|
||||
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
$new_log_entry = [
|
||||
'command' => $kill_command,
|
||||
'output' => "Deployment cancelled by user.",
|
||||
'type' => 'stderr',
|
||||
'order' => count($previous_logs) + 1,
|
||||
'timestamp' => Carbon::now('UTC'),
|
||||
'hidden' => false,
|
||||
];
|
||||
$previous_logs[] = $new_log_entry;
|
||||
$this->application_deployment_queue->update([
|
||||
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
|
||||
'current_process_id' => null,
|
||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
app/Http/Livewire/Project/Application/Deployments.php
Normal file
52
app/Http/Livewire/Project/Application/Deployments.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
|
||||
class Deployments extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public $deployments = [];
|
||||
public int $deployments_count = 0;
|
||||
public string $current_url;
|
||||
public int $skip = 0;
|
||||
public int $default_take = 8;
|
||||
public bool $show_next = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->current_url = url()->current();
|
||||
$this->show_more();
|
||||
}
|
||||
|
||||
private function show_more()
|
||||
{
|
||||
if (count($this->deployments) !== 0) {
|
||||
$this->show_next = true;
|
||||
if (count($this->deployments) < $this->default_take) {
|
||||
$this->show_next = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function reload_deployments()
|
||||
{
|
||||
$this->load_deployments();
|
||||
}
|
||||
|
||||
public function load_deployments(int|null $take = null)
|
||||
{
|
||||
if ($take) {
|
||||
$this->skip = $this->skip + $take;
|
||||
}
|
||||
$take = $this->default_take;
|
||||
|
||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
|
||||
$this->deployments = $deployments;
|
||||
$this->deployments_count = $count;
|
||||
$this->show_more();
|
||||
}
|
||||
}
|
||||
152
app/Http/Livewire/Project/Application/General.php
Normal file
152
app/Http/Livewire/Project/Application/General.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
public string $applicationId;
|
||||
|
||||
public Application $application;
|
||||
public Collection $services;
|
||||
public string $name;
|
||||
public ?string $fqdn = null;
|
||||
public string $git_repository;
|
||||
public string $git_branch;
|
||||
public ?string $git_commit_sha = null;
|
||||
public string $build_pack;
|
||||
|
||||
public bool $is_static;
|
||||
public bool $is_git_submodules_enabled;
|
||||
public bool $is_git_lfs_enabled;
|
||||
public bool $is_debug_enabled;
|
||||
public bool $is_preview_deployments_enabled;
|
||||
public bool $is_auto_deploy_enabled;
|
||||
public bool $is_force_https_enabled;
|
||||
|
||||
|
||||
protected $rules = [
|
||||
'application.name' => 'required',
|
||||
'application.description' => 'nullable',
|
||||
'application.fqdn' => 'nullable',
|
||||
'application.git_repository' => 'required',
|
||||
'application.git_branch' => 'required',
|
||||
'application.git_commit_sha' => 'nullable',
|
||||
'application.install_command' => 'nullable',
|
||||
'application.build_command' => 'nullable',
|
||||
'application.start_command' => 'nullable',
|
||||
'application.build_pack' => 'required',
|
||||
'application.static_image' => 'required',
|
||||
'application.base_directory' => 'required',
|
||||
'application.publish_directory' => 'nullable',
|
||||
'application.ports_exposes' => 'required',
|
||||
'application.ports_mappings' => 'nullable',
|
||||
'application.dockerfile' => 'nullable',
|
||||
'application.docker_registry_image_name' => 'nullable',
|
||||
'application.docker_registry_image_tag' => 'nullable',
|
||||
'application.dockerfile_location' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'application.name' => 'name',
|
||||
'application.description' => 'description',
|
||||
'application.fqdn' => 'FQDN',
|
||||
'application.git_repository' => 'Git repository',
|
||||
'application.git_branch' => 'Git branch',
|
||||
'application.git_commit_sha' => 'Git commit SHA',
|
||||
'application.install_command' => 'Install command',
|
||||
'application.build_command' => 'Build command',
|
||||
'application.start_command' => 'Start command',
|
||||
'application.build_pack' => 'Build pack',
|
||||
'application.static_image' => 'Static image',
|
||||
'application.base_directory' => 'Base directory',
|
||||
'application.publish_directory' => 'Publish directory',
|
||||
'application.ports_exposes' => 'Ports exposes',
|
||||
'application.ports_mappings' => 'Ports mappings',
|
||||
'application.dockerfile' => 'Dockerfile',
|
||||
'application.docker_registry_image_name' => 'Docker registry image name',
|
||||
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
||||
'application.dockerfile_location' => 'Dockerfile location',
|
||||
|
||||
];
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
// @TODO: find another way - if possible
|
||||
$this->application->settings->is_static = $this->is_static;
|
||||
if ($this->is_static) {
|
||||
$this->application->ports_exposes = 80;
|
||||
} else {
|
||||
$this->application->ports_exposes = 3000;
|
||||
}
|
||||
$this->application->settings->is_git_submodules_enabled = $this->is_git_submodules_enabled;
|
||||
$this->application->settings->is_git_lfs_enabled = $this->is_git_lfs_enabled;
|
||||
$this->application->settings->is_debug_enabled = $this->is_debug_enabled;
|
||||
$this->application->settings->is_preview_deployments_enabled = $this->is_preview_deployments_enabled;
|
||||
$this->application->settings->is_auto_deploy_enabled = $this->is_auto_deploy_enabled;
|
||||
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
|
||||
$this->application->settings->save();
|
||||
$this->application->save();
|
||||
$this->application->refresh();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
}
|
||||
|
||||
public function getWildcardDomain() {
|
||||
$server = data_get($this->application, 'destination.server');
|
||||
if ($server) {
|
||||
$fqdn = generateFqdn($server, $this->application->uuid);
|
||||
ray($fqdn);
|
||||
$this->application->fqdn = $fqdn;
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
}
|
||||
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
if (data_get($this->application,'settings')) {
|
||||
$this->is_static = $this->application->settings->is_static;
|
||||
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
||||
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
||||
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
if (data_get($this->application, 'fqdn')) {
|
||||
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||
return Str::of($domain)->trim()->lower();
|
||||
});
|
||||
$this->application->fqdn = $domains->implode(',');
|
||||
}
|
||||
if (data_get($this->application, 'dockerfile')) {
|
||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
||||
if ($port && !$this->application->ports_exposes) {
|
||||
$this->application->ports_exposes = $port;
|
||||
}
|
||||
}
|
||||
if ($this->application->base_directory && $this->application->base_directory !== '/') {
|
||||
$this->application->base_directory = rtrim($this->application->base_directory, '/');
|
||||
}
|
||||
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
|
||||
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
|
||||
}
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
app/Http/Livewire/Project/Application/Heading.php
Normal file
80
app/Http/Livewire/Project/Application/Heading.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Heading extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public array $parameters;
|
||||
|
||||
protected string $deploymentUuid;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
}
|
||||
|
||||
public function check_status()
|
||||
{
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||
$this->application->refresh();
|
||||
$this->application->previews->each(function ($preview) {
|
||||
$preview->refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function force_deploy_without_cache()
|
||||
{
|
||||
$this->deploy(force_rebuild: true);
|
||||
}
|
||||
|
||||
public function deploy(bool $force_rebuild = false)
|
||||
{
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
force_rebuild: $force_rebuild,
|
||||
);
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function setDeploymentUuid()
|
||||
{
|
||||
$this->deploymentUuid = new Cuid2(7);
|
||||
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||
if ($containers->count() === 0) {
|
||||
return;
|
||||
}
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$this->application->destination->server
|
||||
);
|
||||
$this->application->status = 'exited';
|
||||
$this->application->save();
|
||||
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
|
||||
}
|
||||
}
|
||||
$this->application->refresh();
|
||||
}
|
||||
}
|
||||
52
app/Http/Livewire/Project/Application/Preview/Form.php
Normal file
52
app/Http/Livewire/Project/Application/Preview/Form.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Application\Preview;
|
||||
|
||||
use App\Models\Application;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public string $preview_url_template;
|
||||
protected $rules = [
|
||||
'application.preview_url_template' => 'required',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'application.preview_url_template' => 'preview url template',
|
||||
];
|
||||
|
||||
public function resetToDefault()
|
||||
{
|
||||
$this->application->preview_url_template = '{{pr_id}}.{{domain}}';
|
||||
$this->preview_url_template = $this->application->preview_url_template;
|
||||
$this->application->save();
|
||||
$this->generate_real_url();
|
||||
}
|
||||
|
||||
public function generate_real_url()
|
||||
{
|
||||
if (data_get($this->application, 'fqdn')) {
|
||||
$firstFqdn = Str::of($this->application->fqdn)->before(',');
|
||||
$url = Url::fromString($firstFqdn);
|
||||
$host = $url->getHost();
|
||||
$this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host);
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->generate_real_url();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$this->application->preview_url_template = str_replace(' ', '', $this->application->preview_url_template);
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Preview url template updated successfully.');
|
||||
$this->generate_real_url();
|
||||
}
|
||||
}
|
||||
91
app/Http/Livewire/Project/Application/Previews.php
Normal file
91
app/Http/Livewire/Project/Application/Previews.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Previews extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public string $deployment_uuid;
|
||||
public array $parameters;
|
||||
public Collection $pull_requests;
|
||||
public int $rate_limit_remaining;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->pull_requests = collect();
|
||||
$this->parameters = get_route_parameters();
|
||||
}
|
||||
|
||||
public function load_prs()
|
||||
{
|
||||
try {
|
||||
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
|
||||
$this->rate_limit_remaining = $rate_limit_remaining;
|
||||
$this->pull_requests = $data->sortBy('number')->values();
|
||||
} catch (\Throwable $e) {
|
||||
$this->rate_limit_remaining = 0;
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function deploy(int $pull_request_id, string|null $pull_request_html_url = null)
|
||||
{
|
||||
try {
|
||||
$this->setDeploymentUuid();
|
||||
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (!$found && !is_null($pull_request_html_url)) {
|
||||
ApplicationPreview::create([
|
||||
'application_id' => $this->application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url
|
||||
]);
|
||||
}
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
deployment_uuid: $this->deployment_uuid,
|
||||
force_rebuild: true,
|
||||
pull_request_id: $pull_request_id,
|
||||
);
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deployment_uuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setDeploymentUuid()
|
||||
{
|
||||
$this->deployment_uuid = new Cuid2(7);
|
||||
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
|
||||
}
|
||||
|
||||
public function stop(int $pull_request_id)
|
||||
{
|
||||
try {
|
||||
$container_name = generateApplicationContainerName($this->application, $pull_request_id);
|
||||
|
||||
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
|
||||
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
|
||||
$this->application->refresh();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function previewRefresh()
|
||||
{
|
||||
$this->application->previews->each(function ($preview) {
|
||||
$preview->refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
71
app/Http/Livewire/Project/Application/Rollback.php
Normal file
71
app/Http/Livewire/Project/Application/Rollback.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Rollback extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public $images = [];
|
||||
public string|null $current;
|
||||
public array $parameters;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
}
|
||||
|
||||
public function rollbackImage($commit)
|
||||
{
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
commit: $commit,
|
||||
force_rebuild: false,
|
||||
);
|
||||
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function loadImages()
|
||||
{
|
||||
try {
|
||||
$image = $this->application->uuid;
|
||||
$output = instant_remote_process([
|
||||
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
|
||||
], $this->application->destination->server, throwError: false);
|
||||
$current_tag = Str::of($output)->trim()->explode(":");
|
||||
$this->current = data_get($current_tag, 1);
|
||||
|
||||
$output = instant_remote_process([
|
||||
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
|
||||
], $this->application->destination->server);
|
||||
$this->images = Str::of($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
|
||||
return Str::of($item)->contains($image);
|
||||
})->map(function ($item) {
|
||||
$item = Str::of($item)->explode('#');
|
||||
if ($item[1] === $this->current) {
|
||||
// $is_current = true;
|
||||
}
|
||||
return [
|
||||
'tag' => $item[1],
|
||||
'created_at' => $item[2],
|
||||
'is_current' => $is_current ?? null,
|
||||
];
|
||||
})->toArray();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
app/Http/Livewire/Project/Application/Source.php
Normal file
54
app/Http/Livewire/Project/Application/Source.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\PrivateKey;
|
||||
use Livewire\Component;
|
||||
|
||||
class Source extends Component
|
||||
{
|
||||
public $applicationId;
|
||||
public Application $application;
|
||||
public $private_keys;
|
||||
protected $rules = [
|
||||
'application.git_repository' => 'required',
|
||||
'application.git_branch' => 'required',
|
||||
'application.git_commit_sha' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'application.git_repository' => 'repository',
|
||||
'application.git_branch' => 'branch',
|
||||
'application.git_commit_sha' => 'commit sha',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->get_private_keys();
|
||||
}
|
||||
|
||||
private function get_private_keys()
|
||||
{
|
||||
$this->private_keys = PrivateKey::whereTeamId(currentTeam()->id)->get()->reject(function ($key) {
|
||||
return $key->id == $this->application->private_key_id;
|
||||
});
|
||||
}
|
||||
|
||||
public function setPrivateKey(int $private_key_id)
|
||||
{
|
||||
$this->application->private_key_id = $private_key_id;
|
||||
$this->application->save();
|
||||
$this->application->refresh();
|
||||
$this->get_private_keys();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
if (!$this->application->git_commit_sha) {
|
||||
$this->application->git_commit_sha = 'HEAD';
|
||||
}
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application source updated!');
|
||||
}
|
||||
}
|
||||
84
app/Http/Livewire/Project/Database/BackupEdit.php
Normal file
84
app/Http/Livewire/Project/Database/BackupEdit.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Database;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class BackupEdit extends Component
|
||||
{
|
||||
public $backup;
|
||||
public $s3s;
|
||||
public ?string $status = null;
|
||||
public array $parameters;
|
||||
|
||||
protected $rules = [
|
||||
'backup.enabled' => 'required|boolean',
|
||||
'backup.frequency' => 'required|string',
|
||||
'backup.number_of_backups_locally' => 'required|integer|min:1',
|
||||
'backup.save_s3' => 'required|boolean',
|
||||
'backup.s3_storage_id' => 'nullable|integer',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'backup.enabled' => 'Enabled',
|
||||
'backup.frequency' => 'Frequency',
|
||||
'backup.number_of_backups_locally' => 'Number of Backups Locally',
|
||||
'backup.save_s3' => 'Save to S3',
|
||||
'backup.s3_storage_id' => 'S3 Storage',
|
||||
];
|
||||
protected $messages = [
|
||||
'backup.s3_storage_id' => 'Select a S3 Storage',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
if (is_null($this->backup->s3_storage_id)) {
|
||||
$this->backup->s3_storage_id = 'default';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function delete()
|
||||
{
|
||||
// TODO: Delete backup from server and add a confirmation modal
|
||||
$this->backup->delete();
|
||||
redirect()->route('project.database.backups.all', $this->parameters);
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->custom_validate();
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function custom_validate()
|
||||
{
|
||||
if (!is_numeric($this->backup->s3_storage_id)) {
|
||||
$this->backup->s3_storage_id = null;
|
||||
}
|
||||
$isValid = validate_cron_expression($this->backup->frequency);
|
||||
if (!$isValid) {
|
||||
throw new \Exception('Invalid Cron / Human expression');
|
||||
}
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
ray($this->backup->s3_storage_id);
|
||||
try {
|
||||
$this->custom_validate();
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
23
app/Http/Livewire/Project/Database/BackupExecution.php
Normal file
23
app/Http/Livewire/Project/Database/BackupExecution.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Database;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackupExecution;
|
||||
use Livewire\Component;
|
||||
|
||||
class BackupExecution extends Component
|
||||
{
|
||||
public ScheduledDatabaseBackupExecution $execution;
|
||||
|
||||
public function download()
|
||||
{
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
delete_backup_locally($this->execution->filename, $this->execution->scheduledDatabaseBackup->database->destination->server);
|
||||
$this->execution->delete();
|
||||
$this->emit('success', 'Backup deleted successfully.');
|
||||
$this->emit('refreshBackupExecutions');
|
||||
}
|
||||
}
|
||||
17
app/Http/Livewire/Project/Database/BackupExecutions.php
Normal file
17
app/Http/Livewire/Project/Database/BackupExecutions.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Database;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class BackupExecutions extends Component
|
||||
{
|
||||
public $backup;
|
||||
public $executions;
|
||||
protected $listeners = ['refreshBackupExecutions'];
|
||||
|
||||
public function refreshBackupExecutions(): void
|
||||
{
|
||||
$this->executions = $this->backup->executions;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user