mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
577 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b80a519b80 | ||
|
|
2fa7ffc931 | ||
|
|
4abec14a21 | ||
|
|
18d0623011 | ||
|
|
aa634c78d1 | ||
|
|
a2d4373104 | ||
|
|
702e16d643 | ||
|
|
3b25c8f96b | ||
|
|
1c8c567791 | ||
|
|
807a3c9d66 | ||
|
|
2abd7bd7bb | ||
|
|
343957ab8b | ||
|
|
49261308f7 | ||
|
|
d037409237 | ||
|
|
338cbf62a1 | ||
|
|
4c51bffc7b | ||
|
|
fd98ba8812 | ||
|
|
930251e9c8 | ||
|
|
7cd441266a | ||
|
|
990fb8ec15 | ||
|
|
3fe982b2f4 | ||
|
|
9dd874e959 | ||
|
|
b91368223b | ||
|
|
139670372b | ||
|
|
1c0769ad75 | ||
|
|
e6cbcf98cb | ||
|
|
64b0481055 | ||
|
|
ce15161926 | ||
|
|
4003d4d894 | ||
|
|
6e011025a7 | ||
|
|
6c0544adb2 | ||
|
|
8e4f7c9065 | ||
|
|
e71f890b54 | ||
|
|
4dc35dea97 | ||
|
|
b63dfb4bcd | ||
|
|
b2ffd9183b | ||
|
|
5cb0bcfd9b | ||
|
|
1fbcfcaf74 | ||
|
|
3ba44a1e23 | ||
|
|
de4efbb555 | ||
|
|
6f443680f3 | ||
|
|
23b22e5ca8 | ||
|
|
1bba747ce5 | ||
|
|
9ebfc6646e | ||
|
|
055ff6dbbd | ||
|
|
6430e7b288 | ||
|
|
87b0050161 | ||
|
|
369d8b408d | ||
|
|
505abc592c | ||
|
|
c9df812258 | ||
|
|
0bfcf6b66f | ||
|
|
67853acabd | ||
|
|
79c98657b1 | ||
|
|
d1be7e44af | ||
|
|
33b853b981 | ||
|
|
e6063fb93a | ||
|
|
f30f23af59 | ||
|
|
d4798a3b22 | ||
|
|
eefc2a3d0e | ||
|
|
d14ca724e9 | ||
|
|
7b05aaffc3 | ||
|
|
f3beb5d8db | ||
|
|
e86b916415 | ||
|
|
e14cc6f2f0 | ||
|
|
8c1eb94401 | ||
|
|
29fa421945 | ||
|
|
7cfe98d988 | ||
|
|
e2314c350b | ||
|
|
3713b33578 | ||
|
|
e007a773fd | ||
|
|
e2821118eb | ||
|
|
4c8e73ac86 | ||
|
|
cb980fb814 | ||
|
|
41c84e3642 | ||
|
|
2bad98424f | ||
|
|
bc6b1e2dea | ||
|
|
911c15d1be | ||
|
|
f79d570870 | ||
|
|
7fffa9fba5 | ||
|
|
cbd634fb99 | ||
|
|
7ae7436d4f | ||
|
|
641bada100 | ||
|
|
3416d8d88e | ||
|
|
0bb503368b | ||
|
|
ac3a77c3c7 | ||
|
|
79b4178d76 | ||
|
|
42a61296d7 | ||
|
|
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 | ||
|
|
e038865693 | ||
|
|
dfd29dc37a | ||
|
|
4448b86b93 |
@@ -1,5 +1,6 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
.pnpm-store
|
||||
build
|
||||
.svelte-kit
|
||||
package
|
||||
@@ -7,6 +8,9 @@ package
|
||||
.env.*
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
local-serve
|
||||
local-serve
|
||||
apps/api/db/migration.db-journal
|
||||
apps/api/core*
|
||||
logs
|
||||
others/certificates
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
13
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
@@ -2,20 +2,25 @@ 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
|
||||
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: input
|
||||
id: repository
|
||||
attributes:
|
||||
label: Example public repository
|
||||
description: "An example public git repository to reproduce the issue easily (if applicable)."
|
||||
placeholder: "ex: https://github.com/coollabsio/coolify"
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
|
||||
@@ -2,9 +2,6 @@ name: 🛠️ Feature request
|
||||
description: Suggest an idea to improve coolify
|
||||
title: '[Feature]: '
|
||||
labels: [Enhancement]
|
||||
assignees:
|
||||
- andrasbacsai
|
||||
- vasani-arpit
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
106
.github/workflows/production-release.yml
vendored
106
.github/workflows/production-release.yml
vendored
@@ -1,91 +1,81 @@
|
||||
name: production-release
|
||||
name: Production Release to ghcr.io
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
jobs:
|
||||
arm64:
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "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
|
||||
- name: Login to ghcr.io
|
||||
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
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{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
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "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
|
||||
- name: Login to ghcr.io
|
||||
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
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}-aarch64
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-aarch64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-aarch64,mode=max
|
||||
tags: ${{ steps.meta.outputs.tags }}-aarch64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [amd64, arm64, aarch64]
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -93,18 +83,22 @@ jobs:
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
- name: Login to ghcr.io
|
||||
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
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{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 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
||||
docker manifest push coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
||||
docker buildx imagetools create --append ${{ fromJSON(steps.meta.outputs.json).tags[0] }}-aarch64 --tag ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
|
||||
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}}
|
||||
|
||||
111
.github/workflows/staging-release.yml
vendored
111
.github/workflows/staging-release.yml
vendored
@@ -1,70 +1,76 @@
|
||||
name: staging-release
|
||||
|
||||
name: Staging Release to ghcr.io
|
||||
concurrency:
|
||||
group: staging_environment
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
- "v3"
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
jobs:
|
||||
arm64:
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "next"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
ref: "v3"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
- name: Login to ghcr.io
|
||||
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
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/coolify:next-amd64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
aarch64:
|
||||
runs-on:
|
||||
group: aarch-runners
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "v3"
|
||||
- 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: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}-aarch64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [arm64, amd64]
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -72,15 +78,20 @@ jobs:
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
- 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
|
||||
docker buildx imagetools create --append ${{ steps.meta.outputs.tags }}-aarch64 --tag ${{ steps.meta.outputs.tags }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
|
||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -1,17 +1,25 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
.pnpm-store
|
||||
build
|
||||
/apps/ui/build
|
||||
/build
|
||||
.svelte-kit
|
||||
package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
local-serve
|
||||
apps/api/db/migration.db-journal
|
||||
apps/api/core*
|
||||
logs
|
||||
apps/server/build
|
||||
apps/backup/backups/*
|
||||
!apps/backup/backups/.gitkeep
|
||||
/logs
|
||||
others/certificates
|
||||
backups/*
|
||||
!backups/.gitkeep
|
||||
|
||||
# Trpc
|
||||
apps/server/db/*.db
|
||||
apps/server/db/*.db-journal
|
||||
32
.vscode/settings.json
vendored
32
.vscode/settings.json
vendored
@@ -1,11 +1,31 @@
|
||||
{
|
||||
"i18n-ally.localesPaths": ["src/lib/locales"],
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/lib/locales"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.extract.ignoredByFiles": {
|
||||
"src\\routes\\__layout.svelte": ["Coolify", "coolLabs logo"]
|
||||
"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
|
||||
}
|
||||
"i18n-ally.enabledFrameworks": [
|
||||
"svelte"
|
||||
],
|
||||
"i18n-ally.enabledParsers": [
|
||||
"js",
|
||||
"ts",
|
||||
"json"
|
||||
],
|
||||
"i18n-ally.extract.autoDetect": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true
|
||||
},
|
||||
"hide-files.files": []
|
||||
}
|
||||
@@ -1,57 +1,48 @@
|
||||
# Contribution
|
||||
# Contributing
|
||||
|
||||
First, thanks for considering to contribute to my project. It really means a lot! :)
|
||||
> "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 in the #contribution channel.
|
||||
You can ask for guidance anytime on our
|
||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
||||
|
||||
## Setup your development environment
|
||||
### Container based development flow (recommended and the easiest)
|
||||
All you need is to intall [Docker Engine 20.11+](https://docs.docker.com/engine/install/) on your local machine and run `pnpm dev:container`. It will build the base image for Coolify and start the development server inside Docker. All required ports (3000, 3001) will be exposed to your host.
|
||||
You'll need a set of skills to [get started](docs/contribution/GettingStarted.md).
|
||||
|
||||
### Github codespaces
|
||||
## 1) Setup your development environment
|
||||
|
||||
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.
|
||||
- 🌟 [Container based](docs/dev_setup/Container.md) ← *Recommended*
|
||||
- 📦 [DockerContainer](docs/dev_setup/DockerContiner.md) *WIP
|
||||
- 🐙 [Github Codespaces](docs/dev_setup/GithubCodespaces.md)
|
||||
- ☁️ [GitPod](docs/dev_setup/GitPod.md)
|
||||
- 🍏 [Local Mac](docs/dev_setup/Mac.md)
|
||||
|
||||
### Gitpod
|
||||
1. Use [container based development flow](#container-based-development-flow-easiest)
|
||||
2. Or setup your workspace manually:
|
||||
## 2) Basic requirements
|
||||
|
||||
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.
|
||||
- [Install Pnpm](https://pnpm.io/installation)
|
||||
- [Install Docker Engine](https://docs.docker.com/engine/install/)
|
||||
- [Setup Docker Compose Plugin](https://docs.docker.com/compose/install/)
|
||||
- [Setup GIT LFS Support](https://git-lfs.github.com/)
|
||||
|
||||
> Some packages, just `pack` are not installed in this way. You cannot test all the features. Please use the [container based development flow](#container-based-development-flow-easiest).
|
||||
## 3) Setup Coolify
|
||||
|
||||
### Local Machine
|
||||
> At the moment, Coolify `doesn't support Windows`. You must use `Linux` or `MacOS` or consider using Gitpod or Github Codespaces.
|
||||
- Copy `apps/api/.env.example` to `apps/api/.env`
|
||||
- Edit `apps/api/.env`, set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||
- Run `pnpm install` to install dependencies.
|
||||
- Run `pnpm db:push` to create a local SQlite database. This will apply all migrations at `db/dev.db`.
|
||||
- Run `pnpm db:seed` seed the database.
|
||||
- Run `pnpm dev` start coding.
|
||||
|
||||
Install all the prerequisites manually to your host system. If you would not like to install anything, I suggest to use the [container based development flow](#container-based-development-flow-easiest).
|
||||
```sh
|
||||
# Or... Copy and paste commands below:
|
||||
cp apps/api/.env.example apps/api/.env
|
||||
pnpm install
|
||||
pnpm db:push
|
||||
pnpm db:seed
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- 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.
|
||||
## 4) Start Coding
|
||||
|
||||
Optional:
|
||||
- To test Heroku buildpacks, you need [pack](https://github.com/buildpacks/pack) binary installed locally.
|
||||
You should be able to access `http://localhost:3000`.
|
||||
|
||||
### 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**
|
||||
|
||||
## How to add a new service?
|
||||
You can find all details [here](https://github.com/coollabsio/coolify-community-templates)
|
||||
1. Click `Register` and setup your first user.
|
||||
|
||||
@@ -22,9 +22,9 @@ ARG DOCKER_VERSION=20.10.18
|
||||
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
||||
ARG DOCKER_COMPOSE_VERSION=2.6.1
|
||||
# https://github.com/buildpacks/pack/releases
|
||||
ARG PACK_VERSION=v0.27.0
|
||||
ARG PACK_VERSION=0.27.0
|
||||
|
||||
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 update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3 vim
|
||||
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}
|
||||
@@ -38,7 +38,7 @@ RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /
|
||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack
|
||||
|
||||
COPY --from=build /app/apps/api/build/ .
|
||||
COPY --from=build /app/others/fluentbit/ ./fluentbit
|
||||
# COPY --from=build /app/others/fluentbit/ ./fluentbit
|
||||
COPY --from=build /app/apps/ui/build/ ./public
|
||||
COPY --from=build /app/apps/api/prisma/ ./prisma
|
||||
COPY --from=build /app/apps/api/package.json .
|
||||
@@ -50,4 +50,4 @@ RUN pnpm install -p
|
||||
|
||||
EXPOSE 3000
|
||||
ENV CHECKPOINT_DISABLE=1
|
||||
CMD pnpm start
|
||||
CMD pnpm start
|
||||
|
||||
@@ -9,7 +9,7 @@ ARG DOCKER_VERSION=20.10.18
|
||||
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
||||
ARG DOCKER_COMPOSE_VERSION=2.6.1
|
||||
# https://github.com/buildpacks/pack/releases
|
||||
ARG PACK_VERSION=v0.27.0
|
||||
ARG PACK_VERSION=0.27.0
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||
@@ -28,4 +28,4 @@ RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /
|
||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack
|
||||
|
||||
EXPOSE 3000
|
||||
ENV CHECKPOINT_DISABLE=1
|
||||
ENV CHECKPOINT_DISABLE=1
|
||||
|
||||
55
README.md
55
README.md
@@ -16,7 +16,7 @@ If you have a new service / build pack you would like to add, raise an idea [her
|
||||
|
||||
## How to install
|
||||
|
||||
For more details goto the [docs](https://docs.coollabs.io/coolify/installation).
|
||||
For more details goto the [docs](https://docs.coollabs.io/coolify-v3/installation).
|
||||
|
||||
Installation is automated with the following command:
|
||||
|
||||
@@ -77,10 +77,11 @@ Deploy your resource to:
|
||||
<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)
|
||||
- [WordPress](https://docs.coollabs.io/coolify-v3/services/wordpress)
|
||||
- [Ghost](https://ghost.org)
|
||||
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
||||
- [Plausible Analytics](https://docs.coollabs.io/coolify-v3/services/plausible-analytics)
|
||||
- [NocoDB](https://nocodb.com)
|
||||
- [VSCode Server](https://github.com/cdr/code-server)
|
||||
- [MinIO](https://min.io)
|
||||
@@ -93,28 +94,48 @@ Deploy your resource to:
|
||||
- [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.
|
||||
- And more...
|
||||
|
||||
## Support
|
||||
|
||||
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
||||
- Mastodon: [@andrasbacsai@fosstodon.org](https://fosstodon.org/@andrasbacsai)
|
||||
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
|
||||
- Twitter: [@andrasbacsai](https://twitter.com/heyandras)
|
||||
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
||||
- Discord: [Invitation](https://coollabs.io/discord)
|
||||
|
||||
## Financial Contributors
|
||||
---
|
||||
|
||||
## ⚗️ Expertise Contributions
|
||||
|
||||
Coolify is developed under the [Apache License](./LICENSE) and you can help to make it grow.
|
||||
Our community will be glad to have you on board!
|
||||
|
||||
Learn how to contribute to Coolify as as ...
|
||||
|
||||
→ [👩🏾💻 Software developer](./CONTRIBUTION.md)
|
||||
|
||||
→ [🧑🏻🏫 Translator](./docs/contribution/Translating.md)
|
||||
|
||||
<!--
|
||||
→ 🧑🏽🎨 Designer
|
||||
→ 🙋♀️ Community Manager
|
||||
→ 🧙🏻♂️ Text Content Creator
|
||||
→ 👨🏼🎤 Video Content Creator
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## 💰 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
|
||||
|
||||
Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
|
||||
|
||||

|
||||
|
||||
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>
|
||||
@@ -127,3 +148,11 @@ Support this project with your organization. Your logo will show up here with a
|
||||
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||
|
||||
### Individuals
|
||||
|
||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#coollabsio/coolify&Date)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
COOLIFY_APP_ID=local-dev
|
||||
# 32 bits long secret key
|
||||
COOLIFY_SECRET_KEY=12341234123412341234123412341234
|
||||
COOLIFY_DATABASE_URL=file:../db/dev.db
|
||||
COOLIFY_SENTRY_DSN=
|
||||
|
||||
COOLIFY_IS_ON=docker
|
||||
COOLIFY_WHITE_LABELED=false
|
||||
COOLIFY_WHITE_LABELED_ICON=
|
||||
COOLIFY_AUTO_UPDATE=
|
||||
COOLIFY_APP_ID=local-dev
|
||||
# 32 bits long secret key
|
||||
COOLIFY_SECRET_KEY=12341234123412341234123412341234
|
||||
COOLIFY_DATABASE_URL=file:../db/dev.db
|
||||
|
||||
COOLIFY_IS_ON=docker
|
||||
COOLIFY_WHITE_LABELED=false
|
||||
COOLIFY_WHITE_LABELED_ICON=
|
||||
COOLIFY_AUTO_UPDATE=
|
||||
|
||||
4
apps/api/.gitignore
vendored
4
apps/api/.gitignore
vendored
@@ -8,4 +8,6 @@ package
|
||||
!.env.example
|
||||
dist
|
||||
dev.db
|
||||
client
|
||||
client
|
||||
testTemplate.yaml
|
||||
testTags.json
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ignore": ["src/**/*.test.ts"],
|
||||
"ext": "ts,mjs,json,graphql",
|
||||
"exec": "rimraf build && esbuild `find src \\( -name '*.ts' \\)` --minify=true --platform=node --outdir=build --format=cjs && node build",
|
||||
"legacyWatch": true
|
||||
}
|
||||
"watch": [
|
||||
"src"
|
||||
],
|
||||
"ignore": [
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"ext": "ts,mjs,json,graphql",
|
||||
"exec": "rimraf build && esbuild `find src \\( -name '*.ts' \\)` --platform=node --outdir=build --format=cjs && node build",
|
||||
"legacyWatch": true
|
||||
}
|
||||
@@ -1,80 +1,83 @@
|
||||
{
|
||||
"name": "api",
|
||||
"description": "Coolify's Fastify API",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push && prisma generate",
|
||||
"db:seed": "prisma db seed",
|
||||
"db:studio": "prisma studio",
|
||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||
"dev": "nodemon",
|
||||
"build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --minify=true --platform=node --outdir=build --format=cjs",
|
||||
"format": "prettier --write 'src/**/*.{js,ts,json,md}'",
|
||||
"lint": "prettier --check 'src/**/*.{js,ts,json,md}' && eslint --ignore-path .eslintignore .",
|
||||
"start": "NODE_ENV=production pnpm prisma migrate deploy && pnpm prisma generate && pnpm prisma db seed && node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breejs/ts-worker": "2.0.0",
|
||||
"@fastify/autoload": "5.4.1",
|
||||
"@fastify/cookie": "8.3.0",
|
||||
"@fastify/cors": "8.1.1",
|
||||
"@fastify/env": "4.1.0",
|
||||
"@fastify/jwt": "6.3.2",
|
||||
"@fastify/multipart": "7.3.0",
|
||||
"@fastify/static": "6.5.0",
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@ladjs/graceful": "3.0.2",
|
||||
"@prisma/client": "4.5.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bree": "9.1.2",
|
||||
"cabin": "9.1.2",
|
||||
"compare-versions": "5.0.1",
|
||||
"csv-parse": "5.3.1",
|
||||
"csvtojson": "2.0.10",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.11.6",
|
||||
"dockerode": "3.3.4",
|
||||
"dotenv-extended": "2.9.0",
|
||||
"execa": "6.1.0",
|
||||
"fastify": "4.9.2",
|
||||
"fastify-plugin": "4.3.0",
|
||||
"fastify-socket.io": "4.0.0",
|
||||
"generate-password": "1.7.0",
|
||||
"got": "12.5.2",
|
||||
"is-ip": "5.0.0",
|
||||
"is-port-reachable": "4.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"node-forge": "1.3.1",
|
||||
"node-os-utils": "1.3.7",
|
||||
"p-all": "4.0.0",
|
||||
"p-throttle": "5.0.0",
|
||||
"prisma": "4.5.0",
|
||||
"public-ip": "6.0.1",
|
||||
"pump": "3.0.0",
|
||||
"socket.io": "4.5.3",
|
||||
"ssh-config": "4.1.6",
|
||||
"strip-ansi": "7.0.1",
|
||||
"unique-names-generator": "4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.6",
|
||||
"@types/node-os-utils": "1.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.41.0",
|
||||
"@typescript-eslint/parser": "5.41.0",
|
||||
"esbuild": "0.15.12",
|
||||
"eslint": "8.26.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"nodemon": "2.0.20",
|
||||
"prettier": "2.7.1",
|
||||
"rimraf": "3.0.2",
|
||||
"tsconfig-paths": "4.1.0",
|
||||
"types-fastify-socket.io": "0.0.1",
|
||||
"typescript": "4.8.4"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "node prisma/seed.js"
|
||||
}
|
||||
}
|
||||
"name": "api",
|
||||
"description": "Coolify's Fastify API",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push && prisma generate",
|
||||
"db:seed": "prisma db seed",
|
||||
"db:studio": "prisma studio",
|
||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||
"dev": "nodemon",
|
||||
"build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --platform=node --outdir=build --format=cjs",
|
||||
"format": "prettier --write 'src/**/*.{js,ts,json,md}'",
|
||||
"lint": "prettier --check 'src/**/*.{js,ts,json,md}' && eslint --ignore-path .eslintignore .",
|
||||
"start": "NODE_ENV=production pnpm prisma migrate deploy && pnpm prisma generate && pnpm prisma db seed && node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breejs/ts-worker": "2.0.0",
|
||||
"@fastify/autoload": "5.7.0",
|
||||
"@fastify/cookie": "8.3.0",
|
||||
"@fastify/cors": "8.2.0",
|
||||
"@fastify/env": "4.2.0",
|
||||
"@fastify/jwt": "6.5.0",
|
||||
"@fastify/multipart": "7.4.1",
|
||||
"@fastify/static": "6.6.0",
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@ladjs/graceful": "3.2.1",
|
||||
"@prisma/client": "4.8.1",
|
||||
"axe": "11.2.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bree": "9.1.3",
|
||||
"cabin": "11.1.1",
|
||||
"compare-versions": "5.0.1",
|
||||
"csv-parse": "5.3.3",
|
||||
"csvtojson": "2.0.10",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.11.7",
|
||||
"dockerode": "3.3.4",
|
||||
"dotenv-extended": "2.9.0",
|
||||
"execa": "6.1.0",
|
||||
"fastify": "4.11.0",
|
||||
"fastify-plugin": "4.3.0",
|
||||
"fastify-socket.io": "4.0.0",
|
||||
"generate-password": "1.7.0",
|
||||
"got": "12.5.3",
|
||||
"is-ip": "5.0.0",
|
||||
"is-port-reachable": "4.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"minimist": "^1.2.7",
|
||||
"node-forge": "1.3.1",
|
||||
"node-os-utils": "1.3.7",
|
||||
"p-all": "4.0.0",
|
||||
"p-throttle": "5.0.0",
|
||||
"prisma": "4.8.1",
|
||||
"public-ip": "6.0.1",
|
||||
"pump": "3.0.0",
|
||||
"shell-quote": "^1.7.4",
|
||||
"socket.io": "4.5.4",
|
||||
"ssh-config": "4.2.0",
|
||||
"strip-ansi": "7.0.1",
|
||||
"unique-names-generator": "4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.18",
|
||||
"@types/node-os-utils": "1.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.48.1",
|
||||
"@typescript-eslint/parser": "5.48.1",
|
||||
"esbuild": "0.16.16",
|
||||
"eslint": "8.31.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"nodemon": "2.0.20",
|
||||
"prettier": "2.8.2",
|
||||
"rimraf": "3.0.2",
|
||||
"tsconfig-paths": "4.1.2",
|
||||
"types-fastify-socket.io": "0.0.1",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "node prisma/seed.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"isAPIDebuggingEnabled" BOOLEAN DEFAULT false,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||
"proxyPassword" TEXT NOT NULL,
|
||||
"proxyUser" TEXT NOT NULL,
|
||||
"proxyHash" TEXT,
|
||||
"proxyDefaultRedirect" TEXT,
|
||||
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"DNSServers" TEXT,
|
||||
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"ipv4" TEXT,
|
||||
"ipv6" TEXT,
|
||||
"arch" TEXT,
|
||||
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
INSERT INTO "new_Setting" ("DNSServers", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "DNSServers", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
CREATE TABLE "new_ApplicationPersistentStorage" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"oldPath" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationPersistentStorage_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationPersistentStorage" ("applicationId", "createdAt", "id", "path", "updatedAt") SELECT "applicationId", "createdAt", "id", "path", "updatedAt" FROM "ApplicationPersistentStorage";
|
||||
DROP TABLE "ApplicationPersistentStorage";
|
||||
ALTER TABLE "new_ApplicationPersistentStorage" RENAME TO "ApplicationPersistentStorage";
|
||||
CREATE UNIQUE INDEX "ApplicationPersistentStorage_applicationId_path_key" ON "ApplicationPersistentStorage"("applicationId", "path");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `proxyHash` on the `Setting` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `proxyPassword` on the `Setting` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `proxyUser` on the `Setting` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||
"DNSServers" TEXT,
|
||||
"ipv4" TEXT,
|
||||
"ipv6" TEXT,
|
||||
"arch" TEXT,
|
||||
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||
"proxyDefaultRedirect" TEXT,
|
||||
"isAPIDebuggingEnabled" BOOLEAN DEFAULT false,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt") SELECT "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,59 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "DockerRegistry" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"username" TEXT,
|
||||
"password" TEXT,
|
||||
"isSystemWide" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"teamId" TEXT,
|
||||
CONSTRAINT "DockerRegistry_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Application" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"fqdn" TEXT,
|
||||
"repository" TEXT,
|
||||
"configHash" TEXT,
|
||||
"branch" TEXT,
|
||||
"buildPack" TEXT,
|
||||
"projectId" INTEGER,
|
||||
"port" INTEGER,
|
||||
"exposePort" INTEGER,
|
||||
"installCommand" TEXT,
|
||||
"buildCommand" TEXT,
|
||||
"startCommand" TEXT,
|
||||
"baseDirectory" TEXT,
|
||||
"publishDirectory" TEXT,
|
||||
"deploymentType" TEXT,
|
||||
"phpModules" TEXT,
|
||||
"pythonWSGI" TEXT,
|
||||
"pythonModule" TEXT,
|
||||
"pythonVariable" TEXT,
|
||||
"dockerFileLocation" TEXT,
|
||||
"denoMainFile" TEXT,
|
||||
"denoOptions" TEXT,
|
||||
"dockerComposeFile" TEXT,
|
||||
"dockerComposeFileLocation" TEXT,
|
||||
"dockerComposeConfiguration" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"destinationDockerId" TEXT,
|
||||
"gitSourceId" TEXT,
|
||||
"baseImage" TEXT,
|
||||
"baseBuildImage" TEXT,
|
||||
"dockerRegistryId" TEXT NOT NULL DEFAULT '0',
|
||||
CONSTRAINT "Application_gitSourceId_fkey" FOREIGN KEY ("gitSourceId") REFERENCES "GitSource" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Application_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Application_dockerRegistryId_fkey" FOREIGN KEY ("dockerRegistryId") REFERENCES "DockerRegistry" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Application" ("baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "exposePort", "fqdn", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt") SELECT "baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "exposePort", "fqdn", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt" FROM "Application";
|
||||
DROP TABLE "Application";
|
||||
ALTER TABLE "new_Application" RENAME TO "Application";
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,30 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||
"DNSServers" TEXT,
|
||||
"ipv4" TEXT,
|
||||
"ipv6" TEXT,
|
||||
"arch" TEXT,
|
||||
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||
"proxyDefaultRedirect" TEXT,
|
||||
"doNotTrack" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isAPIDebuggingEnabled" BOOLEAN DEFAULT false,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt") SELECT "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,60 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||
"DNSServers" TEXT,
|
||||
"ipv4" TEXT,
|
||||
"ipv6" TEXT,
|
||||
"arch" TEXT,
|
||||
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||
"proxyDefaultRedirect" TEXT,
|
||||
"doNotTrack" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isAPIDebuggingEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt") SELECT "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", coalesce("isAPIDebuggingEnabled", false) AS "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
CREATE TABLE "new_GlitchTip" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"postgresqlUser" TEXT NOT NULL,
|
||||
"postgresqlPassword" TEXT NOT NULL,
|
||||
"postgresqlDatabase" TEXT NOT NULL,
|
||||
"postgresqlPublicPort" INTEGER,
|
||||
"secretKeyBase" TEXT,
|
||||
"defaultEmail" TEXT NOT NULL,
|
||||
"defaultUsername" TEXT NOT NULL,
|
||||
"defaultPassword" TEXT NOT NULL,
|
||||
"defaultEmailFrom" TEXT NOT NULL DEFAULT 'glitchtip@domain.tdl',
|
||||
"emailSmtpHost" TEXT DEFAULT 'domain.tdl',
|
||||
"emailSmtpPort" INTEGER DEFAULT 25,
|
||||
"emailSmtpUser" TEXT,
|
||||
"emailSmtpPassword" TEXT,
|
||||
"emailSmtpUseTls" BOOLEAN NOT NULL DEFAULT false,
|
||||
"emailSmtpUseSsl" BOOLEAN NOT NULL DEFAULT false,
|
||||
"emailBackend" TEXT,
|
||||
"mailgunApiKey" TEXT,
|
||||
"sendgridApiKey" TEXT,
|
||||
"enableOpenUserRegistration" BOOLEAN NOT NULL DEFAULT true,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "GlitchTip_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_GlitchTip" ("createdAt", "defaultEmail", "defaultEmailFrom", "defaultPassword", "defaultUsername", "emailBackend", "emailSmtpHost", "emailSmtpPassword", "emailSmtpPort", "emailSmtpUseSsl", "emailSmtpUseTls", "emailSmtpUser", "enableOpenUserRegistration", "id", "mailgunApiKey", "postgresqlDatabase", "postgresqlPassword", "postgresqlPublicPort", "postgresqlUser", "secretKeyBase", "sendgridApiKey", "serviceId", "updatedAt") SELECT "createdAt", "defaultEmail", "defaultEmailFrom", "defaultPassword", "defaultUsername", "emailBackend", "emailSmtpHost", "emailSmtpPassword", "emailSmtpPort", coalesce("emailSmtpUseSsl", false) AS "emailSmtpUseSsl", coalesce("emailSmtpUseTls", false) AS "emailSmtpUseTls", "emailSmtpUser", "enableOpenUserRegistration", "id", "mailgunApiKey", "postgresqlDatabase", "postgresqlPassword", "postgresqlPublicPort", "postgresqlUser", "secretKeyBase", "sendgridApiKey", "serviceId", "updatedAt" FROM "GlitchTip";
|
||||
DROP TABLE "GlitchTip";
|
||||
ALTER TABLE "new_GlitchTip" RENAME TO "GlitchTip";
|
||||
CREATE UNIQUE INDEX "GlitchTip_serviceId_key" ON "GlitchTip"("serviceId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Setting" ADD COLUMN "sentryDSN" TEXT;
|
||||
@@ -0,0 +1,31 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||
"DNSServers" TEXT NOT NULL DEFAULT '1.1.1.1,8.8.8.8',
|
||||
"ipv4" TEXT,
|
||||
"ipv6" TEXT,
|
||||
"arch" TEXT,
|
||||
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||
"proxyDefaultRedirect" TEXT,
|
||||
"doNotTrack" BOOLEAN NOT NULL DEFAULT false,
|
||||
"sentryDSN" TEXT,
|
||||
"isAPIDebuggingEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "sentryDSN", "updatedAt") SELECT coalesce("DNSServers", '1.1.1.1,8.8.8.8') AS "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "sentryDSN", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,33 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||
"DNSServers" TEXT NOT NULL DEFAULT '1.1.1.1,8.8.8.8',
|
||||
"ipv4" TEXT,
|
||||
"ipv6" TEXT,
|
||||
"arch" TEXT,
|
||||
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||
"numberOfDockerImagesKeptLocally" INTEGER NOT NULL DEFAULT 3,
|
||||
"proxyDefaultRedirect" TEXT,
|
||||
"doNotTrack" BOOLEAN NOT NULL DEFAULT false,
|
||||
"sentryDSN" TEXT,
|
||||
"previewSeparator" TEXT NOT NULL DEFAULT '.',
|
||||
"isAPIDebuggingEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "numberOfDockerImagesKeptLocally", "proxyDefaultRedirect", "sentryDSN", "updatedAt") SELECT "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", 3, "proxyDefaultRedirect", "sentryDSN", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "gitCommitHash" TEXT;
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `isSystemWide` on the `DockerRegistry` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_DockerRegistry" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"username" TEXT,
|
||||
"password" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"teamId" TEXT,
|
||||
CONSTRAINT "DockerRegistry_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_DockerRegistry" ("createdAt", "id", "name", "password", "teamId", "updatedAt", "url", "username") SELECT "createdAt", "id", "name", "password", "teamId", "updatedAt", "url", "username" FROM "DockerRegistry";
|
||||
DROP TABLE "DockerRegistry";
|
||||
ALTER TABLE "new_DockerRegistry" RENAME TO "DockerRegistry";
|
||||
CREATE TABLE "new_Application" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"fqdn" TEXT,
|
||||
"repository" TEXT,
|
||||
"configHash" TEXT,
|
||||
"branch" TEXT,
|
||||
"buildPack" TEXT,
|
||||
"projectId" INTEGER,
|
||||
"port" INTEGER,
|
||||
"exposePort" INTEGER,
|
||||
"installCommand" TEXT,
|
||||
"buildCommand" TEXT,
|
||||
"startCommand" TEXT,
|
||||
"baseDirectory" TEXT,
|
||||
"publishDirectory" TEXT,
|
||||
"deploymentType" TEXT,
|
||||
"phpModules" TEXT,
|
||||
"pythonWSGI" TEXT,
|
||||
"pythonModule" TEXT,
|
||||
"pythonVariable" TEXT,
|
||||
"dockerFileLocation" TEXT,
|
||||
"denoMainFile" TEXT,
|
||||
"denoOptions" TEXT,
|
||||
"dockerComposeFile" TEXT,
|
||||
"dockerComposeFileLocation" TEXT,
|
||||
"dockerComposeConfiguration" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"destinationDockerId" TEXT,
|
||||
"gitSourceId" TEXT,
|
||||
"gitCommitHash" TEXT,
|
||||
"baseImage" TEXT,
|
||||
"baseBuildImage" TEXT,
|
||||
"dockerRegistryId" TEXT,
|
||||
CONSTRAINT "Application_gitSourceId_fkey" FOREIGN KEY ("gitSourceId") REFERENCES "GitSource" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Application_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Application_dockerRegistryId_fkey" FOREIGN KEY ("dockerRegistryId") REFERENCES "DockerRegistry" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Application" ("baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "dockerRegistryId", "exposePort", "fqdn", "gitCommitHash", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt") SELECT "baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "dockerRegistryId", "exposePort", "fqdn", "gitCommitHash", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt" FROM "Application";
|
||||
DROP TABLE "Application";
|
||||
ALTER TABLE "new_Application" RENAME TO "Application";
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "simpleDockerfile" TEXT;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "dockerRegistryImageName" TEXT;
|
||||
@@ -0,0 +1,27 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_GitSource" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"forPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||
"type" TEXT,
|
||||
"apiUrl" TEXT,
|
||||
"htmlUrl" TEXT,
|
||||
"customPort" INTEGER NOT NULL DEFAULT 22,
|
||||
"customUser" TEXT NOT NULL DEFAULT 'git',
|
||||
"organization" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"githubAppId" TEXT,
|
||||
"gitlabAppId" TEXT,
|
||||
"isSystemWide" BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt" FROM "GitSource";
|
||||
DROP TABLE "GitSource";
|
||||
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
||||
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,24 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||
DROP TABLE "ApplicationSettings";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "ApplicationPersistentStorage" ADD COLUMN "hostPath" TEXT;
|
||||
@@ -0,0 +1,29 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "basicAuthPw" TEXT;
|
||||
ALTER TABLE "Application" ADD COLUMN "basicAuthUser" TEXT;
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
|
||||
"basicAuth" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||
DROP TABLE "ApplicationSettings";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -19,27 +19,29 @@ model Certificate {
|
||||
}
|
||||
|
||||
model Setting {
|
||||
id String @id @default(cuid())
|
||||
fqdn String? @unique
|
||||
isAPIDebuggingEnabled Boolean? @default(false)
|
||||
isRegistrationEnabled Boolean @default(false)
|
||||
dualCerts Boolean @default(false)
|
||||
minPort Int @default(9000)
|
||||
maxPort Int @default(9100)
|
||||
proxyPassword String
|
||||
proxyUser String
|
||||
proxyHash String?
|
||||
proxyDefaultRedirect String?
|
||||
isAutoUpdateEnabled Boolean @default(false)
|
||||
isDNSCheckEnabled Boolean @default(true)
|
||||
DNSServers String?
|
||||
isTraefikUsed Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
ipv4 String?
|
||||
ipv6 String?
|
||||
arch String?
|
||||
concurrentBuilds Int @default(1)
|
||||
id String @id @default(cuid())
|
||||
fqdn String? @unique
|
||||
dualCerts Boolean @default(false)
|
||||
minPort Int @default(9000)
|
||||
maxPort Int @default(9100)
|
||||
DNSServers String @default("1.1.1.1,8.8.8.8")
|
||||
ipv4 String?
|
||||
ipv6 String?
|
||||
arch String?
|
||||
concurrentBuilds Int @default(1)
|
||||
applicationStoragePathMigrationFinished Boolean @default(false)
|
||||
numberOfDockerImagesKeptLocally Int @default(3)
|
||||
proxyDefaultRedirect String?
|
||||
doNotTrack Boolean @default(false)
|
||||
sentryDSN String?
|
||||
previewSeparator String @default(".")
|
||||
isAPIDebuggingEnabled Boolean @default(false)
|
||||
isRegistrationEnabled Boolean @default(true)
|
||||
isAutoUpdateEnabled Boolean @default(false)
|
||||
isDNSCheckEnabled Boolean @default(true)
|
||||
isTraefikUsed Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model User {
|
||||
@@ -82,6 +84,7 @@ model Team {
|
||||
service Service[]
|
||||
users User[]
|
||||
certificate Certificate[]
|
||||
dockerRegistry DockerRegistry[]
|
||||
}
|
||||
|
||||
model TeamInvitation {
|
||||
@@ -95,7 +98,7 @@ model TeamInvitation {
|
||||
}
|
||||
|
||||
model Application {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
fqdn String?
|
||||
repository String?
|
||||
@@ -121,20 +124,28 @@ model Application {
|
||||
dockerComposeFile String?
|
||||
dockerComposeFileLocation String?
|
||||
dockerComposeConfiguration String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
destinationDockerId String?
|
||||
gitSourceId String?
|
||||
gitCommitHash String?
|
||||
baseImage String?
|
||||
baseBuildImage String?
|
||||
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
persistentStorage ApplicationPersistentStorage[]
|
||||
settings ApplicationSettings?
|
||||
secrets Secret[]
|
||||
teams Team[]
|
||||
connectedDatabase ApplicationConnectedDatabase?
|
||||
previewApplication PreviewApplication[]
|
||||
dockerRegistryId String?
|
||||
dockerRegistryImageName String?
|
||||
simpleDockerfile String?
|
||||
basicAuthUser String?
|
||||
basicAuthPw String?
|
||||
|
||||
persistentStorage ApplicationPersistentStorage[]
|
||||
secrets Secret[]
|
||||
teams Team[]
|
||||
connectedDatabase ApplicationConnectedDatabase?
|
||||
previewApplication PreviewApplication[]
|
||||
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
dockerRegistry DockerRegistry? @relation(fields: [dockerRegistryId], references: [id])
|
||||
}
|
||||
|
||||
model PreviewApplication {
|
||||
@@ -177,6 +188,8 @@ model ApplicationSettings {
|
||||
isPublicRepository Boolean @default(false)
|
||||
isDBBranching Boolean @default(false)
|
||||
isCustomSSL Boolean @default(false)
|
||||
isHttp2 Boolean @default(false)
|
||||
basicAuth Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
@@ -185,7 +198,9 @@ model ApplicationSettings {
|
||||
model ApplicationPersistentStorage {
|
||||
id String @id @default(cuid())
|
||||
applicationId String
|
||||
hostPath String?
|
||||
path String
|
||||
oldPath Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
@@ -294,6 +309,19 @@ model SshKey {
|
||||
destinationDocker DestinationDocker[]
|
||||
}
|
||||
|
||||
model DockerRegistry {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
url String
|
||||
username String?
|
||||
password String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
teamId String?
|
||||
team Team? @relation(fields: [teamId], references: [id])
|
||||
application Application[]
|
||||
}
|
||||
|
||||
model GitSource {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
@@ -302,6 +330,7 @@ model GitSource {
|
||||
apiUrl String?
|
||||
htmlUrl String?
|
||||
customPort Int @default(22)
|
||||
customUser String @default("git")
|
||||
organization String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -624,8 +653,8 @@ model GlitchTip {
|
||||
emailSmtpPort Int? @default(25)
|
||||
emailSmtpUser String?
|
||||
emailSmtpPassword String?
|
||||
emailSmtpUseTls Boolean? @default(false)
|
||||
emailSmtpUseSsl Boolean? @default(false)
|
||||
emailSmtpUseTls Boolean @default(false)
|
||||
emailSmtpUseSsl Boolean @default(false)
|
||||
emailBackend String?
|
||||
mailgunApiKey String?
|
||||
sendgridApiKey String?
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
const dotEnvExtended = require('dotenv-extended');
|
||||
dotEnvExtended.load();
|
||||
const crypto = require('crypto');
|
||||
const generator = require('generate-password');
|
||||
const cuid = require('cuid');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
function generatePassword(length = 24) {
|
||||
return generator.generate({
|
||||
length,
|
||||
numbers: true,
|
||||
strict: true
|
||||
});
|
||||
}
|
||||
const algorithm = 'aes-256-ctr';
|
||||
|
||||
async function main() {
|
||||
@@ -21,11 +11,8 @@ async function main() {
|
||||
if (!settingsFound) {
|
||||
await prisma.setting.create({
|
||||
data: {
|
||||
isRegistrationEnabled: true,
|
||||
proxyPassword: encrypt(generatePassword()),
|
||||
proxyUser: cuid(),
|
||||
arch: process.arch,
|
||||
DNSServers: '1.1.1.1,8.8.8.8'
|
||||
id: '0',
|
||||
arch: process.arch
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -34,11 +21,11 @@ async function main() {
|
||||
id: settingsFound.id
|
||||
},
|
||||
data: {
|
||||
isTraefikUsed: true,
|
||||
proxyHash: null
|
||||
id: '0'
|
||||
}
|
||||
});
|
||||
}
|
||||
// Create local docker engine
|
||||
const localDocker = await prisma.destinationDocker.findFirst({
|
||||
where: { engine: '/var/run/docker.sock' }
|
||||
});
|
||||
@@ -55,23 +42,18 @@ async function main() {
|
||||
|
||||
// Set auto-update based on env variable
|
||||
const isAutoUpdateEnabled = process.env['COOLIFY_AUTO_UPDATE'] === 'true';
|
||||
const settings = await prisma.setting.findFirst({});
|
||||
if (settings) {
|
||||
await prisma.setting.update({
|
||||
where: {
|
||||
id: settings.id
|
||||
},
|
||||
data: {
|
||||
isAutoUpdateEnabled
|
||||
}
|
||||
});
|
||||
}
|
||||
await prisma.setting.update({
|
||||
where: {
|
||||
id: '0'
|
||||
},
|
||||
data: {
|
||||
isAutoUpdateEnabled
|
||||
}
|
||||
});
|
||||
// Create public github source
|
||||
const github = await prisma.gitSource.findFirst({
|
||||
where: { htmlUrl: 'https://github.com', forPublic: true }
|
||||
});
|
||||
const gitlab = await prisma.gitSource.findFirst({
|
||||
where: { htmlUrl: 'https://gitlab.com', forPublic: true }
|
||||
});
|
||||
if (!github) {
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
@@ -83,6 +65,10 @@ async function main() {
|
||||
}
|
||||
});
|
||||
}
|
||||
// Create public gitlab source
|
||||
const gitlab = await prisma.gitSource.findFirst({
|
||||
where: { htmlUrl: 'https://gitlab.com', forPublic: true }
|
||||
});
|
||||
if (!gitlab) {
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
@@ -95,16 +81,295 @@ async function main() {
|
||||
});
|
||||
}
|
||||
// Set new preview secrets
|
||||
const secrets = await prisma.secret.findMany({ where: { isPRMRSecret: false } })
|
||||
const secrets = await prisma.secret.findMany({ where: { isPRMRSecret: false } });
|
||||
if (secrets.length > 0) {
|
||||
for (const secret of secrets) {
|
||||
const previewSecrets = await prisma.secret.findMany({ where: { applicationId: secret.applicationId, name: secret.name, isPRMRSecret: true } })
|
||||
const previewSecrets = await prisma.secret.findMany({
|
||||
where: { applicationId: secret.applicationId, name: secret.name, isPRMRSecret: true }
|
||||
});
|
||||
if (previewSecrets.length === 0) {
|
||||
await prisma.secret.create({ data: { ...secret, id: undefined, isPRMRSecret: true } })
|
||||
await prisma.secret.create({ data: { ...secret, id: undefined, isPRMRSecret: true } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async function reEncryptSecrets() {
|
||||
const { execaCommand } = await import('execa');
|
||||
const image = await execaCommand("docker inspect coolify --format '{{ .Config.Image }}'", {
|
||||
shell: true
|
||||
});
|
||||
const version = image.stdout.split(':')[1] ?? null;
|
||||
const date = new Date().getTime();
|
||||
|
||||
let backupfile = `/app/db/prod.db_${date}`;
|
||||
if (version) {
|
||||
backupfile = `/app/db/prod.db_${version}_${date}`;
|
||||
}
|
||||
await execaCommand('env | grep "^COOLIFY" | sort > .env', {
|
||||
shell: true
|
||||
});
|
||||
const secretOld = process.env['COOLIFY_SECRET_KEY'];
|
||||
let secretNew = process.env['COOLIFY_SECRET_KEY_BETTER'];
|
||||
if (!secretNew) {
|
||||
console.log('No COOLIFY_SECRET_KEY_BETTER found... Generating new one...');
|
||||
const { stdout: newKey } = await execaCommand(
|
||||
'openssl rand -base64 1024 | sha256sum | base64 | head -c 32',
|
||||
{ shell: true }
|
||||
);
|
||||
secretNew = newKey;
|
||||
}
|
||||
if (secretOld !== secretNew) {
|
||||
console.log(`Backup database to ${backupfile}.`);
|
||||
await execaCommand(`cp /app/db/prod.db ${backupfile}`, { shell: true });
|
||||
console.log(
|
||||
'Secrets (COOLIFY_SECRET_KEY & COOLIFY_SECRET_KEY_BETTER) are different, so re-encrypting everything...'
|
||||
);
|
||||
await execaCommand(`sed -i '/COOLIFY_SECRET_KEY=/d' .env`, { shell: true });
|
||||
await execaCommand(`sed -i '/COOLIFY_SECRET_KEY_BETTER=/d' .env`, { shell: true });
|
||||
await execaCommand(`echo "COOLIFY_SECRET_KEY=${secretNew}" >> .env`, { shell: true });
|
||||
await execaCommand('echo "COOLIFY_SECRET_KEY_BETTER=' + secretNew + '" >> .env ', {
|
||||
shell: true
|
||||
});
|
||||
await execaCommand(`echo "COOLIFY_SECRET_KEY_OLD_${date}=${secretOld}" >> .env`, {
|
||||
shell: true
|
||||
});
|
||||
const transactions = [];
|
||||
const secrets = await prisma.secret.findMany();
|
||||
if (secrets.length > 0) {
|
||||
for (const secret of secrets) {
|
||||
try {
|
||||
const value = decrypt(secret.value, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.secret.update({
|
||||
where: { id: secret.id },
|
||||
data: { value: newValue }
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const serviceSecrets = await prisma.serviceSecret.findMany();
|
||||
if (serviceSecrets.length > 0) {
|
||||
for (const secret of serviceSecrets) {
|
||||
try {
|
||||
const value = decrypt(secret.value, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.serviceSecret.update({
|
||||
where: { id: secret.id },
|
||||
data: { value: newValue }
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const gitlabApps = await prisma.gitlabApp.findMany();
|
||||
if (gitlabApps.length > 0) {
|
||||
for (const gitlabApp of gitlabApps) {
|
||||
try {
|
||||
const value = decrypt(gitlabApp.privateSshKey, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
const appSecret = decrypt(gitlabApp.appSecret, secretOld);
|
||||
const newAppSecret = encrypt(appSecret, secretNew);
|
||||
transactions.push(
|
||||
prisma.gitlabApp.update({
|
||||
where: { id: gitlabApp.id },
|
||||
data: { privateSshKey: newValue, appSecret: newAppSecret }
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const githubApps = await prisma.githubApp.findMany();
|
||||
if (githubApps.length > 0) {
|
||||
for (const githubApp of githubApps) {
|
||||
try {
|
||||
const clientSecret = decrypt(githubApp.clientSecret, secretOld);
|
||||
const newClientSecret = encrypt(clientSecret, secretNew);
|
||||
const webhookSecret = decrypt(githubApp.webhookSecret, secretOld);
|
||||
const newWebhookSecret = encrypt(webhookSecret, secretNew);
|
||||
const privateKey = decrypt(githubApp.privateKey, secretOld);
|
||||
const newPrivateKey = encrypt(privateKey, secretNew);
|
||||
|
||||
transactions.push(
|
||||
prisma.githubApp.update({
|
||||
where: { id: githubApp.id },
|
||||
data: {
|
||||
clientSecret: newClientSecret,
|
||||
webhookSecret: newWebhookSecret,
|
||||
privateKey: newPrivateKey
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const databases = await prisma.database.findMany();
|
||||
if (databases.length > 0) {
|
||||
for (const database of databases) {
|
||||
try {
|
||||
const dbUserPassword = decrypt(database.dbUserPassword, secretOld);
|
||||
const newDbUserPassword = encrypt(dbUserPassword, secretNew);
|
||||
const rootUserPassword = decrypt(database.rootUserPassword, secretOld);
|
||||
const newRootUserPassword = encrypt(rootUserPassword, secretNew);
|
||||
transactions.push(
|
||||
prisma.database.update({
|
||||
where: { id: database.id },
|
||||
data: {
|
||||
dbUserPassword: newDbUserPassword,
|
||||
rootUserPassword: newRootUserPassword
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const databaseSecrets = await prisma.databaseSecret.findMany();
|
||||
if (databaseSecrets.length > 0) {
|
||||
for (const databaseSecret of databaseSecrets) {
|
||||
try {
|
||||
const value = decrypt(databaseSecret.value, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.databaseSecret.update({
|
||||
where: { id: databaseSecret.id },
|
||||
data: { value: newValue }
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const wordpresses = await prisma.wordpress.findMany();
|
||||
if (wordpresses.length > 0) {
|
||||
for (const wordpress of wordpresses) {
|
||||
try {
|
||||
const value = decrypt(wordpress.ftpHostKey, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
const ftpHostKeyPrivate = decrypt(wordpress.ftpHostKeyPrivate, secretOld);
|
||||
const newFtpHostKeyPrivate = encrypt(ftpHostKeyPrivate, secretNew);
|
||||
let newFtpPassword = undefined;
|
||||
if (wordpress.ftpPassword != null) {
|
||||
const ftpPassword = decrypt(wordpress.ftpPassword, secretOld);
|
||||
newFtpPassword = encrypt(ftpPassword, secretNew);
|
||||
}
|
||||
|
||||
transactions.push(
|
||||
prisma.wordpress.update({
|
||||
where: { id: wordpress.id },
|
||||
data: {
|
||||
ftpHostKey: newValue,
|
||||
ftpHostKeyPrivate: newFtpHostKeyPrivate,
|
||||
ftpPassword: newFtpPassword
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const sshKeys = await prisma.sshKey.findMany();
|
||||
if (sshKeys.length > 0) {
|
||||
for (const key of sshKeys) {
|
||||
try {
|
||||
const value = decrypt(key.privateKey, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.sshKey.update({
|
||||
where: { id: key.id },
|
||||
data: {
|
||||
privateKey: newValue
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const dockerRegistries = await prisma.dockerRegistry.findMany();
|
||||
if (dockerRegistries.length > 0) {
|
||||
for (const registry of dockerRegistries) {
|
||||
try {
|
||||
const value = decrypt(registry.password, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.dockerRegistry.update({
|
||||
where: { id: registry.id },
|
||||
data: {
|
||||
password: newValue
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const certificates = await prisma.certificate.findMany();
|
||||
if (certificates.length > 0) {
|
||||
for (const certificate of certificates) {
|
||||
try {
|
||||
const value = decrypt(certificate.key, secretOld);
|
||||
const newValue = encrypt(value, secretNew);
|
||||
transactions.push(
|
||||
prisma.certificate.update({
|
||||
where: { id: certificate.id },
|
||||
data: {
|
||||
key: newValue
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.$transaction(transactions);
|
||||
} else {
|
||||
console.log('secrets are the same, so no need to re-encrypt');
|
||||
}
|
||||
}
|
||||
|
||||
const encrypt = (text, secret) => {
|
||||
if (text && secret) {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(algorithm, secret, iv);
|
||||
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
|
||||
return JSON.stringify({
|
||||
iv: iv.toString('hex'),
|
||||
content: encrypted.toString('hex')
|
||||
});
|
||||
}
|
||||
};
|
||||
const decrypt = (hashString, secret) => {
|
||||
if (hashString && secret) {
|
||||
const hash = JSON.parse(hashString);
|
||||
const decipher = crypto.createDecipheriv(algorithm, secret, Buffer.from(hash.iv, 'hex'));
|
||||
const decrpyted = Buffer.concat([
|
||||
decipher.update(Buffer.from(hash.content, 'hex')),
|
||||
decipher.final()
|
||||
]);
|
||||
if (/<2F>/.test(decrpyted.toString())) {
|
||||
throw new Error('Invalid secret. Skipping...');
|
||||
}
|
||||
return decrpyted.toString();
|
||||
}
|
||||
};
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
@@ -113,15 +378,11 @@ main()
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
const encrypt = (text) => {
|
||||
if (text) {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
||||
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
|
||||
return JSON.stringify({
|
||||
iv: iv.toString('hex'),
|
||||
content: encrypted.toString('hex')
|
||||
});
|
||||
}
|
||||
};
|
||||
reEncryptSecrets()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
@@ -1,44 +1,56 @@
|
||||
import Fastify from 'fastify';
|
||||
import cors from '@fastify/cors';
|
||||
import serve from '@fastify/static';
|
||||
import env from '@fastify/env';
|
||||
import cookie from '@fastify/cookie';
|
||||
import multipart from '@fastify/multipart';
|
||||
import path, { join } from 'path';
|
||||
import autoLoad from '@fastify/autoload';
|
||||
import socketIO from 'fastify-socket.io'
|
||||
import socketIOServer from './realtime'
|
||||
import cookie from '@fastify/cookie';
|
||||
import cors from '@fastify/cors';
|
||||
import env from '@fastify/env';
|
||||
import multipart from '@fastify/multipart';
|
||||
import serve from '@fastify/static';
|
||||
import Fastify from 'fastify';
|
||||
import socketIO from 'fastify-socket.io';
|
||||
import path, { join } from 'path';
|
||||
import socketIOServer from './realtime';
|
||||
|
||||
import { asyncExecShell, cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, encrypt, executeDockerCmd, executeSSHCmd, generateDatabaseConfiguration, isDev, listSettings, prisma, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common';
|
||||
import { scheduler } from './lib/scheduler';
|
||||
import Graceful from '@ladjs/graceful';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import Graceful from '@ladjs/graceful'
|
||||
import yaml from 'js-yaml'
|
||||
import fs from 'fs/promises';
|
||||
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
||||
import yaml from 'js-yaml';
|
||||
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
||||
import {
|
||||
cleanupDockerStorage,
|
||||
createRemoteEngineConfiguration,
|
||||
decrypt,
|
||||
executeCommand,
|
||||
generateDatabaseConfiguration,
|
||||
isDev,
|
||||
listSettings,
|
||||
prisma,
|
||||
startTraefikProxy,
|
||||
startTraefikTCPProxy,
|
||||
version
|
||||
} from './lib/common';
|
||||
import { checkContainer } from './lib/docker';
|
||||
import { migrateServicesToNewTemplate } from './lib';
|
||||
import { scheduler } from './lib/scheduler';
|
||||
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
||||
import { refreshTags, refreshTemplates } from './routes/api/v1/handlers';
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
config: {
|
||||
COOLIFY_APP_ID: string,
|
||||
COOLIFY_SECRET_KEY: string,
|
||||
COOLIFY_DATABASE_URL: string,
|
||||
COOLIFY_SENTRY_DSN: string,
|
||||
COOLIFY_IS_ON: string,
|
||||
COOLIFY_WHITE_LABELED: string,
|
||||
COOLIFY_WHITE_LABELED_ICON: string | null,
|
||||
COOLIFY_AUTO_UPDATE: string,
|
||||
COOLIFY_APP_ID: string;
|
||||
COOLIFY_SECRET_KEY: string;
|
||||
COOLIFY_SECRET_KEY_BETTER: string | null;
|
||||
COOLIFY_DATABASE_URL: string;
|
||||
COOLIFY_IS_ON: string;
|
||||
COOLIFY_WHITE_LABELED: string;
|
||||
COOLIFY_WHITE_LABELED_ICON: string | null;
|
||||
COOLIFY_AUTO_UPDATE: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const port = isDev ? 3001 : 3000;
|
||||
const host = '0.0.0.0';
|
||||
|
||||
(async () => {
|
||||
const settings = await prisma.setting.findFirst()
|
||||
const settings = await prisma.setting.findFirst();
|
||||
const fastify = Fastify({
|
||||
logger: settings?.isAPIDebuggingEnabled || false,
|
||||
trustProxy: true
|
||||
@@ -49,19 +61,19 @@ const host = '0.0.0.0';
|
||||
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
||||
properties: {
|
||||
COOLIFY_APP_ID: {
|
||||
type: 'string',
|
||||
type: 'string'
|
||||
},
|
||||
COOLIFY_SECRET_KEY: {
|
||||
type: 'string'
|
||||
},
|
||||
COOLIFY_SECRET_KEY_BETTER: {
|
||||
type: 'string',
|
||||
default: null
|
||||
},
|
||||
COOLIFY_DATABASE_URL: {
|
||||
type: 'string',
|
||||
default: 'file:../db/dev.db'
|
||||
},
|
||||
COOLIFY_SENTRY_DSN: {
|
||||
type: 'string',
|
||||
default: null
|
||||
},
|
||||
COOLIFY_IS_ON: {
|
||||
type: 'string',
|
||||
default: 'docker'
|
||||
@@ -77,8 +89,7 @@ const host = '0.0.0.0';
|
||||
COOLIFY_AUTO_UPDATE: {
|
||||
type: 'string',
|
||||
default: 'false'
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
const options = {
|
||||
@@ -107,14 +118,13 @@ const host = '0.0.0.0';
|
||||
fastify.register(autoLoad, {
|
||||
dir: join(__dirname, 'routes')
|
||||
});
|
||||
fastify.register(cookie)
|
||||
fastify.register(cookie);
|
||||
fastify.register(cors);
|
||||
fastify.register(socketIO, {
|
||||
cors: {
|
||||
origin: isDev ? "*" : ''
|
||||
origin: isDev ? '*' : ''
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
// To detect allowed origins
|
||||
// fastify.addHook('onRequest', async (request, reply) => {
|
||||
// console.log(request.headers.host)
|
||||
@@ -136,13 +146,13 @@ const host = '0.0.0.0';
|
||||
// }
|
||||
// })
|
||||
|
||||
|
||||
try {
|
||||
await fastify.listen({ port, host })
|
||||
await socketIOServer(fastify)
|
||||
await fastify.listen({ port, host });
|
||||
await socketIOServer(fastify);
|
||||
console.log(`Coolify's API is listening on ${host}:${port}`);
|
||||
|
||||
migrateServicesToNewTemplate()
|
||||
migrateServicesToNewTemplate();
|
||||
await migrateApplicationPersistentStorage();
|
||||
await initServer();
|
||||
|
||||
const graceful = new Graceful({ brees: [scheduler] });
|
||||
@@ -152,112 +162,211 @@ const host = '0.0.0.0';
|
||||
if (!scheduler.workers.has('deployApplication')) {
|
||||
scheduler.run('deployApplication');
|
||||
}
|
||||
}, 2000)
|
||||
}, 2000);
|
||||
|
||||
// autoUpdater
|
||||
setInterval(async () => {
|
||||
await autoUpdater()
|
||||
}, 60000 * 15)
|
||||
await autoUpdater();
|
||||
}, 60000 * 60);
|
||||
|
||||
// cleanupStorage
|
||||
setInterval(async () => {
|
||||
await cleanupStorage()
|
||||
}, 60000 * 10)
|
||||
await cleanupStorage();
|
||||
}, 60000 * 15);
|
||||
|
||||
// Cleanup stucked containers (not defined in Coolify, but still running and managed by Coolify)
|
||||
setInterval(async () => {
|
||||
await cleanupStuckedContainers();
|
||||
}, 60000);
|
||||
|
||||
// checkProxies, checkFluentBit & refresh templates
|
||||
setInterval(async () => {
|
||||
await checkProxies();
|
||||
await checkFluentBit();
|
||||
}, 60000)
|
||||
}, 60000);
|
||||
|
||||
// Refresh and check templates
|
||||
setInterval(async () => {
|
||||
await refreshTemplates()
|
||||
await refreshTags()
|
||||
await migrateServicesToNewTemplate()
|
||||
}, 60000)
|
||||
await refreshTemplates();
|
||||
}, 60000 * 10);
|
||||
|
||||
setInterval(async () => {
|
||||
await refreshTags();
|
||||
}, 60000 * 10);
|
||||
|
||||
setInterval(
|
||||
async () => {
|
||||
await migrateServicesToNewTemplate();
|
||||
},
|
||||
isDev ? 10000 : 60000 * 10
|
||||
);
|
||||
|
||||
setInterval(async () => {
|
||||
await copySSLCertificates();
|
||||
}, 10000)
|
||||
}, 10000);
|
||||
|
||||
await Promise.all([
|
||||
getTagsTemplates(),
|
||||
getArch(),
|
||||
getIPAddress(),
|
||||
configureRemoteDockers(),
|
||||
])
|
||||
|
||||
refreshTemplates(),
|
||||
refreshTags()
|
||||
// cleanupStuckedContainers()
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
async function getIPAddress() {
|
||||
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
||||
const { publicIpv4, publicIpv6 } = await import('public-ip');
|
||||
try {
|
||||
const settings = await listSettings();
|
||||
if (!settings.ipv4) {
|
||||
const ipv4 = await publicIpv4({ timeout: 2000 });
|
||||
console.log(`Getting public IPv4 address...`);
|
||||
const ipv4 = await publicIpv4({ timeout: 2000 })
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } })
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } });
|
||||
}
|
||||
|
||||
if (!settings.ipv6) {
|
||||
const ipv6 = await publicIpv6({ timeout: 2000 });
|
||||
console.log(`Getting public IPv6 address...`);
|
||||
const ipv6 = await publicIpv6({ timeout: 2000 })
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } })
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } });
|
||||
}
|
||||
|
||||
} catch (error) { }
|
||||
}
|
||||
async function getTagsTemplates() {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
try {
|
||||
if (isDev) {
|
||||
const templates = await fs.readFile('./devTemplates.yaml', 'utf8')
|
||||
const tags = await fs.readFile('./devTags.json', 'utf8')
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)))
|
||||
await fs.writeFile('./tags.json', tags)
|
||||
console.log('Tags and templates loaded in dev mode...')
|
||||
} else {
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
||||
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
||||
await fs.writeFile('/app/tags.json', tags)
|
||||
console.log('Tags and templates loaded...')
|
||||
}
|
||||
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||
let tags = await fs.readFile('./devTags.json', 'utf8');
|
||||
try {
|
||||
if (await fs.stat('./testTemplate.yaml')) {
|
||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||
}
|
||||
} catch (error) { }
|
||||
try {
|
||||
if (await fs.stat('./testTags.json')) {
|
||||
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
||||
if (testTags.length > 0) {
|
||||
tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags)));
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)));
|
||||
await fs.writeFile('./tags.json', tags);
|
||||
console.log('[004] Tags and templates loaded in dev mode...');
|
||||
} else {
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
|
||||
const response = await got
|
||||
.get('https://get.coollabs.io/coolify/service-templates.yaml')
|
||||
.text();
|
||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)));
|
||||
await fs.writeFile('/app/tags.json', tags);
|
||||
console.log('[004] Tags and templates loaded...');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Couldn't get latest templates.")
|
||||
console.log(error)
|
||||
console.log("Couldn't get latest templates.");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
async function initServer() {
|
||||
const appId = process.env['COOLIFY_APP_ID'];
|
||||
const settings = await prisma.setting.findUnique({ where: { id: '0' } });
|
||||
try {
|
||||
console.log(`Initializing server...`);
|
||||
await asyncExecShell(`docker network create --attachable coolify`);
|
||||
if (settings.doNotTrack === true) {
|
||||
console.log('[000] Telemetry disabled...');
|
||||
} else {
|
||||
// Initialize Sentry
|
||||
// Sentry.init({
|
||||
// dsn: sentryDSN,
|
||||
// environment: isDev ? 'development' : 'production',
|
||||
// release: version
|
||||
// });
|
||||
// console.log('[000] Sentry initialized...')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
try {
|
||||
console.log(`[001] Initializing server...`);
|
||||
await executeCommand({ command: `docker network create --attachable coolify` });
|
||||
} catch (error) { }
|
||||
try {
|
||||
console.log(`[002] Cleanup stucked builds...`);
|
||||
const isOlder = compareVersions('3.8.1', version);
|
||||
if (isOlder === 1) {
|
||||
await prisma.build.updateMany({ where: { status: { in: ['running', 'queued'] } }, data: { status: 'failed' } });
|
||||
await prisma.build.updateMany({
|
||||
where: { status: { in: ['running', 'queued'] } },
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
}
|
||||
} catch (error) { }
|
||||
try {
|
||||
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
|
||||
if (!isDev) await fs.rm('/tmp/build-sources', { recursive: true, force: true });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getArch() {
|
||||
try {
|
||||
const settings = await prisma.setting.findFirst({})
|
||||
const settings = await prisma.setting.findFirst({});
|
||||
if (settings && !settings.arch) {
|
||||
console.log(`Getting architecture...`);
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } })
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } });
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
async function cleanupStuckedContainers() {
|
||||
try {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
let enginesDone = new Set();
|
||||
for (const destination of destinationDockers) {
|
||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress))
|
||||
return;
|
||||
if (destination.engine) {
|
||||
enginesDone.add(destination.engine);
|
||||
}
|
||||
if (destination.remoteIpAddress) {
|
||||
if (!destination.remoteVerified) continue;
|
||||
enginesDone.add(destination.remoteIpAddress);
|
||||
}
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destination.id,
|
||||
command: `docker container ps -a --filter "label=coolify.managed=true" --format '{{ .Names}}'`
|
||||
});
|
||||
if (containers) {
|
||||
const containersArray = containers.trim().split('\n');
|
||||
if (containersArray.length > 0) {
|
||||
for (const container of containersArray) {
|
||||
const containerId = container.split('-')[0];
|
||||
const application = await prisma.application.findFirst({
|
||||
where: { id: { startsWith: containerId } }
|
||||
});
|
||||
const service = await prisma.service.findFirst({
|
||||
where: { id: { startsWith: containerId } }
|
||||
});
|
||||
const database = await prisma.database.findFirst({
|
||||
where: { id: { startsWith: containerId } }
|
||||
});
|
||||
if (!application && !service && !database) {
|
||||
await executeCommand({ command: `docker container rm -f ${container}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
async function configureRemoteDockers() {
|
||||
try {
|
||||
const remoteDocker = await prisma.destinationDocker.findMany({
|
||||
@@ -266,41 +375,51 @@ async function configureRemoteDockers() {
|
||||
if (remoteDocker.length > 0) {
|
||||
console.log(`Verifying Remote Docker Engines...`);
|
||||
for (const docker of remoteDocker) {
|
||||
console.log('Verifying:', docker.remoteIpAddress)
|
||||
console.log('Verifying:', docker.remoteIpAddress);
|
||||
await verifyRemoteDockerEngineFn(docker.id);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function autoUpdater() {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
const currentVersion = version;
|
||||
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
||||
searchParams: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
}).json()
|
||||
const { coolify } = await got
|
||||
.get('https://get.coollabs.io/versions.json', {
|
||||
searchParams: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
})
|
||||
.json();
|
||||
const latestVersion = coolify.main.version;
|
||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||
if (isUpdateAvailable === 1) {
|
||||
const activeCount = 0
|
||||
const activeCount = 0;
|
||||
if (activeCount === 0) {
|
||||
if (!isDev) {
|
||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||
if (isAutoUpdateEnabled) {
|
||||
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
||||
await asyncExecShell(`env | grep '^COOLIFY' > .env`);
|
||||
await asyncExecShell(
|
||||
`sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
);
|
||||
await asyncExecShell(
|
||||
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
);
|
||||
let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
|
||||
try {
|
||||
await executeCommand({ command: `docker pull ${image}` });
|
||||
} catch (error) {
|
||||
image = `coollabsio/coolify:${latestVersion}`;
|
||||
await executeCommand({ command: `docker pull ${image}` });
|
||||
}
|
||||
|
||||
await executeCommand({ shell: true, command: `ls .env || env | grep "^COOLIFY" | sort > .env` });
|
||||
await executeCommand({
|
||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
});
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db ${image} /bin/sh -c "env | grep "^COOLIFY" | sort > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log('Updating (not really in dev mode).');
|
||||
@@ -308,7 +427,7 @@ async function autoUpdater() {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,14 +438,18 @@ async function checkFluentBit() {
|
||||
const { id } = await prisma.destinationDocker.findFirst({
|
||||
where: { engine, network: 'coolify' }
|
||||
});
|
||||
const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit', remove: true });
|
||||
const { found } = await checkContainer({
|
||||
dockerId: id,
|
||||
container: 'coolify-fluentbit',
|
||||
remove: true
|
||||
});
|
||||
if (!found) {
|
||||
await asyncExecShell(`env | grep '^COOLIFY' > .env`);
|
||||
await asyncExecShell(`docker compose up -d fluent-bit`);
|
||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||
await executeCommand({ command: `docker compose up -d fluent-bit` });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
async function checkProxies() {
|
||||
@@ -342,7 +465,7 @@ async function checkProxies() {
|
||||
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
|
||||
});
|
||||
if (localDocker) {
|
||||
portReachable = await isReachable(80, { host: ipv4 || ipv6 })
|
||||
portReachable = await isReachable(80, { host: ipv4 || ipv6 });
|
||||
if (!portReachable) {
|
||||
await startTraefikProxy(localDocker.id);
|
||||
}
|
||||
@@ -354,13 +477,13 @@ async function checkProxies() {
|
||||
if (remoteDocker.length > 0) {
|
||||
for (const docker of remoteDocker) {
|
||||
if (docker.isCoolifyProxyUsed) {
|
||||
portReachable = await isReachable(80, { host: docker.remoteIpAddress })
|
||||
portReachable = await isReachable(80, { host: docker.remoteIpAddress });
|
||||
if (!portReachable) {
|
||||
await startTraefikProxy(docker.id);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await createRemoteEngineConfiguration(docker.id)
|
||||
await createRemoteEngineConfiguration(docker.id);
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
@@ -400,111 +523,148 @@ async function checkProxies() {
|
||||
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||
// }
|
||||
// }
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
async function copySSLCertificates() {
|
||||
try {
|
||||
const pAll = await import('p-all');
|
||||
const actions = []
|
||||
const certificates = await prisma.certificate.findMany({ include: { team: true } })
|
||||
const teamIds = certificates.map(c => c.teamId)
|
||||
const destinations = await prisma.destinationDocker.findMany({ where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } } })
|
||||
const actions = [];
|
||||
const certificates = await prisma.certificate.findMany({ include: { team: true } });
|
||||
const teamIds = certificates.map((c) => c.teamId);
|
||||
const destinations = await prisma.destinationDocker.findMany({
|
||||
where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } }
|
||||
});
|
||||
for (const certificate of certificates) {
|
||||
const { id, key, cert } = certificate
|
||||
const decryptedKey = decrypt(key)
|
||||
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
|
||||
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
|
||||
const { id, key, cert } = certificate;
|
||||
const decryptedKey = decrypt(key);
|
||||
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey);
|
||||
await fs.writeFile(`/tmp/${id}-cert.pem`, cert);
|
||||
for (const destination of destinations) {
|
||||
if (destination.remoteEngine) {
|
||||
if (destination.remoteVerified) {
|
||||
const { id: dockerId, remoteIpAddress } = destination
|
||||
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress))
|
||||
const { id: dockerId, remoteIpAddress } = destination;
|
||||
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress));
|
||||
}
|
||||
} else {
|
||||
actions.push(async () => copyLocalCertificates(id))
|
||||
actions.push(async () => copyLocalCertificates(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
await pAll.default(actions, { concurrency: 1 })
|
||||
await pAll.default(actions, { concurrency: 1 });
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
} finally {
|
||||
await asyncExecShell(`find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete`)
|
||||
try {
|
||||
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
|
||||
try {
|
||||
await asyncExecShell(`scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`)
|
||||
await executeSSHCmd({ dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
|
||||
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||
await executeCommand({
|
||||
command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`
|
||||
});
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
shell: true,
|
||||
dockerId,
|
||||
command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`
|
||||
});
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
dockerId,
|
||||
command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||
});
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
dockerId,
|
||||
command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||
});
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
console.log({ error });
|
||||
}
|
||||
}
|
||||
async function copyLocalCertificates(id: string) {
|
||||
try {
|
||||
await asyncExecShell(`docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`)
|
||||
await asyncExecShell(`docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`)
|
||||
await asyncExecShell(`docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`)
|
||||
await executeCommand({
|
||||
command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`,
|
||||
shell: true
|
||||
});
|
||||
await executeCommand({
|
||||
command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||
});
|
||||
await executeCommand({
|
||||
command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||
});
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
console.log({ error });
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanupStorage() {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
let enginesDone = new Set()
|
||||
let enginesDone = new Set();
|
||||
for (const destination of destinationDockers) {
|
||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
||||
if (destination.engine) enginesDone.add(destination.engine)
|
||||
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return;
|
||||
if (destination.engine) {
|
||||
enginesDone.add(destination.engine);
|
||||
}
|
||||
if (destination.remoteIpAddress) {
|
||||
if (!destination.remoteVerified) continue;
|
||||
enginesDone.add(destination.remoteIpAddress);
|
||||
}
|
||||
await cleanupDockerStorage(destination.id);
|
||||
// let lowDiskSpace = false;
|
||||
// try {
|
||||
// let stdout = null;
|
||||
// if (!isDev) {
|
||||
// const output = await executeCommand({
|
||||
// dockerId: destination.id,
|
||||
// command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
||||
// shell: true
|
||||
// });
|
||||
// stdout = output.stdout;
|
||||
// } else {
|
||||
// const output = await executeCommand({
|
||||
// command: `df -kPT /`
|
||||
// });
|
||||
// stdout = output.stdout;
|
||||
// }
|
||||
// let lines = stdout.trim().split('\n');
|
||||
// let header = lines[0];
|
||||
// let regex =
|
||||
// /^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||
// const boundaries = [];
|
||||
// let match;
|
||||
|
||||
let lowDiskSpace = false;
|
||||
try {
|
||||
let stdout = null
|
||||
if (!isDev) {
|
||||
const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` })
|
||||
stdout = output.stdout;
|
||||
} else {
|
||||
const output = await asyncExecShell(
|
||||
`df -kPT /`
|
||||
);
|
||||
stdout = output.stdout;
|
||||
}
|
||||
let lines = stdout.trim().split('\n');
|
||||
let header = lines[0];
|
||||
let regex =
|
||||
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||
const boundaries = [];
|
||||
let match;
|
||||
// while ((match = regex.exec(header))) {
|
||||
// boundaries.push(match[0].length);
|
||||
// }
|
||||
|
||||
while ((match = regex.exec(header))) {
|
||||
boundaries.push(match[0].length);
|
||||
}
|
||||
|
||||
boundaries[boundaries.length - 1] = -1;
|
||||
const data = lines.slice(1).map((line) => {
|
||||
const cl = boundaries.map((boundary) => {
|
||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||
line = line.slice(boundary);
|
||||
return column.trim();
|
||||
});
|
||||
return {
|
||||
capacity: Number.parseInt(cl[5], 10) / 100
|
||||
};
|
||||
});
|
||||
if (data.length > 0) {
|
||||
const { capacity } = data[0];
|
||||
if (capacity > 0.8) {
|
||||
lowDiskSpace = true;
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
||||
// boundaries[boundaries.length - 1] = -1;
|
||||
// const data = lines.slice(1).map((line) => {
|
||||
// const cl = boundaries.map((boundary) => {
|
||||
// const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||
// line = line.slice(boundary);
|
||||
// return column.trim();
|
||||
// });
|
||||
// return {
|
||||
// capacity: Number.parseInt(cl[5], 10) / 100
|
||||
// };
|
||||
// });
|
||||
// if (data.length > 0) {
|
||||
// const { capacity } = data[0];
|
||||
// if (capacity > 0.8) {
|
||||
// lowDiskSpace = true;
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {}
|
||||
// if (lowDiskSpace) {
|
||||
// await cleanupDockerStorage(destination.id);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,26 @@ import crypto from 'crypto';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
|
||||
import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma, decryptApplication } from '../lib/common';
|
||||
import {
|
||||
copyBaseConfigurationFiles,
|
||||
makeLabelForSimpleDockerfile,
|
||||
makeLabelForStandaloneApplication,
|
||||
saveBuildLog,
|
||||
saveDockerRegistryCredentials,
|
||||
setDefaultConfiguration
|
||||
} from '../lib/buildPacks/common';
|
||||
import {
|
||||
createDirectories,
|
||||
decrypt,
|
||||
defaultComposeConfiguration,
|
||||
getDomain,
|
||||
prisma,
|
||||
decryptApplication,
|
||||
isDev,
|
||||
pushToRegistry,
|
||||
executeCommand,
|
||||
generateSecrets
|
||||
} from '../lib/common';
|
||||
import * as importers from '../lib/importers';
|
||||
import * as buildpacks from '../lib/buildPacks';
|
||||
|
||||
@@ -14,80 +32,338 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
if (message === 'error') throw new Error('oops');
|
||||
if (message === 'cancel') {
|
||||
parentPort.postMessage('cancelled');
|
||||
await prisma.$disconnect()
|
||||
await prisma.$disconnect();
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
const pThrottle = await import('p-throttle')
|
||||
const pThrottle = await import('p-throttle');
|
||||
const throttle = pThrottle.default({
|
||||
limit: 1,
|
||||
interval: 2000
|
||||
});
|
||||
|
||||
|
||||
const th = throttle(async () => {
|
||||
try {
|
||||
const queuedBuilds = await prisma.build.findMany({ where: { status: { in: ['queued', 'running'] } }, orderBy: { createdAt: 'asc' } });
|
||||
const { concurrentBuilds } = await prisma.setting.findFirst({})
|
||||
const queuedBuilds = await prisma.build.findMany({
|
||||
where: { status: { in: ['queued', 'running'] } },
|
||||
orderBy: { createdAt: 'asc' }
|
||||
});
|
||||
const { concurrentBuilds } = await prisma.setting.findFirst({});
|
||||
if (queuedBuilds.length > 0) {
|
||||
parentPort.postMessage({ deploying: true });
|
||||
const concurrency = concurrentBuilds;
|
||||
const pAll = await import('p-all');
|
||||
const actions = []
|
||||
const actions = [];
|
||||
|
||||
for (const queueBuild of queuedBuilds) {
|
||||
actions.push(async () => {
|
||||
let application = await prisma.application.findUnique({ where: { id: queueBuild.applicationId }, include: { destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } })
|
||||
let { id: buildId, type, sourceBranch = null, pullmergeRequestId = null, previewApplicationId = null, forceRebuild, sourceRepository = null } = queueBuild
|
||||
application = decryptApplication(application)
|
||||
const originalApplicationId = application.id
|
||||
if (pullmergeRequestId) {
|
||||
const previewApplications = await prisma.previewApplication.findMany({ where: { applicationId: originalApplicationId, pullmergeRequestId } })
|
||||
if (previewApplications.length > 0) {
|
||||
previewApplicationId = previewApplications[0].id
|
||||
let application = await prisma.application.findUnique({
|
||||
where: { id: queueBuild.applicationId },
|
||||
include: {
|
||||
dockerRegistry: true,
|
||||
destinationDocker: true,
|
||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||
persistentStorage: true,
|
||||
secrets: true,
|
||||
settings: true,
|
||||
teams: true
|
||||
}
|
||||
});
|
||||
if (!application) {
|
||||
await prisma.build.update({
|
||||
where: { id: queueBuild.id },
|
||||
data: {
|
||||
status: 'failed'
|
||||
}
|
||||
});
|
||||
throw new Error('Application not found');
|
||||
}
|
||||
const usableApplicationId = previewApplicationId || originalApplicationId
|
||||
try {
|
||||
if (queueBuild.status === 'running') {
|
||||
await saveBuildLog({ line: 'Building halted, restarting...', buildId, applicationId: application.id });
|
||||
}
|
||||
let {
|
||||
id: buildId,
|
||||
type,
|
||||
gitSourceId,
|
||||
sourceBranch = null,
|
||||
pullmergeRequestId = null,
|
||||
previewApplicationId = null,
|
||||
forceRebuild,
|
||||
sourceRepository = null
|
||||
} = queueBuild;
|
||||
application = decryptApplication(application);
|
||||
|
||||
if (!gitSourceId && application.simpleDockerfile) {
|
||||
const {
|
||||
id: applicationId,
|
||||
name,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
gitSource,
|
||||
configHash,
|
||||
fqdn,
|
||||
projectId,
|
||||
secrets,
|
||||
phpModules,
|
||||
settings,
|
||||
persistentStorage,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
denoOptions,
|
||||
exposePort,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
} = application
|
||||
let {
|
||||
branch,
|
||||
repository,
|
||||
buildPack,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory,
|
||||
dockerFileLocation,
|
||||
dockerComposeConfiguration,
|
||||
denoMainFile
|
||||
} = application
|
||||
persistentStorage,
|
||||
exposePort,
|
||||
simpleDockerfile,
|
||||
dockerRegistry
|
||||
} = application;
|
||||
const { workdir } = await createDirectories({ repository: applicationId, buildId });
|
||||
try {
|
||||
if (queueBuild.status === 'running') {
|
||||
await saveBuildLog({
|
||||
line: 'Building halted, restarting...',
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
if (storage.oldPath) {
|
||||
return `${applicationId}${storage.path
|
||||
.replace(/\//gi, '-')
|
||||
.replace('-app', '')}:${storage.path}`;
|
||||
}
|
||||
if (storage.hostPath) {
|
||||
return `${storage.hostPath}:${storage.path}`;
|
||||
}
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||
}) || [];
|
||||
|
||||
if (destinationDockerId) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: { status: 'running' }
|
||||
});
|
||||
try {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}`
|
||||
});
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker stop -t 0 ${container}`
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker rm --force ${container}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
let envs = [];
|
||||
if (secrets.length > 0) {
|
||||
envs = [
|
||||
...envs,
|
||||
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||
];
|
||||
}
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
|
||||
if (dockerRegistry) {
|
||||
const { url, username, password } = dockerRegistry;
|
||||
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||
}
|
||||
|
||||
const labels = makeLabelForSimpleDockerfile({
|
||||
applicationId,
|
||||
type,
|
||||
port: exposePort ? `${exposePort}:${port}` : port
|
||||
});
|
||||
try {
|
||||
const composeVolumes = volumes
|
||||
.filter((v) => {
|
||||
if (
|
||||
!v.startsWith('.') &&
|
||||
!v.startsWith('..') &&
|
||||
!v.startsWith('/') &&
|
||||
!v.startsWith('~')
|
||||
) {
|
||||
return v;
|
||||
}
|
||||
})
|
||||
.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[applicationId]: {
|
||||
build: {
|
||||
context: workdir
|
||||
},
|
||||
image: `${applicationId}:${buildId}`,
|
||||
container_name: applicationId,
|
||||
volumes,
|
||||
labels,
|
||||
environment: envs,
|
||||
depends_on: [],
|
||||
expose: [port],
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
...defaultComposeConfiguration(destinationDocker.network)
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[destinationDocker.network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: Object.assign({}, ...composeVolumes)
|
||||
};
|
||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||
await executeCommand({
|
||||
debug: true,
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
|
||||
});
|
||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||
} catch (error) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: {
|
||||
status: 'failed'
|
||||
}
|
||||
});
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: {
|
||||
status: 'failed'
|
||||
}
|
||||
});
|
||||
}
|
||||
if (error !== 1) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
await saveBuildLog({
|
||||
line: error.message,
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (application.dockerRegistryImageName) {
|
||||
const customTag = application.dockerRegistryImageName.split(':')[1] || buildId;
|
||||
const imageName = application.dockerRegistryImageName.split(':')[0];
|
||||
await saveBuildLog({
|
||||
line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`,
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
await pushToRegistry(application, workdir, buildId, imageName, customTag);
|
||||
await saveBuildLog({ line: 'Success', buildId, applicationId: application.id });
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.stdout) {
|
||||
await saveBuildLog({ line: error.stdout, buildId, applicationId });
|
||||
}
|
||||
if (error.stderr) {
|
||||
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
||||
}
|
||||
} finally {
|
||||
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: { status: 'success' }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const originalApplicationId = application.id;
|
||||
const {
|
||||
id: applicationId,
|
||||
name,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
gitSource,
|
||||
configHash,
|
||||
fqdn,
|
||||
projectId,
|
||||
secrets,
|
||||
phpModules,
|
||||
settings,
|
||||
persistentStorage,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
denoOptions,
|
||||
exposePort,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
gitCommitHash,
|
||||
dockerRegistry
|
||||
} = application;
|
||||
|
||||
let {
|
||||
branch,
|
||||
repository,
|
||||
buildPack,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory,
|
||||
dockerFileLocation,
|
||||
dockerComposeFileLocation,
|
||||
dockerComposeConfiguration,
|
||||
denoMainFile
|
||||
} = application;
|
||||
|
||||
let imageId = applicationId;
|
||||
let domain = getDomain(fqdn);
|
||||
|
||||
let location = null;
|
||||
|
||||
let tag = null;
|
||||
let customTag = null;
|
||||
let imageName = null;
|
||||
|
||||
let imageFoundLocally = false;
|
||||
let imageFoundRemotely = false;
|
||||
|
||||
if (pullmergeRequestId) {
|
||||
const previewApplications = await prisma.previewApplication.findMany({
|
||||
where: { applicationId: originalApplicationId, pullmergeRequestId }
|
||||
});
|
||||
if (previewApplications.length > 0) {
|
||||
previewApplicationId = previewApplications[0].id;
|
||||
}
|
||||
// Previews, we need to get the source branch and set subdomain
|
||||
branch = sourceBranch;
|
||||
domain = `${pullmergeRequestId}.${domain}`;
|
||||
imageId = `${applicationId}-${pullmergeRequestId}`;
|
||||
repository = sourceRepository || repository;
|
||||
}
|
||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||
try {
|
||||
if (queueBuild.status === 'running') {
|
||||
await saveBuildLog({
|
||||
line: 'Building halted, restarting...',
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
|
||||
const currentHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(
|
||||
@@ -113,24 +389,29 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
)
|
||||
.digest('hex');
|
||||
const { debug } = settings;
|
||||
let imageId = applicationId;
|
||||
let domain = getDomain(fqdn);
|
||||
if (!debug) {
|
||||
await saveBuildLog({
|
||||
line: `Debug logging is disabled. Enable it above if necessary!`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
if (storage.oldPath) {
|
||||
return `${applicationId}${storage.path
|
||||
.replace(/\//gi, '-')
|
||||
.replace('-app', '')}:${storage.path}`;
|
||||
}
|
||||
if (storage.hostPath) {
|
||||
return `${storage.hostPath}:${storage.path}`;
|
||||
}
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||
}) || [];
|
||||
// Previews, we need to get the source branch and set subdomain
|
||||
if (pullmergeRequestId) {
|
||||
branch = sourceBranch;
|
||||
domain = `${pullmergeRequestId}.${domain}`;
|
||||
imageId = `${applicationId}-${pullmergeRequestId}`;
|
||||
repository = sourceRepository || repository;
|
||||
}
|
||||
|
||||
try {
|
||||
dockerComposeConfiguration = JSON.parse(dockerComposeConfiguration)
|
||||
} catch (error) { }
|
||||
dockerComposeConfiguration = JSON.parse(dockerComposeConfiguration);
|
||||
} catch (error) {}
|
||||
let deployNeeded = true;
|
||||
let destinationType;
|
||||
|
||||
@@ -138,8 +419,11 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
destinationType = 'docker';
|
||||
}
|
||||
if (destinationType === 'docker') {
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: { status: 'running' }
|
||||
});
|
||||
|
||||
const configuration = await setDefaultConfiguration(application);
|
||||
|
||||
buildPack = configuration.buildPack;
|
||||
@@ -147,9 +431,10 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
installCommand = configuration.installCommand;
|
||||
startCommand = configuration.startCommand;
|
||||
buildCommand = configuration.buildCommand;
|
||||
publishDirectory = configuration.publishDirectory;
|
||||
publishDirectory = configuration.publishDirectory || '';
|
||||
baseDirectory = configuration.baseDirectory || '';
|
||||
dockerFileLocation = configuration.dockerFileLocation;
|
||||
dockerComposeFileLocation = configuration.dockerComposeFileLocation;
|
||||
denoMainFile = configuration.denoMainFile;
|
||||
const commit = await importers[gitSource.type]({
|
||||
applicationId,
|
||||
@@ -159,6 +444,9 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
githubAppId: gitSource.githubApp?.id,
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
customPort: gitSource.customPort,
|
||||
customUser: gitSource.customUser,
|
||||
gitCommitHash,
|
||||
configuration,
|
||||
repository,
|
||||
branch,
|
||||
buildId,
|
||||
@@ -172,20 +460,35 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
if (!commit) {
|
||||
throw new Error('No commit found?');
|
||||
}
|
||||
let tag = commit.slice(0, 7);
|
||||
tag = commit.slice(0, 7);
|
||||
if (pullmergeRequestId) {
|
||||
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
||||
}
|
||||
if (application.dockerRegistryImageName) {
|
||||
imageName = application.dockerRegistryImageName.split(':')[0];
|
||||
customTag = application.dockerRegistryImageName.split(':')[1] || tag;
|
||||
} else {
|
||||
customTag = tag;
|
||||
imageName = applicationId;
|
||||
}
|
||||
|
||||
if (pullmergeRequestId) {
|
||||
customTag = `${customTag}-${pullmergeRequestId}`;
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||
} catch (err) { }
|
||||
} catch (err) {}
|
||||
|
||||
if (!pullmergeRequestId) {
|
||||
if (configHash !== currentHash) {
|
||||
deployNeeded = true;
|
||||
if (configHash) {
|
||||
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
||||
await saveBuildLog({
|
||||
line: 'Configuration changed',
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
} else {
|
||||
deployNeeded = false;
|
||||
@@ -194,17 +497,47 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
deployNeeded = true;
|
||||
}
|
||||
|
||||
let imageFound = false;
|
||||
try {
|
||||
await executeDockerCmd({
|
||||
await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker image inspect ${applicationId}:${tag}`
|
||||
})
|
||||
imageFound = true;
|
||||
});
|
||||
imageFoundLocally = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
||||
if (dockerRegistry) {
|
||||
const { url, username, password } = dockerRegistry;
|
||||
location = await saveDockerRegistryCredentials({
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
workdir
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker ${
|
||||
location ? `--config ${location}` : ''
|
||||
} pull ${imageName}:${customTag}`
|
||||
});
|
||||
imageFoundRemotely = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
let imageFound = `${applicationId}:${tag}`;
|
||||
if (imageFoundRemotely) {
|
||||
imageFound = `${imageName}:${customTag}`;
|
||||
}
|
||||
await copyBaseConfigurationFiles(
|
||||
buildPack,
|
||||
workdir,
|
||||
buildId,
|
||||
applicationId,
|
||||
baseImage
|
||||
);
|
||||
const labels = makeLabelForStandaloneApplication({
|
||||
applicationId,
|
||||
fqdn,
|
||||
@@ -223,8 +556,8 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
});
|
||||
if (forceRebuild) deployNeeded = true
|
||||
if (!imageFound || deployNeeded) {
|
||||
if (forceRebuild) deployNeeded = true;
|
||||
if ((!imageFoundLocally && !imageFoundRemotely) || deployNeeded) {
|
||||
if (buildpacks[buildPack])
|
||||
await buildpacks[buildPack]({
|
||||
dockerId: destinationDocker.id,
|
||||
@@ -258,45 +591,85 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
pythonVariable,
|
||||
dockerFileLocation,
|
||||
dockerComposeConfiguration,
|
||||
dockerComposeFileLocation,
|
||||
denoMainFile,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
forceRebuild
|
||||
});
|
||||
else {
|
||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
await saveBuildLog({
|
||||
line: `Build pack ${buildPack} not found`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
throw new Error(`Build pack ${buildPack} not found.`);
|
||||
}
|
||||
} else {
|
||||
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
||||
if (imageFoundRemotely || deployNeeded) {
|
||||
await saveBuildLog({
|
||||
line: `Container image ${imageFound} found in Docker Registry - reuising it`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
} else {
|
||||
if (imageFoundLocally || deployNeeded) {
|
||||
await saveBuildLog({
|
||||
line: `Container image ${imageFound} found locally - reuising it`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buildPack === 'compose') {
|
||||
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`;
|
||||
try {
|
||||
await executeDockerCmd({
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0`
|
||||
})
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
|
||||
})
|
||||
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}`
|
||||
});
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker stop -t 0 ${container}`
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker rm --force ${container}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
try {
|
||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||
await executeCommand({
|
||||
debug,
|
||||
buildId,
|
||||
applicationId,
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker compose --project-directory ${workdir} -f ${fileYaml} up -d`
|
||||
});
|
||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: { status: 'success' }
|
||||
});
|
||||
await prisma.application.update({
|
||||
where: { id: applicationId },
|
||||
data: { configHash: currentHash }
|
||||
});
|
||||
} catch (error) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
@@ -307,69 +680,75 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
} else {
|
||||
try {
|
||||
await executeDockerCmd({
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${pullmergeRequestId ? imageId : applicationId}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0`
|
||||
})
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${pullmergeRequestId ? imageId : applicationId}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
|
||||
})
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
const envs = [
|
||||
`PORT=${port}`
|
||||
];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${
|
||||
pullmergeRequestId ? imageId : applicationId
|
||||
}' --format {{.ID}}`
|
||||
});
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker stop -t 0 ${container}`
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker rm --force ${container}`
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||
|
||||
let envFound = false;
|
||||
try {
|
||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
let envs = [];
|
||||
if (secrets.length > 0) {
|
||||
envs = [
|
||||
...envs,
|
||||
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||
];
|
||||
}
|
||||
if (dockerRegistry) {
|
||||
const { url, username, password } = dockerRegistry;
|
||||
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||
}
|
||||
try {
|
||||
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
const composeVolumes = volumes
|
||||
.filter((v) => {
|
||||
if (
|
||||
!v.startsWith('.') &&
|
||||
!v.startsWith('..') &&
|
||||
!v.startsWith('/') &&
|
||||
!v.startsWith('~')
|
||||
) {
|
||||
return v;
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[imageId]: {
|
||||
image: `${applicationId}:${tag}`,
|
||||
image: imageFound,
|
||||
container_name: imageId,
|
||||
volumes,
|
||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||
environment: envs,
|
||||
labels,
|
||||
depends_on: [],
|
||||
expose: [port],
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
...defaultComposeConfiguration(destinationDocker.network),
|
||||
...defaultComposeConfiguration(destinationDocker.network)
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
@@ -380,11 +759,15 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
volumes: Object.assign({}, ...composeVolumes)
|
||||
};
|
||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||
await executeCommand({
|
||||
debug,
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
|
||||
});
|
||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||
} catch (error) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
@@ -395,17 +778,16 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||
if (!pullmergeRequestId) await prisma.application.update({
|
||||
where: { id: applicationId },
|
||||
data: { configHash: currentHash }
|
||||
});
|
||||
|
||||
if (!pullmergeRequestId)
|
||||
await prisma.application.update({
|
||||
where: { id: applicationId },
|
||||
data: { configHash: currentHash }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
} catch (error) {
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
@@ -417,17 +799,47 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
if (error !== 1) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
await saveBuildLog({
|
||||
line: error.message,
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (application.dockerRegistryImageName && (!imageFoundRemotely || forceRebuild)) {
|
||||
await saveBuildLog({
|
||||
line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`,
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
await pushToRegistry(application, workdir, tag, imageName, customTag);
|
||||
await saveBuildLog({ line: 'Success', buildId, applicationId: application.id });
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.stdout) {
|
||||
await saveBuildLog({ line: error.stdout, buildId, applicationId });
|
||||
}
|
||||
if (error.stderr) {
|
||||
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
||||
}
|
||||
} finally {
|
||||
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||
}
|
||||
});
|
||||
}
|
||||
await pAll.default(actions, { concurrency })
|
||||
await pAll.default(actions, { concurrency });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
})
|
||||
});
|
||||
while (true) {
|
||||
await th()
|
||||
await th();
|
||||
}
|
||||
} else process.exit(0);
|
||||
})();
|
||||
|
||||
@@ -1,7 +1,33 @@
|
||||
import cuid from "cuid";
|
||||
import { decrypt, encrypt, fixType, generatePassword, getDomain, prisma } from "./lib/common";
|
||||
import { decrypt, encrypt, fixType, generatePassword, generateToken, prisma } from "./lib/common";
|
||||
import { getTemplates } from "./lib/services";
|
||||
|
||||
export async function migrateApplicationPersistentStorage() {
|
||||
const settings = await prisma.setting.findFirst()
|
||||
if (settings) {
|
||||
const { id: settingsId, applicationStoragePathMigrationFinished } = settings
|
||||
try {
|
||||
if (!applicationStoragePathMigrationFinished) {
|
||||
const applications = await prisma.application.findMany({ include: { persistentStorage: true } });
|
||||
for (const application of applications) {
|
||||
if (application.persistentStorage && application.persistentStorage.length > 0 && application?.buildPack !== 'docker') {
|
||||
for (const storage of application.persistentStorage) {
|
||||
let { id, path } = storage
|
||||
if (!path.startsWith('/app')) {
|
||||
path = `/app${path}`
|
||||
await prisma.applicationPersistentStorage.update({ where: { id }, data: { path, oldPath: true } })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
await prisma.setting.update({ where: { id: settingsId }, data: { applicationStoragePathMigrationFinished: true } })
|
||||
}
|
||||
}
|
||||
}
|
||||
export async function migrateServicesToNewTemplate() {
|
||||
// This function migrates old hardcoded services to the new template based services
|
||||
try {
|
||||
@@ -30,85 +56,96 @@ export async function migrateServicesToNewTemplate() {
|
||||
}
|
||||
})
|
||||
for (const service of services) {
|
||||
const { id } = service
|
||||
if (!service.type) {
|
||||
continue;
|
||||
}
|
||||
let template = templates.find(t => fixType(t.type) === fixType(service.type));
|
||||
if (template) {
|
||||
template = JSON.parse(JSON.stringify(template).replaceAll('$$id', service.id))
|
||||
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics) await plausibleAnalytics(service, template)
|
||||
if (service.type === 'fider' && service.fider) await fider(service, template)
|
||||
if (service.type === 'minio' && service.minio) await minio(service, template)
|
||||
if (service.type === 'vscodeserver' && service.vscodeserver) await vscodeserver(service, template)
|
||||
if (service.type === 'wordpress' && service.wordpress) await wordpress(service, template)
|
||||
if (service.type === 'ghost' && service.ghost) await ghost(service, template)
|
||||
if (service.type === 'meilisearch' && service.meiliSearch) await meilisearch(service, template)
|
||||
if (service.type === 'umami' && service.umami) await umami(service, template)
|
||||
if (service.type === 'hasura' && service.hasura) await hasura(service, template)
|
||||
if (service.type === 'glitchTip' && service.glitchTip) await glitchtip(service, template)
|
||||
if (service.type === 'searxng' && service.searxng) await searxng(service, template)
|
||||
if (service.type === 'weblate' && service.weblate) await weblate(service, template)
|
||||
if (service.type === 'appwrite' && service.appwrite) await appwrite(service, template)
|
||||
|
||||
await createVolumes(service, template);
|
||||
|
||||
if (template.variables.length > 0) {
|
||||
for (const variable of template.variables) {
|
||||
const { defaultValue } = variable;
|
||||
const regex = /^\$\$.*\((\d+)\)$/g;
|
||||
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
|
||||
if (variable.defaultValue.startsWith('$$generate_password')) {
|
||||
variable.value = generatePassword({ length });
|
||||
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
|
||||
variable.value = generatePassword({ length, isHex: true });
|
||||
} else if (variable.defaultValue.startsWith('$$generate_username')) {
|
||||
variable.value = cuid();
|
||||
} else {
|
||||
variable.value = variable.defaultValue || '';
|
||||
}
|
||||
}
|
||||
try {
|
||||
const { id } = service
|
||||
if (!service.type) {
|
||||
continue;
|
||||
}
|
||||
for (const variable of template.variables) {
|
||||
if (variable.id.startsWith('$$secret_')) {
|
||||
const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||
if (!found) {
|
||||
await prisma.serviceSecret.create({
|
||||
data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } }
|
||||
})
|
||||
}
|
||||
let template = templates.find(t => fixType(t.type) === fixType(service.type));
|
||||
if (template) {
|
||||
template = JSON.parse(JSON.stringify(template).replaceAll('$$id', service.id))
|
||||
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics) await plausibleAnalytics(service, template)
|
||||
if (service.type === 'fider' && service.fider) await fider(service, template)
|
||||
if (service.type === 'minio' && service.minio) await minio(service, template)
|
||||
if (service.type === 'vscodeserver' && service.vscodeserver) await vscodeserver(service, template)
|
||||
if (service.type === 'wordpress' && service.wordpress) await wordpress(service, template)
|
||||
if (service.type === 'ghost' && service.ghost) await ghost(service, template)
|
||||
if (service.type === 'meilisearch' && service.meiliSearch) await meilisearch(service, template)
|
||||
if (service.type === 'umami' && service.umami) await umami(service, template)
|
||||
if (service.type === 'hasura' && service.hasura) await hasura(service, template)
|
||||
if (service.type === 'glitchTip' && service.glitchTip) await glitchtip(service, template)
|
||||
if (service.type === 'searxng' && service.searxng) await searxng(service, template)
|
||||
if (service.type === 'weblate' && service.weblate) await weblate(service, template)
|
||||
if (service.type === 'appwrite' && service.appwrite) await appwrite(service, template)
|
||||
|
||||
try {
|
||||
await createVolumes(service, template);
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
if (variable.id.startsWith('$$config_')) {
|
||||
const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||
if (!found) {
|
||||
await prisma.serviceSetting.create({
|
||||
data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } }
|
||||
})
|
||||
if (template.variables) {
|
||||
if (template.variables.length > 0) {
|
||||
for (const variable of template.variables) {
|
||||
let { defaultValue } = variable;
|
||||
defaultValue = defaultValue.toString();
|
||||
const regex = /^\$\$.*\((\d+)\)$/g;
|
||||
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
|
||||
if (defaultValue.startsWith('$$generate_password')) {
|
||||
variable.value = generatePassword({ length });
|
||||
} else if (defaultValue.startsWith('$$generate_hex')) {
|
||||
variable.value = generatePassword({ length, isHex: true });
|
||||
} else if (defaultValue.startsWith('$$generate_username')) {
|
||||
variable.value = cuid();
|
||||
} else if (defaultValue.startsWith('$$generate_token')) {
|
||||
variable.value = generateToken()
|
||||
} else {
|
||||
variable.value = defaultValue || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const s of Object.keys(template.services)) {
|
||||
if (service.type === 'plausibleanalytics') {
|
||||
continue;
|
||||
}
|
||||
if (template.services[s].volumes) {
|
||||
for (const volume of template.services[s].volumes) {
|
||||
const [volumeName, path] = volume.split(':')
|
||||
if (!volumeName.startsWith('/')) {
|
||||
const found = await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: id } })
|
||||
for (const variable of template.variables) {
|
||||
if (variable.id.startsWith('$$secret_')) {
|
||||
const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||
if (!found) {
|
||||
await prisma.servicePersistentStorage.create({
|
||||
data: { volumeName, path, containerId: s, predefined: true, service: { connect: { id } } }
|
||||
});
|
||||
await prisma.serviceSecret.create({
|
||||
data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } }
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
if (variable.id.startsWith('$$config_')) {
|
||||
const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||
if (!found) {
|
||||
await prisma.serviceSetting.create({
|
||||
data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const s of Object.keys(template.services)) {
|
||||
if (service.type === 'plausibleanalytics') {
|
||||
continue;
|
||||
}
|
||||
if (template.services[s].volumes) {
|
||||
for (const volume of template.services[s].volumes) {
|
||||
const [volumeName, path] = volume.split(':')
|
||||
if (!volumeName.startsWith('/')) {
|
||||
const found = await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: id } })
|
||||
if (!found) {
|
||||
await prisma.servicePersistentStorage.create({
|
||||
data: { volumeName, path, containerId: s, predefined: true, service: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.service.update({ where: { id }, data: { templateVersion: template.templateVersion } })
|
||||
}
|
||||
await prisma.service.update({ where: { id }, data: { templateVersion: template.templateVersion } })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
@@ -214,7 +251,7 @@ async function hasura(service: any, template: any) {
|
||||
const { id } = service
|
||||
|
||||
const secrets = [
|
||||
`HASURA_GRAPHQL_ADMIN_PASSWORD@@@${graphQLAdminPassword}`,
|
||||
`HASURA_GRAPHQL_ADMIN_SECRET@@@${graphQLAdminPassword}`,
|
||||
`HASURA_GRAPHQL_METADATA_DATABASE_URL@@@${encrypt(`postgres://${postgresqlUser}:${decrypt(postgresqlPassword)}@${id}-postgresql:5432/${postgresqlDatabase}`)}`,
|
||||
`POSTGRES_PASSWORD@@@${postgresqlPassword}`,
|
||||
]
|
||||
@@ -231,7 +268,6 @@ async function hasura(service: any, template: any) {
|
||||
async function umami(service: any, template: any) {
|
||||
const { postgresqlUser, postgresqlPassword, postgresqlDatabase, umamiAdminPassword, hashSalt } = service.umami
|
||||
const { id } = service
|
||||
|
||||
const secrets = [
|
||||
`HASH_SALT@@@${hashSalt}`,
|
||||
`POSTGRES_PASSWORD@@@${postgresqlPassword}`,
|
||||
@@ -246,6 +282,8 @@ async function umami(service: any, template: any) {
|
||||
await migrateSettings(settings, service, template);
|
||||
await migrateSecrets(secrets, service);
|
||||
|
||||
await prisma.service.update({ where: { id: service.id }, data: { type: "umami-postgresql" } })
|
||||
|
||||
// Disconnect old service data
|
||||
// await prisma.service.update({ where: { id: service.id }, data: { umami: { disconnect: true } } })
|
||||
}
|
||||
@@ -430,36 +468,43 @@ async function plausibleAnalytics(service: any, template: any) {
|
||||
// Disconnect old service data
|
||||
// await prisma.service.update({ where: { id: service.id }, data: { plausibleAnalytics: { disconnect: true } } })
|
||||
}
|
||||
|
||||
async function migrateSettings(settings: any[], service: any, template: any) {
|
||||
for (const setting of settings) {
|
||||
if (!setting) continue;
|
||||
let [name, value] = setting.split('@@@')
|
||||
let minio = name
|
||||
if (name === 'MINIO_SERVER_URL') {
|
||||
name = 'coolify_fqdn_minio_console'
|
||||
}
|
||||
if (!value || value === 'null') {
|
||||
continue;
|
||||
}
|
||||
let variableName = template.variables.find((v: any) => v.name === name)?.id
|
||||
if (!variableName) {
|
||||
variableName = `$$config_${name.toLowerCase()}`
|
||||
}
|
||||
// console.log('Migrating setting', name, value, 'for service', service.id, ', service name:', service.name, 'variableName: ', variableName)
|
||||
try {
|
||||
if (!setting) continue;
|
||||
let [name, value] = setting.split('@@@')
|
||||
let minio = name
|
||||
if (name === 'MINIO_SERVER_URL') {
|
||||
name = 'coolify_fqdn_minio_console'
|
||||
}
|
||||
if (!value || value === 'null') {
|
||||
continue;
|
||||
}
|
||||
let variableName = template.variables.find((v: any) => v.name === name)?.id
|
||||
if (!variableName) {
|
||||
variableName = `$$config_${name.toLowerCase()}`
|
||||
}
|
||||
// console.log('Migrating setting', name, value, 'for service', service.id, ', service name:', service.name, 'variableName: ', variableName)
|
||||
|
||||
await prisma.serviceSetting.findFirst({ where: { name: minio, serviceId: service.id } }) || await prisma.serviceSetting.create({ data: { name: minio, value, variableName, service: { connect: { id: service.id } } } })
|
||||
await prisma.serviceSetting.findFirst({ where: { name: minio, serviceId: service.id } }) || await prisma.serviceSetting.create({ data: { name: minio, value, variableName, service: { connect: { id: service.id } } } })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
async function migrateSecrets(secrets: any[], service: any) {
|
||||
for (const secret of secrets) {
|
||||
if (!secret) continue;
|
||||
let [name, value] = secret.split('@@@')
|
||||
if (!value || value === 'null') {
|
||||
continue
|
||||
try {
|
||||
if (!secret) continue;
|
||||
let [name, value] = secret.split('@@@')
|
||||
if (!value || value === 'null') {
|
||||
continue
|
||||
}
|
||||
// console.log('Migrating secret', name, value, 'for service', service.id, ', service name:', service.name)
|
||||
await prisma.serviceSecret.findFirst({ where: { name, serviceId: service.id } }) || await prisma.serviceSecret.create({ data: { name, value, service: { connect: { id: service.id } } } })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
// console.log('Migrating secret', name, value, 'for service', service.id, ', service name:', service.name)
|
||||
await prisma.serviceSecret.findFirst({ where: { name, serviceId: service.id } }) || await prisma.serviceSecret.create({ data: { name, value, service: { connect: { id: service.id } } } })
|
||||
}
|
||||
}
|
||||
async function createVolumes(service: any, template: any) {
|
||||
@@ -483,4 +528,4 @@ async function createVolumes(service: any, template: any) {
|
||||
// console.log('Creating volume', volumeName, path, containerId, 'for service', service.id, ', service name:', service.name)
|
||||
await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: service.id } }) || await prisma.servicePersistentStorage.create({ data: { volumeName, path, containerId, predefined: true, service: { connect: { id: service.id } } } })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import { base64Encode, encrypt, executeDockerCmd, generateTimestamp, getDomain, isDev, prisma, version } from "../common";
|
||||
import {
|
||||
base64Encode,
|
||||
decrypt,
|
||||
encrypt,
|
||||
executeCommand,
|
||||
generateSecrets,
|
||||
generateTimestamp,
|
||||
getDomain,
|
||||
isARM,
|
||||
isDev,
|
||||
prisma,
|
||||
version
|
||||
} from '../common';
|
||||
import { promises as fs } from 'fs';
|
||||
import { day } from "../dayjs";
|
||||
import { day } from '../dayjs';
|
||||
|
||||
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
|
||||
const nodeBased = [
|
||||
@@ -17,7 +29,10 @@ const nodeBased = [
|
||||
'nextjs'
|
||||
];
|
||||
|
||||
export function setDefaultBaseImage(buildPack: string | null, deploymentType: string | null = null) {
|
||||
export function setDefaultBaseImage(
|
||||
buildPack: string | null,
|
||||
deploymentType: string | null = null
|
||||
) {
|
||||
const nodeVersions = [
|
||||
{
|
||||
value: 'node:lts',
|
||||
@@ -52,6 +67,14 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
{
|
||||
value: 'webdevops/apache:alpine',
|
||||
label: 'webdevops/apache:alpine'
|
||||
},
|
||||
{
|
||||
value: 'nginx:alpine',
|
||||
label: 'nginx:alpine'
|
||||
},
|
||||
{
|
||||
value: 'httpd:alpine',
|
||||
label: 'httpd:alpine (Apache)'
|
||||
}
|
||||
];
|
||||
const rustVersions = [
|
||||
@@ -214,8 +237,20 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
label: 'webdevops/php-apache:7.1-alpine'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-nginx:7.1-alpine',
|
||||
label: 'webdevops/php-nginx:7.1-alpine'
|
||||
value: 'php:8.1-fpm',
|
||||
label: 'php:8.1-fpm'
|
||||
},
|
||||
{
|
||||
value: 'php:8.0-fpm',
|
||||
label: 'php:8.0-fpm'
|
||||
},
|
||||
{
|
||||
value: 'php:8.1-fpm-alpine',
|
||||
label: 'php:8.1-fpm-alpine'
|
||||
},
|
||||
{
|
||||
value: 'php:8.0-fpm-alpine',
|
||||
label: 'php:8.0-fpm-alpine'
|
||||
}
|
||||
];
|
||||
const pythonVersions = [
|
||||
@@ -296,8 +331,8 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
{
|
||||
value: 'heroku/builder-classic:22',
|
||||
label: 'heroku/builder-classic:22'
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
let payload: any = {
|
||||
baseImage: null,
|
||||
baseBuildImage: null,
|
||||
@@ -306,8 +341,10 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
};
|
||||
if (nodeBased.includes(buildPack)) {
|
||||
if (deploymentType === 'static') {
|
||||
payload.baseImage = 'webdevops/nginx:alpine';
|
||||
payload.baseImages = staticVersions;
|
||||
payload.baseImage = isARM() ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||
payload.baseImages = isARM()
|
||||
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: staticVersions;
|
||||
payload.baseBuildImage = 'node:lts';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
} else {
|
||||
@@ -318,8 +355,10 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
}
|
||||
}
|
||||
if (staticApps.includes(buildPack)) {
|
||||
payload.baseImage = 'webdevops/nginx:alpine';
|
||||
payload.baseImages = staticVersions;
|
||||
payload.baseImage = isARM() ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||
payload.baseImages = isARM()
|
||||
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: staticVersions;
|
||||
payload.baseBuildImage = 'node:lts';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
}
|
||||
@@ -337,12 +376,20 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
payload.baseImage = 'denoland/deno:latest';
|
||||
}
|
||||
if (buildPack === 'php') {
|
||||
payload.baseImage = 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = phpVersions;
|
||||
payload.baseImage = isARM()
|
||||
? 'php:8.1-fpm-alpine'
|
||||
: 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = isARM()
|
||||
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: phpVersions;
|
||||
}
|
||||
if (buildPack === 'laravel') {
|
||||
payload.baseImage = 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = phpVersions;
|
||||
payload.baseImage = isARM()
|
||||
? 'php:8.1-fpm-alpine'
|
||||
: 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = isARM()
|
||||
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: phpVersions;
|
||||
payload.baseBuildImage = 'node:18';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
}
|
||||
@@ -363,6 +410,7 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
publishDirectory,
|
||||
baseDirectory,
|
||||
dockerFileLocation,
|
||||
dockerComposeFileLocation,
|
||||
denoMainFile
|
||||
} = data;
|
||||
//@ts-ignore
|
||||
@@ -381,10 +429,16 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
startCommand = template?.startCommand || 'yarn start';
|
||||
if (!buildCommand && buildPack !== 'static' && buildPack !== 'laravel')
|
||||
buildCommand = template?.buildCommand || null;
|
||||
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
||||
if (!publishDirectory) {
|
||||
publishDirectory = template?.publishDirectory || null;
|
||||
} else {
|
||||
if (!publishDirectory.startsWith('/')) publishDirectory = `/${publishDirectory}`;
|
||||
if (publishDirectory.endsWith('/')) publishDirectory = publishDirectory.slice(0, -1);
|
||||
}
|
||||
if (baseDirectory) {
|
||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||
if (baseDirectory.endsWith('/') && baseDirectory !== '/') baseDirectory = baseDirectory.slice(0, -1);
|
||||
if (baseDirectory.endsWith('/') && baseDirectory !== '/')
|
||||
baseDirectory = baseDirectory.slice(0, -1);
|
||||
}
|
||||
if (dockerFileLocation) {
|
||||
if (!dockerFileLocation.startsWith('/')) dockerFileLocation = `/${dockerFileLocation}`;
|
||||
@@ -392,6 +446,14 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
} else {
|
||||
dockerFileLocation = '/Dockerfile';
|
||||
}
|
||||
if (dockerComposeFileLocation) {
|
||||
if (!dockerComposeFileLocation.startsWith('/'))
|
||||
dockerComposeFileLocation = `/${dockerComposeFileLocation}`;
|
||||
if (dockerComposeFileLocation.endsWith('/'))
|
||||
dockerComposeFileLocation = dockerComposeFileLocation.slice(0, -1);
|
||||
} else {
|
||||
dockerComposeFileLocation = '/Dockerfile';
|
||||
}
|
||||
if (!denoMainFile) {
|
||||
denoMainFile = 'main.ts';
|
||||
}
|
||||
@@ -405,6 +467,7 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
publishDirectory,
|
||||
baseDirectory,
|
||||
dockerFileLocation,
|
||||
dockerComposeFileLocation,
|
||||
denoMainFile
|
||||
};
|
||||
};
|
||||
@@ -451,7 +514,6 @@ export const scanningTemplates = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const saveBuildLog = async ({
|
||||
line,
|
||||
buildId,
|
||||
@@ -461,14 +523,26 @@ export const saveBuildLog = async ({
|
||||
buildId: string;
|
||||
applicationId: string;
|
||||
}): Promise<any> => {
|
||||
const { default: got } = await import('got')
|
||||
|
||||
if (buildId === 'undefined' || buildId === 'null' || !buildId) return;
|
||||
if (applicationId === 'undefined' || applicationId === 'null' || !applicationId) return;
|
||||
const { default: got } = await import('got');
|
||||
if (typeof line === 'object' && line) {
|
||||
if (line.shortMessage) {
|
||||
line = line.shortMessage + '\n' + line.stderr;
|
||||
} else {
|
||||
line = JSON.stringify(line);
|
||||
}
|
||||
}
|
||||
if (line && typeof line === 'string' && line.includes('ghs_')) {
|
||||
const regex = /ghs_.*@/g;
|
||||
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||
}
|
||||
const addTimestamp = `[${generateTimestamp()}] ${line}`;
|
||||
const fluentBitUrl = isDev ? process.env.COOLIFY_CONTAINER_DEV === 'true' ? 'http://coolify-fluentbit:24224' : 'http://localhost:24224' : 'http://coolify-fluentbit:24224';
|
||||
const fluentBitUrl = isDev
|
||||
? process.env.COOLIFY_CONTAINER_DEV === 'true'
|
||||
? 'http://coolify-fluentbit:24224'
|
||||
: 'http://localhost:24224'
|
||||
: 'http://coolify-fluentbit:24224';
|
||||
|
||||
if (isDev && !process.env.COOLIFY_CONTAINER_DEV) {
|
||||
console.debug(`[${applicationId}] ${addTimestamp}`);
|
||||
@@ -478,15 +552,17 @@ export const saveBuildLog = async ({
|
||||
json: {
|
||||
line: encrypt(line)
|
||||
}
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
return await prisma.buildLog.create({
|
||||
data: {
|
||||
line: addTimestamp, buildId, time: Number(day().valueOf()), applicationId
|
||||
line: addTimestamp,
|
||||
buildId,
|
||||
time: Number(day().valueOf()),
|
||||
applicationId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export async function copyBaseConfigurationFiles(
|
||||
@@ -558,6 +634,7 @@ export async function copyBaseConfigurationFiles(
|
||||
`
|
||||
);
|
||||
}
|
||||
// TODO: Add more configuration files for other buildpacks, like apache2, etc.
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
@@ -571,6 +648,29 @@ export function checkPnpm(installCommand = null, buildCommand = null, startComma
|
||||
);
|
||||
}
|
||||
|
||||
export async function saveDockerRegistryCredentials({ url, username, password, workdir }) {
|
||||
if (!username || !password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let decryptedPassword = decrypt(password);
|
||||
const location = `${workdir}/.docker`;
|
||||
|
||||
try {
|
||||
await fs.mkdir(`${workdir}/.docker`);
|
||||
} catch (error) {
|
||||
// console.log(error);
|
||||
}
|
||||
const payload = JSON.stringify({
|
||||
auths: {
|
||||
[url]: {
|
||||
auth: Buffer.from(`${username}:${decryptedPassword}`).toString('base64')
|
||||
}
|
||||
}
|
||||
});
|
||||
await fs.writeFile(`${location}/config.json`, payload);
|
||||
return location;
|
||||
}
|
||||
export async function buildImage({
|
||||
applicationId,
|
||||
tag,
|
||||
@@ -580,36 +680,50 @@ export async function buildImage({
|
||||
isCache = false,
|
||||
debug = false,
|
||||
dockerFileLocation = '/Dockerfile',
|
||||
commit
|
||||
commit,
|
||||
forceRebuild = false
|
||||
}) {
|
||||
if (isCache) {
|
||||
await saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
|
||||
await saveBuildLog({ line: `Building cache image...`, buildId, applicationId });
|
||||
} else {
|
||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||
await saveBuildLog({ line: `Building production image...`, buildId, applicationId });
|
||||
}
|
||||
if (!debug) {
|
||||
await saveBuildLog({
|
||||
line: `Debug turned off. To see more details, allow it in the features tab.`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||
|
||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
|
||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`;
|
||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`;
|
||||
let location = null;
|
||||
|
||||
const { status } = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
if (status === 'canceled') {
|
||||
throw new Error('Deployment canceled.')
|
||||
const { dockerRegistry } = await prisma.application.findUnique({
|
||||
where: { id: applicationId },
|
||||
select: { dockerRegistry: true }
|
||||
});
|
||||
if (dockerRegistry) {
|
||||
const { url, username, password } = dockerRegistry;
|
||||
location = await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||
}
|
||||
if (isCache) {
|
||||
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
||||
} else {
|
||||
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
||||
|
||||
await executeCommand({
|
||||
stream: true,
|
||||
debug,
|
||||
buildId,
|
||||
applicationId,
|
||||
dockerId,
|
||||
command: `docker ${location ? `--config ${location}` : ''} build ${forceRebuild ? '--no-cache' : ''
|
||||
} --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}`
|
||||
});
|
||||
|
||||
const { status } = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (status === 'canceled') {
|
||||
throw new Error('Canceled.');
|
||||
}
|
||||
}
|
||||
|
||||
export function makeLabelForSimpleDockerfile({ applicationId, port, type }) {
|
||||
return [
|
||||
'coolify.managed=true',
|
||||
`coolify.version=${version}`,
|
||||
`coolify.applicationId=${applicationId}`,
|
||||
`coolify.type=standalone-application`
|
||||
];
|
||||
}
|
||||
export function makeLabelForStandaloneApplication({
|
||||
applicationId,
|
||||
fqdn,
|
||||
@@ -638,6 +752,7 @@ export function makeLabelForStandaloneApplication({
|
||||
`coolify.version=${version}`,
|
||||
`coolify.applicationId=${applicationId}`,
|
||||
`coolify.type=standalone-application`,
|
||||
`coolify.name=${name}`,
|
||||
`coolify.configuration=${base64Encode(
|
||||
JSON.stringify({
|
||||
applicationId,
|
||||
@@ -677,21 +792,8 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
@@ -701,50 +803,33 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
if (installCommand) {
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
}
|
||||
// Dockerfile.push(`ARG CACHEBUST=1`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ ...data, isCache: true });
|
||||
}
|
||||
|
||||
export async function buildCacheImageForLaravel(data, imageForBuild) {
|
||||
const { workdir, buildId, secrets, pullmergeRequestId } = data;
|
||||
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
Dockerfile.push(`COPY *.json *.mix.js /app/`);
|
||||
Dockerfile.push(`COPY resources /app/resources`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`RUN yarn install && yarn production`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ ...data, isCache: true });
|
||||
}
|
||||
|
||||
export async function buildCacheImageWithCargo(data, imageForBuild) {
|
||||
const {
|
||||
applicationId,
|
||||
workdir,
|
||||
buildId,
|
||||
} = data;
|
||||
const { applicationId, workdir, buildId } = data;
|
||||
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
|
||||
@@ -759,6 +844,7 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
|
||||
Dockerfile.push('RUN cargo install cargo-chef');
|
||||
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
|
||||
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ ...data, isCache: true });
|
||||
}
|
||||
|
||||
@@ -1,100 +1,197 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { defaultComposeConfiguration, executeDockerCmd } from '../common';
|
||||
import { buildImage, saveBuildLog } from './common';
|
||||
import { defaultComposeConfiguration, executeCommand, generateSecrets } from '../common';
|
||||
import { saveBuildLog } from './common';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
export default async function (data) {
|
||||
let {
|
||||
applicationId,
|
||||
debug,
|
||||
buildId,
|
||||
dockerId,
|
||||
network,
|
||||
volumes,
|
||||
labels,
|
||||
workdir,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId,
|
||||
port,
|
||||
dockerComposeConfiguration
|
||||
} = data
|
||||
const fileYml = `${workdir}${baseDirectory}/docker-compose.yml`;
|
||||
const fileYaml = `${workdir}${baseDirectory}/docker-compose.yaml`;
|
||||
let dockerComposeRaw = null;
|
||||
let isYml = false;
|
||||
try {
|
||||
dockerComposeRaw = await fs.readFile(`${fileYml}`, 'utf8')
|
||||
isYml = true
|
||||
} catch (error) { }
|
||||
try {
|
||||
dockerComposeRaw = await fs.readFile(`${fileYaml}`, 'utf8')
|
||||
} catch (error) { }
|
||||
let {
|
||||
applicationId,
|
||||
debug,
|
||||
buildId,
|
||||
dockerId,
|
||||
network,
|
||||
volumes,
|
||||
labels,
|
||||
workdir,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId,
|
||||
dockerComposeConfiguration,
|
||||
dockerComposeFileLocation
|
||||
} = data;
|
||||
const baseDir = `${workdir}${baseDirectory}`;
|
||||
const envFile = `${baseDir}/.env`;
|
||||
const fileYaml = `${baseDir}${dockerComposeFileLocation}`;
|
||||
const dockerComposeRaw = await fs.readFile(fileYaml, 'utf8');
|
||||
const dockerComposeYaml = yaml.load(dockerComposeRaw);
|
||||
if (!dockerComposeYaml.services) {
|
||||
throw 'No Services found in docker-compose file.';
|
||||
}
|
||||
let envs = [];
|
||||
let buildEnvs = [];
|
||||
if (secrets.length > 0) {
|
||||
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
||||
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
||||
}
|
||||
await fs.writeFile(envFile, envs.join('\n'));
|
||||
const composeVolumes = [];
|
||||
if (volumes.length > 0) {
|
||||
for (const volume of volumes) {
|
||||
let [v, path] = volume.split(':');
|
||||
if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) {
|
||||
composeVolumes[v] = {
|
||||
name: v
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
let networks = {};
|
||||
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||
value['container_name'] = `${applicationId}-${key}`;
|
||||
|
||||
if (!dockerComposeRaw) {
|
||||
throw ('docker-compose.yml or docker-compose.yaml are not found!');
|
||||
}
|
||||
const dockerComposeYaml = yaml.load(dockerComposeRaw)
|
||||
if (!dockerComposeYaml.services) {
|
||||
throw 'No Services found in docker-compose file.'
|
||||
}
|
||||
const envs = [
|
||||
`PORT=${port}`
|
||||
];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||
let envFound = false;
|
||||
try {
|
||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
let networks = {}
|
||||
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||
value['container_name'] = `${applicationId}-${key}`
|
||||
value['env_file'] = envFound ? [`${workdir}/.env`] : []
|
||||
value['labels'] = labels
|
||||
value['volumes'] = volumes
|
||||
if (dockerComposeConfiguration[key].port) {
|
||||
value['expose'] = [dockerComposeConfiguration[key].port]
|
||||
}
|
||||
if (value['networks']?.length > 0) {
|
||||
value['networks'].forEach((network) => {
|
||||
networks[network] = {
|
||||
name: network
|
||||
}
|
||||
})
|
||||
}
|
||||
value['networks'] = [...value['networks'] || '', network]
|
||||
dockerComposeYaml.services[key] = { ...dockerComposeYaml.services[key], restart: defaultComposeConfiguration(network).restart, deploy: defaultComposeConfiguration(network).deploy }
|
||||
}
|
||||
dockerComposeYaml['volumes'] = Object.assign({ ...dockerComposeYaml['volumes'] }, ...composeVolumes)
|
||||
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } })
|
||||
await fs.writeFile(`${workdir}/docker-compose.${isYml ? 'yml' : 'yaml'}`, yaml.dump(dockerComposeYaml));
|
||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} pull` })
|
||||
await saveBuildLog({ line: 'Pulling images from Compose file.', buildId, applicationId });
|
||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} build --progress plain` })
|
||||
await saveBuildLog({ line: 'Building images from Compose file.', buildId, applicationId });
|
||||
if (value['env_file']) {
|
||||
delete value['env_file'];
|
||||
}
|
||||
value['env_file'] = [envFile];
|
||||
|
||||
// let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
||||
// let finalEnvs = [...envs];
|
||||
// if (Object.keys(environment).length > 0) {
|
||||
// for (const arg of Object.keys(environment)) {
|
||||
// const [key, _] = arg.split('=');
|
||||
// if (finalEnvs.filter((env) => env.startsWith(key)).length === 0) {
|
||||
// finalEnvs.push(arg);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// value['environment'] = [...finalEnvs];
|
||||
|
||||
let build = typeof value['build'] === 'undefined' ? [] : value['build'];
|
||||
if (typeof build === 'string') {
|
||||
build = { context: build };
|
||||
}
|
||||
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
|
||||
let finalBuildArgs = [...buildEnvs];
|
||||
if (Object.keys(buildArgs).length > 0) {
|
||||
for (const arg of Object.keys(buildArgs)) {
|
||||
const [key, _] = arg.split('=');
|
||||
if (finalBuildArgs.filter((env) => env.startsWith(key)).length === 0) {
|
||||
finalBuildArgs.push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (build.length > 0 || buildArgs.length > 0) {
|
||||
value['build'] = {
|
||||
...build,
|
||||
args: finalBuildArgs
|
||||
};
|
||||
}
|
||||
|
||||
value['labels'] = labels;
|
||||
// TODO: If we support separated volume for each service, we need to add it here
|
||||
if (value['volumes']?.length > 0) {
|
||||
value['volumes'] = value['volumes'].map((volume) => {
|
||||
if (typeof volume === 'string') {
|
||||
let [v, path, permission] = volume.split(':');
|
||||
if (
|
||||
v.startsWith('.') ||
|
||||
v.startsWith('..') ||
|
||||
v.startsWith('/') ||
|
||||
v.startsWith('~') ||
|
||||
v.startsWith('$PWD')
|
||||
) {
|
||||
v = v
|
||||
.replace(/^\./, `~`)
|
||||
.replace(/^\.\./, '~')
|
||||
.replace(/^\$PWD/, '~');
|
||||
} else {
|
||||
if (!path) {
|
||||
path = v;
|
||||
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||
} else {
|
||||
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||
}
|
||||
composeVolumes[v] = {
|
||||
name: v
|
||||
};
|
||||
}
|
||||
return `${v}:${path}${permission ? ':' + permission : ''}`;
|
||||
}
|
||||
if (typeof volume === 'object') {
|
||||
let { source, target, mode } = volume;
|
||||
if (
|
||||
source.startsWith('.') ||
|
||||
source.startsWith('..') ||
|
||||
source.startsWith('/') ||
|
||||
source.startsWith('~') ||
|
||||
source.startsWith('$PWD')
|
||||
) {
|
||||
source = source
|
||||
.replace(/^\./, `~`)
|
||||
.replace(/^\.\./, '~')
|
||||
.replace(/^\$PWD/, '~');
|
||||
} else {
|
||||
if (!target) {
|
||||
target = source;
|
||||
source = `${applicationId}${source.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||
} else {
|
||||
source = `${applicationId}${source.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${source}:${target}${mode ? ':' + mode : ''}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (volumes.length > 0) {
|
||||
for (const volume of volumes) {
|
||||
value['volumes'].push(volume);
|
||||
}
|
||||
}
|
||||
if (dockerComposeConfiguration[key]?.port) {
|
||||
value['expose'] = [dockerComposeConfiguration[key].port];
|
||||
}
|
||||
value['networks'] = [network];
|
||||
if (value['build']?.network) {
|
||||
delete value['build']['network'];
|
||||
}
|
||||
// if (value['networks']?.length > 0) {
|
||||
// value['networks'].forEach((network) => {
|
||||
// networks[network] = {
|
||||
// name: network
|
||||
// };
|
||||
// });
|
||||
// value['networks'] = [...(value['networks'] || ''), network];
|
||||
// } else {
|
||||
// value['networks'] = [network];
|
||||
// }
|
||||
|
||||
dockerComposeYaml.services[key] = {
|
||||
...dockerComposeYaml.services[key],
|
||||
restart: defaultComposeConfiguration(network).restart,
|
||||
deploy: defaultComposeConfiguration(network).deploy
|
||||
};
|
||||
}
|
||||
if (Object.keys(composeVolumes).length > 0) {
|
||||
dockerComposeYaml['volumes'] = { ...composeVolumes };
|
||||
}
|
||||
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
|
||||
|
||||
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
|
||||
await executeCommand({
|
||||
debug,
|
||||
buildId,
|
||||
applicationId,
|
||||
dockerId,
|
||||
command: `docker compose --project-directory ${workdir} -f ${fileYaml} pull`
|
||||
});
|
||||
await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId });
|
||||
await executeCommand({
|
||||
debug,
|
||||
buildId,
|
||||
applicationId,
|
||||
dockerId,
|
||||
command: `docker compose --project-directory ${workdir} -f ${fileYaml} build --progress plain`
|
||||
});
|
||||
await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (depsFound) {
|
||||
@@ -48,6 +36,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN deno cache ${denoMainFile}`);
|
||||
Dockerfile.push(`ENV NO_COLOR true`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD deno run ${denoOptions || ''} ${denoMainFile}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
|
||||
@@ -1,45 +1,27 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage } from './common';
|
||||
|
||||
export default async function (data) {
|
||||
let {
|
||||
applicationId,
|
||||
debug,
|
||||
tag,
|
||||
workdir,
|
||||
buildId,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId,
|
||||
dockerFileLocation
|
||||
} = data
|
||||
let { workdir, buildId, baseDirectory, secrets, pullmergeRequestId, dockerFileLocation } = data;
|
||||
const file = `${workdir}${baseDirectory}${dockerFileLocation}`;
|
||||
data.workdir = `${workdir}${baseDirectory}`;
|
||||
const DockerfileRaw = await fs.readFile(`${file}`, 'utf8')
|
||||
const Dockerfile: Array<string> = DockerfileRaw
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
const DockerfileRaw = await fs.readFile(`${file}`, 'utf8');
|
||||
const Dockerfile: Array<string> = DockerfileRaw.toString().trim().split('\n');
|
||||
Dockerfile.forEach((line, index) => {
|
||||
if (line.startsWith('FROM')) {
|
||||
Dockerfile.splice(index + 1, 0, `LABEL coolify.buildId=${buildId}`);
|
||||
}
|
||||
});
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (
|
||||
(pullmergeRequestId && secret.isPRMRSecret) ||
|
||||
(!pullmergeRequestId && !secret.isPRMRSecret)
|
||||
) {
|
||||
Dockerfile.unshift(`ARG ${secret.name}=${secret.value}`);
|
||||
|
||||
Dockerfile.forEach((line, index) => {
|
||||
if (line.startsWith('FROM')) {
|
||||
Dockerfile.splice(index + 1, 0, `ARG ${secret.name}`);
|
||||
}
|
||||
});
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.forEach((line, index) => {
|
||||
if (line.startsWith('FROM')) {
|
||||
Dockerfile.splice(index + 1, 0, env);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await fs.writeFile(`${workdir}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||
await fs.writeFile(`${data.workdir}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||
await buildImage(data);
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => {
|
||||
Dockerfile.push(`FROM ${imageforBuild}`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { executeDockerCmd, prisma } from "../common"
|
||||
import { executeCommand } from "../common"
|
||||
import { saveBuildLog } from "./common";
|
||||
|
||||
export default async function (data: any): Promise<void> {
|
||||
const { buildId, applicationId, tag, dockerId, debug, workdir, baseDirectory, baseImage } = data
|
||||
try {
|
||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||
await executeDockerCmd({
|
||||
await saveBuildLog({ line: `Building production image...`, buildId, applicationId });
|
||||
await executeCommand({
|
||||
buildId,
|
||||
debug,
|
||||
dockerId,
|
||||
command: `pack build -p ${workdir}${baseDirectory} ${applicationId}:${tag} --builder ${baseImage}`
|
||||
})
|
||||
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildCacheImageForLaravel, buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { workdir, applicationId, tag, buildId, port } = data;
|
||||
const { workdir, applicationId, tag, buildId, port, secrets, pullmergeRequestId } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`);
|
||||
Dockerfile.push(`COPY --chown=application:application composer.* ./`);
|
||||
@@ -24,6 +30,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
`COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json`
|
||||
);
|
||||
Dockerfile.push(`COPY --chown=application:application . ./`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
|
||||
import { buildCacheImageWithNode, buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { buildId, applicationId, tag, port, startCommand, workdir, baseDirectory } = data;
|
||||
const { buildId, applicationId, tag, port, startCommand, workdir, publishDirectory } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
const isPnpm = startCommand.includes('pnpm');
|
||||
|
||||
@@ -12,8 +12,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||
}
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`);
|
||||
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
@@ -48,13 +36,15 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
} else if (deploymentType === 'static') {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage, checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -20,21 +21,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
@@ -46,6 +34,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
@@ -48,13 +36,15 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
} else if (deploymentType === 'static') {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE 80`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||
@@ -13,21 +14,8 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
@@ -40,6 +28,7 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||
}
|
||||
|
||||
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -18,21 +19,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
|
||||
@@ -64,7 +52,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
} else {
|
||||
Dockerfile.push(`CMD python ${pythonModule}`);
|
||||
}
|
||||
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import TOML from '@iarna/toml';
|
||||
import { asyncExecShell } from '../common';
|
||||
import { executeCommand } from '../common';
|
||||
import { buildCacheImageWithCargo, buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image, name): Promise<void> => {
|
||||
@@ -20,6 +20,7 @@ const createDockerfile = async (data, image, name): Promise<void> => {
|
||||
);
|
||||
Dockerfile.push(`RUN update-ca-certificates`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ["/app/${name}"]`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
@@ -28,7 +29,7 @@ const createDockerfile = async (data, image, name): Promise<void> => {
|
||||
export default async function (data) {
|
||||
try {
|
||||
const { workdir, baseImage, baseBuildImage } = data;
|
||||
const { stdout: cargoToml } = await asyncExecShell(`cat ${workdir}/Cargo.toml`);
|
||||
const { stdout: cargoToml } = await executeCommand({ command: `cat ${workdir}/Cargo.toml` });
|
||||
const parsedToml: any = TOML.parse(cargoToml);
|
||||
const name = parsedToml.package.name;
|
||||
await buildCacheImageWithCargo(data, baseBuildImage);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildCacheImageWithNode, buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -18,34 +19,26 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
if (baseImage?.includes('httpd')) {
|
||||
Dockerfile.push('WORKDIR /usr/local/apache2/htdocs/');
|
||||
} else {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
}
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||
} else {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
}
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
@@ -8,10 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
@@ -8,10 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
import { executeDockerCmd } from './common';
|
||||
import { executeCommand } from './common';
|
||||
|
||||
export function formatLabelsOnDocker(data) {
|
||||
return data.trim().split('\n').map(a => JSON.parse(a)).map((container) => {
|
||||
@@ -16,7 +16,7 @@ export function formatLabelsOnDocker(data) {
|
||||
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<{ found: boolean, status?: { isExited: boolean, isRunning: boolean, isRestarting: boolean } }> {
|
||||
let containerFound = false;
|
||||
try {
|
||||
const { stdout } = await executeDockerCmd({
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId,
|
||||
command:
|
||||
`docker inspect --format '{{json .State}}' ${container}`
|
||||
@@ -28,27 +28,26 @@ export async function checkContainer({ dockerId, container, remove = false }: {
|
||||
const isRestarting = status === 'restarting'
|
||||
const isExited = status === 'exited'
|
||||
if (status === 'created') {
|
||||
await executeDockerCmd({
|
||||
await executeCommand({
|
||||
dockerId,
|
||||
command:
|
||||
`docker rm ${container}`
|
||||
});
|
||||
}
|
||||
if (remove && status === 'exited') {
|
||||
await executeDockerCmd({
|
||||
await executeCommand({
|
||||
dockerId,
|
||||
command:
|
||||
`docker rm ${container}`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
found: containerFound,
|
||||
status: {
|
||||
isRunning,
|
||||
isRestarting,
|
||||
isExited
|
||||
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
@@ -63,7 +62,7 @@ export async function checkContainer({ dockerId, container, remove = false }: {
|
||||
export async function isContainerExited(dockerId: string, containerName: string): Promise<boolean> {
|
||||
let isExited = false;
|
||||
try {
|
||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` })
|
||||
const { stdout } = await executeCommand({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` })
|
||||
if (stdout.trim() === 'exited') {
|
||||
isExited = true;
|
||||
}
|
||||
@@ -82,13 +81,13 @@ export async function removeContainer({
|
||||
dockerId: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||
const { stdout } = await executeCommand({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||
if (JSON.parse(stdout).Running) {
|
||||
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
||||
await executeCommand({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||
await executeCommand({ dockerId, command: `docker rm ${id}` })
|
||||
}
|
||||
if (JSON.parse(stdout).Status === 'exited') {
|
||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
||||
await executeCommand({ dockerId, command: `docker rm ${id}` })
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { saveBuildLog } from '../buildPacks/common';
|
||||
import { asyncExecShell, decrypt, prisma } from '../common';
|
||||
import { decrypt, executeCommand, prisma } from '../common';
|
||||
|
||||
export default async function ({
|
||||
applicationId,
|
||||
@@ -9,6 +9,7 @@ export default async function ({
|
||||
githubAppId,
|
||||
repository,
|
||||
apiUrl,
|
||||
gitCommitHash,
|
||||
htmlUrl,
|
||||
branch,
|
||||
buildId,
|
||||
@@ -20,6 +21,7 @@ export default async function ({
|
||||
githubAppId: string;
|
||||
repository: string;
|
||||
apiUrl: string;
|
||||
gitCommitHash?: string;
|
||||
htmlUrl: string;
|
||||
branch: string;
|
||||
buildId: string;
|
||||
@@ -28,16 +30,24 @@ export default async function ({
|
||||
}): Promise<string> {
|
||||
const { default: got } = await import('got')
|
||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||
if (forPublic) {
|
||||
await saveBuildLog({
|
||||
line: `Cloning ${repository}:${branch} branch.`,
|
||||
line: `Cloning ${repository}:${branch}...`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||
);
|
||||
if (gitCommitHash) {
|
||||
await saveBuildLog({
|
||||
line: `Checking out ${gitCommitHash} commit...`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
await executeCommand({
|
||||
command:
|
||||
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `,
|
||||
shell: true
|
||||
});
|
||||
|
||||
} else {
|
||||
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
||||
@@ -62,15 +72,23 @@ export default async function ({
|
||||
})
|
||||
.json();
|
||||
await saveBuildLog({
|
||||
line: `Cloning ${repository}:${branch} branch.`,
|
||||
line: `Cloning ${repository}:${branch}...`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||
);
|
||||
if (gitCommitHash) {
|
||||
await saveBuildLog({
|
||||
line: `Checking out ${gitCommitHash} commit...`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
await executeCommand({
|
||||
command:
|
||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `,
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||
|
||||
const { stdout: commit } = await executeCommand({ command: `cd ${workdir}/ && git rev-parse HEAD`, shell: true });
|
||||
return commit.replace('\n', '');
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { saveBuildLog } from "../buildPacks/common";
|
||||
import { asyncExecShell } from "../common";
|
||||
import { executeCommand } from "../common";
|
||||
|
||||
export default async function ({
|
||||
applicationId,
|
||||
workdir,
|
||||
repodir,
|
||||
htmlUrl,
|
||||
gitCommitHash,
|
||||
repository,
|
||||
branch,
|
||||
buildId,
|
||||
privateSshKey,
|
||||
customPort,
|
||||
forPublic
|
||||
forPublic,
|
||||
customUser,
|
||||
}: {
|
||||
applicationId: string;
|
||||
workdir: string;
|
||||
@@ -20,34 +22,44 @@ export default async function ({
|
||||
branch: string;
|
||||
buildId: string;
|
||||
repodir: string;
|
||||
gitCommitHash: string;
|
||||
privateSshKey: string;
|
||||
customPort: number;
|
||||
forPublic: boolean;
|
||||
customUser: string;
|
||||
}): Promise<string> {
|
||||
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
||||
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
|
||||
|
||||
if (!forPublic) {
|
||||
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
|
||||
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
|
||||
await executeCommand({ command: `echo '${privateSshKey}' > ${repodir}/id.rsa`, shell: true });
|
||||
await executeCommand({ command: `chmod 600 ${repodir}/id.rsa` });
|
||||
}
|
||||
|
||||
await saveBuildLog({
|
||||
line: `Cloning ${repository}:${branch} branch.`,
|
||||
line: `Cloning ${repository}:${branch}...`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
|
||||
if (gitCommitHash) {
|
||||
await saveBuildLog({
|
||||
line: `Checking out ${gitCommitHash} commit...`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
if (forPublic) {
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||
await executeCommand({
|
||||
command:
|
||||
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||
await executeCommand({
|
||||
command:
|
||||
`git clone -q -b ${branch} ${customUser}@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||
|
||||
const { stdout: commit } = await executeCommand({ command: `cd ${workdir}/ && git rev-parse HEAD`, shell: true });
|
||||
return commit.replace('\n', '');
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ Bree.extend(TSBree);
|
||||
|
||||
const options: any = {
|
||||
defaultExtension: 'js',
|
||||
logger: new Cabin(),
|
||||
logger: false,
|
||||
// logger: false,
|
||||
// workerMessageHandler: async ({ name, message }) => {
|
||||
// if (name === 'deployApplication' && message?.deploying) {
|
||||
|
||||
@@ -1,170 +1,47 @@
|
||||
import { isDev } from "./common";
|
||||
import { isARM, isDev } from './common';
|
||||
import fs from 'fs/promises';
|
||||
export async function getTemplates() {
|
||||
let templates: any = [];
|
||||
if (isDev) {
|
||||
templates = JSON.parse(await (await fs.readFile('./templates.json')).toString())
|
||||
} else {
|
||||
templates = JSON.parse(await (await fs.readFile('/app/templates.json')).toString())
|
||||
}
|
||||
// if (!isDev) {
|
||||
// templates.push({
|
||||
// "templateVersion": "1.0.0",
|
||||
// "defaultVersion": "latest",
|
||||
// "name": "Test-Fake-Service",
|
||||
// "description": "",
|
||||
// "services": {
|
||||
// "$$id": {
|
||||
// "name": "Test-Fake-Service",
|
||||
// "depends_on": [
|
||||
// "$$id-postgresql",
|
||||
// "$$id-redis"
|
||||
// ],
|
||||
// "image": "weblate/weblate:$$core_version",
|
||||
// "volumes": [
|
||||
// "$$id-data:/app/data",
|
||||
// ],
|
||||
// "environment": [
|
||||
// `POSTGRES_SECRET=$$secret_postgres_secret`,
|
||||
// `WEBLATE_SITE_DOMAIN=$$config_weblate_site_domain`,
|
||||
// `WEBLATE_ADMIN_PASSWORD=$$secret_weblate_admin_password`,
|
||||
// `POSTGRES_PASSWORD=$$secret_postgres_password`,
|
||||
// `POSTGRES_USER=$$config_postgres_user`,
|
||||
// `POSTGRES_DATABASE=$$config_postgres_db`,
|
||||
// `POSTGRES_HOST=$$id-postgresql`,
|
||||
// `POSTGRES_PORT=5432`,
|
||||
// `REDIS_HOST=$$id-redis`,
|
||||
// ],
|
||||
// "ports": [
|
||||
// "8080"
|
||||
// ]
|
||||
// },
|
||||
// "$$id-postgresql": {
|
||||
// "name": "PostgreSQL",
|
||||
// "depends_on": [],
|
||||
// "image": "postgres:14-alpine",
|
||||
// "volumes": [
|
||||
// "$$id-postgresql-data:/var/lib/postgresql/data",
|
||||
// ],
|
||||
// "environment": [
|
||||
// "POSTGRES_USER=$$config_postgres_user",
|
||||
// "POSTGRES_PASSWORD=$$secret_postgres_password",
|
||||
// "POSTGRES_DB=$$config_postgres_db",
|
||||
// ],
|
||||
// "ports": []
|
||||
// },
|
||||
// "$$id-redis": {
|
||||
// "name": "Redis",
|
||||
// "depends_on": [],
|
||||
// "image": "redis:7-alpine",
|
||||
// "volumes": [
|
||||
// "$$id-redis-data:/data",
|
||||
// ],
|
||||
// "environment": [],
|
||||
// "ports": [],
|
||||
// }
|
||||
// },
|
||||
// "variables": [
|
||||
// {
|
||||
// "id": "$$config_weblate_site_domain",
|
||||
// "main": "$$id",
|
||||
// "name": "WEBLATE_SITE_DOMAIN",
|
||||
// "label": "Weblate Domain",
|
||||
// "defaultValue": "$$generate_domain",
|
||||
// "description": "",
|
||||
// },
|
||||
// {
|
||||
// "id": "$$secret_weblate_admin_password",
|
||||
// "main": "$$id",
|
||||
// "name": "WEBLATE_ADMIN_PASSWORD",
|
||||
// "label": "Weblate Admin Password",
|
||||
// "defaultValue": "$$generate_password",
|
||||
// "description": "",
|
||||
// "extras": {
|
||||
// "isVisibleOnUI": true,
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "id": "$$secret_weblate_admin_password2",
|
||||
// "name": "WEBLATE_ADMIN_PASSWORD2",
|
||||
// "label": "Weblate Admin Password2",
|
||||
// "defaultValue": "$$generate_password",
|
||||
// "description": "",
|
||||
// },
|
||||
// {
|
||||
// "id": "$$config_postgres_user",
|
||||
// "main": "$$id-postgresql",
|
||||
// "name": "POSTGRES_USER",
|
||||
// "label": "PostgreSQL User",
|
||||
// "defaultValue": "$$generate_username",
|
||||
// "description": "",
|
||||
// },
|
||||
// {
|
||||
// "id": "$$secret_postgres_password",
|
||||
// "main": "$$id-postgresql",
|
||||
// "name": "POSTGRES_PASSWORD",
|
||||
// "label": "PostgreSQL Password",
|
||||
// "defaultValue": "$$generate_password(32)",
|
||||
// "description": "",
|
||||
// },
|
||||
// {
|
||||
// "id": "$$secret_postgres_password_hex32",
|
||||
// "name": "POSTGRES_PASSWORD_hex32",
|
||||
// "label": "PostgreSQL Password hex32",
|
||||
// "defaultValue": "$$generate_hex(32)",
|
||||
// "description": "",
|
||||
// },
|
||||
// {
|
||||
// "id": "$$config_postgres_something_hex32",
|
||||
// "name": "POSTGRES_SOMETHING_HEX32",
|
||||
// "label": "PostgreSQL Something hex32",
|
||||
// "defaultValue": "$$generate_hex(32)",
|
||||
// "description": "",
|
||||
// },
|
||||
// {
|
||||
// "id": "$$config_postgres_db",
|
||||
// "main": "$$id-postgresql",
|
||||
// "name": "POSTGRES_DB",
|
||||
// "label": "PostgreSQL Database",
|
||||
// "defaultValue": "weblate",
|
||||
// "description": "",
|
||||
// },
|
||||
// {
|
||||
// "id": "$$secret_postgres_secret",
|
||||
// "name": "POSTGRES_SECRET",
|
||||
// "label": "PostgreSQL Secret",
|
||||
// "defaultValue": "",
|
||||
// "description": "",
|
||||
// },
|
||||
// ]
|
||||
// })
|
||||
// }
|
||||
return templates
|
||||
const templatePath = isDev ? './templates.json' : '/app/templates.json';
|
||||
const open = await fs.open(templatePath, 'r');
|
||||
try {
|
||||
let data = await open.readFile({ encoding: 'utf-8' });
|
||||
let jsonData = JSON.parse(data);
|
||||
if (isARM()) {
|
||||
jsonData = jsonData.filter((d) => d.arch !== 'amd64');
|
||||
}
|
||||
return jsonData;
|
||||
} catch (error) {
|
||||
return [];
|
||||
} finally {
|
||||
await open?.close();
|
||||
}
|
||||
}
|
||||
const compareSemanticVersions = (a: string, b: string) => {
|
||||
const a1 = a.split('.');
|
||||
const b1 = b.split('.');
|
||||
const len = Math.min(a1.length, b1.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const a2 = +a1[i] || 0;
|
||||
const b2 = +b1[i] || 0;
|
||||
if (a2 !== b2) {
|
||||
return a2 > b2 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return b1.length - a1.length;
|
||||
const a1 = a.split('.');
|
||||
const b1 = b.split('.');
|
||||
const len = Math.min(a1.length, b1.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const a2 = +a1[i] || 0;
|
||||
const b2 = +b1[i] || 0;
|
||||
if (a2 !== b2) {
|
||||
return a2 > b2 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return b1.length - a1.length;
|
||||
};
|
||||
export async function getTags(type: string) {
|
||||
if (type) {
|
||||
let tags: any = [];
|
||||
if (isDev) {
|
||||
tags = JSON.parse(await (await fs.readFile('./tags.json')).toString())
|
||||
} else {
|
||||
tags = JSON.parse(await (await fs.readFile('/app/tags.json')).toString())
|
||||
}
|
||||
tags = tags.find((tag: any) => tag.name.includes(type))
|
||||
tags.tags = tags.tags.sort(compareSemanticVersions).reverse();
|
||||
return tags
|
||||
}
|
||||
return []
|
||||
try {
|
||||
if (type) {
|
||||
const tagsPath = isDev ? './tags.json' : '/app/tags.json';
|
||||
const data = await fs.readFile(tagsPath, 'utf8');
|
||||
let tags = JSON.parse(data);
|
||||
if (tags) {
|
||||
tags = tags.find((tag: any) => tag.name.includes(type));
|
||||
tags.tags = tags.tags.sort(compareSemanticVersions).reverse();
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import { prisma } from '../common';
|
||||
import { decrypt, prisma } from '../common';
|
||||
|
||||
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||
@@ -22,4 +22,18 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.taiga.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.service.delete({ where: { id } });
|
||||
}
|
||||
export async function verifyAndDecryptServiceSecrets(id: string) {
|
||||
const secrets = await prisma.serviceSecret.findMany({ where: { serviceId: id } })
|
||||
let decryptedSecrets = secrets.map(secret => {
|
||||
const { name, value } = secret
|
||||
if (value) {
|
||||
let rawValue = decrypt(value)
|
||||
rawValue = rawValue.replaceAll(/\$/gi, '$$$')
|
||||
return { name, value: rawValue }
|
||||
}
|
||||
return { name, value }
|
||||
|
||||
})
|
||||
return decryptedSecrets
|
||||
}
|
||||
@@ -2,11 +2,12 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
import path from 'path';
|
||||
import { asyncSleep, ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, errorHandler, executeDockerCmd, getServiceFromDB, isARM, makeLabelForServices, persistentVolumes, prisma, stopTcpHttpProxy } from '../common';
|
||||
import { asyncSleep, ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, errorHandler, executeCommand, getServiceFromDB, isARM, makeLabelForServices, persistentVolumes, prisma, stopTcpHttpProxy } from '../common';
|
||||
import { parseAndFindServiceTemplates } from '../../routes/api/v1/services/handlers';
|
||||
|
||||
import { ServiceStartStop } from '../../routes/api/v1/services/types';
|
||||
import { OnlyId } from '../../types';
|
||||
import { verifyAndDecryptServiceSecrets } from './common';
|
||||
|
||||
export async function stopService(request: FastifyRequest<ServiceStartStop>) {
|
||||
try {
|
||||
@@ -14,14 +15,19 @@ export async function stopService(request: FastifyRequest<ServiceStartStop>) {
|
||||
const teamId = request.user.teamId;
|
||||
const { destinationDockerId } = await getServiceFromDB({ id, teamId });
|
||||
if (destinationDockerId) {
|
||||
await executeDockerCmd({
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0`
|
||||
})
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}`
|
||||
})
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
|
||||
}
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
throw { status: 500, message: 'Could not stop containers.' }
|
||||
@@ -34,7 +40,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
||||
const { id } = request.params;
|
||||
const teamId = request.user.teamId;
|
||||
const service = await getServiceFromDB({ id, teamId });
|
||||
const arm = isARM(service.arch)
|
||||
const arm = isARM();
|
||||
const { type, destinationDockerId, destinationDocker, persistentStorage, exposePort } =
|
||||
service;
|
||||
|
||||
@@ -44,47 +50,38 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
||||
const config = {};
|
||||
for (const s in template.services) {
|
||||
let newEnvironments = []
|
||||
if (arm) {
|
||||
if (template.services[s]?.environmentArm?.length > 0) {
|
||||
for (const environment of template.services[s].environmentArm) {
|
||||
let [env, ...value] = environment.split("=");
|
||||
value = value.join("=")
|
||||
if (!value.startsWith('$$secret') && value !== '') {
|
||||
newEnvironments.push(`${env}=${value}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (template.services[s]?.environment?.length > 0) {
|
||||
for (const environment of template.services[s].environment) {
|
||||
let [env, ...value] = environment.split("=");
|
||||
value = value.join("=")
|
||||
if (!value.startsWith('$$secret') && value !== '') {
|
||||
newEnvironments.push(`${env}=${value}`)
|
||||
}
|
||||
if (template.services[s]?.environment?.length > 0) {
|
||||
for (const environment of template.services[s].environment) {
|
||||
let [env, ...value] = environment.split("=");
|
||||
value = value.join("=")
|
||||
if (!value.startsWith('$$secret') && value !== '') {
|
||||
newEnvironments.push(`${env}=${value}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const secrets = await prisma.serviceSecret.findMany({ where: { serviceId: id } })
|
||||
const secrets = await verifyAndDecryptServiceSecrets(id)
|
||||
for (const secret of secrets) {
|
||||
const { name, value } = secret
|
||||
if (value) {
|
||||
const foundEnv = !!template.services[s].environment?.find(env => env.startsWith(`${name}=`))
|
||||
const foundNewEnv = !!newEnvironments?.find(env => env.startsWith(`${name}=`))
|
||||
if (foundEnv && !foundNewEnv) {
|
||||
newEnvironments.push(`${name}=${decrypt(value)}`)
|
||||
newEnvironments.push(`${name}=${value}`)
|
||||
}
|
||||
if (!foundEnv && !foundNewEnv && s === id) {
|
||||
newEnvironments.push(`${name}=${value}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
const customVolumes = await prisma.servicePersistentStorage.findMany({ where: { serviceId: id } })
|
||||
let volumes = new Set()
|
||||
if (arm) {
|
||||
template.services[s]?.volumesArm && template.services[s].volumesArm.length > 0 && template.services[s].volumesArm.forEach(v => volumes.add(v))
|
||||
if (arm && template.services[s]?.volumesArm?.length > 0) {
|
||||
template.services[s].volumesArm.forEach(v => volumes.add(v))
|
||||
} else {
|
||||
template.services[s]?.volumes && template.services[s].volumes.length > 0 && template.services[s].volumes.forEach(v => volumes.add(v))
|
||||
if (template.services[s]?.volumes?.length > 0) {
|
||||
template.services[s].volumes.forEach(v => volumes.add(v))
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround: old plausible analytics service wrong volume id name
|
||||
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics?.id) {
|
||||
let temp = Array.from(volumes)
|
||||
@@ -103,15 +100,34 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ports = []
|
||||
if (template.services[s].proxy?.length > 0) {
|
||||
for (const proxy of template.services[s].proxy) {
|
||||
if (proxy.hostPort) {
|
||||
ports.push(`${proxy.hostPort}:${proxy.port}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (template.services[s].ports?.length === 1) {
|
||||
for (const port of template.services[s].ports) {
|
||||
if (exposePort) {
|
||||
ports.push(`${exposePort}:${port}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let image = template.services[s].image
|
||||
if (arm && template.services[s].imageArm) {
|
||||
image = template.services[s].imageArm
|
||||
}
|
||||
config[s] = {
|
||||
container_name: s,
|
||||
build: template.services[s].build || undefined,
|
||||
command: template.services[s].command,
|
||||
entrypoint: template.services[s]?.entrypoint,
|
||||
image: arm ? template.services[s].imageArm : template.services[s].image,
|
||||
image,
|
||||
expose: template.services[s].ports,
|
||||
...(exposePort ? { ports: [`${exposePort}:${exposePort}`] } : {}),
|
||||
ports: ports.length > 0 ? ports : undefined,
|
||||
volumes: Array.from(volumes),
|
||||
environment: newEnvironments,
|
||||
depends_on: template.services[s]?.depends_on,
|
||||
@@ -121,7 +137,6 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
||||
labels: makeLabelForServices(type),
|
||||
...defaultComposeConfiguration(network),
|
||||
}
|
||||
|
||||
// Generate files for builds
|
||||
if (template.services[s]?.files?.length > 0) {
|
||||
if (!config[s].build) {
|
||||
@@ -161,21 +176,37 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
||||
// Workaround: Stop old minio proxies
|
||||
if (service.type === 'minio') {
|
||||
try {
|
||||
await executeDockerCmd({
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command:
|
||||
`docker container ls -a --filter 'name=${id}-' --format {{.ID}}|xargs -r -n 1 docker container stop -t 0`
|
||||
`docker container ls -a --filter 'name=${id}-' --format {{.ID}}`
|
||||
});
|
||||
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
try {
|
||||
await executeDockerCmd({
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command:
|
||||
`docker container ls -a --filter 'name=${id}-' --format {{.ID}}|xargs -r -n 1 docker container rm -f`
|
||||
`docker container ls -a --filter 'name=${id}-' --format {{.ID}}`
|
||||
});
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
|
||||
}
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
@@ -185,16 +216,16 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
||||
async function startServiceContainers(fastify, id, teamId, dockerId, composeFileDestination) {
|
||||
try {
|
||||
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Pulling images...' })
|
||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} pull` })
|
||||
await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} pull` })
|
||||
} catch (error) { }
|
||||
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Building images...' })
|
||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} build --no-cache` })
|
||||
await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} build --no-cache` })
|
||||
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Creating containers...' })
|
||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} create` })
|
||||
await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} create` })
|
||||
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Starting containers...' })
|
||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} start` })
|
||||
await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} start` })
|
||||
await asyncSleep(1000);
|
||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} up -d` })
|
||||
await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} up -d` })
|
||||
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 0 })
|
||||
}
|
||||
export async function migrateAppwriteDB(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||
@@ -206,7 +237,7 @@ export async function migrateAppwriteDB(request: FastifyRequest<OnlyId>, reply:
|
||||
destinationDocker,
|
||||
} = await getServiceFromDB({ id, teamId });
|
||||
if (destinationDockerId) {
|
||||
await executeDockerCmd({
|
||||
await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker exec ${id} migrate`
|
||||
})
|
||||
|
||||
@@ -624,7 +624,7 @@ export const glitchTip = [{
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'emailSmtpUseSsl',
|
||||
name: 'emailSmtpUseTls',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
import fp from 'fastify-plugin'
|
||||
import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt'
|
||||
import fp from 'fastify-plugin';
|
||||
import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt';
|
||||
|
||||
declare module "@fastify/jwt" {
|
||||
interface FastifyJWT {
|
||||
user: {
|
||||
userId: string,
|
||||
teamId: string,
|
||||
permission: string,
|
||||
isAdmin: boolean
|
||||
}
|
||||
}
|
||||
declare module '@fastify/jwt' {
|
||||
interface FastifyJWT {
|
||||
user: {
|
||||
userId: string;
|
||||
teamId: string;
|
||||
permission: string;
|
||||
isAdmin: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default fp<FastifyJWTOptions>(async (fastify, opts) => {
|
||||
fastify.register(fastifyJwt, {
|
||||
secret: fastify.config.COOLIFY_SECRET_KEY
|
||||
})
|
||||
let secretKey = fastify.config.COOLIFY_SECRET_KEY_BETTER;
|
||||
if (!secretKey) {
|
||||
secretKey = fastify.config.COOLIFY_SECRET_KEY;
|
||||
}
|
||||
fastify.register(fastifyJwt, {
|
||||
secret: secretKey
|
||||
});
|
||||
|
||||
fastify.decorate("authenticate", async function (request, reply) {
|
||||
try {
|
||||
await request.jwtVerify()
|
||||
} catch (err) {
|
||||
reply.send(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
fastify.decorate('authenticate', async function (request, reply) {
|
||||
try {
|
||||
await request.jwtVerify();
|
||||
} catch (err) {
|
||||
reply.send(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
declare module 'fastify' {
|
||||
export interface FastifyInstance {
|
||||
authenticate(): Promise<void>
|
||||
}
|
||||
export interface FastifyInstance {
|
||||
authenticate(): Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { OnlyId } from '../../../../types';
|
||||
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, getUsageByContainer, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
|
||||
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getDockerImages, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, getUsageByContainer, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRegistry, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
|
||||
|
||||
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
||||
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartApplication, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.addHook('onRequest', async (request) => {
|
||||
@@ -21,7 +21,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
|
||||
|
||||
fastify.post<OnlyId>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
|
||||
fastify.post<RestartApplication>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
|
||||
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
||||
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
|
||||
|
||||
@@ -45,7 +45,6 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get<RestartPreviewApplication>('/:id/previews/:pullmergeRequestId/status', async (request) => await getPreviewStatus(request));
|
||||
fastify.post<RestartPreviewApplication>('/:id/previews/:pullmergeRequestId/restart', async (request, reply) => await restartPreview(request, reply));
|
||||
|
||||
// fastify.get<GetApplicationLogs>('/:id/logs', async (request) => await getApplicationLogs(request));
|
||||
fastify.get<GetApplicationLogs>('/:id/logs/:containerId', async (request) => await getApplicationLogs(request));
|
||||
fastify.get<GetBuilds>('/:id/logs/build', async (request) => await getBuilds(request));
|
||||
fastify.get<GetBuildIdLogs>('/:id/logs/build/:buildId', async (request) => await getBuildIdLogs(request));
|
||||
@@ -53,6 +52,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get('/:id/usage', async (request) => await getUsage(request))
|
||||
fastify.get('/:id/usage/:containerId', async (request) => await getUsageByContainer(request))
|
||||
|
||||
fastify.get('/:id/images', async (request) => await getDockerImages(request))
|
||||
|
||||
fastify.post<DeployApplication>('/:id/deploy', async (request) => await deployApplication(request))
|
||||
fastify.post<CancelDeployment>('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply));
|
||||
|
||||
@@ -64,6 +65,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request));
|
||||
fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply));
|
||||
|
||||
fastify.post('/:id/configuration/registry', async (request, reply) => await saveRegistry(request, reply));
|
||||
|
||||
fastify.post('/:id/configuration/database', async (request, reply) => await saveConnectedDatabase(request, reply));
|
||||
|
||||
fastify.get<OnlyId>('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request));
|
||||
|
||||
@@ -1,143 +1,174 @@
|
||||
import type { OnlyId } from "../../../../types";
|
||||
import type { OnlyId } from '../../../../types';
|
||||
|
||||
export interface SaveApplication extends OnlyId {
|
||||
Body: {
|
||||
name: string,
|
||||
buildPack: string,
|
||||
fqdn: string,
|
||||
port: number,
|
||||
exposePort: number,
|
||||
installCommand: string,
|
||||
buildCommand: string,
|
||||
startCommand: string,
|
||||
baseDirectory: string,
|
||||
publishDirectory: string,
|
||||
pythonWSGI: string,
|
||||
pythonModule: string,
|
||||
pythonVariable: string,
|
||||
dockerFileLocation: string,
|
||||
denoMainFile: string,
|
||||
denoOptions: string,
|
||||
baseImage: string,
|
||||
baseBuildImage: string,
|
||||
deploymentType: string,
|
||||
baseDatabaseBranch: string,
|
||||
dockerComposeFile: string,
|
||||
dockerComposeFileLocation: string,
|
||||
dockerComposeConfiguration: string
|
||||
}
|
||||
Body: {
|
||||
name: string;
|
||||
buildPack: string;
|
||||
fqdn: string;
|
||||
port: number;
|
||||
exposePort: number;
|
||||
installCommand: string;
|
||||
buildCommand: string;
|
||||
startCommand: string;
|
||||
baseDirectory: string;
|
||||
publishDirectory: string;
|
||||
pythonWSGI: string;
|
||||
pythonModule: string;
|
||||
pythonVariable: string;
|
||||
dockerFileLocation: string;
|
||||
denoMainFile: string;
|
||||
denoOptions: string;
|
||||
baseImage: string;
|
||||
gitCommitHash: string;
|
||||
baseBuildImage: string;
|
||||
deploymentType: string;
|
||||
baseDatabaseBranch: string;
|
||||
dockerComposeFile: string;
|
||||
dockerComposeFileLocation: string;
|
||||
dockerComposeConfiguration: string;
|
||||
simpleDockerfile: string;
|
||||
dockerRegistryImageName: string;
|
||||
basicAuthPw: string;
|
||||
basicAuthUser: string;
|
||||
};
|
||||
}
|
||||
export interface SaveApplicationSettings extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean, isCustomSSL: boolean };
|
||||
Querystring: { domain: string };
|
||||
Body: {
|
||||
debug: boolean;
|
||||
previews: boolean;
|
||||
dualCerts: boolean;
|
||||
autodeploy: boolean;
|
||||
branch: string;
|
||||
projectId: number;
|
||||
isBot: boolean;
|
||||
isDBBranching: boolean;
|
||||
isCustomSSL: boolean;
|
||||
isHttp2: boolean;
|
||||
basicAuth: boolean;
|
||||
};
|
||||
}
|
||||
export interface DeleteApplication extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Body: { force: boolean }
|
||||
Querystring: { domain: string };
|
||||
Body: { force: boolean };
|
||||
}
|
||||
export interface CheckDomain extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Querystring: { domain: string };
|
||||
}
|
||||
export interface CheckDNS extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Body: {
|
||||
exposePort: number,
|
||||
fqdn: string,
|
||||
forceSave: boolean,
|
||||
dualCerts: boolean
|
||||
}
|
||||
Querystring: { domain: string };
|
||||
Body: {
|
||||
exposePort: number;
|
||||
fqdn: string;
|
||||
forceSave: boolean;
|
||||
dualCerts: boolean;
|
||||
};
|
||||
}
|
||||
export interface DeployApplication {
|
||||
Querystring: { domain: string }
|
||||
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
|
||||
Querystring: { domain: string };
|
||||
Body: { pullmergeRequestId: string | null; branch: string; forceRebuild?: boolean };
|
||||
}
|
||||
export interface GetImages {
|
||||
Body: { buildPack: string, deploymentType: string }
|
||||
Body: { buildPack: string; deploymentType: string };
|
||||
}
|
||||
export interface SaveApplicationSource extends OnlyId {
|
||||
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string }
|
||||
Body: {
|
||||
gitSourceId?: string | null;
|
||||
forPublic?: boolean;
|
||||
type?: string;
|
||||
simpleDockerfile?: string;
|
||||
};
|
||||
}
|
||||
export interface CheckRepository extends OnlyId {
|
||||
Querystring: { repository: string, branch: string }
|
||||
Querystring: { repository: string; branch: string };
|
||||
}
|
||||
export interface SaveDestination extends OnlyId {
|
||||
Body: { destinationId: string }
|
||||
Body: { destinationId: string };
|
||||
}
|
||||
export interface SaveSecret extends OnlyId {
|
||||
Body: {
|
||||
name: string,
|
||||
value: string,
|
||||
isBuildSecret: boolean,
|
||||
previewSecret: boolean,
|
||||
isNew: boolean
|
||||
}
|
||||
Body: {
|
||||
name: string;
|
||||
value: string;
|
||||
isBuildSecret: boolean;
|
||||
previewSecret: boolean;
|
||||
isNew: boolean;
|
||||
};
|
||||
}
|
||||
export interface DeleteSecret extends OnlyId {
|
||||
Body: { name: string }
|
||||
Body: { name: string };
|
||||
}
|
||||
export interface SaveStorage extends OnlyId {
|
||||
Body: {
|
||||
path: string,
|
||||
newStorage: boolean,
|
||||
storageId: string
|
||||
}
|
||||
Body: {
|
||||
hostPath?: string;
|
||||
path: string;
|
||||
newStorage: boolean;
|
||||
storageId: string;
|
||||
};
|
||||
}
|
||||
export interface DeleteStorage extends OnlyId {
|
||||
Body: {
|
||||
path: string,
|
||||
}
|
||||
Body: {
|
||||
path: string;
|
||||
};
|
||||
}
|
||||
export interface GetApplicationLogs {
|
||||
Params: {
|
||||
id: string,
|
||||
containerId: string
|
||||
}
|
||||
Querystring: {
|
||||
since: number,
|
||||
}
|
||||
Params: {
|
||||
id: string;
|
||||
containerId: string;
|
||||
};
|
||||
Querystring: {
|
||||
since: number;
|
||||
};
|
||||
}
|
||||
export interface GetBuilds extends OnlyId {
|
||||
Querystring: {
|
||||
buildId: string
|
||||
skip: number,
|
||||
}
|
||||
Querystring: {
|
||||
buildId: string;
|
||||
skip: number;
|
||||
};
|
||||
}
|
||||
export interface GetBuildIdLogs {
|
||||
Params: {
|
||||
id: string,
|
||||
buildId: string
|
||||
},
|
||||
Querystring: {
|
||||
sequence: number
|
||||
}
|
||||
Params: {
|
||||
id: string;
|
||||
buildId: string;
|
||||
};
|
||||
Querystring: {
|
||||
sequence: number;
|
||||
};
|
||||
}
|
||||
export interface SaveDeployKey extends OnlyId {
|
||||
Body: {
|
||||
deployKeyId: number
|
||||
}
|
||||
Body: {
|
||||
deployKeyId: number;
|
||||
};
|
||||
}
|
||||
export interface CancelDeployment {
|
||||
Body: {
|
||||
buildId: string,
|
||||
applicationId: string
|
||||
}
|
||||
Body: {
|
||||
buildId: string;
|
||||
applicationId: string;
|
||||
};
|
||||
}
|
||||
export interface DeployApplication extends OnlyId {
|
||||
Body: {
|
||||
pullmergeRequestId: string | null,
|
||||
branch: string,
|
||||
forceRebuild?: boolean
|
||||
}
|
||||
Body: {
|
||||
pullmergeRequestId: string | null;
|
||||
branch: string;
|
||||
forceRebuild?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StopPreviewApplication extends OnlyId {
|
||||
Body: {
|
||||
pullmergeRequestId: string | null,
|
||||
}
|
||||
Body: {
|
||||
pullmergeRequestId: string | null;
|
||||
};
|
||||
}
|
||||
export interface RestartPreviewApplication {
|
||||
Params: {
|
||||
id: string,
|
||||
pullmergeRequestId: string | null,
|
||||
}
|
||||
}
|
||||
Params: {
|
||||
id: string;
|
||||
pullmergeRequestId: string | null;
|
||||
};
|
||||
}
|
||||
export interface RestartApplication {
|
||||
Params: {
|
||||
id: string;
|
||||
};
|
||||
Body: {
|
||||
imageId: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { errorHandler, listSettings, version } from '../../../../lib/common';
|
||||
import { errorHandler, isARM, listSettings, version } from '../../../../lib/common';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get('/', async (request) => {
|
||||
const teamId = request.user?.teamId;
|
||||
const settings = await listSettings()
|
||||
try {
|
||||
return {
|
||||
ipv4: teamId ? settings.ipv4 : 'nope',
|
||||
ipv6: teamId ? settings.ipv6 : 'nope',
|
||||
version,
|
||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||
isRegistrationEnabled: settings.isRegistrationEnabled,
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
});
|
||||
|
||||
fastify.addHook('onRequest', async (request) => {
|
||||
try {
|
||||
await request.jwtVerify();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
fastify.get('/', async (request) => {
|
||||
const teamId = request.user?.teamId;
|
||||
const settings = await listSettings();
|
||||
try {
|
||||
return {
|
||||
ipv4: teamId ? settings.ipv4 : null,
|
||||
ipv6: teamId ? settings.ipv6 : null,
|
||||
version,
|
||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||
isRegistrationEnabled: settings.isRegistrationEnabled,
|
||||
isARM: isARM()
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
||||
import { backupDatabase, cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
|
||||
@@ -39,6 +39,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request));
|
||||
fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request));
|
||||
fastify.post<OnlyId>('/:id/backup', async (request, reply) => await backupDatabase(request, reply));
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface SaveDatabaseType extends OnlyId {
|
||||
Body: { type: string }
|
||||
}
|
||||
export interface DeleteDatabase extends OnlyId {
|
||||
Body: { force: string }
|
||||
Body: { }
|
||||
}
|
||||
export interface SaveVersion extends OnlyId {
|
||||
Body: {
|
||||
|
||||
@@ -1,272 +1,384 @@
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import sshConfig from 'ssh-config'
|
||||
import fs from 'fs/promises'
|
||||
import os from 'os';
|
||||
|
||||
import { asyncExecShell, createRemoteEngineConfiguration, decrypt, errorHandler, executeDockerCmd, executeSSHCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
||||
import {
|
||||
errorHandler,
|
||||
executeCommand,
|
||||
listSettings,
|
||||
prisma,
|
||||
startTraefikProxy,
|
||||
stopTraefikProxy
|
||||
} from '../../../../lib/common';
|
||||
import { checkContainer } from '../../../../lib/docker';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
||||
import type {
|
||||
CheckDestination,
|
||||
ListDestinations,
|
||||
NewDestination,
|
||||
Proxy,
|
||||
SaveDestinationSettings
|
||||
} from './types';
|
||||
import { removeService } from '../../../../lib/services/common';
|
||||
|
||||
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { onlyVerified = false } = request.query
|
||||
let destinations = []
|
||||
if (teamId === '0') {
|
||||
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
|
||||
} else {
|
||||
destinations = await prisma.destinationDocker.findMany({
|
||||
where: { teams: { some: { id: teamId } } },
|
||||
include: { teams: true }
|
||||
});
|
||||
}
|
||||
if (onlyVerified) {
|
||||
destinations = destinations.filter(destination => destination.engine || (destination.remoteEngine && destination.remoteVerified))
|
||||
}
|
||||
return {
|
||||
destinations
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { onlyVerified = false } = request.query;
|
||||
let destinations = [];
|
||||
if (teamId === '0') {
|
||||
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
|
||||
} else {
|
||||
destinations = await prisma.destinationDocker.findMany({
|
||||
where: { teams: { some: { id: teamId } } },
|
||||
include: { teams: true }
|
||||
});
|
||||
}
|
||||
if (onlyVerified) {
|
||||
destinations = destinations.filter(
|
||||
(destination) =>
|
||||
destination.engine || (destination.remoteEngine && destination.remoteVerified)
|
||||
);
|
||||
}
|
||||
return {
|
||||
destinations
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function checkDestination(request: FastifyRequest<CheckDestination>) {
|
||||
try {
|
||||
const { network } = request.body;
|
||||
const found = await prisma.destinationDocker.findFirst({ where: { network } });
|
||||
if (found) {
|
||||
throw {
|
||||
message: `Network already exists: ${network}`
|
||||
};
|
||||
}
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { network } = request.body;
|
||||
const found = await prisma.destinationDocker.findFirst({ where: { network } });
|
||||
if (found) {
|
||||
throw {
|
||||
message: `Network already exists: ${network}`
|
||||
};
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function getDestination(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const teamId = request.user?.teamId;
|
||||
const destination = await prisma.destinationDocker.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { sshKey: true, application: true, service: true, database: true }
|
||||
});
|
||||
if (!destination && id !== 'new') {
|
||||
throw { status: 404, message: `Destination not found.` };
|
||||
}
|
||||
const settings = await listSettings();
|
||||
const payload = {
|
||||
destination,
|
||||
settings
|
||||
};
|
||||
return {
|
||||
...payload
|
||||
};
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const teamId = request.user?.teamId;
|
||||
const destination = await prisma.destinationDocker.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { sshKey: true, application: true, service: true, database: true }
|
||||
});
|
||||
if (!destination && id !== 'new') {
|
||||
throw { status: 404, message: `Destination not found.` };
|
||||
}
|
||||
const settings = await listSettings();
|
||||
const payload = {
|
||||
destination,
|
||||
settings
|
||||
};
|
||||
return {
|
||||
...payload
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params;
|
||||
|
||||
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
||||
if (id === 'new') {
|
||||
if (engine) {
|
||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
|
||||
if (stdout === '') {
|
||||
await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network create --attachable ${network}`);
|
||||
}
|
||||
await prisma.destinationDocker.create({
|
||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
||||
});
|
||||
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
|
||||
const destination = destinations.find((destination) => destination.network === network);
|
||||
if (destinations.length > 0) {
|
||||
const proxyConfigured = destinations.find(
|
||||
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||
);
|
||||
if (proxyConfigured) {
|
||||
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
||||
}
|
||||
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
||||
}
|
||||
if (isCoolifyProxyUsed) {
|
||||
await startTraefikProxy(destination.id);
|
||||
}
|
||||
return reply.code(201).send({ id: destination.id });
|
||||
} else {
|
||||
const destination = await prisma.destinationDocker.create({
|
||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort }
|
||||
});
|
||||
return reply.code(201).send({ id: destination.id })
|
||||
}
|
||||
} else {
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
||||
return reply.code(201).send();
|
||||
}
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } =
|
||||
request.body;
|
||||
if (id === 'new') {
|
||||
if (engine) {
|
||||
const { stdout } = await await executeCommand({
|
||||
command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'`
|
||||
});
|
||||
if (stdout === '') {
|
||||
await await executeCommand({ command: `docker network create --attachable ${network}` });
|
||||
}
|
||||
await prisma.destinationDocker.create({
|
||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
||||
});
|
||||
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
|
||||
const destination = destinations.find((destination) => destination.network === network);
|
||||
if (destinations.length > 0) {
|
||||
const proxyConfigured = destinations.find(
|
||||
(destination) =>
|
||||
destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||
);
|
||||
if (proxyConfigured) {
|
||||
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
||||
}
|
||||
await prisma.destinationDocker.updateMany({
|
||||
where: { engine },
|
||||
data: { isCoolifyProxyUsed }
|
||||
});
|
||||
}
|
||||
if (isCoolifyProxyUsed) {
|
||||
await startTraefikProxy(destination.id);
|
||||
}
|
||||
return reply.code(201).send({ id: destination.id });
|
||||
} else {
|
||||
const destination = await prisma.destinationDocker.create({
|
||||
data: {
|
||||
name,
|
||||
teams: { connect: { id: teamId } },
|
||||
engine,
|
||||
network,
|
||||
isCoolifyProxyUsed,
|
||||
remoteEngine: true,
|
||||
remoteIpAddress,
|
||||
remoteUser,
|
||||
remotePort: Number(remotePort)
|
||||
}
|
||||
});
|
||||
return reply.code(201).send({ id: destination.id });
|
||||
}
|
||||
} else {
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
||||
return reply.code(201).send();
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function forceDeleteDestination(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const services = await prisma.service.findMany({ where: { destinationDockerId: id } });
|
||||
for (const service of services) {
|
||||
await removeService({ id: service.id });
|
||||
}
|
||||
const applications = await prisma.application.findMany({ where: { destinationDockerId: id } });
|
||||
for (const application of applications) {
|
||||
await prisma.applicationSettings.deleteMany({ where: { application: { id: application.id } } });
|
||||
await prisma.buildLog.deleteMany({ where: { applicationId: application.id } });
|
||||
await prisma.build.deleteMany({ where: { applicationId: application.id } });
|
||||
await prisma.secret.deleteMany({ where: { applicationId: application.id } });
|
||||
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: application.id } });
|
||||
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: application.id } });
|
||||
await prisma.previewApplication.deleteMany({ where: { applicationId: application.id } });
|
||||
}
|
||||
const databases = await prisma.database.findMany({ where: { destinationDockerId: id } });
|
||||
for (const database of databases) {
|
||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: database.id } });
|
||||
await prisma.databaseSecret.deleteMany({ where: { databaseId: database.id } });
|
||||
await prisma.database.delete({ where: { id: database.id } });
|
||||
}
|
||||
await prisma.destinationDocker.delete({ where: { id } });
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
|
||||
if (isCoolifyProxyUsed) {
|
||||
if (engine || remoteVerified) {
|
||||
const { stdout: found } = await executeDockerCmd({
|
||||
dockerId: id,
|
||||
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
|
||||
})
|
||||
if (found) {
|
||||
await executeDockerCmd({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
|
||||
await executeDockerCmd({ dockerId: id, command: `docker network rm ${network}` })
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.destinationDocker.delete({ where: { id } });
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const appFound = await prisma.application.findFirst({ where: { destinationDockerId: id } });
|
||||
const serviceFound = await prisma.service.findFirst({ where: { destinationDockerId: id } });
|
||||
const databaseFound = await prisma.database.findFirst({ where: { destinationDockerId: id } });
|
||||
if (appFound || serviceFound || databaseFound) {
|
||||
throw {
|
||||
message: `Destination is in use.<br>Remove all applications, services and databases using this destination first.`
|
||||
};
|
||||
}
|
||||
const { network, remoteVerified, engine, isCoolifyProxyUsed } =
|
||||
await prisma.destinationDocker.findUnique({ where: { id } });
|
||||
if (isCoolifyProxyUsed) {
|
||||
if (engine || remoteVerified) {
|
||||
const { stdout: found } = await executeCommand({
|
||||
dockerId: id,
|
||||
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
|
||||
});
|
||||
if (found) {
|
||||
await executeCommand({
|
||||
dockerId: id,
|
||||
command: `docker network disconnect ${network} coolify-proxy`
|
||||
});
|
||||
await executeCommand({ dockerId: id, command: `docker network rm ${network}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.destinationDocker.delete({ where: { id } });
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function saveDestinationSettings(request: FastifyRequest<SaveDestinationSettings>) {
|
||||
try {
|
||||
const { engine, isCoolifyProxyUsed } = request.body;
|
||||
await prisma.destinationDocker.updateMany({
|
||||
where: { engine },
|
||||
data: { isCoolifyProxyUsed }
|
||||
});
|
||||
try {
|
||||
const { engine, isCoolifyProxyUsed } = request.body;
|
||||
await prisma.destinationDocker.updateMany({
|
||||
where: { engine },
|
||||
data: { isCoolifyProxyUsed }
|
||||
});
|
||||
|
||||
return {
|
||||
status: 202
|
||||
}
|
||||
// return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
return {
|
||||
status: 202
|
||||
};
|
||||
// return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function startProxy(request: FastifyRequest<Proxy>) {
|
||||
const { id } = request.params
|
||||
try {
|
||||
await startTraefikProxy(id);
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
await stopTraefikProxy(id);
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await startTraefikProxy(id);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
await stopTraefikProxy(id);
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function stopProxy(request: FastifyRequest<Proxy>) {
|
||||
const { id } = request.params
|
||||
try {
|
||||
await stopTraefikProxy(id);
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await stopTraefikProxy(id);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function restartProxy(request: FastifyRequest<Proxy>) {
|
||||
const { id } = request.params
|
||||
try {
|
||||
await stopTraefikProxy(id);
|
||||
await startTraefikProxy(id);
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { isCoolifyProxyUsed: true }
|
||||
});
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { isCoolifyProxyUsed: false }
|
||||
});
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await stopTraefikProxy(id);
|
||||
await startTraefikProxy(id);
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { isCoolifyProxyUsed: true }
|
||||
});
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { isCoolifyProxyUsed: false }
|
||||
});
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function assignSSHKey(request: FastifyRequest) {
|
||||
try {
|
||||
const { id: sshKeyId } = request.body;
|
||||
const { id } = request.params;
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { sshKey: { connect: { id: sshKeyId } } } })
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id: sshKeyId } = request.body;
|
||||
const { id } = request.params;
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { sshKey: { connect: { id: sshKeyId } } }
|
||||
});
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function verifyRemoteDockerEngineFn(id: string) {
|
||||
await createRemoteEngineConfiguration(id);
|
||||
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
||||
const host = `ssh://${remoteIpAddress}-remote`
|
||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
|
||||
if (!stdout) {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
|
||||
}
|
||||
const { stdout: coolifyNetwork } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"`);
|
||||
if (!coolifyNetwork) {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`);
|
||||
}
|
||||
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
||||
try {
|
||||
const { stdout: daemonJson } = await executeSSHCmd({ dockerId: id, command: `cat /etc/docker/daemon.json` });
|
||||
let daemonJsonParsed = JSON.parse(daemonJson);
|
||||
let isUpdated = false;
|
||||
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
||||
isUpdated = true;
|
||||
daemonJsonParsed['live-restore'] = true
|
||||
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst(
|
||||
{ where: { id } }
|
||||
);
|
||||
const daemonJson = `daemon-${id}.json`;
|
||||
try {
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
command: `docker network inspect ${network}`,
|
||||
dockerId: id
|
||||
});
|
||||
} catch (error) {
|
||||
await executeCommand({
|
||||
command: `docker network create --attachable ${network}`,
|
||||
dockerId: id
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
if (!daemonJsonParsed?.features?.buildkit) {
|
||||
isUpdated = true;
|
||||
daemonJsonParsed.features = {
|
||||
buildkit: true
|
||||
}
|
||||
}
|
||||
if (isUpdated) {
|
||||
await executeSSHCmd({ dockerId: id, command: `echo '${JSON.stringify(daemonJsonParsed)}' > /etc/docker/daemon.json` });
|
||||
await executeSSHCmd({ dockerId: id, command: `systemctl restart docker` });
|
||||
}
|
||||
} catch (error) {
|
||||
const daemonJsonParsed = {
|
||||
"live-restore": true,
|
||||
"features": {
|
||||
"buildkit": true
|
||||
}
|
||||
}
|
||||
await executeSSHCmd({ dockerId: id, command: `echo '${JSON.stringify(daemonJsonParsed)}' > /etc/docker/daemon.json` });
|
||||
await executeSSHCmd({ dockerId: id, command: `systemctl restart docker` });
|
||||
} finally {
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
|
||||
}
|
||||
try {
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
command: `docker network inspect coolify-infra`,
|
||||
dockerId: id
|
||||
});
|
||||
} catch (error) {
|
||||
await executeCommand({
|
||||
command: `docker network create --attachable coolify-infra`,
|
||||
dockerId: id
|
||||
});
|
||||
}
|
||||
|
||||
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
||||
let isUpdated = false;
|
||||
let daemonJsonParsed = {
|
||||
'live-restore': true,
|
||||
features: {
|
||||
buildkit: true
|
||||
}
|
||||
};
|
||||
try {
|
||||
const { stdout: daemonJson } = await executeCommand({
|
||||
sshCommand: true,
|
||||
dockerId: id,
|
||||
command: `cat /etc/docker/daemon.json`
|
||||
});
|
||||
daemonJsonParsed = JSON.parse(daemonJson);
|
||||
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
||||
isUpdated = true;
|
||||
daemonJsonParsed['live-restore'] = true;
|
||||
}
|
||||
if (!daemonJsonParsed?.features?.buildkit) {
|
||||
isUpdated = true;
|
||||
daemonJsonParsed.features = {
|
||||
buildkit: true
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
isUpdated = true;
|
||||
}
|
||||
try {
|
||||
if (isUpdated) {
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}`
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: id,
|
||||
command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json`
|
||||
});
|
||||
await executeCommand({ command: `rm /tmp/${daemonJson}` });
|
||||
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
|
||||
}
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } });
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw new Error('Error while verifying remote docker engine');
|
||||
}
|
||||
}
|
||||
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await verifyRemoteDockerEngineFn(id);
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } })
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
export async function verifyRemoteDockerEngine(
|
||||
request: FastifyRequest<OnlyId>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await verifyRemoteDockerEngineFn(id);
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } });
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const destination = await prisma.destinationDocker.findUnique({ where: { id } })
|
||||
const { found: isRunning } = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy', remove: true })
|
||||
return {
|
||||
isRunning
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const destination = await prisma.destinationDocker.findUnique({ where: { id } });
|
||||
const { found: isRunning } = await checkContainer({
|
||||
dockerId: destination.id,
|
||||
container: 'coolify-proxy',
|
||||
remove: true
|
||||
});
|
||||
return {
|
||||
isRunning
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { assignSSHKey, checkDestination, deleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
||||
import { assignSSHKey, checkDestination, deleteDestination, forceDeleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
||||
@@ -14,6 +14,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
|
||||
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
|
||||
fastify.delete<OnlyId>('/:id/force', async (request) => await forceDeleteDestination(request));
|
||||
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
|
||||
|
||||
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { compareVersions } from "compare-versions";
|
||||
import cuid from "cuid";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import cuid from 'cuid';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
import {
|
||||
asyncExecShell,
|
||||
asyncSleep,
|
||||
cleanupDockerStorage,
|
||||
errorHandler,
|
||||
@@ -13,24 +12,52 @@ import {
|
||||
prisma,
|
||||
uniqueName,
|
||||
version,
|
||||
} from "../../../lib/common";
|
||||
import { scheduler } from "../../../lib/scheduler";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import type { Login, Update } from ".";
|
||||
import type { GetCurrentUser } from "./types";
|
||||
executeCommand
|
||||
} from '../../../lib/common';
|
||||
import { scheduler } from '../../../lib/scheduler';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import type { Login, Update } from '.';
|
||||
import type { GetCurrentUser } from './types';
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
const saltRounds = 15;
|
||||
export async function hashPassword(password: string, saltRounds = 15): Promise<string> {
|
||||
return bcrypt.hash(password, saltRounds);
|
||||
}
|
||||
|
||||
export async function backup(request: FastifyRequest) {
|
||||
try {
|
||||
const { backupData } = request.params;
|
||||
let std = null;
|
||||
const [id, backupType, type, zipped, storage] = backupData.split(':');
|
||||
console.log(id, backupType, type, zipped, storage);
|
||||
const database = await prisma.database.findUnique({ where: { id } });
|
||||
if (database) {
|
||||
// await executeDockerCmd({
|
||||
// dockerId: database.destinationDockerId,
|
||||
// command: `docker pull coollabsio/backup:latest`,
|
||||
// })
|
||||
std = await executeCommand({
|
||||
dockerId: database.destinationDockerId,
|
||||
command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v coolify-local-backup:/app/backups -e CONTAINERS_TO_BACKUP="${backupData}" coollabsio/backup`
|
||||
});
|
||||
}
|
||||
if (std.stdout) {
|
||||
return std.stdout;
|
||||
}
|
||||
if (std.stderr) {
|
||||
return std.stderr;
|
||||
}
|
||||
return 'nope';
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function cleanupManually(request: FastifyRequest) {
|
||||
try {
|
||||
const { serverId } = request.body;
|
||||
const destination = await prisma.destinationDocker.findUnique({
|
||||
where: { id: serverId },
|
||||
where: { id: serverId }
|
||||
});
|
||||
await cleanupDockerStorage(destination.id, true, true);
|
||||
await cleanupDockerStorage(destination.id, true);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -38,21 +65,25 @@ export async function cleanupManually(request: FastifyRequest) {
|
||||
}
|
||||
export async function refreshTags() {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
try {
|
||||
if (isDev) {
|
||||
const tags = await fs.readFile('./devTags.json', 'utf8')
|
||||
await fs.writeFile('./tags.json', tags)
|
||||
let tags = await fs.readFile('./devTags.json', 'utf8');
|
||||
try {
|
||||
if (await fs.stat('./testTags.json')) {
|
||||
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
||||
if (testTags.length > 0) {
|
||||
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
await fs.writeFile('./tags.json', tags);
|
||||
} else {
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
||||
await fs.writeFile('/app/tags.json', tags)
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
|
||||
await fs.writeFile('/app/tags.json', tags);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw {
|
||||
status: 500,
|
||||
message: 'Could not fetch templates from get.coollabs.io'
|
||||
};
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -62,23 +93,26 @@ export async function refreshTags() {
|
||||
}
|
||||
export async function refreshTemplates() {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
try {
|
||||
if (isDev) {
|
||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8')
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)))
|
||||
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||
try {
|
||||
if (await fs.stat('./testTemplate.yaml')) {
|
||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||
}
|
||||
} catch (error) { }
|
||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
||||
} else {
|
||||
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
||||
const response = await got
|
||||
.get('https://get.coollabs.io/coolify/service-templates.yaml')
|
||||
.text();
|
||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw {
|
||||
status: 500,
|
||||
message: 'Could not fetch templates from get.coollabs.io'
|
||||
};
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -86,28 +120,29 @@ export async function refreshTemplates() {
|
||||
}
|
||||
export async function checkUpdate(request: FastifyRequest) {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
const isStaging =
|
||||
request.hostname === "staging.coolify.io" ||
|
||||
request.hostname === "arm.coolify.io";
|
||||
request.hostname === 'staging.coolify.io' || request.hostname === 'arm.coolify.io';
|
||||
const currentVersion = version;
|
||||
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
||||
searchParams: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
}).json()
|
||||
const { coolify } = await got
|
||||
.get('https://get.coollabs.io/versions.json', {
|
||||
searchParams: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
})
|
||||
.json();
|
||||
const latestVersion = coolify.main.version;
|
||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||
if (isStaging) {
|
||||
return {
|
||||
isUpdateAvailable: true,
|
||||
latestVersion: "next",
|
||||
latestVersion: 'next'
|
||||
};
|
||||
}
|
||||
return {
|
||||
isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1,
|
||||
latestVersion,
|
||||
latestVersion
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -119,14 +154,22 @@ export async function update(request: FastifyRequest<Update>) {
|
||||
try {
|
||||
if (!isDev) {
|
||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
||||
await asyncExecShell(`env | grep COOLIFY > .env`);
|
||||
await asyncExecShell(
|
||||
`sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
);
|
||||
await asyncExecShell(
|
||||
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
);
|
||||
let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
|
||||
try {
|
||||
await executeCommand({ command: `docker pull ${image}` });
|
||||
} catch (error) {
|
||||
image = `coollabsio/coolify:${latestVersion}`;
|
||||
await executeCommand({ command: `docker pull ${image}` });
|
||||
}
|
||||
|
||||
await executeCommand({ shell: true, command: `ls .env || env | grep "^COOLIFY" | sort > .env` });
|
||||
await executeCommand({
|
||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
});
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db ${image} /bin/sh -c "env | grep "^COOLIFY" | sort > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
});
|
||||
return {};
|
||||
} else {
|
||||
await asyncSleep(2000);
|
||||
@@ -139,12 +182,12 @@ export async function update(request: FastifyRequest<Update>) {
|
||||
export async function resetQueue(request: FastifyRequest<any>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
if (teamId === "0") {
|
||||
if (teamId === '0') {
|
||||
await prisma.build.updateMany({
|
||||
where: { status: { in: ["queued", "running"] } },
|
||||
data: { status: "canceled" },
|
||||
where: { status: { in: ['queued', 'running'] } },
|
||||
data: { status: 'canceled' }
|
||||
});
|
||||
scheduler.workers.get("deployApplication").postMessage("cancel");
|
||||
scheduler.workers.get('deployApplication').postMessage('cancel');
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -153,9 +196,9 @@ export async function resetQueue(request: FastifyRequest<any>) {
|
||||
export async function restartCoolify(request: FastifyRequest<any>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
if (teamId === "0") {
|
||||
if (teamId === '0') {
|
||||
if (!isDev) {
|
||||
asyncExecShell(`docker restart coolify`);
|
||||
await executeCommand({ command: `docker restart coolify` });
|
||||
return {};
|
||||
} else {
|
||||
return {};
|
||||
@@ -163,7 +206,7 @@ export async function restartCoolify(request: FastifyRequest<any>) {
|
||||
}
|
||||
throw {
|
||||
status: 500,
|
||||
message: "You are not authorized to restart Coolify.",
|
||||
message: 'You are not authorized to restart Coolify.'
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -175,43 +218,52 @@ export async function showDashboard(request: FastifyRequest) {
|
||||
const userId = request.user.userId;
|
||||
const teamId = request.user.teamId;
|
||||
let applications = await prisma.application.findMany({
|
||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true },
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true }
|
||||
});
|
||||
const databases = await prisma.database.findMany({
|
||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true },
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true }
|
||||
});
|
||||
const services = await prisma.service.findMany({
|
||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, teams: true },
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, teams: true }
|
||||
});
|
||||
const gitSources = await prisma.gitSource.findMany({
|
||||
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
||||
include: { teams: true },
|
||||
where: {
|
||||
OR: [
|
||||
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
{ isSystemWide: true }
|
||||
]
|
||||
},
|
||||
include: { teams: true }
|
||||
});
|
||||
const destinations = await prisma.destinationDocker.findMany({
|
||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
||||
include: { teams: true },
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { teams: true }
|
||||
});
|
||||
const settings = await listSettings();
|
||||
|
||||
let foundUnconfiguredApplication = false;
|
||||
for (const application of applications) {
|
||||
if (!application.buildPack || !application.destinationDockerId || !application.branch || (!application.settings?.isBot && !application?.fqdn) && application.buildPack !== "compose") {
|
||||
foundUnconfiguredApplication = true
|
||||
if (
|
||||
((!application.buildPack || !application.branch) && !application.simpleDockerfile) ||
|
||||
!application.destinationDockerId ||
|
||||
(!application.settings?.isBot && !application?.fqdn && application.buildPack !== 'compose')
|
||||
) {
|
||||
foundUnconfiguredApplication = true;
|
||||
}
|
||||
}
|
||||
let foundUnconfiguredService = false;
|
||||
for (const service of services) {
|
||||
if (!service.fqdn) {
|
||||
foundUnconfiguredService = true
|
||||
foundUnconfiguredService = true;
|
||||
}
|
||||
}
|
||||
let foundUnconfiguredDatabase = false;
|
||||
for (const database of databases) {
|
||||
if (!database.version) {
|
||||
foundUnconfiguredDatabase = true
|
||||
foundUnconfiguredDatabase = true;
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -223,101 +275,94 @@ export async function showDashboard(request: FastifyRequest) {
|
||||
services,
|
||||
gitSources,
|
||||
destinations,
|
||||
settings,
|
||||
settings
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(
|
||||
request: FastifyRequest<Login>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
export async function login(request: FastifyRequest<Login>, reply: FastifyReply) {
|
||||
if (request.user) {
|
||||
return reply.redirect("/dashboard");
|
||||
return reply.redirect('/dashboard');
|
||||
} else {
|
||||
const { email, password, isLogin } = request.body || {};
|
||||
if (!email || !password) {
|
||||
throw { status: 500, message: "Email and password are required." };
|
||||
throw { status: 500, message: 'Email and password are required.' };
|
||||
}
|
||||
const users = await prisma.user.count();
|
||||
const userFound = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
include: { teams: true, permission: true },
|
||||
rejectOnNotFound: false,
|
||||
rejectOnNotFound: false
|
||||
});
|
||||
if (!userFound && isLogin) {
|
||||
throw { status: 500, message: "User not found." };
|
||||
throw { status: 500, message: 'User not found.' };
|
||||
}
|
||||
const { isRegistrationEnabled, id } = await prisma.setting.findFirst();
|
||||
let uid = cuid();
|
||||
let permission = "read";
|
||||
let permission = 'read';
|
||||
let isAdmin = false;
|
||||
|
||||
if (users === 0) {
|
||||
await prisma.setting.update({
|
||||
where: { id },
|
||||
data: { isRegistrationEnabled: false },
|
||||
data: { isRegistrationEnabled: false }
|
||||
});
|
||||
uid = "0";
|
||||
uid = '0';
|
||||
}
|
||||
if (userFound) {
|
||||
if (userFound.type === "email") {
|
||||
if (userFound.password === "RESETME") {
|
||||
if (userFound.type === 'email') {
|
||||
if (userFound.password === 'RESETME') {
|
||||
const hashedPassword = await hashPassword(password);
|
||||
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
||||
if (userFound.id === "0") {
|
||||
if (userFound.id === '0') {
|
||||
await prisma.user.update({
|
||||
where: { email: userFound.email },
|
||||
data: { password: "RESETME" },
|
||||
data: { password: 'RESETME' }
|
||||
});
|
||||
} else {
|
||||
await prisma.user.update({
|
||||
where: { email: userFound.email },
|
||||
data: { password: "RESETTIMEOUT" },
|
||||
data: { password: 'RESETTIMEOUT' }
|
||||
});
|
||||
}
|
||||
|
||||
throw {
|
||||
status: 500,
|
||||
message:
|
||||
"Password reset link has expired. Please request a new one.",
|
||||
message: 'Password reset link has expired. Please request a new one.'
|
||||
};
|
||||
} else {
|
||||
await prisma.user.update({
|
||||
where: { email: userFound.email },
|
||||
data: { password: hashedPassword },
|
||||
data: { password: hashedPassword }
|
||||
});
|
||||
return {
|
||||
userId: userFound.id,
|
||||
teamId: userFound.id,
|
||||
permission: userFound.permission,
|
||||
isAdmin: true,
|
||||
isAdmin: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const passwordMatch = await bcrypt.compare(
|
||||
password,
|
||||
userFound.password
|
||||
);
|
||||
const passwordMatch = await bcrypt.compare(password, userFound.password);
|
||||
if (!passwordMatch) {
|
||||
throw {
|
||||
status: 500,
|
||||
message: "Wrong password or email address.",
|
||||
message: 'Wrong password or email address.'
|
||||
};
|
||||
}
|
||||
uid = userFound.id;
|
||||
isAdmin = true;
|
||||
}
|
||||
} else {
|
||||
permission = "owner";
|
||||
permission = 'owner';
|
||||
isAdmin = true;
|
||||
if (!isRegistrationEnabled) {
|
||||
throw {
|
||||
status: 404,
|
||||
message: "Registration disabled by administrator.",
|
||||
message: 'Registration disabled by administrator.'
|
||||
};
|
||||
}
|
||||
const hashedPassword = await hashPassword(password);
|
||||
@@ -327,17 +372,17 @@ export async function login(
|
||||
id: uid,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
type: "email",
|
||||
type: 'email',
|
||||
teams: {
|
||||
create: {
|
||||
id: uid,
|
||||
name: uniqueName(),
|
||||
destinationDocker: { connect: { network: "coolify" } },
|
||||
},
|
||||
destinationDocker: { connect: { network: 'coolify' } }
|
||||
}
|
||||
},
|
||||
permission: { create: { teamId: uid, permission: "owner" } },
|
||||
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||
},
|
||||
include: { teams: true },
|
||||
include: { teams: true }
|
||||
});
|
||||
} else {
|
||||
await prisma.user.create({
|
||||
@@ -345,16 +390,16 @@ export async function login(
|
||||
id: uid,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
type: "email",
|
||||
type: 'email',
|
||||
teams: {
|
||||
create: {
|
||||
id: uid,
|
||||
name: uniqueName(),
|
||||
},
|
||||
name: uniqueName()
|
||||
}
|
||||
},
|
||||
permission: { create: { teamId: uid, permission: "owner" } },
|
||||
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||
},
|
||||
include: { teams: true },
|
||||
include: { teams: true }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -362,23 +407,20 @@ export async function login(
|
||||
userId: uid,
|
||||
teamId: uid,
|
||||
permission,
|
||||
isAdmin,
|
||||
isAdmin
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentUser(
|
||||
request: FastifyRequest<GetCurrentUser>,
|
||||
fastify
|
||||
) {
|
||||
export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fastify) {
|
||||
let token = null;
|
||||
const { teamId } = request.query;
|
||||
try {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: request.user.userId },
|
||||
where: { id: request.user.userId }
|
||||
});
|
||||
if (!user) {
|
||||
throw "User not found";
|
||||
throw 'User not found';
|
||||
}
|
||||
} catch (error) {
|
||||
throw { status: 401, message: error };
|
||||
@@ -387,17 +429,15 @@ export async function getCurrentUser(
|
||||
try {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { id: request.user.userId, teams: { some: { id: teamId } } },
|
||||
include: { teams: true, permission: true },
|
||||
include: { teams: true, permission: true }
|
||||
});
|
||||
if (user) {
|
||||
const permission = user.permission.find(
|
||||
(p) => p.teamId === teamId
|
||||
).permission;
|
||||
const permission = user.permission.find((p) => p.teamId === teamId).permission;
|
||||
const payload = {
|
||||
...request.user,
|
||||
teamId,
|
||||
permission: permission || null,
|
||||
isAdmin: permission === "owner" || permission === "admin",
|
||||
isAdmin: permission === 'owner' || permission === 'admin'
|
||||
};
|
||||
token = fastify.jwt.sign(payload);
|
||||
}
|
||||
@@ -405,11 +445,13 @@ export async function getCurrentUser(
|
||||
// No new token -> not switching teams
|
||||
}
|
||||
}
|
||||
const pendingInvitations = await prisma.teamInvitation.findMany({ where: { uid: request.user.userId } })
|
||||
const pendingInvitations = await prisma.teamInvitation.findMany({
|
||||
where: { uid: request.user.userId }
|
||||
});
|
||||
return {
|
||||
settings: await prisma.setting.findFirst(),
|
||||
settings: await prisma.setting.findUnique({ where: { id: '0' } }),
|
||||
pendingInvitations,
|
||||
token,
|
||||
...request.user,
|
||||
...request.user
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { checkUpdate, login, showDashboard, update, resetQueue, getCurrentUser, cleanupManually, restartCoolify, refreshTemplates } from './handlers';
|
||||
import { checkUpdate, login, showDashboard, update, resetQueue, getCurrentUser, cleanupManually, restartCoolify, backup } from './handlers';
|
||||
import { GetCurrentUser } from './types';
|
||||
|
||||
export interface Update {
|
||||
@@ -53,9 +53,9 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
onRequest: [fastify.authenticate]
|
||||
}, async (request) => await cleanupManually(request));
|
||||
|
||||
fastify.post('/internal/refreshTemplates', {
|
||||
onRequest: [fastify.authenticate]
|
||||
}, async () => await refreshTemplates());
|
||||
// fastify.get('/internal/backup/:backupData', {
|
||||
// onRequest: [fastify.authenticate]
|
||||
// }, async (request) => await backup(request));
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { errorHandler, executeDockerCmd, prisma, createRemoteEngineConfiguration, executeSSHCmd } from '../../../../lib/common';
|
||||
import { errorHandler, prisma, executeCommand } from '../../../../lib/common';
|
||||
import os from 'node:os';
|
||||
import osu from 'node-os-utils';
|
||||
|
||||
@@ -71,10 +71,10 @@ export async function showUsage(request: FastifyRequest) {
|
||||
let { remoteEngine } = request.query
|
||||
remoteEngine = remoteEngine === 'true' ? true : false
|
||||
if (remoteEngine) {
|
||||
const { stdout: stats } = await executeSSHCmd({ dockerId: id, command: `vmstat -s` })
|
||||
const { stdout: disks } = await executeSSHCmd({ dockerId: id, command: `df -m / --output=size,used,pcent|grep -v 'Used'| xargs` })
|
||||
const { stdout: cpus } = await executeSSHCmd({ dockerId: id, command: `nproc --all` })
|
||||
const { stdout: cpuUsage } = await executeSSHCmd({ dockerId: id, command: `echo $[100-$(vmstat 1 2|tail -1|awk '{print $15}')]` })
|
||||
const { stdout: stats } = await executeCommand({ sshCommand: true, dockerId: id, command: `vmstat -s` })
|
||||
const { stdout: disks } = await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `df -m / --output=size,used,pcent|grep -v 'Used'| xargs` })
|
||||
const { stdout: cpus } = await executeCommand({ sshCommand: true, dockerId: id, command: `nproc --all` })
|
||||
const { stdout: cpuUsage } = await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `echo $[100-$(vmstat 1 2|tail -1|awk '{print $15}')]` })
|
||||
const parsed: any = parseFromText(stats)
|
||||
return {
|
||||
usage: {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,151 +1,312 @@
|
||||
import { promises as dns } from 'dns';
|
||||
import { X509Certificate } from 'node:crypto';
|
||||
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { asyncExecShell, checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, isDev, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
|
||||
import { CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey } from './types';
|
||||
|
||||
import {
|
||||
checkDomainsIsValidInDNS,
|
||||
decrypt,
|
||||
encrypt,
|
||||
errorHandler,
|
||||
executeCommand,
|
||||
getDomain,
|
||||
isDev,
|
||||
isDNSValid,
|
||||
isDomainConfigured,
|
||||
listSettings,
|
||||
prisma
|
||||
} from '../../../../lib/common';
|
||||
import {
|
||||
AddDefaultRegistry,
|
||||
CheckDNS,
|
||||
CheckDomain,
|
||||
DeleteDomain,
|
||||
OnlyIdInBody,
|
||||
SaveSettings,
|
||||
SaveSSHKey,
|
||||
SetDefaultRegistry
|
||||
} from './types';
|
||||
|
||||
export async function listAllSettings(request: FastifyRequest) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const settings = await listSettings();
|
||||
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
|
||||
const unencryptedKeys = []
|
||||
if (sshKeys.length > 0) {
|
||||
for (const key of sshKeys) {
|
||||
unencryptedKeys.push({ id: key.id, name: key.name, privateKey: decrypt(key.privateKey), createdAt: key.createdAt })
|
||||
}
|
||||
}
|
||||
const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } })
|
||||
let cns = [];
|
||||
for (const certificate of certificates) {
|
||||
const x509 = new X509Certificate(certificate.cert);
|
||||
cns.push({ commonName: x509.subject.split('\n').find((s) => s.startsWith('CN=')).replace('CN=', ''), id: certificate.id, createdAt: certificate.createdAt })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const settings = await listSettings();
|
||||
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } });
|
||||
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } });
|
||||
registries = registries.map((registry) => {
|
||||
if (registry.password) {
|
||||
registry.password = decrypt(registry.password);
|
||||
}
|
||||
return registry;
|
||||
});
|
||||
const unencryptedKeys = [];
|
||||
if (sshKeys.length > 0) {
|
||||
for (const key of sshKeys) {
|
||||
unencryptedKeys.push({
|
||||
id: key.id,
|
||||
name: key.name,
|
||||
privateKey: decrypt(key.privateKey),
|
||||
createdAt: key.createdAt
|
||||
});
|
||||
}
|
||||
}
|
||||
const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } });
|
||||
let cns = [];
|
||||
for (const certificate of certificates) {
|
||||
const x509 = new X509Certificate(certificate.cert);
|
||||
cns.push({
|
||||
commonName: x509.subject
|
||||
.split('\n')
|
||||
.find((s) => s.startsWith('CN='))
|
||||
.replace('CN=', ''),
|
||||
id: certificate.id,
|
||||
createdAt: certificate.createdAt
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
settings,
|
||||
certificates: cns,
|
||||
sshKeys: unencryptedKeys
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
return {
|
||||
settings,
|
||||
certificates: cns,
|
||||
sshKeys: unencryptedKeys,
|
||||
registries
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
|
||||
try {
|
||||
const {
|
||||
fqdn,
|
||||
isAPIDebuggingEnabled,
|
||||
isRegistrationEnabled,
|
||||
dualCerts,
|
||||
minPort,
|
||||
maxPort,
|
||||
isAutoUpdateEnabled,
|
||||
isDNSCheckEnabled,
|
||||
DNSServers,
|
||||
proxyDefaultRedirect
|
||||
} = request.body
|
||||
const { id } = await listSettings();
|
||||
await prisma.setting.update({
|
||||
where: { id },
|
||||
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled, }
|
||||
});
|
||||
if (fqdn) {
|
||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||
}
|
||||
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
|
||||
if (minPort && maxPort) {
|
||||
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
||||
}
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
let {
|
||||
previewSeparator,
|
||||
numberOfDockerImagesKeptLocally,
|
||||
doNotTrack,
|
||||
fqdn,
|
||||
isAPIDebuggingEnabled,
|
||||
isRegistrationEnabled,
|
||||
dualCerts,
|
||||
minPort,
|
||||
maxPort,
|
||||
isAutoUpdateEnabled,
|
||||
isDNSCheckEnabled,
|
||||
DNSServers,
|
||||
proxyDefaultRedirect
|
||||
} = request.body;
|
||||
const { id, previewSeparator: SetPreviewSeparator } = await listSettings();
|
||||
if (numberOfDockerImagesKeptLocally) {
|
||||
numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally);
|
||||
}
|
||||
if (previewSeparator == '') {
|
||||
previewSeparator = '.';
|
||||
}
|
||||
if (SetPreviewSeparator != previewSeparator) {
|
||||
const applications = await prisma.application.findMany({
|
||||
where: { previewApplication: { some: { id: { not: undefined } } } },
|
||||
include: { previewApplication: true }
|
||||
});
|
||||
for (const application of applications) {
|
||||
for (const preview of application.previewApplication) {
|
||||
const { protocol } = new URL(preview.customDomain);
|
||||
const { pullmergeRequestId } = preview;
|
||||
const { fqdn } = application;
|
||||
const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(
|
||||
fqdn
|
||||
)}`;
|
||||
await prisma.previewApplication.update({
|
||||
where: { id: preview.id },
|
||||
data: { customDomain: newPreviewDomain }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.setting.update({
|
||||
where: { id },
|
||||
data: {
|
||||
previewSeparator,
|
||||
numberOfDockerImagesKeptLocally,
|
||||
doNotTrack,
|
||||
isRegistrationEnabled,
|
||||
dualCerts,
|
||||
isAutoUpdateEnabled,
|
||||
isDNSCheckEnabled,
|
||||
DNSServers,
|
||||
isAPIDebuggingEnabled
|
||||
}
|
||||
});
|
||||
if (fqdn) {
|
||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||
}
|
||||
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
|
||||
if (minPort && maxPort) {
|
||||
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
||||
}
|
||||
if (doNotTrack === false) {
|
||||
// Sentry.init({
|
||||
// dsn: sentryDSN,
|
||||
// environment: isDev ? 'development' : 'production',
|
||||
// release: version
|
||||
// });
|
||||
// console.log('Sentry initialized')
|
||||
}
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
||||
try {
|
||||
const { fqdn } = request.body
|
||||
const { DNSServers } = await listSettings();
|
||||
if (DNSServers) {
|
||||
dns.setServers([...DNSServers.split(',')]);
|
||||
}
|
||||
let ip;
|
||||
try {
|
||||
ip = await dns.resolve(fqdn);
|
||||
} catch (error) {
|
||||
// Do not care.
|
||||
}
|
||||
await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||
return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined)
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { fqdn } = request.body;
|
||||
const { DNSServers } = await listSettings();
|
||||
if (DNSServers) {
|
||||
dns.setServers([...DNSServers.split(',')]);
|
||||
}
|
||||
let ip;
|
||||
try {
|
||||
ip = await dns.resolve(fqdn);
|
||||
} catch (error) {
|
||||
// Do not care.
|
||||
}
|
||||
await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||
return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined);
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
const found = await isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw "Domain already configured";
|
||||
}
|
||||
if (isDNSCheckEnabled && !forceSave && !isDev) {
|
||||
const hostname = request.hostname.split(':')[0]
|
||||
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body;
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
const found = await isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw { message: 'Domain already configured' };
|
||||
}
|
||||
if (isDNSCheckEnabled && !forceSave && !isDev) {
|
||||
const hostname = request.hostname.split(':')[0];
|
||||
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
||||
try {
|
||||
const { domain } = request.params;
|
||||
await isDNSValid(request.hostname, domain);
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { domain } = request.params;
|
||||
await isDNSValid(request.hostname, domain);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { privateKey, name } = request.body;
|
||||
const found = await prisma.sshKey.findMany({ where: { name } })
|
||||
if (found.length > 0) {
|
||||
throw {
|
||||
message: "Name already used. Choose another one please."
|
||||
}
|
||||
}
|
||||
const encryptedSSHKey = encrypt(privateKey)
|
||||
await prisma.sshKey.create({ data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { privateKey, name } = request.body;
|
||||
const found = await prisma.sshKey.findMany({ where: { name } });
|
||||
if (found.length > 0) {
|
||||
throw {
|
||||
message: 'Name already used. Choose another one please.'
|
||||
};
|
||||
}
|
||||
const encryptedSSHKey = encrypt(privateKey);
|
||||
await prisma.sshKey.create({
|
||||
data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } }
|
||||
});
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.body;
|
||||
await prisma.sshKey.delete({ where: { id } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.body;
|
||||
await prisma.sshKey.deleteMany({ where: { id, teamId } });
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.body;
|
||||
await asyncExecShell(`docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`)
|
||||
await prisma.certificate.delete({ where: { id } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function deleteCertificates(
|
||||
request: FastifyRequest<OnlyIdInBody>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.body;
|
||||
await executeCommand({
|
||||
command: `docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`,
|
||||
shell: true
|
||||
});
|
||||
await prisma.certificate.deleteMany({ where: { id, teamId } });
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function setDockerRegistry(
|
||||
request: FastifyRequest<SetDefaultRegistry>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id, username, password } = request.body;
|
||||
|
||||
let encryptedPassword = '';
|
||||
if (password) encryptedPassword = encrypt(password);
|
||||
|
||||
if (teamId === '0') {
|
||||
await prisma.dockerRegistry.update({
|
||||
where: { id },
|
||||
data: { username, password: encryptedPassword }
|
||||
});
|
||||
} else {
|
||||
await prisma.dockerRegistry.updateMany({
|
||||
where: { id, teamId },
|
||||
data: { username, password: encryptedPassword }
|
||||
});
|
||||
}
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function addDockerRegistry(
|
||||
request: FastifyRequest<AddDefaultRegistry>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { name, url, username, password } = request.body;
|
||||
|
||||
let encryptedPassword = '';
|
||||
if (password) encryptedPassword = encrypt(password);
|
||||
await prisma.dockerRegistry.create({
|
||||
data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } }
|
||||
});
|
||||
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function deleteDockerRegistry(
|
||||
request: FastifyRequest<OnlyIdInBody>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.body;
|
||||
await prisma.application.updateMany({
|
||||
where: { dockerRegistryId: id },
|
||||
data: { dockerRegistryId: null }
|
||||
});
|
||||
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } });
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { FastifyPluginAsync } from 'fastify';
|
||||
import { X509Certificate } from 'node:crypto';
|
||||
|
||||
import { encrypt, errorHandler, prisma } from '../../../../lib/common';
|
||||
import { checkDNS, checkDomain, deleteCertificates, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey } from './handlers';
|
||||
import { CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey } from './types';
|
||||
import { addDockerRegistry, checkDNS, checkDomain, deleteCertificates, deleteDockerRegistry, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey, setDockerRegistry } from './handlers';
|
||||
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types';
|
||||
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
@@ -20,6 +20,10 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.post<SaveSSHKey>('/sshKey', async (request, reply) => await saveSSHKey(request, reply));
|
||||
fastify.delete<OnlyIdInBody>('/sshKey', async (request, reply) => await deleteSSHKey(request, reply));
|
||||
|
||||
fastify.post<SetDefaultRegistry>('/registry', async (request, reply) => await setDockerRegistry(request, reply));
|
||||
fastify.post<AddDefaultRegistry>('/registry/new', async (request, reply) => await addDockerRegistry(request, reply));
|
||||
fastify.delete<OnlyIdInBody>('/registry', async (request, reply) => await deleteDockerRegistry(request, reply));
|
||||
|
||||
fastify.post('/upload', async (request) => {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
@@ -53,7 +57,6 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
});
|
||||
fastify.delete<OnlyIdInBody>('/certificate', async (request, reply) => await deleteCertificates(request, reply))
|
||||
// fastify.get('/certificates', async (request) => await getCertificates(request))
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
@@ -2,6 +2,9 @@ import { OnlyId } from "../../../../types"
|
||||
|
||||
export interface SaveSettings {
|
||||
Body: {
|
||||
previewSeparator: string,
|
||||
numberOfDockerImagesKeptLocally: number,
|
||||
doNotTrack: boolean,
|
||||
fqdn: string,
|
||||
isAPIDebuggingEnabled: boolean,
|
||||
isRegistrationEnabled: boolean,
|
||||
@@ -21,30 +24,46 @@ export interface DeleteDomain {
|
||||
}
|
||||
export interface CheckDomain extends OnlyId {
|
||||
Body: {
|
||||
fqdn: string,
|
||||
forceSave: boolean,
|
||||
dualCerts: boolean,
|
||||
isDNSCheckEnabled: boolean,
|
||||
fqdn: string,
|
||||
forceSave: boolean,
|
||||
dualCerts: boolean,
|
||||
isDNSCheckEnabled: boolean,
|
||||
}
|
||||
}
|
||||
export interface CheckDNS {
|
||||
Params: {
|
||||
domain: string,
|
||||
domain: string,
|
||||
}
|
||||
}
|
||||
export interface SaveSSHKey {
|
||||
Body: {
|
||||
privateKey: string,
|
||||
privateKey: string,
|
||||
name: string
|
||||
}
|
||||
}
|
||||
export interface DeleteSSHKey {
|
||||
Body: {
|
||||
id: string
|
||||
id: string
|
||||
}
|
||||
}
|
||||
export interface OnlyIdInBody {
|
||||
Body: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SetDefaultRegistry {
|
||||
Body: {
|
||||
id: string
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
}
|
||||
export interface AddDefaultRegistry {
|
||||
Body: {
|
||||
url: string
|
||||
name: string
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
}
|
||||
@@ -1,192 +1,231 @@
|
||||
import cuid from 'cuid';
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common';
|
||||
import { OnlyId } from '../../../../types';
|
||||
import { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types';
|
||||
|
||||
export async function listSources(request: FastifyRequest) {
|
||||
try {
|
||||
const teamId = request.user?.teamId;
|
||||
const sources = await prisma.gitSource.findMany({
|
||||
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
||||
include: { teams: true, githubApp: true, gitlabApp: true }
|
||||
});
|
||||
return {
|
||||
sources
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user?.teamId;
|
||||
const sources = await prisma.gitSource.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
{ isSystemWide: true }
|
||||
]
|
||||
},
|
||||
include: { teams: true, githubApp: true, gitlabApp: true }
|
||||
});
|
||||
return {
|
||||
sources
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function saveSource(request, reply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
let { name, htmlUrl, apiUrl, customPort, isSystemWide } = request.body
|
||||
if (customPort) customPort = Number(customPort)
|
||||
await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { name, htmlUrl, apiUrl, customPort, isSystemWide }
|
||||
});
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body;
|
||||
if (customPort) customPort = Number(customPort);
|
||||
await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
|
||||
});
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function getSource(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { teamId } = request.user;
|
||||
const settings = await prisma.setting.findFirst({});
|
||||
|
||||
const settings = await prisma.setting.findFirst({});
|
||||
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
|
||||
if (id === 'new') {
|
||||
return {
|
||||
source: {
|
||||
name: null,
|
||||
type: null,
|
||||
htmlUrl: null,
|
||||
apiUrl: null,
|
||||
organization: null,
|
||||
customPort: 22,
|
||||
customUser: 'git'
|
||||
},
|
||||
settings
|
||||
};
|
||||
}
|
||||
|
||||
if (id === 'new') {
|
||||
return {
|
||||
source: {
|
||||
name: null,
|
||||
type: null,
|
||||
htmlUrl: null,
|
||||
apiUrl: null,
|
||||
organization: null,
|
||||
customPort: 22,
|
||||
},
|
||||
settings
|
||||
}
|
||||
}
|
||||
const source = await prisma.gitSource.findFirst({
|
||||
where: {
|
||||
id,
|
||||
OR: [
|
||||
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
{ isSystemWide: true }
|
||||
]
|
||||
},
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (!source) {
|
||||
throw { status: 404, message: 'Source not found.' };
|
||||
}
|
||||
|
||||
const source = await prisma.gitSource.findFirst({
|
||||
where: { id, OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (!source) {
|
||||
throw { status: 404, message: 'Source not found.' }
|
||||
}
|
||||
if (source?.githubApp?.clientSecret)
|
||||
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
|
||||
if (source?.githubApp?.webhookSecret)
|
||||
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
|
||||
if (source?.githubApp?.privateKey)
|
||||
source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
|
||||
if (source?.gitlabApp?.appSecret)
|
||||
source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
|
||||
|
||||
if (source?.githubApp?.clientSecret)
|
||||
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
|
||||
if (source?.githubApp?.webhookSecret)
|
||||
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
|
||||
if (source?.githubApp?.privateKey) source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
|
||||
if (source?.gitlabApp?.appSecret) source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
|
||||
|
||||
return {
|
||||
source,
|
||||
settings
|
||||
};
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
return {
|
||||
source,
|
||||
settings
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSource(request) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const source = await prisma.gitSource.delete({
|
||||
where: { id },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (source.githubAppId) {
|
||||
await prisma.githubApp.delete({ where: { id: source.githubAppId } });
|
||||
}
|
||||
if (source.gitlabAppId) {
|
||||
await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
|
||||
}
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const gitAppFound = await prisma.application.findFirst({ where: { gitSourceId: id } });
|
||||
if (gitAppFound) {
|
||||
throw {
|
||||
status: 400,
|
||||
message: 'This source is used by an application. Please remove the application first.'
|
||||
};
|
||||
}
|
||||
const source = await prisma.gitSource.delete({
|
||||
where: { id },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (source.githubAppId) {
|
||||
await prisma.githubApp.delete({ where: { id: source.githubAppId } });
|
||||
}
|
||||
if (source.gitlabAppId) {
|
||||
await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function saveGitHubSource(request: FastifyRequest<SaveGitHubSource>) {
|
||||
try {
|
||||
const { teamId } = request.user
|
||||
try {
|
||||
const { teamId } = request.user;
|
||||
|
||||
const { id } = request.params
|
||||
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body
|
||||
const { id } = request.params;
|
||||
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body;
|
||||
|
||||
if (customPort) customPort = Number(customPort)
|
||||
if (id === 'new') {
|
||||
const newId = cuid()
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
id: newId,
|
||||
name,
|
||||
htmlUrl,
|
||||
apiUrl,
|
||||
organization,
|
||||
customPort,
|
||||
isSystemWide,
|
||||
type: 'github',
|
||||
teams: { connect: { id: teamId } }
|
||||
}
|
||||
});
|
||||
return {
|
||||
id: newId
|
||||
}
|
||||
}
|
||||
throw { status: 500, message: 'Wrong request.' }
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
if (customPort) customPort = Number(customPort);
|
||||
if (id === 'new') {
|
||||
const newId = cuid();
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
id: newId,
|
||||
name,
|
||||
htmlUrl,
|
||||
apiUrl,
|
||||
organization,
|
||||
customPort,
|
||||
isSystemWide,
|
||||
type: 'github',
|
||||
teams: { connect: { id: teamId } }
|
||||
}
|
||||
});
|
||||
return {
|
||||
id: newId
|
||||
};
|
||||
}
|
||||
throw { status: 500, message: 'Wrong request.' };
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort } =
|
||||
request.body
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { teamId } = request.user;
|
||||
let {
|
||||
type,
|
||||
name,
|
||||
htmlUrl,
|
||||
apiUrl,
|
||||
oauthId,
|
||||
appId,
|
||||
appSecret,
|
||||
groupName,
|
||||
customPort,
|
||||
customUser
|
||||
} = request.body;
|
||||
|
||||
if (oauthId) oauthId = Number(oauthId);
|
||||
if (customPort) customPort = Number(customPort)
|
||||
const encryptedAppSecret = encrypt(appSecret);
|
||||
if (oauthId) oauthId = Number(oauthId);
|
||||
if (customPort) customPort = Number(customPort);
|
||||
const encryptedAppSecret = encrypt(appSecret);
|
||||
|
||||
if (id === 'new') {
|
||||
const newId = cuid()
|
||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, teams: { connect: { id: teamId } } } });
|
||||
await prisma.gitlabApp.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
appId,
|
||||
oauthId,
|
||||
groupName,
|
||||
appSecret: encryptedAppSecret,
|
||||
gitSource: { connect: { id: newId } }
|
||||
}
|
||||
});
|
||||
return {
|
||||
status: 201,
|
||||
id: newId
|
||||
}
|
||||
} else {
|
||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort } });
|
||||
await prisma.gitlabApp.update({
|
||||
where: { id },
|
||||
data: {
|
||||
appId,
|
||||
oauthId,
|
||||
groupName,
|
||||
appSecret: encryptedAppSecret,
|
||||
}
|
||||
});
|
||||
}
|
||||
return { status: 201 };
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
if (id === 'new') {
|
||||
const newId = cuid();
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
id: newId,
|
||||
type,
|
||||
apiUrl,
|
||||
htmlUrl,
|
||||
name,
|
||||
customPort,
|
||||
customUser,
|
||||
teams: { connect: { id: teamId } }
|
||||
}
|
||||
});
|
||||
await prisma.gitlabApp.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
appId,
|
||||
oauthId,
|
||||
groupName,
|
||||
appSecret: encryptedAppSecret,
|
||||
gitSource: { connect: { id: newId } }
|
||||
}
|
||||
});
|
||||
return {
|
||||
status: 201,
|
||||
id: newId
|
||||
};
|
||||
} else {
|
||||
await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { type, apiUrl, htmlUrl, name, customPort, customUser }
|
||||
});
|
||||
await prisma.gitlabApp.update({
|
||||
where: { id },
|
||||
data: {
|
||||
appId,
|
||||
oauthId,
|
||||
groupName,
|
||||
appSecret: encryptedAppSecret
|
||||
}
|
||||
});
|
||||
}
|
||||
return { status: 201 };
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkGitLabOAuthID(request: FastifyRequest<CheckGitLabOAuthId>) {
|
||||
try {
|
||||
const { oauthId } = request.body
|
||||
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
|
||||
if (found) {
|
||||
throw { status: 500, message: 'OAuthID already configured in Coolify.' }
|
||||
}
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
try {
|
||||
const { oauthId } = request.body;
|
||||
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
|
||||
if (found) {
|
||||
throw { status: 500, message: 'OAuthID already configured in Coolify.' };
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface SaveGitLabSource extends OnlyId {
|
||||
appSecret: string,
|
||||
groupName: string,
|
||||
customPort: number,
|
||||
customUser: string,
|
||||
}
|
||||
}
|
||||
export interface CheckGitLabOAuthId extends OnlyId {
|
||||
|
||||
@@ -71,7 +71,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
||||
const githubEvent = request.headers['x-github-event']?.toString().toLowerCase();
|
||||
const githubSignature = request.headers['x-hub-signature-256']?.toString().toLowerCase();
|
||||
if (!allowedGithubEvents.includes(githubEvent)) {
|
||||
throw { status: 500, message: 'Event not allowed.' }
|
||||
throw { status: 500, message: 'Event not allowed.', type: 'webhook' }
|
||||
}
|
||||
if (githubEvent === 'ping') {
|
||||
return { pong: 'cool' }
|
||||
@@ -89,9 +89,10 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
||||
branch = body.pull_request.base.ref
|
||||
}
|
||||
if (!projectId || !branch) {
|
||||
throw { status: 500, message: 'Cannot parse projectId or branch from the webhook?!' }
|
||||
throw { status: 500, message: 'Cannot parse projectId or branch from the webhook?!', type: 'webhook' }
|
||||
}
|
||||
const applicationsFound = await getApplicationFromDBWebhook(projectId, branch);
|
||||
const settings = await prisma.setting.findUnique({ where: { id: '0' } });
|
||||
if (applicationsFound && applicationsFound.length > 0) {
|
||||
for (const application of applicationsFound) {
|
||||
const buildId = cuid();
|
||||
@@ -106,7 +107,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
||||
const checksum = Buffer.from(githubSignature, 'utf8');
|
||||
//@ts-ignore
|
||||
if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) {
|
||||
throw { status: 500, message: 'SHA256 checksum failed. Are you doing something fishy?' }
|
||||
throw { status: 500, message: 'SHA256 checksum failed. Are you doing something fishy?', type: 'webhook' }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -156,7 +157,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
||||
const sourceBranch = body.pull_request.head.ref
|
||||
const sourceRepository = body.pull_request.head.repo.full_name
|
||||
if (!allowedActions.includes(pullmergeRequestAction)) {
|
||||
throw { status: 500, message: 'Action not allowed.' }
|
||||
throw { status: 500, message: 'Action not allowed.', type: 'webhook' }
|
||||
}
|
||||
|
||||
if (application.settings.previews) {
|
||||
@@ -168,7 +169,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
||||
}
|
||||
);
|
||||
if (!isRunning) {
|
||||
throw { status: 500, message: 'Application not running.' }
|
||||
throw { status: 500, message: 'Application not running.', type: 'webhook' }
|
||||
}
|
||||
}
|
||||
if (
|
||||
@@ -192,7 +193,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
||||
data: {
|
||||
pullmergeRequestId,
|
||||
sourceBranch,
|
||||
customDomain: `${protocol}${pullmergeRequestId}.${getDomain(application.fqdn)}`,
|
||||
customDomain: `${protocol}${pullmergeRequestId}${settings.previewSeparator}${getDomain(application.fqdn)}`,
|
||||
application: { connect: { id: application.id } }
|
||||
}
|
||||
})
|
||||
@@ -257,8 +258,8 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
} catch ({ status, message, type }) {
|
||||
return errorHandler({ status, message, type })
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,8 +44,9 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
||||
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
|
||||
const webhookToken = request.headers['x-gitlab-token'];
|
||||
if (!webhookToken && !isDev) {
|
||||
throw { status: 500, message: 'Invalid webhookToken.' }
|
||||
throw { status: 500, message: 'Invalid webhookToken.', type: 'webhook' }
|
||||
}
|
||||
const settings = await prisma.setting.findUnique({ where: { id: '0' } });
|
||||
if (objectKind === 'push') {
|
||||
const projectId = Number(project_id);
|
||||
const branch = ref.split('/')[2];
|
||||
@@ -95,10 +96,10 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
||||
const pullmergeRequestId = request.body.object_attributes.iid.toString();
|
||||
const projectId = Number(id);
|
||||
if (!allowedActions.includes(action)) {
|
||||
throw { status: 500, message: 'Action not allowed.' }
|
||||
throw { status: 500, message: 'Action not allowed.', type: 'webhook' }
|
||||
}
|
||||
if (isDraft) {
|
||||
throw { status: 500, message: 'Draft MR, do nothing.' }
|
||||
throw { status: 500, message: 'Draft MR, do nothing.', type: 'webhook' }
|
||||
}
|
||||
const applicationsFound = await getApplicationFromDBWebhook(projectId, targetBranch);
|
||||
if (applicationsFound && applicationsFound.length > 0) {
|
||||
@@ -113,11 +114,11 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
||||
}
|
||||
);
|
||||
if (!isRunning) {
|
||||
throw { status: 500, message: 'Application not running.' }
|
||||
throw { status: 500, message: 'Application not running.', type: 'webhook' }
|
||||
}
|
||||
}
|
||||
if (!isDev && application.gitSource.gitlabApp.webhookToken !== webhookToken) {
|
||||
throw { status: 500, message: 'Invalid webhookToken. Are you doing something nasty?!' }
|
||||
throw { status: 500, message: 'Invalid webhookToken. Are you doing something nasty?!', type: 'webhook' }
|
||||
}
|
||||
if (
|
||||
action === 'opened' ||
|
||||
@@ -140,7 +141,7 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
||||
data: {
|
||||
pullmergeRequestId,
|
||||
sourceBranch,
|
||||
customDomain: `${protocol}${pullmergeRequestId}.${getDomain(application.fqdn)}`,
|
||||
customDomain: `${protocol}${pullmergeRequestId}${settings.previewSeparator}${getDomain(application.fqdn)}`,
|
||||
application: { connect: { id: application.id } }
|
||||
}
|
||||
})
|
||||
@@ -188,7 +189,7 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
} catch ({ status, message, type }) {
|
||||
return errorHandler({ status, message, type })
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,35 @@
|
||||
import { FastifyRequest } from "fastify";
|
||||
import { errorHandler, getDomain, isDev, prisma, executeDockerCmd, fixType } from "../../../lib/common";
|
||||
import { getTemplates } from "../../../lib/services";
|
||||
import { OnlyId } from "../../../types";
|
||||
import { FastifyRequest } from 'fastify';
|
||||
import { errorHandler, executeCommand, getDomain, isDev, prisma } from '../../../lib/common';
|
||||
import { getTemplates } from '../../../lib/services';
|
||||
import { OnlyId } from '../../../types';
|
||||
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
|
||||
import { hashPassword } from '../../api/v1/handlers';
|
||||
|
||||
function generateServices(id, containerId, port) {
|
||||
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
|
||||
if (isHttp2) {
|
||||
return {
|
||||
[serviceId]: {
|
||||
loadbalancer: {
|
||||
servers: [
|
||||
{
|
||||
url: `${isHttps ? 'https' : 'http'}://${containerId}:${port}`
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
[`${serviceId}-http2`]: {
|
||||
loadbalancer: {
|
||||
servers: [
|
||||
{
|
||||
url: `h2c://${containerId}:${port}`
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
[`${id}-${port || 'default'}`]: {
|
||||
[serviceId]: {
|
||||
loadbalancer: {
|
||||
servers: [
|
||||
{
|
||||
@@ -14,43 +38,59 @@ function generateServices(id, containerId, port) {
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function generateRouters(id, domain, nakedDomain, pathPrefix, isHttps, isWWW, isDualCerts, isCustomSSL) {
|
||||
let http: any = {
|
||||
async function generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2 = false,
|
||||
httpBasicAuth = null,
|
||||
}) {
|
||||
const rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||
const ruleWWW = `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
||||
}`;
|
||||
|
||||
|
||||
const http: any = {
|
||||
entrypoints: ['web'],
|
||||
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||
service: `${id}`,
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
}
|
||||
let https: any = {
|
||||
};
|
||||
const https: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||
service: `${id}`,
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
tls: {
|
||||
certresolver: 'letsencrypt'
|
||||
},
|
||||
middlewares: []
|
||||
}
|
||||
let httpWWW: any = {
|
||||
};
|
||||
const httpWWW: any = {
|
||||
entrypoints: ['web'],
|
||||
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||
service: `${id}`,
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
}
|
||||
let httpsWWW: any = {
|
||||
};
|
||||
const httpsWWW: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||
service: `${id}`,
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
tls: {
|
||||
certresolver: 'letsencrypt'
|
||||
},
|
||||
middlewares: []
|
||||
}
|
||||
};
|
||||
// 2. http + non-www only
|
||||
if (!isHttps && !isWWW) {
|
||||
https.middlewares.push('redirect-to-http');
|
||||
@@ -58,19 +98,27 @@ function generateRouters(id, domain, nakedDomain, pathPrefix, isHttps, isWWW, is
|
||||
|
||||
httpWWW.middlewares.push('redirect-to-non-www');
|
||||
httpsWWW.middlewares.push('redirect-to-non-www');
|
||||
delete https.tls
|
||||
delete httpsWWW.tls
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
|
||||
if (httpBasicAuth) {
|
||||
http.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. http + www only
|
||||
// 3. http + www only
|
||||
if (!isHttps && isWWW) {
|
||||
https.middlewares.push('redirect-to-http');
|
||||
httpsWWW.middlewares.push('redirect-to-http');
|
||||
|
||||
http.middlewares.push('redirect-to-www');
|
||||
https.middlewares.push('redirect-to-www');
|
||||
delete https.tls
|
||||
delete httpsWWW.tls
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
|
||||
if (httpBasicAuth) {
|
||||
httpWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
// 5. https + non-www only
|
||||
if (isHttps && !isWWW) {
|
||||
@@ -86,19 +134,23 @@ function generateRouters(id, domain, nakedDomain, pathPrefix, isHttps, isWWW, is
|
||||
httpsWWW.tls = true;
|
||||
} else {
|
||||
https.tls = true;
|
||||
delete httpsWWW.tls.certresolver
|
||||
delete httpsWWW.tls.certresolver;
|
||||
httpsWWW.tls.domains = {
|
||||
main: domain
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (!isDualCerts) {
|
||||
delete httpsWWW.tls.certresolver
|
||||
delete httpsWWW.tls.certresolver;
|
||||
httpsWWW.tls.domains = {
|
||||
main: domain
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (httpBasicAuth) {
|
||||
https.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
// 6. https + www only
|
||||
if (isHttps && isWWW) {
|
||||
@@ -108,34 +160,75 @@ function generateRouters(id, domain, nakedDomain, pathPrefix, isHttps, isWWW, is
|
||||
http.middlewares.push('redirect-to-www');
|
||||
https.middlewares.push('redirect-to-www');
|
||||
}
|
||||
|
||||
if (httpBasicAuth) {
|
||||
httpsWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
|
||||
if (isCustomSSL) {
|
||||
if (isDualCerts) {
|
||||
https.tls = true;
|
||||
httpsWWW.tls = true;
|
||||
} else {
|
||||
httpsWWW.tls = true;
|
||||
delete https.tls.certresolver
|
||||
delete https.tls.certresolver;
|
||||
https.tls.domains = {
|
||||
main: domain
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (!isDualCerts) {
|
||||
delete https.tls.certresolver
|
||||
delete https.tls.certresolver;
|
||||
https.tls.domains = {
|
||||
main: domain
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
[id]: { ...http },
|
||||
[`${id}-secure`]: { ...https },
|
||||
[`${id}-www`]: { ...httpWWW },
|
||||
[`${id}-secure-www`]: { ...httpsWWW },
|
||||
if (isHttp2) {
|
||||
const http2 = {
|
||||
...http,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
const http2WWW = {
|
||||
...httpWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
const https2 = {
|
||||
...https,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
|
||||
const https2WWW = {
|
||||
...httpsWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
return {
|
||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||
[`${serviceId}-${pathPrefix}-http2`]: { ...http2 },
|
||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||
[`${serviceId}-${pathPrefix}-secure-http2`]: { ...https2 },
|
||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||
[`${serviceId}-${pathPrefix}-www-http2`]: { ...http2WWW },
|
||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW },
|
||||
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
||||
};
|
||||
}
|
||||
|
||||
const result = {
|
||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
|
||||
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote = false) {
|
||||
const traefik = {
|
||||
tls: {
|
||||
certificates: []
|
||||
@@ -171,29 +264,29 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
};
|
||||
try {
|
||||
const { id = null } = request.params;
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (settings.isTraefikUsed && settings.proxyDefaultRedirect) {
|
||||
const coolifySettings = await prisma.setting.findFirst();
|
||||
if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) {
|
||||
traefik.http.routers['catchall-http'] = {
|
||||
entrypoints: ["web"],
|
||||
rule: "HostRegexp(`{catchall:.*}`)",
|
||||
service: "noop",
|
||||
entrypoints: ['web'],
|
||||
rule: 'HostRegexp(`{catchall:.*}`)',
|
||||
service: 'noop',
|
||||
priority: 1,
|
||||
middlewares: ["redirect-regexp"]
|
||||
}
|
||||
middlewares: ['redirect-regexp']
|
||||
};
|
||||
traefik.http.routers['catchall-https'] = {
|
||||
entrypoints: ["websecure"],
|
||||
rule: "HostRegexp(`{catchall:.*}`)",
|
||||
service: "noop",
|
||||
entrypoints: ['websecure'],
|
||||
rule: 'HostRegexp(`{catchall:.*}`)',
|
||||
service: 'noop',
|
||||
priority: 1,
|
||||
middlewares: ["redirect-regexp"]
|
||||
}
|
||||
middlewares: ['redirect-regexp']
|
||||
};
|
||||
traefik.http.middlewares['redirect-regexp'] = {
|
||||
redirectregex: {
|
||||
regex: '(.*)',
|
||||
replacement: settings.proxyDefaultRedirect,
|
||||
replacement: coolifySettings.proxyDefaultRedirect,
|
||||
permanent: false
|
||||
}
|
||||
}
|
||||
};
|
||||
traefik.http.services['noop'] = {
|
||||
loadBalancer: {
|
||||
servers: [
|
||||
@@ -202,25 +295,41 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
const sslpath = '/etc/traefik/acme/custom';
|
||||
|
||||
let certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } })
|
||||
let certificates = await prisma.certificate.findMany({
|
||||
where: {
|
||||
team: {
|
||||
applications: { some: { settings: { isCustomSSL: true } } },
|
||||
destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (remote) {
|
||||
certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
|
||||
certificates = await prisma.certificate.findMany({
|
||||
where: {
|
||||
team: {
|
||||
applications: { some: { settings: { isCustomSSL: true } } },
|
||||
destinationDocker: {
|
||||
some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let parsedCertificates = []
|
||||
const parsedCertificates = [];
|
||||
for (const certificate of certificates) {
|
||||
parsedCertificates.push({
|
||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
||||
keyFile: `${sslpath}/${certificate.id}-key.pem`
|
||||
})
|
||||
});
|
||||
}
|
||||
if (parsedCertificates.length > 0) {
|
||||
traefik.tls.certificates = parsedCertificates
|
||||
traefik.tls.certificates = parsedCertificates;
|
||||
}
|
||||
|
||||
let applications = [];
|
||||
@@ -236,7 +345,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
serviceSecret: true,
|
||||
serviceSetting: true,
|
||||
serviceSetting: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
@@ -251,22 +360,26 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
serviceSecret: true,
|
||||
serviceSetting: true,
|
||||
serviceSetting: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (applications.length > 0) {
|
||||
const dockerIds = new Set()
|
||||
const runningContainers = {}
|
||||
const dockerIds = new Set();
|
||||
const runningContainers = {};
|
||||
applications.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
||||
for (const dockerId of dockerIds) {
|
||||
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
|
||||
const containersArray = container.trim().split('\n');
|
||||
if (containersArray.length > 0) {
|
||||
runningContainers[dockerId] = containersArray
|
||||
const { stdout: container } = await executeCommand({
|
||||
dockerId,
|
||||
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
|
||||
});
|
||||
if (container) {
|
||||
const containersArray = container.trim().split('\n');
|
||||
if (containersArray.length > 0) {
|
||||
runningContainers[dockerId] = containersArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const application of applications) {
|
||||
@@ -279,7 +392,10 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
dockerComposeConfiguration,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
settings
|
||||
settings,
|
||||
basicAuthUser,
|
||||
basicAuthPw,
|
||||
settings: { basicAuth: isBasicAuthEnabled }
|
||||
} = application;
|
||||
if (!destinationDockerId) {
|
||||
continue;
|
||||
@@ -287,38 +403,68 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
if (
|
||||
!runningContainers[destinationDockerId] ||
|
||||
runningContainers[destinationDockerId].length === 0 ||
|
||||
!runningContainers[destinationDockerId].includes(id)
|
||||
runningContainers[destinationDockerId].filter((container) => container.startsWith(id))
|
||||
.length === 0
|
||||
) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
let httpBasicAuth = null;
|
||||
if (basicAuthUser && basicAuthPw && isBasicAuthEnabled) {
|
||||
httpBasicAuth = {
|
||||
basicAuth: {
|
||||
users: [basicAuthUser + ':' + await hashPassword(basicAuthPw, 1)]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (buildPack === 'compose') {
|
||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration))
|
||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration));
|
||||
if (services.length > 0) {
|
||||
for (const service of services) {
|
||||
const [key, value] = service
|
||||
const [key, value] = service;
|
||||
if (key && value) {
|
||||
if (!value.fqdn || !value.port) {
|
||||
continue;
|
||||
}
|
||||
const { fqdn, port } = value
|
||||
const containerId = `${id}-${key}`
|
||||
const { fqdn, port } = value;
|
||||
const containerId = `${id}-${key}`;
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const pathPrefix = '/'
|
||||
const pathPrefix = '/';
|
||||
const isCustomSSL = false;
|
||||
const dualCerts = false;
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(`${id}-${port || 'default'}`, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(id, containerId, port) }
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
httpBasicAuth
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, containerId, port)
|
||||
};
|
||||
if (httpBasicAuth) {
|
||||
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||
...httpBasicAuth
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const { previews, dualCerts, isCustomSSL } = settings;
|
||||
const { previews, dualCerts, isCustomSSL, isHttp2, basicAuth } = settings;
|
||||
const { network, id: dockerId } = destinationDocker;
|
||||
if (!fqdn) {
|
||||
continue;
|
||||
@@ -327,54 +473,102 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const pathPrefix = '/'
|
||||
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(`${id}-${port || 'default'}`, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(id, id, port) }
|
||||
const pathPrefix = '/';
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2,
|
||||
httpBasicAuth
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
||||
};
|
||||
if (httpBasicAuth) {
|
||||
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||
...httpBasicAuth
|
||||
};
|
||||
}
|
||||
if (previews) {
|
||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
|
||||
const containers = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((a) => a)
|
||||
.map((c) => c.replace(/"/g, ''));
|
||||
if (containers.length > 0) {
|
||||
for (const container of containers) {
|
||||
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||
const pathPrefix = '/'
|
||||
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(`${container}-${port || 'default'}`, previewDomain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(container, container, port) }
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId,
|
||||
command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
||||
});
|
||||
if (stdout) {
|
||||
const containers = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((a) => a)
|
||||
.map((c) => c.replace(/"/g, ''));
|
||||
if (containers.length > 0) {
|
||||
for (const container of containers) {
|
||||
const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator
|
||||
}${domain}`;
|
||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||
const pathPrefix = '/';
|
||||
const serviceId = `${container}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain: previewDomain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2: false,
|
||||
httpBasicAuth
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, container, port, isHttp2)
|
||||
};
|
||||
if (httpBasicAuth) {
|
||||
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||
...httpBasicAuth
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (services.length > 0) {
|
||||
const dockerIds = new Set()
|
||||
const runningContainers = {}
|
||||
const dockerIds = new Set();
|
||||
const runningContainers = {};
|
||||
services.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
||||
for (const dockerId of dockerIds) {
|
||||
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
|
||||
const containersArray = container.trim().split('\n');
|
||||
if (containersArray.length > 0) {
|
||||
runningContainers[dockerId] = containersArray
|
||||
const { stdout: container } = await executeCommand({
|
||||
dockerId,
|
||||
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
|
||||
});
|
||||
if (container) {
|
||||
const containersArray = container.trim().split('\n');
|
||||
if (containersArray.length > 0) {
|
||||
runningContainers[dockerId] = containersArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const service of services) {
|
||||
try {
|
||||
let {
|
||||
fqdn,
|
||||
id,
|
||||
type,
|
||||
destinationDockerId,
|
||||
dualCerts,
|
||||
serviceSetting
|
||||
} = service;
|
||||
let { fqdn, id, type, destinationDockerId, dualCerts, serviceSetting } = service;
|
||||
if (!fqdn) {
|
||||
continue;
|
||||
}
|
||||
@@ -386,7 +580,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
runningContainers[destinationDockerId].length === 0 ||
|
||||
!runningContainers[destinationDockerId].includes(id)
|
||||
) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
const templates = await getTemplates();
|
||||
let found = templates.find((a) => a.type === type);
|
||||
@@ -395,83 +589,147 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
}
|
||||
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
|
||||
for (const oneService of Object.keys(found.services)) {
|
||||
const isProxyConfiguration = found.services[oneService].proxy;
|
||||
if (isProxyConfiguration) {
|
||||
const { proxy } = found.services[oneService];
|
||||
for (let configuration of proxy) {
|
||||
const isDomainAndProxyConfiguration =
|
||||
found?.services[oneService]?.proxy?.filter((p) => p.port) ?? [];
|
||||
if (isDomainAndProxyConfiguration.length > 0) {
|
||||
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
||||
const { proxy } = template.services[oneService] || found.services[oneService];
|
||||
for (const configuration of proxy) {
|
||||
if (configuration.hostPort) {
|
||||
continue;
|
||||
}
|
||||
if (configuration.domain) {
|
||||
const setting = serviceSetting.find((a) => a.variableName === configuration.domain);
|
||||
const setting = serviceSetting.find(
|
||||
(a) => a.variableName === configuration.domain
|
||||
);
|
||||
if (setting) {
|
||||
configuration.domain = configuration.domain.replace(configuration.domain, setting.value);
|
||||
configuration.domain = configuration.domain.replace(
|
||||
configuration.domain,
|
||||
setting.value
|
||||
);
|
||||
}
|
||||
}
|
||||
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
|
||||
const foundPortVariable = serviceSetting.find(
|
||||
(a) => a.name.toLowerCase() === 'port'
|
||||
);
|
||||
if (foundPortVariable) {
|
||||
configuration.port = foundPortVariable.value
|
||||
configuration.port = foundPortVariable.value;
|
||||
}
|
||||
let port, pathPrefix, customDomain;
|
||||
if (configuration) {
|
||||
port = configuration?.port;
|
||||
pathPrefix = configuration?.pathPrefix || null;
|
||||
customDomain = configuration?.domain
|
||||
pathPrefix = configuration?.pathPrefix || '/';
|
||||
customDomain = configuration?.domain;
|
||||
}
|
||||
if (customDomain) {
|
||||
fqdn = customDomain
|
||||
fqdn = customDomain;
|
||||
} else {
|
||||
fqdn = service.fqdn
|
||||
fqdn = service.fqdn;
|
||||
}
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const isCustomSSL = false;
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(`${id}-${port || 'default'}`, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(id, id, port) }
|
||||
const serviceId = `${oneService}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, oneService, port)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (found.services[oneService].ports && found.services[oneService].ports.length > 0) {
|
||||
let port = found.services[oneService].ports[0]
|
||||
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
|
||||
if (foundPortVariable) {
|
||||
port = foundPortVariable.value
|
||||
for (let [index, port] of found.services[oneService].ports.entries()) {
|
||||
if (port == 22) continue;
|
||||
if (index === 0) {
|
||||
const foundPortVariable = serviceSetting.find(
|
||||
(a) => a.name.toLowerCase() === 'port'
|
||||
);
|
||||
if (foundPortVariable) {
|
||||
port = foundPortVariable.value;
|
||||
}
|
||||
}
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const pathPrefix = '/';
|
||||
const isCustomSSL = false;
|
||||
const serviceId = `${oneService}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, id, port)
|
||||
};
|
||||
}
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const pathPrefix = '/'
|
||||
const isCustomSSL = false
|
||||
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(`${id}-${port || 'default'}`, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(id, id, port) }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!remote) {
|
||||
const { fqdn, dualCerts } = await prisma.setting.findFirst();
|
||||
if (!fqdn) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const id = isDev ? 'host.docker.internal' : 'coolify'
|
||||
const container = isDev ? 'host.docker.internal' : 'coolify'
|
||||
const port = 3000
|
||||
const pathPrefix = '/'
|
||||
const isCustomSSL = false
|
||||
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(`${id}-${port || 'default'}`, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(id, container, port) }
|
||||
const id = isDev ? 'host.docker.internal' : 'coolify';
|
||||
const container = isDev ? 'host.docker.internal' : 'coolify';
|
||||
const port = 3000;
|
||||
const pathPrefix = '/';
|
||||
const isCustomSSL = false;
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, container, port)
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
} finally {
|
||||
if (Object.keys(traefik.http.routers).length === 0) {
|
||||
traefik.http.routers = null;
|
||||
@@ -485,9 +743,9 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
|
||||
export async function otherProxyConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
|
||||
try {
|
||||
const { id } = request.query
|
||||
const { id } = request.query;
|
||||
if (id) {
|
||||
const { privatePort, publicPort, type, address = id } = request.query
|
||||
const { privatePort, publicPort, type, address = id } = request.query;
|
||||
let traefik = {};
|
||||
if (publicPort && type && privatePort) {
|
||||
if (type === 'tcp') {
|
||||
@@ -548,18 +806,18 @@ export async function otherProxyConfiguration(request: FastifyRequest<TraefikOth
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw { status: 500 }
|
||||
throw { status: 500 };
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw { status: 500 }
|
||||
throw { status: 500 };
|
||||
}
|
||||
return {
|
||||
...traefik
|
||||
};
|
||||
}
|
||||
throw { status: 500 }
|
||||
throw { status: 500 };
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import { proxyConfiguration, otherProxyConfiguration } from './handlers';
|
||||
import { OtherProxyConfiguration } from './types';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) => otherProxyConfiguration(request));
|
||||
fastify.get<OnlyId>('/main.json', async (request) => proxyConfiguration(request, false));
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request) =>
|
||||
otherProxyConfiguration(request)
|
||||
);
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
1072
apps/api/tags.json
1072
apps/api/tags.json
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
apps/others/backup/.dockerignore
Normal file
2
apps/others/backup/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
backup/*
|
||||
27
apps/others/backup/Dockerfile
Normal file
27
apps/others/backup/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
||||
ARG PNPM_VERSION=7.17.1
|
||||
|
||||
FROM node:18-slim as build
|
||||
WORKDIR /app
|
||||
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||
|
||||
COPY ./package*.json .
|
||||
RUN pnpm install -p
|
||||
COPY . .
|
||||
|
||||
# Production build
|
||||
FROM node:18-slim
|
||||
ARG DOCKER_VERSION=20.10.18
|
||||
ARG TARGETPLATFORM
|
||||
ENV NODE_ENV production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt update && apt -y install curl
|
||||
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-$DOCKER_VERSION -o /usr/bin/docker
|
||||
RUN chmod +x /usr/bin/docker
|
||||
COPY --from=minio/mc:latest /usr/bin/mc /usr/bin/mc
|
||||
COPY --from=build /app/ .
|
||||
|
||||
ENV CHECKPOINT_DISABLE=1
|
||||
CMD node /app/src/index.mjs
|
||||
0
apps/others/backup/backups/.gitkeep
Normal file
0
apps/others/backup/backups/.gitkeep
Normal file
24
apps/others/backup/package.json
Normal file
24
apps/others/backup/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "backup",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "Andras Bacsai",
|
||||
"license": "Apache-2.0",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production node src/index.mjs",
|
||||
"dev": "pnpm cleanup && NODE_ENV=development node src/index.mjs",
|
||||
"build": "docker build -t backup .",
|
||||
"test": "pnpm build && docker run -ti --rm -v /var/run/docker.sock:/var/run/docker.sock -v /root/devel/coolify/apps/backup/backups:/app/backups -e CONTAINERS_TO_BACKUP='clatmhc6000008lvb5a5tnvsk:database:mysql:local' backup",
|
||||
"cleanup": "rm -rf backups/*"
|
||||
},
|
||||
"keywords": [],
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.222.0",
|
||||
"@aws-sdk/lib-storage": "^3.222.0",
|
||||
"cuid": "2.1.8",
|
||||
"dotenv": "16.0.3",
|
||||
"zx": "7.1.1"
|
||||
}
|
||||
}
|
||||
126
apps/others/backup/src/index.mjs
Normal file
126
apps/others/backup/src/index.mjs
Normal file
@@ -0,0 +1,126 @@
|
||||
import * as dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
import 'zx/globals';
|
||||
import cuid from 'cuid';
|
||||
import { S3, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
import fs from 'fs';
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
$.verbose = !!isDev
|
||||
|
||||
if (!process.env.CONTAINERS_TO_BACKUP && !isDev) {
|
||||
console.log(chalk.red(`No containers to backup!`))
|
||||
process.exit(1)
|
||||
}
|
||||
const mysqlGzipLocal = 'clb6c9ue4000a8lputdd5g1cl:database:mysql:gzip:local';
|
||||
const mysqlRawLocal = 'clb6c9ue4000a8lputdd5g1cl:database:mysql:raw:local';
|
||||
const postgresqlGzipLocal = 'clb6c15yi00008lpuezop7cy0:database:postgresql:gzip:local';
|
||||
const postgresqlRawLocal = 'clb6c15yi00008lpuezop7cy0:database:postgresql:raw:local';
|
||||
|
||||
const minio = 'clb6c9ue4000a8lputdd5g1cl:database:mysql:gzip:minio|http|min.arm.coolify.io|backups|<access_key>|<secret_key>';
|
||||
const digitalOcean = 'clb6c9ue4000a8lputdd5g1cl:database:mysql:gzip:do|https|fra1.digitaloceanspaces.com|backups|<access_key>|<secret_key>';
|
||||
|
||||
const devContainers = [mysqlGzipLocal, mysqlRawLocal, postgresqlGzipLocal, postgresqlRawLocal]
|
||||
|
||||
const containers = isDev
|
||||
? devContainers
|
||||
: process.env.CONTAINERS_TO_BACKUP.split(',')
|
||||
|
||||
const backup = async (container) => {
|
||||
const id = cuid()
|
||||
const [name, backupType, type, zipped, storage] = container.split(':')
|
||||
const directory = `backups`;
|
||||
const filename = zipped === 'raw'
|
||||
? `${name}-${type}-${backupType}-${new Date().getTime()}.sql`
|
||||
: `${name}-${type}-${backupType}-${new Date().getTime()}.tgz`
|
||||
const backup = `${directory}/${filename}`;
|
||||
|
||||
try {
|
||||
await $`docker inspect ${name.split(' ')[0]}`.quiet()
|
||||
if (backupType === 'database') {
|
||||
if (type === 'mysql') {
|
||||
console.log(chalk.blue(`Backing up ${name}:${type}...`))
|
||||
const { stdout: rootPassword } = await $`docker exec ${name} printenv MYSQL_ROOT_PASSWORD`.quiet()
|
||||
if (zipped === 'raw') {
|
||||
await $`docker exec ${name} sh -c "exec mysqldump --all-databases -uroot -p${rootPassword.trim()}" > ${backup}`
|
||||
} else if (zipped === 'gzip') {
|
||||
await $`docker exec ${name} sh -c "exec mysqldump --all-databases -uroot -p${rootPassword.trim()}" | gzip > ${backup}`
|
||||
}
|
||||
}
|
||||
if (type === 'postgresql') {
|
||||
console.log(chalk.blue(`Backing up ${name}:${type}...`))
|
||||
const { stdout: userPassword } = await $`docker exec ${name} printenv POSTGRES_PASSWORD`
|
||||
const { stdout: user } = await $`docker exec ${name} printenv POSTGRES_USER`
|
||||
if (zipped === 'raw') {
|
||||
await $`docker exec ${name} sh -c "exec pg_dumpall -c -U${user.trim()}" -W${userPassword.trim()}> ${backup}`
|
||||
} else if (zipped === 'gzip') {
|
||||
await $`docker exec ${name} sh -c "exec pg_dumpall -c -U${user.trim()}" -W${userPassword.trim()} | gzip > ${backup}`
|
||||
}
|
||||
}
|
||||
const [storageType, ...storageArgs] = storage.split('|')
|
||||
if (storageType !== 'local') {
|
||||
let s3Protocol, s3Url, s3Bucket, s3Key, s3Secret = null
|
||||
if (storageArgs.length > 0) {
|
||||
[s3Protocol, s3Url, s3Bucket, s3Key, s3Secret] = storageArgs
|
||||
}
|
||||
if (storageType === 'minio') {
|
||||
if (!s3Protocol || !s3Url || !s3Bucket || !s3Key || !s3Secret) {
|
||||
console.log(chalk.red(`Invalid storage arguments for ${name}:${type}!`))
|
||||
return
|
||||
}
|
||||
await $`mc alias set ${id} ${s3Protocol}://${s3Url} ${s3Key} ${s3Secret}`
|
||||
await $`mc stat ${id}`
|
||||
await $`mc cp ${backup} ${id}/${s3Bucket}`
|
||||
await $`rm ${backup}`
|
||||
await $`mc alias rm ${id}`
|
||||
} else if (storageType === 'do') {
|
||||
if (!s3Protocol || !s3Url || !s3Bucket || !s3Key || !s3Secret) {
|
||||
console.log(chalk.red(`Invalid storage arguments for ${name}:${type}!`))
|
||||
return
|
||||
}
|
||||
console.log({ s3Protocol, s3Url, s3Bucket, s3Key, s3Secret })
|
||||
console.log(chalk.blue(`Uploading ${name}:${type} to DigitalOcean Spaces...`))
|
||||
const readstream = fs.createReadStream(backup)
|
||||
const bucketParams = {
|
||||
Bucket: s3Bucket,
|
||||
Key: filename,
|
||||
Body: readstream
|
||||
};
|
||||
const s3Client = new S3({
|
||||
forcePathStyle: false,
|
||||
endpoint: `${s3Protocol}://${s3Url}`,
|
||||
region: "us-east-1",
|
||||
credentials: {
|
||||
accessKeyId: s3Key,
|
||||
secretAccessKey: s3Secret
|
||||
},
|
||||
});
|
||||
try {
|
||||
const data = await s3Client.send(new PutObjectCommand(bucketParams));
|
||||
console.log(chalk.green("Successfully uploaded backup: " +
|
||||
bucketParams.Bucket +
|
||||
"/" +
|
||||
bucketParams.Key
|
||||
)
|
||||
);
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.log("Error", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green(`Backup of ${name}:${type} complete!`))
|
||||
} catch (error) {
|
||||
console.log(chalk.red(`Backup of ${name}:${type} failed!`))
|
||||
console.log(chalk.red(error))
|
||||
}
|
||||
}
|
||||
const promises = []
|
||||
for (const container of containers) {
|
||||
// await backup(container);
|
||||
promises.push(backup(container))
|
||||
}
|
||||
await Promise.all(promises)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user