mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 20:59:24 +00:00
Compare commits
505 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53975fcf61 | ||
|
|
76296c1f19 | ||
|
|
c25baf69e1 | ||
|
|
f952512615 | ||
|
|
c6557eada8 | ||
|
|
2c2d74c0d6 | ||
|
|
028a2eb275 | ||
|
|
ce7fad5bef | ||
|
|
cd7852e4f9 | ||
|
|
7b022a2482 | ||
|
|
12d9b6538b | ||
|
|
335788c2d6 | ||
|
|
2352e4a71d | ||
|
|
dc03179bd1 | ||
|
|
cc72f416e8 | ||
|
|
3b67d0a8de | ||
|
|
0135ba7e89 | ||
|
|
a28a28cd23 | ||
|
|
b52680a2d8 | ||
|
|
0670e6c1d6 | ||
|
|
c3882b75c1 | ||
|
|
64b6f86a36 | ||
|
|
b9efc22253 | ||
|
|
e3d9eb0154 | ||
|
|
66f3967479 | ||
|
|
c54439e84c | ||
|
|
db4a4c74fc | ||
|
|
5c7ef80219 | ||
|
|
243d1c06fc | ||
|
|
ef25f7d800 | ||
|
|
45640ffdb1 | ||
|
|
378291b209 | ||
|
|
3583e552f1 | ||
|
|
d7dfeaf988 | ||
|
|
7fe5eca661 | ||
|
|
0dff57e69f | ||
|
|
f4803ad58b | ||
|
|
2d7bbbe300 | ||
|
|
928b68043b | ||
|
|
b21add0210 | ||
|
|
c41ffd6bfb | ||
|
|
b4874c7df3 | ||
|
|
c505a6ce9c | ||
|
|
706e4b13ee | ||
|
|
4af471ee31 | ||
|
|
87062e4e22 | ||
|
|
500ba0fab8 | ||
|
|
1c72c127d5 | ||
|
|
69bb4ae5ee | ||
|
|
5f8b8bd730 | ||
|
|
44f6d93639 | ||
|
|
e35b8a0f96 | ||
|
|
b26e23e7c3 | ||
|
|
e6f7e32037 | ||
|
|
1c386db41d | ||
|
|
085b655d9f | ||
|
|
2788fcf4e1 | ||
|
|
d058e04213 | ||
|
|
066f171163 | ||
|
|
2001be07d0 | ||
|
|
39552cc42f | ||
|
|
7f5d7e0eb0 | ||
|
|
0eda49b104 | ||
|
|
636995d0e4 | ||
|
|
4c0623f022 | ||
|
|
3e2e1080f5 | ||
|
|
3f866a07d8 | ||
|
|
23571ae104 | ||
|
|
c1710c8f7b | ||
|
|
d4d2cc71a0 | ||
|
|
8d86d53292 | ||
|
|
fae97e4dee | ||
|
|
8d0c3abf2e | ||
|
|
d396f649df | ||
|
|
ec21155c9e | ||
|
|
58111f53b9 | ||
|
|
2cbe1e8489 | ||
|
|
10e5a58b9e | ||
|
|
6f886e8b6f | ||
|
|
f96a91eb31 | ||
|
|
65a1961722 | ||
|
|
c5a932ab88 | ||
|
|
d1e10dacc0 | ||
|
|
96327af838 | ||
|
|
1cb6d594d0 | ||
|
|
16261fc36e | ||
|
|
cff694b0c4 | ||
|
|
97fd56b9e4 | ||
|
|
72cfa3e7b0 | ||
|
|
3cf41e1e23 | ||
|
|
2a7a63a672 | ||
|
|
7fb9e672cf | ||
|
|
9012f6b953 | ||
|
|
407eba8b76 | ||
|
|
68f6ab5796 | ||
|
|
3dd36a2271 | ||
|
|
7f69eb3c2e | ||
|
|
6ccbf911b2 | ||
|
|
5c77cec68f | ||
|
|
25a0489f7f | ||
|
|
5e27b88bef | ||
|
|
ec98afe707 | ||
|
|
ce26127705 | ||
|
|
ef7fc1b260 | ||
|
|
f58e6766e1 | ||
|
|
4a21102983 | ||
|
|
e78b6758d8 | ||
|
|
16eb7f4fb4 | ||
|
|
4974ce6eda | ||
|
|
6cdba17aca | ||
|
|
30f8e8f232 | ||
|
|
608f0b7840 | ||
|
|
d0366c4054 | ||
|
|
f88e3c5b29 | ||
|
|
e33fec0e1a | ||
|
|
912b0a263e | ||
|
|
8f963adbd4 | ||
|
|
8f2c24d7e9 | ||
|
|
9f3dbc3cbb | ||
|
|
8a9ee84925 | ||
|
|
689480003a | ||
|
|
3b20eee909 | ||
|
|
e8cadc176b | ||
|
|
b0c96e64c9 | ||
|
|
9ce3b43e09 | ||
|
|
4c2b3df861 | ||
|
|
467471f54a | ||
|
|
60171093c5 | ||
|
|
38f2a2dac7 | ||
|
|
307ee52ac0 | ||
|
|
b66c9835b7 | ||
|
|
d38d50dca2 | ||
|
|
40023be4ea | ||
|
|
48d7c6e76f | ||
|
|
debacfe2f7 | ||
|
|
d430813230 | ||
|
|
e30c37b041 | ||
|
|
8c73068cc7 | ||
|
|
2c4e69ad50 | ||
|
|
5ae08d009e | ||
|
|
673b944647 | ||
|
|
16281248ac | ||
|
|
8670b41671 | ||
|
|
9c69044da5 | ||
|
|
ebc4ab9af5 | ||
|
|
57738198ad | ||
|
|
b8252b85b0 | ||
|
|
479c2743bd | ||
|
|
81e6482d7a | ||
|
|
88c5d87084 | ||
|
|
6c7e091e1b | ||
|
|
91e3d33c0b | ||
|
|
aa00389824 | ||
|
|
b4e54ab3e3 | ||
|
|
8f3c5d4bd3 | ||
|
|
26668c71a1 | ||
|
|
bd7637c696 | ||
|
|
cff54f48a3 | ||
|
|
5c0f239f62 | ||
|
|
d56c28c8d9 | ||
|
|
2b666ff121 | ||
|
|
fb42c43953 | ||
|
|
81437e6822 | ||
|
|
2fe429fe92 | ||
|
|
4f0b214042 | ||
|
|
c866213f34 | ||
|
|
7cec6330cf | ||
|
|
f5de21a343 | ||
|
|
ecbfc4d790 | ||
|
|
55ff00e028 | ||
|
|
a0fc2bbb85 | ||
|
|
51a704b22a | ||
|
|
6d49678842 | ||
|
|
0459b3a115 | ||
|
|
82592c8222 | ||
|
|
25bf8895e2 | ||
|
|
f4f7bdf7d5 | ||
|
|
c008564aa3 | ||
|
|
b825d98b2d | ||
|
|
1f711d9281 | ||
|
|
1de850f640 | ||
|
|
f176247b02 | ||
|
|
3f3a1283df | ||
|
|
087bfcad08 | ||
|
|
efd2899ae3 | ||
|
|
e4b2195932 | ||
|
|
0590ed7b2e | ||
|
|
3a3c9448a4 | ||
|
|
36d65ad5a8 | ||
|
|
8db66952e8 | ||
|
|
45fa88ca4d | ||
|
|
84b74f0b57 | ||
|
|
423cf62d92 | ||
|
|
c4d9deabef | ||
|
|
776b1cb68d | ||
|
|
fc3025398e | ||
|
|
457c16c4dc | ||
|
|
ccf63c67e8 | ||
|
|
945157b30c | ||
|
|
13798392be | ||
|
|
0d05b0a3d6 | ||
|
|
e0d2f88d99 | ||
|
|
e260bfae02 | ||
|
|
5abd4a6d78 | ||
|
|
9dff1e5631 | ||
|
|
02332ade1b | ||
|
|
486de58d5b | ||
|
|
606aeb2b61 | ||
|
|
3fc264560c | ||
|
|
3dd9182281 | ||
|
|
c838ff7198 | ||
|
|
ca6db9c1a9 | ||
|
|
f27e00e80e | ||
|
|
60cf296f31 | ||
|
|
ea64e9d5ad | ||
|
|
55846c5635 | ||
|
|
7763594e6e | ||
|
|
6b5339c1c1 | ||
|
|
f2980738e4 | ||
|
|
f0e3ad0461 | ||
|
|
187050e098 | ||
|
|
9e7823795d | ||
|
|
239459dfa8 | ||
|
|
ce0f560c44 | ||
|
|
95baec99dd | ||
|
|
363e8fc0b5 | ||
|
|
e49caba920 | ||
|
|
30db2b2a09 | ||
|
|
285666e181 | ||
|
|
003934ee1d | ||
|
|
44c7958aa6 | ||
|
|
35b1a81dfe | ||
|
|
e40f397cc7 | ||
|
|
9fd8cd7e6c | ||
|
|
a94b7ee611 | ||
|
|
fc68bf50b5 | ||
|
|
0f99ee787c | ||
|
|
95777e978e | ||
|
|
fb0b9dbfed | ||
|
|
9617000daa | ||
|
|
1818404172 | ||
|
|
d9a966fd98 | ||
|
|
763ce5fc14 | ||
|
|
df021760a7 | ||
|
|
fb2598f2e4 | ||
|
|
7af07b2718 | ||
|
|
23a94c9378 | ||
|
|
ed34fc9645 | ||
|
|
cafd9e0ab2 | ||
|
|
e882477e21 | ||
|
|
db0e3cfcc4 | ||
|
|
b3c4429028 | ||
|
|
87ab4bd71e | ||
|
|
61e1fdede9 | ||
|
|
b189919f97 | ||
|
|
8f5b084931 | ||
|
|
eb96a5ae7b | ||
|
|
f0fb9dbb94 | ||
|
|
cb2d4b4a0a | ||
|
|
3aace2d4f9 | ||
|
|
8c2ed75653 | ||
|
|
8c8aafbc65 | ||
|
|
a2c39fd07e | ||
|
|
9698a051d9 | ||
|
|
51423394ba | ||
|
|
8e5e36dd5b | ||
|
|
c1dd05dcd8 | ||
|
|
dd1ce6ee6c | ||
|
|
fe4c6d396c | ||
|
|
8db54ec069 | ||
|
|
3abc720926 | ||
|
|
64cc0b63f1 | ||
|
|
c78068466b | ||
|
|
88e407756d | ||
|
|
1538116e6e | ||
|
|
aba47d58a4 | ||
|
|
e7f184dd82 | ||
|
|
2ad8d7812b | ||
|
|
8212bb99a1 | ||
|
|
5fc382d09d | ||
|
|
78a80c46da | ||
|
|
c365d132af | ||
|
|
4dc3db3845 | ||
|
|
b9427d2ec1 | ||
|
|
332a0b9e04 | ||
|
|
18e98aaf52 | ||
|
|
a7f9fad627 | ||
|
|
b01f6ac414 | ||
|
|
e1bc2cc406 | ||
|
|
74830b12f3 | ||
|
|
56a977c676 | ||
|
|
a0bb5733e6 | ||
|
|
516e10ddf2 | ||
|
|
2976c72e09 | ||
|
|
7377e9e415 | ||
|
|
d77c55148b | ||
|
|
ad7aa2eed6 | ||
|
|
5cec50efbe | ||
|
|
ef8686d4da | ||
|
|
581cc73cd4 | ||
|
|
358fbf6b3d | ||
|
|
ca0535c285 | ||
|
|
9007a645a6 | ||
|
|
68b1b9774d | ||
|
|
b9b4c23d5b | ||
|
|
149fee2452 | ||
|
|
87af9e46a6 | ||
|
|
d6f87d3fb6 | ||
|
|
493af61233 | ||
|
|
ab03908f1d | ||
|
|
9ef7cf3c12 | ||
|
|
eab7fd44d4 | ||
|
|
0d1d25a945 | ||
|
|
534372c29c | ||
|
|
1ccb239797 | ||
|
|
66287b43d0 | ||
|
|
143e4e0d23 | ||
|
|
73f3a09157 | ||
|
|
5ce449aa08 | ||
|
|
6203804713 | ||
|
|
0858faf628 | ||
|
|
a84f3e0577 | ||
|
|
8d571a5eab | ||
|
|
7a117c61c4 | ||
|
|
8b034f15fc | ||
|
|
b4a6499c83 | ||
|
|
c083acaeef | ||
|
|
9c6d8320d8 | ||
|
|
0e7a304610 | ||
|
|
83993cbbb2 | ||
|
|
6840ddd3e6 | ||
|
|
2c6ece62bb | ||
|
|
3f8514050e | ||
|
|
a4a653603e | ||
|
|
b6d8851c99 | ||
|
|
bcd7697f50 | ||
|
|
f1da735c40 | ||
|
|
01331c287b | ||
|
|
3320de787a | ||
|
|
2bddb09384 | ||
|
|
6f673d7a07 | ||
|
|
0a5a101ef4 | ||
|
|
88590fbf0f | ||
|
|
90291b2edf | ||
|
|
070573f0df | ||
|
|
e583beb753 | ||
|
|
d31683df61 | ||
|
|
0a83ed82fa | ||
|
|
4031e477ee | ||
|
|
0c1991d1de | ||
|
|
05b697b18c | ||
|
|
061aeba605 | ||
|
|
f446e784cc | ||
|
|
72fe24d98e | ||
|
|
0cd3a3d848 | ||
|
|
126b2dc65b | ||
|
|
a0031efce0 | ||
|
|
3bffe3f010 | ||
|
|
b9a37233a2 | ||
|
|
8ae18f49dc | ||
|
|
4feb99cbe0 | ||
|
|
aab122d97e | ||
|
|
658d608f55 | ||
|
|
0838343841 | ||
|
|
b557ea1e1d | ||
|
|
4520070df3 | ||
|
|
be8ea78b1b | ||
|
|
1175d68ab5 | ||
|
|
f56d373ed2 | ||
|
|
c6253658ca | ||
|
|
4249aec936 | ||
|
|
25f80aba5f | ||
|
|
4550983761 | ||
|
|
b0238372a2 | ||
|
|
a021b71496 | ||
|
|
e3958d9626 | ||
|
|
55891d7001 | ||
|
|
b12ac8bb29 | ||
|
|
728a9f88eb | ||
|
|
57267c3ee0 | ||
|
|
d3d133ed1f | ||
|
|
f5240abbe5 | ||
|
|
abf5840f97 | ||
|
|
dc6d5af4aa | ||
|
|
0b88cd69f2 | ||
|
|
ce165719d6 | ||
|
|
4f543ce20f | ||
|
|
55f957df21 | ||
|
|
38f59b9410 | ||
|
|
ebe6655349 | ||
|
|
038ea08ca7 | ||
|
|
ba424efd39 | ||
|
|
75aef0e60b | ||
|
|
eda8b34297 | ||
|
|
d8151ddb2e | ||
|
|
7925228f97 | ||
|
|
a7dc62aaa0 | ||
|
|
632dbd155b | ||
|
|
fe092bb7a5 | ||
|
|
928345c8ea | ||
|
|
52d6fb51d5 | ||
|
|
06d7c69487 | ||
|
|
756c7f81ca | ||
|
|
d7af57a95e | ||
|
|
722ff15fbd | ||
|
|
f9c469497e | ||
|
|
7ecbedb48a | ||
|
|
76878f66b9 | ||
|
|
b9afef50c4 | ||
|
|
83ebd1e649 | ||
|
|
76431c3fd5 | ||
|
|
96a4d0bbb0 | ||
|
|
4cfc739730 | ||
|
|
fcd0d8d359 | ||
|
|
3fcac0ac35 | ||
|
|
fcc8a7f0ed | ||
|
|
6950ead041 | ||
|
|
f78c49fc82 | ||
|
|
5f2581020b | ||
|
|
2fb674ae85 | ||
|
|
a95bd906bc | ||
|
|
21795cf788 | ||
|
|
6e98fd9403 | ||
|
|
ead1edc2b9 | ||
|
|
db822cb876 | ||
|
|
65bfce43c0 | ||
|
|
50fc05ab52 | ||
|
|
c9cf5c486f | ||
|
|
379f4b9dff | ||
|
|
aa02b8d433 | ||
|
|
70ecb92e82 | ||
|
|
d5cc2a2eed | ||
|
|
2b91bd24c5 | ||
|
|
5e8ac1b48e | ||
|
|
dc86170ef5 | ||
|
|
0232cf5b4c | ||
|
|
6e73f7f2e4 | ||
|
|
61c43804e3 | ||
|
|
72421d692b | ||
|
|
f801bb98cd | ||
|
|
b2d111e49a | ||
|
|
c82e02218f | ||
|
|
29f64076de | ||
|
|
393c334b12 | ||
|
|
678b264688 | ||
|
|
2620bfbf08 | ||
|
|
18c32decad | ||
|
|
a6f9e5f0af | ||
|
|
f187040b7e | ||
|
|
5510321776 | ||
|
|
69691b2ca7 | ||
|
|
8bfc1a7c06 | ||
|
|
554222abc7 | ||
|
|
b1a1aeeb75 | ||
|
|
91acd4cb6a | ||
|
|
6c5a1c317a | ||
|
|
b09a9f871e | ||
|
|
b5506f006b | ||
|
|
a6c3594448 | ||
|
|
5dd3952230 | ||
|
|
22ec0f8826 | ||
|
|
da6e04bb1a | ||
|
|
aaeacad781 | ||
|
|
b539f40fa5 | ||
|
|
fae340afcb | ||
|
|
69ebff1a7a | ||
|
|
5d9cfc393e | ||
|
|
e2a256b31c | ||
|
|
4855af7e57 | ||
|
|
a664174c02 | ||
|
|
c19c13b4e2 | ||
|
|
266b99bc25 | ||
|
|
51ef24e1fb | ||
|
|
33d38ccf40 | ||
|
|
f470ebbbe0 | ||
|
|
11bd46b200 | ||
|
|
53f5674771 | ||
|
|
c53d88902c | ||
|
|
e342c4fd65 | ||
|
|
aab7bd5e28 | ||
|
|
3adefb9e49 | ||
|
|
1bfce6716c | ||
|
|
c904441787 | ||
|
|
b7f79ae034 | ||
|
|
2d63fcdc7f | ||
|
|
c1d0cabcfb | ||
|
|
166419b13a | ||
|
|
cfc4d3acc7 | ||
|
|
13a0c2cf43 | ||
|
|
6ef6975432 | ||
|
|
2c40e93d3b | ||
|
|
a30ae4fb38 | ||
|
|
5b8785d1a9 | ||
|
|
f6f3364269 | ||
|
|
2f93f4450f | ||
|
|
2ad7c2b1ce | ||
|
|
6c848199ed | ||
|
|
76aab722b8 | ||
|
|
618d5d837c | ||
|
|
d234e8969d | ||
|
|
1be77b3fea | ||
|
|
5831dd6196 | ||
|
|
423d31f227 | ||
|
|
fbb063030d | ||
|
|
1968726cfe |
2
.github/workflows/development-build.yml
vendored
2
.github/workflows/development-build.yml
vendored
@@ -13,7 +13,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: [self-hosted, x64]
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
|
|||||||
2
.github/workflows/production-build.yml
vendored
2
.github/workflows/production-build.yml
vendored
@@ -10,7 +10,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: [self-hosted, x64]
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
|
|||||||
22
.tinkerwell/snippets/DeleteUser.php
Normal file
22
.tinkerwell/snippets/DeleteUser.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
$email = 'test@example.com';
|
||||||
|
$user = User::whereEmail($email)->first();
|
||||||
|
$teams = $user->teams;
|
||||||
|
foreach ($teams as $team) {
|
||||||
|
$servers = $team->servers;
|
||||||
|
if ($servers->count() > 0) {
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
dump($server);
|
||||||
|
$server->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dump($team);
|
||||||
|
$team->delete();
|
||||||
|
}
|
||||||
|
if ($user) {
|
||||||
|
dump($user);
|
||||||
|
$user->delete();
|
||||||
|
}
|
||||||
28
.tinkerwell/snippets/SendEmail.php
Normal file
28
.tinkerwell/snippets/SendEmail.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @label Send Email
|
||||||
|
* @description Send email to all users
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
|
||||||
|
set_transanctional_email_settings();
|
||||||
|
|
||||||
|
$users = User::whereEmail('test@example.com');
|
||||||
|
foreach ($users as $user) {
|
||||||
|
Mail::send([], [], function ($message) use ($user) {
|
||||||
|
$message
|
||||||
|
->to($user->email)
|
||||||
|
->subject("Testing")
|
||||||
|
->text(
|
||||||
|
<<<EOF
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
Welcome to Coolify Cloud.
|
||||||
|
Here is your user id: $user->id
|
||||||
|
|
||||||
|
EOF
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,13 +6,14 @@
|
|||||||
You can ask for guidance anytime on our
|
You can ask for guidance anytime on our
|
||||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
||||||
|
|
||||||
|
## Code Contribution
|
||||||
|
|
||||||
## 1) Setup your development environment
|
### 1) Setup your development environment
|
||||||
|
|
||||||
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
||||||
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
||||||
|
|
||||||
## 2) Set your environment variables
|
### 2) Set your environment variables
|
||||||
|
|
||||||
- Copy [.env.development.example](./.env.development.example) to .env.
|
- Copy [.env.development.example](./.env.development.example) to .env.
|
||||||
|
|
||||||
@@ -23,9 +24,14 @@ You can ask for guidance anytime on our
|
|||||||
|
|
||||||
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
|
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
|
||||||
|
|
||||||
## 4) Start development
|
### 4) Start development
|
||||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
||||||
|
|
||||||
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
||||||
|
|
||||||
Mails are caught by Mailpit: `localhost:8025`
|
Mails are caught by Mailpit: `localhost:8025`
|
||||||
|
|
||||||
|
|
||||||
|
## New Service Contribution
|
||||||
|
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).
|
||||||
|
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -4,42 +4,48 @@ Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Verc
|
|||||||
|
|
||||||
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
|
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
|
||||||
|
|
||||||
Image if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
||||||
|
|
||||||
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
||||||
|
|
||||||
For more information, take a look at our landing page [here](https://coolify.io).
|
For more information, take a look at our landing page [here](https://coolify.io).
|
||||||
|
|
||||||
> If you are looking for previous (v3) version, it is [here](https://github.com/coollabsio/coolify/tree/v3).
|
# Donations
|
||||||
|
To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
|
||||||
|
|
||||||
|
https://coolify.io/sponsorships
|
||||||
|
|
||||||
|
Thank you so much!
|
||||||
|
|
||||||
# Cloud
|
# Cloud
|
||||||
|
|
||||||
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
||||||
|
|
||||||
You can easily attach your own servers, get all the automations, free email notifications, etc.
|
|
||||||
|
|
||||||
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
||||||
|
|
||||||
# Beta
|
## Why should I use the Cloud version?
|
||||||
|
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
|
||||||
|
|
||||||
The latest version (v4) is still in beta. That does not mean it is unstable. All the features that are available are stable enough be usable in real-life.
|
By subscribing to the cloud version, you get the Coolify server for the same price, but with:
|
||||||
|
- High-availability
|
||||||
There are hundreds of people using it for managing their client's applications, freelancers, hobbyists, businesses.
|
- Free email notifications
|
||||||
|
- Better support
|
||||||
|
- Less maintenance for you
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
```
|
```
|
||||||
|
You can find the installation script source [here](./scripts/install.sh).
|
||||||
|
|
||||||
You can find the installation script [here](./scripts/install.sh).
|
# Support
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
Contact us [here](https://coolify.io/docs/contact).
|
Contact us [here](https://coolify.io/docs/contact).
|
||||||
|
|
||||||
## Recognitions
|
# Recognitions
|
||||||
|
|
||||||
|
<p>
|
||||||
<a href="https://news.ycombinator.com/item?id=26624341">
|
<a href="https://news.ycombinator.com/item?id=26624341">
|
||||||
<img
|
<img
|
||||||
style="width: 250px; height: 54px;" width="250" height="54"
|
style="width: 250px; height: 54px;" width="250" height="54"
|
||||||
@@ -47,14 +53,17 @@ Contact us [here](https://coolify.io/docs/contact).
|
|||||||
src="https://hackernews-badge.vercel.app/api?id=26624341"
|
src="https://hackernews-badge.vercel.app/api?id=26624341"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
## 💰 Financial Contributors
|
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
|
# 💰 Financial Contributors
|
||||||
|
|
||||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
||||||
|
|
||||||
### Organizations
|
## Organizations
|
||||||
|
|
||||||
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
|
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
|
||||||
|
|
||||||
@@ -74,10 +83,10 @@ Support this project with your organization. Your logo will show up here with a
|
|||||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||||
|
|
||||||
### Individuals
|
## Individuals
|
||||||
|
|
||||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||||
|
|
||||||
## Star History
|
# Star History
|
||||||
|
|
||||||
[](https://star-history.com/#coollabsio/coolify&Date)
|
[](https://star-history.com/#coollabsio/coolify&Date)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Application;
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopApplication
|
class StopApplication
|
||||||
@@ -12,19 +11,34 @@ class StopApplication
|
|||||||
public function handle(Application $application)
|
public function handle(Application $application)
|
||||||
{
|
{
|
||||||
$server = $application->destination->server;
|
$server = $application->destination->server;
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id);
|
if ($server->isSwarm()) {
|
||||||
if ($containers->count() > 0) {
|
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
||||||
foreach ($containers as $container) {
|
} else {
|
||||||
$containerName = data_get($container, 'Names');
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
if ($containerName) {
|
if ($containers->count() > 0) {
|
||||||
instant_remote_process(
|
foreach ($containers as $container) {
|
||||||
["docker rm -f {$containerName}"],
|
$containerName = data_get($container, 'Names');
|
||||||
$server
|
if ($containerName) {
|
||||||
);
|
instant_remote_process(
|
||||||
|
["docker rm -f {$containerName}"],
|
||||||
|
$server
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: make notification for application
|
||||||
|
// $application->environment->project->team->notify(new StatusChanged($application));
|
||||||
|
}
|
||||||
|
// Delete Preview Deployments
|
||||||
|
$previewDeployments = $application->previews;
|
||||||
|
foreach ($previewDeployments as $previewDeployment) {
|
||||||
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id);
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$name = str_replace('/', '', $container['Names']);
|
||||||
|
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: make notification for application
|
|
||||||
// $application->environment->project->team->notify(new StatusChanged($application));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -11,15 +15,53 @@ class StartDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||||
{
|
{
|
||||||
$internalPort = null;
|
$internalPort = null;
|
||||||
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
|
$type = $database->getMorphClass();
|
||||||
$internalPort = 6379;
|
$network = data_get($database, 'destination.network');
|
||||||
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
|
$server = data_get($database, 'destination.server');
|
||||||
$internalPort = 5432;
|
$containerName = data_get($database, 'uuid');
|
||||||
|
$proxyContainerName = "{$database->uuid}-proxy";
|
||||||
|
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$databaseType = $database->databaseType();
|
||||||
|
$network = data_get($database, 'service.destination.network');
|
||||||
|
$server = data_get($database, 'service.destination.server');
|
||||||
|
$proxyContainerName = "{$database->service->uuid}-proxy";
|
||||||
|
switch ($databaseType) {
|
||||||
|
case 'standalone-mariadb':
|
||||||
|
$type = 'App\Models\StandaloneMariadb';
|
||||||
|
$containerName = "mariadb-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-mongodb':
|
||||||
|
$type = 'App\Models\StandaloneMongodb';
|
||||||
|
$containerName = "mongodb-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-mysql':
|
||||||
|
$type = 'App\Models\StandaloneMysql';
|
||||||
|
$containerName = "mysql-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-postgresql':
|
||||||
|
$type = 'App\Models\StandalonePostgresql';
|
||||||
|
$containerName = "postgresql-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-redis':
|
||||||
|
$type = 'App\Models\StandaloneRedis';
|
||||||
|
$containerName = "redis-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($type === 'App\Models\StandaloneRedis') {
|
||||||
|
$internalPort = 6379;
|
||||||
|
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||||
|
$internalPort = 5432;
|
||||||
|
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||||
|
$internalPort = 27017;
|
||||||
|
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||||
|
$internalPort = 3306;
|
||||||
|
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||||
|
$internalPort = 3306;
|
||||||
}
|
}
|
||||||
$containerName = "{$database->uuid}-proxy";
|
|
||||||
$configuration_dir = database_proxy_dir($database->uuid);
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
$nginxconf = <<<EOF
|
$nginxconf = <<<EOF
|
||||||
user nginx;
|
user nginx;
|
||||||
@@ -33,7 +75,7 @@ class StartDatabaseProxy
|
|||||||
stream {
|
stream {
|
||||||
server {
|
server {
|
||||||
listen $database->public_port;
|
listen $database->public_port;
|
||||||
proxy_pass $database->uuid:$internalPort;
|
proxy_pass $containerName:$internalPort;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF;
|
EOF;
|
||||||
@@ -45,19 +87,19 @@ class StartDatabaseProxy
|
|||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
'version' => '3.8',
|
||||||
'services' => [
|
'services' => [
|
||||||
$containerName => [
|
$proxyContainerName => [
|
||||||
'build' => [
|
'build' => [
|
||||||
'context' => $configuration_dir,
|
'context' => $configuration_dir,
|
||||||
'dockerfile' => 'Dockerfile',
|
'dockerfile' => 'Dockerfile',
|
||||||
],
|
],
|
||||||
'image' => "nginx:stable-alpine",
|
'image' => "nginx:stable-alpine",
|
||||||
'container_name' => $containerName,
|
'container_name' => $proxyContainerName,
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'ports' => [
|
'ports' => [
|
||||||
"$database->public_port:$database->public_port",
|
"$database->public_port:$database->public_port",
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$database->destination->network,
|
$network,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
@@ -72,9 +114,9 @@ class StartDatabaseProxy
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$database->destination->network => [
|
$network => [
|
||||||
'external' => true,
|
'external' => true,
|
||||||
'name' => $database->destination->network,
|
'name' => $network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -87,7 +129,8 @@ class StartDatabaseProxy
|
|||||||
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
||||||
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||||
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||||
|
"docker compose --project-directory {$configuration_dir} pull",
|
||||||
"docker compose --project-directory {$configuration_dir} up --build -d",
|
"docker compose --project-directory {$configuration_dir} up --build -d",
|
||||||
], $database->destination->server);
|
], $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
170
app/Actions/Database/StartMariadb.php
Normal file
170
app/Actions/Database/StartMariadb.php
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartMariadb
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneMariadb $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneMariadb $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$container_name = $this->database->uuid;
|
||||||
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
|
$this->commands = [
|
||||||
|
"echo 'Starting {$database->name}.'",
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
];
|
||||||
|
|
||||||
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
$this->add_custom_mysql();
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '5s',
|
||||||
|
'retries' => 10,
|
||||||
|
'start_period' => '5s'
|
||||||
|
],
|
||||||
|
'mem_limit' => $this->database->limits_memory,
|
||||||
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $this->database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "true",
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
|
}
|
||||||
|
if (count($persistent_storages) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
|
}
|
||||||
|
if (count($volume_names) > 0) {
|
||||||
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
}
|
||||||
|
if (!is_null($this->database->mariadb_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $database->destination->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
||||||
|
}
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
|
}
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_mysql()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->mariadb_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'custom-config.cnf';
|
||||||
|
$content = $this->database->mariadb_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
|
}
|
||||||
190
app/Actions/Database/StartMongodb.php
Normal file
190
app/Actions/Database/StartMongodb.php
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartMongodb
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneMongodb $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneMongodb $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$startCommand = "mongod";
|
||||||
|
|
||||||
|
$container_name = $this->database->uuid;
|
||||||
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
|
$this->commands = [
|
||||||
|
"echo 'Starting {$database->name}.'",
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
];
|
||||||
|
|
||||||
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
$this->add_custom_mongo_conf();
|
||||||
|
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'command' => $startCommand,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => [
|
||||||
|
'CMD-SHELL',
|
||||||
|
'mongosh --eval "printjson(db.runCommand(\"ping\"))"'
|
||||||
|
],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '5s',
|
||||||
|
'retries' => 10,
|
||||||
|
'start_period' => '5s'
|
||||||
|
],
|
||||||
|
'mem_limit' => $this->database->limits_memory,
|
||||||
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $this->database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "true",
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
|
}
|
||||||
|
if (count($persistent_storages) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
|
}
|
||||||
|
if (count($volume_names) > 0) {
|
||||||
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
}
|
||||||
|
if (!is_null($this->database->mongo_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/mongod.conf',
|
||||||
|
'target' => '/etc/mongo/mongod.conf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
|
||||||
|
}
|
||||||
|
$this->add_default_database();
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
|
||||||
|
'target' => '/docker-entrypoint-initdb.d',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $database->destination->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||||
|
}
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_mongo_conf()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->mongo_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'mongod.conf';
|
||||||
|
$content = $this->database->mongo_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
|
private function add_default_database()
|
||||||
|
{
|
||||||
|
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js";
|
||||||
|
}
|
||||||
|
}
|
||||||
170
app/Actions/Database/StartMysql.php
Normal file
170
app/Actions/Database/StartMysql.php
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartMysql
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneMysql $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneMysql $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$container_name = $this->database->uuid;
|
||||||
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
|
$this->commands = [
|
||||||
|
"echo 'Starting {$database->name}.'",
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
];
|
||||||
|
|
||||||
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
$this->add_custom_mysql();
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '5s',
|
||||||
|
'retries' => 10,
|
||||||
|
'start_period' => '5s'
|
||||||
|
],
|
||||||
|
'mem_limit' => $this->database->limits_memory,
|
||||||
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $this->database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "true",
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
|
}
|
||||||
|
if (count($persistent_storages) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
|
}
|
||||||
|
if (count($volume_names) > 0) {
|
||||||
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
}
|
||||||
|
if (!is_null($this->database->mysql_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $database->destination->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
||||||
|
}
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
|
}
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_mysql()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->mysql_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'custom-config.cnf';
|
||||||
|
$content = $this->database->mysql_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@@ -17,14 +16,14 @@ class StartPostgresql
|
|||||||
public array $init_scripts = [];
|
public array $init_scripts = [];
|
||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
public function handle(Server $server, StandalonePostgresql $database)
|
public function handle(StandalonePostgresql $database)
|
||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo '####### Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
||||||
];
|
];
|
||||||
@@ -33,6 +32,8 @@ class StartPostgresql
|
|||||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->generate_init_scripts();
|
$this->generate_init_scripts();
|
||||||
|
$this->add_custom_conf();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
'version' => '3.8',
|
||||||
'services' => [
|
'services' => [
|
||||||
@@ -65,7 +66,7 @@ class StartPostgresql
|
|||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => $this->database->limits_cpus,
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
'cpuset' => $this->database->limits_cpuset,
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
@@ -78,6 +79,17 @@ class StartPostgresql
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
ray('Log Drain Enabled');
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "true",
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
}
|
}
|
||||||
@@ -97,14 +109,29 @@ class StartPostgresql
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!is_null($this->database->postgres_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/custom-postgres.conf',
|
||||||
|
'target' => '/etc/postgresql/postgresql.conf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
$docker_compose['services'][$container_name]['command'] = [
|
||||||
|
'postgres',
|
||||||
|
'-c',
|
||||||
|
'config_file=/etc/postgresql/postgresql.conf',
|
||||||
|
];
|
||||||
|
}
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
return remote_process($this->commands, $server);
|
return remote_process($this->commands, $database->destination->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
private function generate_local_persistent_volumes()
|
||||||
@@ -145,6 +172,9 @@ class StartPostgresql
|
|||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||||
}
|
}
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
||||||
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||||
@@ -169,4 +199,14 @@ class StartPostgresql
|
|||||||
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function add_custom_conf()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->postgres_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'custom-postgres.conf';
|
||||||
|
$content = $this->database->postgres_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@@ -17,7 +16,7 @@ class StartRedis
|
|||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
|
||||||
public function handle(Server $server, StandaloneRedis $database)
|
public function handle(StandaloneRedis $database)
|
||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ class StartRedis
|
|||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo '####### Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -66,7 +65,7 @@ class StartRedis
|
|||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => $this->database->limits_cpus,
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
'cpuset' => $this->database->limits_cpuset,
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
@@ -79,6 +78,16 @@ class StartRedis
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||||
|
'fluentd-async' => "true",
|
||||||
|
'fluentd-sub-second-precision' => "true",
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
}
|
}
|
||||||
@@ -102,9 +111,11 @@ class StartRedis
|
|||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
$this->commands[] = "echo '{$database->name} started.'";
|
||||||
return remote_process($this->commands, $server);
|
return remote_process($this->commands, $database->destination->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
private function generate_local_persistent_volumes()
|
||||||
@@ -155,6 +166,5 @@ class StartRedis
|
|||||||
$content = $this->database->redis_conf;
|
$content = $this->database->redis_conf;
|
||||||
$content_base64 = base64_encode($content);
|
$content_base64 = base64_encode($content);
|
||||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopDatabase
|
class StopDatabase
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||||
{
|
{
|
||||||
$server = $database->destination->server;
|
$server = $database->destination->server;
|
||||||
instant_remote_process(
|
instant_remote_process(
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -10,9 +14,13 @@ class StopDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||||
{
|
{
|
||||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
$server = data_get($database, 'destination.server');
|
||||||
|
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$server = data_get($database, 'service.server');
|
||||||
|
}
|
||||||
|
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $server);
|
||||||
$database->is_public = false;
|
$database->is_public = false;
|
||||||
$database->save();
|
$database->save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,26 @@ namespace App\Actions\License;
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
|
||||||
class CheckResaleLicense
|
class CheckResaleLicense
|
||||||
{
|
{
|
||||||
public function __invoke()
|
use AsAction;
|
||||||
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
$settings->update([
|
|
||||||
'is_resale_license_active' => false,
|
|
||||||
]);
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
$settings->update([
|
||||||
|
'is_resale_license_active' => true,
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!$settings->resale_license) {
|
// if (!$settings->resale_license) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
$base_url = config('coolify.license_url');
|
$base_url = config('coolify.license_url');
|
||||||
if (isDev()) {
|
|
||||||
$base_url = 'http://host.docker.internal:8787';
|
|
||||||
}
|
|
||||||
$instance_id = config('app.id');
|
$instance_id = config('app.id');
|
||||||
|
|
||||||
ray("Checking license key against $base_url/lemon/validate");
|
ray("Checking license key against $base_url/lemon/validate");
|
||||||
|
|||||||
@@ -17,35 +17,42 @@ class CheckProxy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$status = getContainerStatus($server, 'coolify-proxy');
|
if ($server->isSwarm()) {
|
||||||
if ($status === 'running') {
|
$status = getContainerStatus($server, 'coolify-proxy_traefik');
|
||||||
$server->proxy->set('status', 'running');
|
$server->proxy->set('status', $status);
|
||||||
$server->save();
|
$server->save();
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
$ip = $server->ip;
|
$status = getContainerStatus($server, 'coolify-proxy');
|
||||||
if ($server->id === 0) {
|
if ($status === 'running') {
|
||||||
$ip = 'host.docker.internal';
|
$server->proxy->set('status', 'running');
|
||||||
}
|
$server->save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$ip = $server->ip;
|
||||||
|
if ($server->id === 0) {
|
||||||
|
$ip = 'host.docker.internal';
|
||||||
|
}
|
||||||
|
|
||||||
$connection80 = @fsockopen($ip, '80');
|
$connection80 = @fsockopen($ip, '80');
|
||||||
$connection443 = @fsockopen($ip, '443');
|
$connection443 = @fsockopen($ip, '443');
|
||||||
$port80 = is_resource($connection80) && fclose($connection80);
|
$port80 = is_resource($connection80) && fclose($connection80);
|
||||||
$port443 = is_resource($connection443) && fclose($connection443);
|
$port443 = is_resource($connection443) && fclose($connection443);
|
||||||
if ($port80) {
|
if ($port80) {
|
||||||
if ($fromUI) {
|
if ($fromUI) {
|
||||||
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if ($port443) {
|
||||||
if ($port443) {
|
if ($fromUI) {
|
||||||
if ($fromUI) {
|
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||||
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
} else {
|
||||||
} else {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class StartProxy
|
|||||||
public function handle(Server $server, bool $async = true): string|Activity
|
public function handle(Server $server, bool $async = true): string|Activity
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
@@ -24,18 +25,29 @@ class StartProxy
|
|||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
$commands = $commands->merge([
|
if ($server->isSwarm()) {
|
||||||
"mkdir -p $proxy_path && cd $proxy_path",
|
$commands = $commands->merge([
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"mkdir -p $proxy_path && cd $proxy_path",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
'docker compose pull',
|
"echo 'Starting coolify-proxy.'",
|
||||||
"echo 'Stopping existing coolify-proxy.'",
|
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
||||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
"echo 'Proxy started successfully.'"
|
||||||
"echo 'Starting coolify-proxy.'",
|
]);
|
||||||
'docker compose up -d --remove-orphans',
|
} else {
|
||||||
"echo 'Proxy started successfully.'"
|
$commands = $commands->merge([
|
||||||
]);
|
"mkdir -p $proxy_path && cd $proxy_path",
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
"echo 'Creating required Docker Compose file.'",
|
||||||
|
"echo 'Pulling docker image.'",
|
||||||
|
'docker compose pull',
|
||||||
|
"echo 'Stopping existing coolify-proxy.'",
|
||||||
|
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||||
|
"echo 'Starting coolify-proxy.'",
|
||||||
|
'docker compose up -d --remove-orphans',
|
||||||
|
"echo 'Proxy started successfully.'"
|
||||||
|
]);
|
||||||
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
|
}
|
||||||
|
|
||||||
if ($async) {
|
if ($async) {
|
||||||
$activity = remote_process($commands, $server);
|
$activity = remote_process($commands, $server);
|
||||||
return $activity;
|
return $activity;
|
||||||
@@ -46,11 +58,9 @@ class StartProxy
|
|||||||
$server->save();
|
$server->save();
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
} catch(\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ class InstallDocker
|
|||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
|
$supported_os_type = $server->validateOS();
|
||||||
|
if (!$supported_os_type) {
|
||||||
|
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/servers#install-docker-engine-manually">documentation</a>.');
|
||||||
|
}
|
||||||
|
ray('Installing Docker on server: ' . $server->name . ' (' . $server->ip . ')' . ' with OS type: ' . $supported_os_type);
|
||||||
$dockerVersion = '24.0';
|
$dockerVersion = '24.0';
|
||||||
$config = base64_encode('{
|
$config = base64_encode('{
|
||||||
"log-driver": "json-file",
|
"log-driver": "json-file",
|
||||||
@@ -27,36 +32,65 @@ class InstallDocker
|
|||||||
'server_id' => $server->id,
|
'server_id' => $server->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
$command = collect([]);
|
||||||
if (isDev() && $server->id === 0) {
|
if (isDev() && $server->id === 0) {
|
||||||
$command = [
|
$command = $command->merge([
|
||||||
"echo '####### Installing Prerequisites...'",
|
"echo 'Installing Prerequisites...'",
|
||||||
"sleep 1",
|
"sleep 1",
|
||||||
"echo '####### Installing/updating Docker Engine...'",
|
"echo 'Installing Docker Engine...'",
|
||||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||||
"sleep 4",
|
"sleep 4",
|
||||||
"echo '####### Restarting Docker Engine...'",
|
"echo 'Restarting Docker Engine...'",
|
||||||
"ls -l /tmp"
|
"ls -l /tmp"
|
||||||
];
|
]);
|
||||||
} else {
|
} else {
|
||||||
$command = [
|
if ($supported_os_type->contains('debian')) {
|
||||||
"echo '####### Installing Prerequisites...'",
|
$command = $command->merge([
|
||||||
"command -v jq >/dev/null || apt-get update",
|
"echo 'Installing Prerequisites...'",
|
||||||
"command -v jq >/dev/null || apt install -y jq",
|
"command -v jq >/dev/null || apt-get update -y",
|
||||||
"echo '####### Installing/updating Docker Engine...'",
|
"command -v jq >/dev/null || apt install -y curl wget git jq",
|
||||||
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh",
|
|
||||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
]);
|
||||||
|
} else if ($supported_os_type->contains('rhel')) {
|
||||||
|
$command = $command->merge([
|
||||||
|
"echo 'Installing Prerequisites...'",
|
||||||
|
"command -v jq >/dev/null || dnf install -y curl wget git jq",
|
||||||
|
]);
|
||||||
|
} else if ($supported_os_type->contains('sles')) {
|
||||||
|
$command = $command->merge([
|
||||||
|
"echo 'Installing Prerequisites...'",
|
||||||
|
"command -v jq >/dev/null || zypper update -y",
|
||||||
|
"command -v jq >/dev/null || zypper install -y curl wget git jq",
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unsupported OS');
|
||||||
|
}
|
||||||
|
$command = $command->merge([
|
||||||
|
"echo 'Installing Docker Engine...'",
|
||||||
|
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
|
||||||
|
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||||
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
||||||
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
||||||
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
|
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
|
||||||
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
|
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
|
||||||
"echo '####### Restarting Docker Engine...'",
|
"echo 'Restarting Docker Engine...'",
|
||||||
|
"systemctl enable docker >/dev/null 2>&1 || true",
|
||||||
"systemctl restart docker",
|
"systemctl restart docker",
|
||||||
"echo '####### Creating default Docker network (coolify)...'",
|
]);
|
||||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
if ($server->isSwarm()) {
|
||||||
"echo '####### Done!'"
|
$command = $command->merge([
|
||||||
];
|
"docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true",
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$command = $command->merge([
|
||||||
|
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||||
|
]);
|
||||||
|
$command = $command->merge([
|
||||||
|
"echo 'Done!'",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return remote_process($command, $server);
|
||||||
}
|
}
|
||||||
return remote_process($command, $server);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
211
app/Actions/Server/InstallLogDrain.php
Normal file
211
app/Actions/Server/InstallLogDrain.php
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use App\Models\Server;
|
||||||
|
|
||||||
|
class InstallLogDrain
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
if ($server->settings->is_logdrain_newrelic_enabled) {
|
||||||
|
$type = 'newrelic';
|
||||||
|
} else if ($server->settings->is_logdrain_highlight_enabled) {
|
||||||
|
$type = 'highlight';
|
||||||
|
} else if ($server->settings->is_logdrain_axiom_enabled) {
|
||||||
|
$type = 'axiom';
|
||||||
|
} else if ($server->settings->is_logdrain_custom_enabled) {
|
||||||
|
$type = 'custom';
|
||||||
|
} else {
|
||||||
|
$type = 'none';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if ($type === 'none') {
|
||||||
|
$command = [
|
||||||
|
"echo 'Stopping old Fluent Bit'",
|
||||||
|
"docker rm -f coolify-log-drain || true",
|
||||||
|
];
|
||||||
|
return instant_remote_process($command, $server);
|
||||||
|
} else if ($type === 'newrelic') {
|
||||||
|
if (!$server->settings->is_logdrain_newrelic_enabled) {
|
||||||
|
throw new \Exception('New Relic log drain is not enabled.');
|
||||||
|
}
|
||||||
|
$config = base64_encode("
|
||||||
|
[SERVICE]
|
||||||
|
Flush 5
|
||||||
|
Daemon off
|
||||||
|
Tag container_logs
|
||||||
|
Log_Level debug
|
||||||
|
Parsers_File parsers.conf
|
||||||
|
[INPUT]
|
||||||
|
Name forward
|
||||||
|
Buffer_Chunk_Size 1M
|
||||||
|
Buffer_Max_Size 6M
|
||||||
|
[FILTER]
|
||||||
|
Name grep
|
||||||
|
Match *
|
||||||
|
Exclude log 127.0.0.1
|
||||||
|
[FILTER]
|
||||||
|
Name modify
|
||||||
|
Match *
|
||||||
|
Set server_name {$server->name}
|
||||||
|
[OUTPUT]
|
||||||
|
Name nrlogs
|
||||||
|
Match *
|
||||||
|
license_key \${LICENSE_KEY}
|
||||||
|
# https://log-api.eu.newrelic.com/log/v1 - EU
|
||||||
|
# https://log-api.newrelic.com/log/v1 - US
|
||||||
|
base_uri \${BASE_URI}
|
||||||
|
");
|
||||||
|
} else if ($type === 'highlight') {
|
||||||
|
if (!$server->settings->is_logdrain_highlight_enabled) {
|
||||||
|
throw new \Exception('Highlight log drain is not enabled.');
|
||||||
|
}
|
||||||
|
$config = base64_encode("
|
||||||
|
[SERVICE]
|
||||||
|
Flush 5
|
||||||
|
Daemon off
|
||||||
|
Log_Level debug
|
||||||
|
Parsers_File parsers.conf
|
||||||
|
[INPUT]
|
||||||
|
Name forward
|
||||||
|
tag \${HIGHLIGHT_PROJECT_ID}
|
||||||
|
Buffer_Chunk_Size 1M
|
||||||
|
Buffer_Max_Size 6M
|
||||||
|
[OUTPUT]
|
||||||
|
Name forward
|
||||||
|
Match *
|
||||||
|
Host otel.highlight.io
|
||||||
|
Port 24224
|
||||||
|
");
|
||||||
|
} else if ($type === 'axiom') {
|
||||||
|
if (!$server->settings->is_logdrain_axiom_enabled) {
|
||||||
|
throw new \Exception('Axiom log drain is not enabled.');
|
||||||
|
}
|
||||||
|
$config = base64_encode("
|
||||||
|
[SERVICE]
|
||||||
|
Flush 5
|
||||||
|
Daemon off
|
||||||
|
Log_Level debug
|
||||||
|
Parsers_File parsers.conf
|
||||||
|
[INPUT]
|
||||||
|
Name forward
|
||||||
|
Buffer_Chunk_Size 1M
|
||||||
|
Buffer_Max_Size 6M
|
||||||
|
[FILTER]
|
||||||
|
Name grep
|
||||||
|
Match *
|
||||||
|
Exclude log 127.0.0.1
|
||||||
|
[FILTER]
|
||||||
|
Name modify
|
||||||
|
Match *
|
||||||
|
Set server_name {$server->name}
|
||||||
|
[OUTPUT]
|
||||||
|
Name http
|
||||||
|
Match *
|
||||||
|
Host api.axiom.co
|
||||||
|
Port 443
|
||||||
|
URI /v1/datasets/\${AXIOM_DATASET_NAME}/ingest
|
||||||
|
# Authorization Bearer should be an API token
|
||||||
|
Header Authorization Bearer \${AXIOM_API_KEY}
|
||||||
|
compress gzip
|
||||||
|
format json
|
||||||
|
json_date_key _time
|
||||||
|
json_date_format iso8601
|
||||||
|
tls On
|
||||||
|
");
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
if (!$server->settings->is_logdrain_custom_enabled) {
|
||||||
|
throw new \Exception('Custom log drain is not enabled.');
|
||||||
|
}
|
||||||
|
$config = base64_encode($server->settings->logdrain_custom_config);
|
||||||
|
$parsers = base64_encode($server->settings->logdrain_custom_config_parser);
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unknown log drain type.');
|
||||||
|
}
|
||||||
|
if ($type !== 'custom') {
|
||||||
|
$parsers = base64_encode("
|
||||||
|
[PARSER]
|
||||||
|
Name empty_line_skipper
|
||||||
|
Format regex
|
||||||
|
Regex /^(?!\s*$).+/
|
||||||
|
");
|
||||||
|
}
|
||||||
|
$compose = base64_encode("
|
||||||
|
services:
|
||||||
|
coolify-log-drain:
|
||||||
|
image: cr.fluentbit.io/fluent/fluent-bit:2.0
|
||||||
|
container_name: coolify-log-drain
|
||||||
|
command: -c /fluent-bit.conf
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- ./fluent-bit.conf:/fluent-bit.conf
|
||||||
|
- ./parsers.conf:/parsers.conf
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:24224:24224
|
||||||
|
restart: unless-stopped
|
||||||
|
");
|
||||||
|
$readme = base64_encode('# New Relic Log Drain
|
||||||
|
This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder.
|
||||||
|
|
||||||
|
Files:
|
||||||
|
- `fluent-bit.conf` - configuration file for Fluent Bit
|
||||||
|
- `docker-compose.yml` - docker-compose file to run Fluent Bit
|
||||||
|
- `.env` - environment variables for Fluent Bit
|
||||||
|
');
|
||||||
|
$license_key = $server->settings->logdrain_newrelic_license_key;
|
||||||
|
$base_uri = $server->settings->logdrain_newrelic_base_uri;
|
||||||
|
$base_path = config('coolify.base_config_path');
|
||||||
|
|
||||||
|
$config_path = $base_path . '/log-drains';
|
||||||
|
$fluent_bit_config = $config_path . '/fluent-bit.conf';
|
||||||
|
$parsers_config = $config_path . '/parsers.conf';
|
||||||
|
$compose_path = $config_path . '/docker-compose.yml';
|
||||||
|
$readme_path = $config_path . '/README.md';
|
||||||
|
$command = [
|
||||||
|
"echo 'Saving configuration'",
|
||||||
|
"mkdir -p $config_path",
|
||||||
|
"echo '{$parsers}' | base64 -d > $parsers_config",
|
||||||
|
"echo '{$config}' | base64 -d > $fluent_bit_config",
|
||||||
|
"echo '{$compose}' | base64 -d > $compose_path",
|
||||||
|
"echo '{$readme}' | base64 -d > $readme_path",
|
||||||
|
"test -f $config_path/.env && rm $config_path/.env",
|
||||||
|
|
||||||
|
];
|
||||||
|
if ($type === 'newrelic') {
|
||||||
|
$add_envs_command = [
|
||||||
|
"echo LICENSE_KEY=$license_key >> $config_path/.env",
|
||||||
|
"echo BASE_URI=$base_uri >> $config_path/.env",
|
||||||
|
];
|
||||||
|
} else if ($type === 'highlight') {
|
||||||
|
$add_envs_command = [
|
||||||
|
"echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env",
|
||||||
|
];
|
||||||
|
} else if ($type === 'axiom') {
|
||||||
|
$add_envs_command = [
|
||||||
|
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
|
||||||
|
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
|
||||||
|
];
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
$add_envs_command = [
|
||||||
|
"touch $config_path/.env"
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unknown log drain type.');
|
||||||
|
}
|
||||||
|
$restart_command = [
|
||||||
|
"echo 'Stopping old Fluent Bit'",
|
||||||
|
"cd $config_path && docker rm -f coolify-log-drain || true",
|
||||||
|
"echo 'Starting Fluent Bit'",
|
||||||
|
"cd $config_path && docker compose up -d --remove-orphans",
|
||||||
|
];
|
||||||
|
$command = array_merge($command, $add_envs_command, $restart_command);
|
||||||
|
return instant_remote_process($command, $server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,19 +14,19 @@ class StartService
|
|||||||
$network = $service->destination->network;
|
$network = $service->destination->network;
|
||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = "cd " . $service->workdir();
|
$commands[] = "cd " . $service->workdir();
|
||||||
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
$commands[] = "echo '####### Creating Docker network.'";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "docker network create --attachable {$service->uuid} >/dev/null 2>/dev/null || true";
|
$commands[] = "docker network create --attachable '{$service->uuid}' >/dev/null 2>&1 || true";
|
||||||
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
|
$commands[] = "echo 'Starting service {$service->name} on {$service->server->name}.'";
|
||||||
$commands[] = "echo '####### Pulling images.'";
|
$commands[] = "echo 'Pulling images.'";
|
||||||
$commands[] = "docker compose pull";
|
$commands[] = "docker compose pull";
|
||||||
$commands[] = "echo '####### Starting containers.'";
|
$commands[] = "echo 'Starting containers.'";
|
||||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate";
|
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
|
||||||
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
|
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||||
$compose = data_get($service,'docker_compose',[]);
|
$compose = data_get($service,'docker_compose',[]);
|
||||||
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
|
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
|
||||||
foreach($serviceNames as $serviceName => $serviceConfig){
|
foreach($serviceNames as $serviceName => $serviceConfig){
|
||||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} 2>/dev/null || true";
|
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
||||||
}
|
}
|
||||||
$activity = remote_process($commands, $service->server);
|
$activity = remote_process($commands, $service->server);
|
||||||
return $activity;
|
return $activity;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Actions\Service;
|
|||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
|
|
||||||
class StopService
|
class StopService
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,19 +3,83 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init';
|
protected $signature = 'app:init {--cleanup}';
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$this->alive();
|
||||||
|
$cleanup = $this->option('cleanup');
|
||||||
|
if ($cleanup) {
|
||||||
|
echo "Running cleanup\n";
|
||||||
|
$this->cleanup_stucked_resources();
|
||||||
|
// $this->cleanup_ssh();
|
||||||
|
}
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
|
$this->cleanup_stucked_helper_containers();
|
||||||
}
|
}
|
||||||
|
private function cleanup_stucked_helper_containers()
|
||||||
|
{
|
||||||
|
$servers = Server::all();
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
if ($server->isFunctional()) {
|
||||||
|
CleanupHelperContainersJob::dispatch($server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function alive()
|
||||||
|
{
|
||||||
|
$id = config('app.id');
|
||||||
|
$version = config('version');
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$do_not_track = data_get($settings, 'do_not_track');
|
||||||
|
if ($do_not_track == true) {
|
||||||
|
echo "Skipping alive as do_not_track is enabled\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Http::get("https://get.coollabs.io/coolify/v4/alive?appId=$id&version=$version");
|
||||||
|
echo "I am alive!\n";
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in alive: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// private function cleanup_ssh()
|
||||||
|
// {
|
||||||
|
|
||||||
|
// TODO: it will cleanup id.root@host.docker.internal
|
||||||
|
// try {
|
||||||
|
// $files = Storage::allFiles('ssh/keys');
|
||||||
|
// foreach ($files as $file) {
|
||||||
|
// Storage::delete($file);
|
||||||
|
// }
|
||||||
|
// $files = Storage::allFiles('ssh/mux');
|
||||||
|
// foreach ($files as $file) {
|
||||||
|
// Storage::delete($file);
|
||||||
|
// }
|
||||||
|
// } catch (\Throwable $e) {
|
||||||
|
// echo "Error in cleaning ssh: {$e->getMessage()}\n";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanup_in_progress_application_deployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
@@ -30,4 +94,167 @@ class Init extends Command
|
|||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function cleanup_stucked_resources()
|
||||||
|
{
|
||||||
|
// Cleanup any resources that are not attached to any environment or destination or server
|
||||||
|
try {
|
||||||
|
$applications = Application::all();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
if (!data_get($application, 'environment')) {
|
||||||
|
echo 'Application without environment' . $application->name . 'deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
}
|
||||||
|
if (!$application->destination()) {
|
||||||
|
echo 'Application without destination' . $application->name . 'deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($application, 'destination.server')) {
|
||||||
|
echo 'Application without server' . $application->name . 'deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in application: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$postgresqls = StandalonePostgresql::all();
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
if (!data_get($postgresql, 'environment')) {
|
||||||
|
echo 'Postgresql without environment' . $postgresql->name . 'deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
}
|
||||||
|
if (!$postgresql->destination()) {
|
||||||
|
echo 'Postgresql without destination' . $postgresql->name . 'deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($postgresql, 'destination.server')) {
|
||||||
|
echo 'Postgresql without server' . $postgresql->name . 'deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in postgresql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$redis = StandaloneRedis::all();
|
||||||
|
foreach ($redis as $redis) {
|
||||||
|
if (!data_get($redis, 'environment')) {
|
||||||
|
echo 'Redis without environment' . $redis->name . 'deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
}
|
||||||
|
if (!$redis->destination()) {
|
||||||
|
echo 'Redis without destination' . $redis->name . 'deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($redis, 'destination.server')) {
|
||||||
|
echo 'Redis without server' . $redis->name . 'deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in redis: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mongodbs = StandaloneMongodb::all();
|
||||||
|
foreach ($mongodbs as $mongodb) {
|
||||||
|
if (!data_get($mongodb, 'environment')) {
|
||||||
|
echo 'Mongodb without environment' . $mongodb->name . 'deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
}
|
||||||
|
if (!$mongodb->destination()) {
|
||||||
|
echo 'Mongodb without destination' . $mongodb->name . 'deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($mongodb, 'destination.server')) {
|
||||||
|
echo 'Mongodb without server' . $mongodb->name . 'deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mongodb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mysqls = StandaloneMysql::all();
|
||||||
|
foreach ($mysqls as $mysql) {
|
||||||
|
if (!data_get($mysql, 'environment')) {
|
||||||
|
echo 'Mysql without environment' . $mysql->name . 'deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
}
|
||||||
|
if (!$mysql->destination()) {
|
||||||
|
echo 'Mysql without destination' . $mysql->name . 'deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($mysql, 'destination.server')) {
|
||||||
|
echo 'Mysql without server' . $mysql->name . 'deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mysql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mariadbs = StandaloneMariadb::all();
|
||||||
|
foreach ($mariadbs as $mariadb) {
|
||||||
|
if (!data_get($mariadb, 'environment')) {
|
||||||
|
echo 'Mariadb without environment' . $mariadb->name . 'deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
}
|
||||||
|
if (!$mariadb->destination()) {
|
||||||
|
echo 'Mariadb without destination' . $mariadb->name . 'deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($mariadb, 'destination.server')) {
|
||||||
|
echo 'Mariadb without server' . $mariadb->name . 'deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mariadb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$services = Service::all();
|
||||||
|
foreach ($services as $service) {
|
||||||
|
if (!data_get($service, 'environment')) {
|
||||||
|
echo 'Service without environment' . $service->name . 'deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
if (!$service->destination()) {
|
||||||
|
echo 'Service without destination' . $service->name . 'deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($service, 'server')) {
|
||||||
|
echo 'Service without server' . $service->name . 'deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in service: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceApplications = ServiceApplication::all();
|
||||||
|
foreach ($serviceApplications as $service) {
|
||||||
|
if (!data_get($service, 'service')) {
|
||||||
|
echo 'ServiceApplication without service' . $service->name . 'deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceDatabases = ServiceDatabase::all();
|
||||||
|
foreach ($serviceDatabases as $service) {
|
||||||
|
if (!data_get($service, 'service')) {
|
||||||
|
echo 'ServiceDatabase without service' . $service->name . 'deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
app/Console/Commands/RootChangeEmail.php
Normal file
41
app/Console/Commands/RootChangeEmail.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class RootChangeEmail extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'root:change-email';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Change Root Email';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
$this->info('You are about to change the root user\'s email.');
|
||||||
|
$email = $this->ask('Give me a new email for root user');
|
||||||
|
$this->info('Updating root email...');
|
||||||
|
try {
|
||||||
|
User::find(0)->update(['email' => $email]);
|
||||||
|
$this->info('Root user\'s email updated successfully.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error('Failed to update root user\'s email.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,14 +8,14 @@ use Illuminate\Support\Facades\Hash;
|
|||||||
|
|
||||||
use function Laravel\Prompts\password;
|
use function Laravel\Prompts\password;
|
||||||
|
|
||||||
class UsersResetRoot extends Command
|
class RootResetPassword extends Command
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'users:reset-root';
|
protected $signature = 'root:reset-password';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -12,21 +12,21 @@ use function Laravel\Prompts\confirm;
|
|||||||
use function Laravel\Prompts\multiselect;
|
use function Laravel\Prompts\multiselect;
|
||||||
use function Laravel\Prompts\select;
|
use function Laravel\Prompts\select;
|
||||||
|
|
||||||
class ResourcesDelete extends Command
|
class ServicesDelete extends Command
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'resources:delete';
|
protected $signature = 'services:delete';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Delete a resource from the database';
|
protected $description = 'Delete a service from the database';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
@@ -34,7 +34,7 @@ class ResourcesDelete extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$resource = select(
|
$resource = select(
|
||||||
'What resource do you want to delete?',
|
'What service do you want to delete?',
|
||||||
['Application', 'Database', 'Service', 'Server'],
|
['Application', 'Database', 'Service', 'Server'],
|
||||||
);
|
);
|
||||||
if ($resource === 'Application') {
|
if ($resource === 'Application') {
|
||||||
@@ -61,12 +61,14 @@ class ResourcesDelete extends Command
|
|||||||
|
|
||||||
foreach ($serversToDelete as $server) {
|
foreach ($serversToDelete as $server) {
|
||||||
$toDelete = $servers->where('id', $server)->first();
|
$toDelete = $servers->where('id', $server)->first();
|
||||||
$this->info($toDelete);
|
if ($toDelete) {
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
$this->info($toDelete);
|
||||||
if (!$confirmed) {
|
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||||
break;
|
if (!$confirmed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$toDelete->delete();
|
||||||
}
|
}
|
||||||
$toDelete->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function deleteApplication()
|
private function deleteApplication()
|
||||||
@@ -82,14 +84,15 @@ class ResourcesDelete extends Command
|
|||||||
);
|
);
|
||||||
|
|
||||||
foreach ($applicationsToDelete as $application) {
|
foreach ($applicationsToDelete as $application) {
|
||||||
ray($application);
|
|
||||||
$toDelete = $applications->where('id', $application)->first();
|
$toDelete = $applications->where('id', $application)->first();
|
||||||
$this->info($toDelete);
|
if ($toDelete) {
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
$this->info($toDelete);
|
||||||
if (!$confirmed) {
|
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
||||||
break;
|
if (!$confirmed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$toDelete->delete();
|
||||||
}
|
}
|
||||||
$toDelete->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function deleteDatabase()
|
private function deleteDatabase()
|
||||||
@@ -106,12 +109,14 @@ class ResourcesDelete extends Command
|
|||||||
|
|
||||||
foreach ($databasesToDelete as $database) {
|
foreach ($databasesToDelete as $database) {
|
||||||
$toDelete = $databases->where('id', $database)->first();
|
$toDelete = $databases->where('id', $database)->first();
|
||||||
$this->info($toDelete);
|
if ($toDelete) {
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
$this->info($toDelete);
|
||||||
if (!$confirmed) {
|
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||||
return;
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$toDelete->delete();
|
||||||
}
|
}
|
||||||
$toDelete->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function deleteService()
|
private function deleteService()
|
||||||
@@ -128,12 +133,14 @@ class ResourcesDelete extends Command
|
|||||||
|
|
||||||
foreach ($servicesToDelete as $service) {
|
foreach ($servicesToDelete as $service) {
|
||||||
$toDelete = $services->where('id', $service)->first();
|
$toDelete = $services->where('id', $service)->first();
|
||||||
$this->info($toDelete);
|
if ($toDelete) {
|
||||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
$this->info($toDelete);
|
||||||
if (!$confirmed) {
|
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||||
return;
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$toDelete->delete();
|
||||||
}
|
}
|
||||||
$toDelete->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
107
app/Console/Commands/ServicesGenerate.php
Normal file
107
app/Console/Commands/ServicesGenerate.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class ServicesGenerate extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'services:generate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
// ray()->clearAll();
|
||||||
|
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
|
||||||
|
$files = array_filter($files, function ($file) {
|
||||||
|
return strpos($file, '.yaml') !== false;
|
||||||
|
});
|
||||||
|
$serviceTemplatesJson = [];
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$parsed = $this->process_file($file);
|
||||||
|
if ($parsed) {
|
||||||
|
$name = data_get($parsed, 'name');
|
||||||
|
$parsed = data_forget($parsed, 'name');
|
||||||
|
$serviceTemplatesJson[$name] = $parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT);
|
||||||
|
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function process_file($file)
|
||||||
|
{
|
||||||
|
$serviceName = str($file)->before('.yaml')->value();
|
||||||
|
$content = file_get_contents(base_path("templates/compose/$file"));
|
||||||
|
// $this->info($content);
|
||||||
|
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
|
||||||
|
if ($ignore->count() > 0) {
|
||||||
|
$ignore = (bool)str($ignore[0])->after('# ignore:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$ignore = false;
|
||||||
|
}
|
||||||
|
if ($ignore) {
|
||||||
|
$this->info("Ignoring $file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->info("Processing $file");
|
||||||
|
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
|
||||||
|
if ($documentation->count() > 0) {
|
||||||
|
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$documentation = 'https://coolify.io/docs';
|
||||||
|
}
|
||||||
|
|
||||||
|
$slogan = collect(preg_grep('/^# slogan:/', explode("\n", $content)))->values();
|
||||||
|
if ($slogan->count() > 0) {
|
||||||
|
$slogan = str($slogan[0])->after('# slogan:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$slogan = str($file)->headline()->value();
|
||||||
|
}
|
||||||
|
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
|
||||||
|
if ($env_file->count() > 0) {
|
||||||
|
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$env_file = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tags = collect(preg_grep('/^# tags:/', explode("\n", $content)))->values();
|
||||||
|
if ($tags->count() > 0) {
|
||||||
|
$tags = str($tags[0])->after('# tags:')->trim()->explode(',')->map(function ($tag) {
|
||||||
|
return str($tag)->trim()->lower()->value();
|
||||||
|
})->values();
|
||||||
|
} else {
|
||||||
|
$tags = null;
|
||||||
|
}
|
||||||
|
$json = Yaml::parse($content);
|
||||||
|
$yaml = base64_encode(Yaml::dump($json, 10, 2));
|
||||||
|
$payload = [
|
||||||
|
'name' => $serviceName,
|
||||||
|
'documentation' => $documentation,
|
||||||
|
'slogan' => $slogan,
|
||||||
|
'compose' => $yaml,
|
||||||
|
'tags' => $tags,
|
||||||
|
];
|
||||||
|
if ($env_file) {
|
||||||
|
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
|
||||||
|
$env_file_base64 = base64_encode($env_file_content);
|
||||||
|
$payload['envs'] = $env_file_base64;
|
||||||
|
}
|
||||||
|
return $payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ class SyncBunny extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'sync:bunny {--only-template} {--only-version}';
|
protected $signature = 'sync:bunny {--templates} {--release}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -31,8 +31,8 @@ class SyncBunny extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$that = $this;
|
$that = $this;
|
||||||
$only_template = $this->option('only-template');
|
$only_template = $this->option('templates');
|
||||||
$only_version = $this->option('only-version');
|
$only_version = $this->option('release');
|
||||||
$bunny_cdn = "https://cdn.coollabs.io";
|
$bunny_cdn = "https://cdn.coollabs.io";
|
||||||
$bunny_cdn_path = "coolify";
|
$bunny_cdn_path = "coolify";
|
||||||
$bunny_cdn_storage_name = "coolcdn";
|
$bunny_cdn_storage_name = "coolcdn";
|
||||||
@@ -71,6 +71,15 @@ class SyncBunny extends Command
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
|
if (!$only_template && !$only_version) {
|
||||||
|
$this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
|
||||||
|
}
|
||||||
|
if ($only_template) {
|
||||||
|
$this->info('About to sync service-templates.json to BunnyCDN.');
|
||||||
|
}
|
||||||
|
if ($only_version) {
|
||||||
|
$this->info('About to sync versions.json to BunnyCDN.');
|
||||||
|
}
|
||||||
$confirmed = confirm('Are you sure you want to sync?');
|
$confirmed = confirm('Are you sure you want to sync?');
|
||||||
if (!$confirmed) {
|
if (!$confirmed) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2,15 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Jobs\CheckResaleLicenseJob;
|
use App\Jobs\CheckLogDrainContainerJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
use App\Jobs\DockerCleanupJob;
|
|
||||||
use App\Jobs\InstanceAutoUpdateJob;
|
use App\Jobs\InstanceAutoUpdateJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
|
use App\Jobs\PullHelperImageJob;
|
||||||
|
use App\Jobs\ServerStatusJob;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\Team;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
@@ -19,42 +21,50 @@ class Kernel extends ConsoleKernel
|
|||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
|
// Instance Jobs
|
||||||
// $schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
// Server Jobs
|
||||||
// $this->instance_auto_update($schedule);
|
|
||||||
// $this->check_scheduled_backups($schedule);
|
|
||||||
$this->check_resources($schedule);
|
|
||||||
$this->cleanup_servers($schedule);
|
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
|
$this->check_resources($schedule);
|
||||||
|
$this->check_scheduled_backups($schedule);
|
||||||
|
$this->pull_helper_image($schedule);
|
||||||
} else {
|
} else {
|
||||||
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
|
|
||||||
|
// Server Jobs
|
||||||
$this->instance_auto_update($schedule);
|
$this->instance_auto_update($schedule);
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->cleanup_servers($schedule);
|
$this->pull_helper_image($schedule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function cleanup_servers($schedule)
|
private function pull_helper_image($schedule)
|
||||||
{
|
{
|
||||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
|
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
|
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function check_resources($schedule)
|
private function check_resources($schedule)
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
|
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||||
|
$own = Team::find(0)->servers;
|
||||||
|
$servers = $servers->merge($own);
|
||||||
} else {
|
} else {
|
||||||
$servers = Server::all();
|
$servers = Server::all()->where('ip', '!=', '1.2.3.4');
|
||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
|
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
|
||||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||||
|
if ($server->isLogDrainEnabled()) {
|
||||||
|
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function instance_auto_update($schedule)
|
private function instance_auto_update($schedule)
|
||||||
@@ -69,7 +79,6 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
private function check_scheduled_backups($schedule)
|
private function check_scheduled_backups($schedule)
|
||||||
{
|
{
|
||||||
ray('check_scheduled_backups');
|
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||||
if ($scheduled_backups->isEmpty()) {
|
if ($scheduled_backups->isEmpty()) {
|
||||||
ray('no scheduled backups');
|
ray('no scheduled backups');
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ namespace App\Exceptions;
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\AuthenticationException;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
|
use RuntimeException;
|
||||||
use Sentry\Laravel\Integration;
|
use Sentry\Laravel\Integration;
|
||||||
use Sentry\State\Scope;
|
use Sentry\State\Scope;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
@@ -40,6 +42,13 @@ class Handler extends ExceptionHandler
|
|||||||
];
|
];
|
||||||
private InstanceSettings $settings;
|
private InstanceSettings $settings;
|
||||||
|
|
||||||
|
protected function unauthenticated($request, AuthenticationException $exception)
|
||||||
|
{
|
||||||
|
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
|
||||||
|
return response()->json(['message' => $exception->getMessage()], 401);
|
||||||
|
}
|
||||||
|
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Register the exception handling callbacks for the application.
|
* Register the exception handling callbacks for the application.
|
||||||
*/
|
*/
|
||||||
@@ -47,6 +56,9 @@ class Handler extends ExceptionHandler
|
|||||||
{
|
{
|
||||||
$this->reportable(function (Throwable $e) {
|
$this->reportable(function (Throwable $e) {
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
if ($e instanceof RuntimeException) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = InstanceSettings::get();
|
||||||
@@ -65,6 +77,7 @@ class Handler extends ExceptionHandler
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
ray('reporting to sentry');
|
||||||
Integration::captureUnhandledException($e);
|
Integration::captureUnhandledException($e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,23 +10,6 @@ class ApplicationController extends Controller
|
|||||||
{
|
{
|
||||||
use AuthorizesRequests, ValidatesRequests;
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
|
|
||||||
public function configuration()
|
|
||||||
{
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
|
||||||
if (!$project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
|
||||||
if (!$environment) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
|
||||||
if (!$application) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
return view('project.application.configuration', ['application' => $application]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deployments()
|
public function deployments()
|
||||||
{
|
{
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||||
@@ -41,7 +24,7 @@ class ApplicationController extends Controller
|
|||||||
if (!$application) {
|
if (!$application) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 8);
|
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40);
|
||||||
return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]);
|
return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ class Controller extends BaseController
|
|||||||
} else {
|
} else {
|
||||||
$team = $user->teams()->first();
|
$team = $user->teams()->first();
|
||||||
}
|
}
|
||||||
|
if (is_null(data_get($user, 'email_verified_at'))){
|
||||||
|
$user->email_verified_at = now();
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
session(['currentTeam' => $team]);
|
session(['currentTeam' => $team]);
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
|
|||||||
@@ -32,8 +32,14 @@ class MagicController extends Controller
|
|||||||
|
|
||||||
public function environments()
|
public function environments()
|
||||||
{
|
{
|
||||||
|
$project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first();
|
||||||
|
if (!$project) {
|
||||||
|
return response()->json([
|
||||||
|
'environments' => []
|
||||||
|
]);
|
||||||
|
}
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments
|
'environments' => $project->environments
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ class ProjectController extends Controller
|
|||||||
$database = create_standalone_postgresql($environment->id, $destination_uuid);
|
$database = create_standalone_postgresql($environment->id, $destination_uuid);
|
||||||
} else if ($type->value() === 'redis') {
|
} else if ($type->value() === 'redis') {
|
||||||
$database = create_standalone_redis($environment->id, $destination_uuid);
|
$database = create_standalone_redis($environment->id, $destination_uuid);
|
||||||
|
} else if ($type->value() === 'mongodb') {
|
||||||
|
$database = create_standalone_mongodb($environment->id, $destination_uuid);
|
||||||
|
} else if ($type->value() === 'mysql') {
|
||||||
|
$database = create_standalone_mysql($environment->id, $destination_uuid);
|
||||||
|
} else if ($type->value() === 'mariadb') {
|
||||||
|
$database = create_standalone_mariadb($environment->id, $destination_uuid);
|
||||||
}
|
}
|
||||||
return redirect()->route('project.database.configuration', [
|
return redirect()->route('project.database.configuration', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
@@ -70,12 +76,14 @@ class ProjectController extends Controller
|
|||||||
'database_uuid' => $database->uuid,
|
'database_uuid' => $database->uuid,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
|
if ($type->startsWith('one-click-service-') && !is_null((int)$server_id)) {
|
||||||
$oneClickServiceName = $type->after('one-click-service-')->value();
|
$oneClickServiceName = $type->after('one-click-service-')->value();
|
||||||
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
||||||
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
||||||
if ($oneClickDotEnvs) {
|
if ($oneClickDotEnvs) {
|
||||||
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
|
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
|
||||||
|
return !empty($value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if ($oneClickService) {
|
if ($oneClickService) {
|
||||||
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
|
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
|
||||||
@@ -96,27 +104,7 @@ class ProjectController extends Controller
|
|||||||
$generatedValue = $value;
|
$generatedValue = $value;
|
||||||
if ($value->contains('SERVICE_')) {
|
if ($value->contains('SERVICE_')) {
|
||||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||||
// TODO: make it shared with Service.php
|
$generatedValue = generateEnvValue($command->value());
|
||||||
switch ($command->value()) {
|
|
||||||
case 'PASSWORD':
|
|
||||||
$generatedValue = Str::password(symbols: false);
|
|
||||||
break;
|
|
||||||
case 'PASSWORD_64':
|
|
||||||
$generatedValue = Str::password(length: 64, symbols: false);
|
|
||||||
break;
|
|
||||||
case 'BASE64_64':
|
|
||||||
$generatedValue = Str::random(64);
|
|
||||||
break;
|
|
||||||
case 'BASE64_128':
|
|
||||||
$generatedValue = Str::random(128);
|
|
||||||
break;
|
|
||||||
case 'BASE64':
|
|
||||||
$generatedValue = Str::random(32);
|
|
||||||
break;
|
|
||||||
case 'USER':
|
|
||||||
$generatedValue = Str::random(16);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EnvironmentVariable::create([
|
EnvironmentVariable::create([
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
@@ -129,7 +117,7 @@ class ProjectController extends Controller
|
|||||||
}
|
}
|
||||||
$service->parse(isNew: true);
|
$service->parse(isNew: true);
|
||||||
|
|
||||||
return redirect()->route('project.service', [
|
return redirect()->route('project.service.configuration', [
|
||||||
'service_uuid' => $service->uuid,
|
'service_uuid' => $service->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ class Index extends Component
|
|||||||
public ?string $remoteServerHost = null;
|
public ?string $remoteServerHost = null;
|
||||||
public ?int $remoteServerPort = 22;
|
public ?int $remoteServerPort = 22;
|
||||||
public ?string $remoteServerUser = 'root';
|
public ?string $remoteServerUser = 'root';
|
||||||
|
public bool $isSwarmManager = false;
|
||||||
|
public bool $isCloudflareTunnel = false;
|
||||||
public ?Server $createdServer = null;
|
public ?Server $createdServer = null;
|
||||||
|
|
||||||
public Collection|array $projects = [];
|
public Collection $projects;
|
||||||
public ?int $selectedExistingProject = null;
|
public ?int $selectedExistingProject = null;
|
||||||
public ?Project $createdProject = null;
|
public ?Project $createdProject = null;
|
||||||
|
|
||||||
@@ -164,7 +166,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
{
|
{
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'remoteServerName' => 'required',
|
'remoteServerName' => 'required',
|
||||||
'remoteServerHost' => 'required|ip',
|
'remoteServerHost' => 'required',
|
||||||
'remoteServerPort' => 'required|integer',
|
'remoteServerPort' => 'required|integer',
|
||||||
'remoteServerUser' => 'required',
|
'remoteServerUser' => 'required',
|
||||||
]);
|
]);
|
||||||
@@ -182,13 +184,15 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
'private_key_id' => $this->createdPrivateKey->id,
|
'private_key_id' => $this->createdPrivateKey->id,
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
]);
|
||||||
$this->createdServer->save();
|
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
|
||||||
|
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||||
|
$this->createdServer->settings->save();
|
||||||
|
$this->createdServer->addInitialNetwork();
|
||||||
$this->validateServer();
|
$this->validateServer();
|
||||||
}
|
}
|
||||||
public function validateServer()
|
public function validateServer()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$customErrorMessage = "Server is not reachable:";
|
|
||||||
config()->set('coolify.mux_enabled', false);
|
config()->set('coolify.mux_enabled', false);
|
||||||
|
|
||||||
instant_remote_process(['uptime'], $this->createdServer, true);
|
instant_remote_process(['uptime'], $this->createdServer, true);
|
||||||
@@ -198,7 +202,8 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->serverReachable = false;
|
$this->serverReachable = false;
|
||||||
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
|
$this->createdServer->delete();
|
||||||
|
return handleError(error: $e, livewire: $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -206,22 +211,28 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||||
if (is_null($dockerVersion)) {
|
if (is_null($dockerVersion)) {
|
||||||
$this->currentState = 'install-docker';
|
$this->currentState = 'install-docker';
|
||||||
throw new \Exception('Docker version is not supported or not installed.');
|
throw new \Exception('Docker not found or old version is installed.');
|
||||||
}
|
}
|
||||||
$this->createdServer->settings()->update([
|
$this->createdServer->settings()->update([
|
||||||
'is_usable' => true,
|
'is_usable' => true,
|
||||||
]);
|
]);
|
||||||
$this->getProxyType();
|
$this->getProxyType();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->dockerInstallationStarted = false;
|
// $this->dockerInstallationStarted = false;
|
||||||
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
|
return handleError(error: $e, livewire: $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function installDocker()
|
public function installDocker()
|
||||||
{
|
{
|
||||||
$this->dockerInstallationStarted = true;
|
try {
|
||||||
$activity = InstallDocker::run($this->createdServer);
|
$this->dockerInstallationStarted = true;
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$activity = InstallDocker::run($this->createdServer);
|
||||||
|
$this->emit('installDocker');
|
||||||
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->dockerInstallationStarted = false;
|
||||||
|
return handleError(error: $e, livewire: $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function dockerInstalledOrSkipped()
|
public function dockerInstalledOrSkipped()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class CheckLicense extends Component
|
|||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
if ($this->settings->resale_license) {
|
if ($this->settings->resale_license) {
|
||||||
try {
|
try {
|
||||||
resolve(CheckResaleLicense::class)();
|
CheckResaleLicense::run();
|
||||||
$this->emit('reloadWindow');
|
$this->emit('reloadWindow');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
|
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Livewire;
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\S3Storage;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class Form extends Component
|
|||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'destination.name' => 'name',
|
'destination.name' => 'name',
|
||||||
'destination.network' => 'network',
|
'destination.network' => 'network',
|
||||||
'destination.server.ip' => 'IP Address',
|
'destination.server.ip' => 'IP Address/Domain',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
|
|||||||
54
app/Http/Livewire/Project/Application/Advanced.php
Normal file
54
app/Http/Livewire/Project/Application/Advanced.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Advanced extends Component
|
||||||
|
{
|
||||||
|
public Application $application;
|
||||||
|
protected $rules = [
|
||||||
|
'application.settings.is_git_submodules_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_git_lfs_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_force_https_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_log_drain_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_gpu_enabled' => 'boolean|required',
|
||||||
|
'application.settings.gpu_driver' => 'string|required',
|
||||||
|
'application.settings.gpu_count' => 'string|required',
|
||||||
|
'application.settings.gpu_device_ids' => 'string|required',
|
||||||
|
'application.settings.gpu_options' => 'string|required',
|
||||||
|
];
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
if ($this->application->isLogDrainEnabled()) {
|
||||||
|
if (!$this->application->destination->server->isLogDrainEnabled()) {
|
||||||
|
$this->application->settings->is_log_drain_enabled = false;
|
||||||
|
$this->emit('error', 'Log drain is not enabled on this server.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->application->settings->is_force_https_enabled) {
|
||||||
|
$this->emit('resetDefaultLabels', false);
|
||||||
|
}
|
||||||
|
$this->application->settings->save();
|
||||||
|
$this->emit('success', 'Settings saved.');
|
||||||
|
}
|
||||||
|
public function submit() {
|
||||||
|
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
|
||||||
|
$this->emit('error', 'You cannot set both GPU count and GPU device IDs.');
|
||||||
|
$this->application->settings->gpu_count = null;
|
||||||
|
$this->application->settings->gpu_device_ids = null;
|
||||||
|
$this->application->settings->save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->application->settings->save();
|
||||||
|
$this->emit('success', 'Settings saved.');
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.application.advanced');
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/Http/Livewire/Project/Application/Configuration.php
Normal file
39
app/Http/Livewire/Project/Application/Configuration.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Configuration extends Component
|
||||||
|
{
|
||||||
|
public Application $application;
|
||||||
|
public $servers;
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||||
|
if (!$project) {
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
|
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
||||||
|
if (!$environment) {
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
|
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
||||||
|
if (!$application) {
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
|
$this->application = $application;
|
||||||
|
$mainServer = $application->destination->server;
|
||||||
|
$servers = Server::ownedByCurrentTeam()->get();
|
||||||
|
$this->servers = $servers->filter(function ($server) use ($mainServer) {
|
||||||
|
return $server->id != $mainServer->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.application.configuration');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,24 +3,31 @@
|
|||||||
namespace App\Http\Livewire\Project\Application;
|
namespace App\Http\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Deployments extends Component
|
class Deployments extends Component
|
||||||
{
|
{
|
||||||
public Application $application;
|
public Application $application;
|
||||||
public $deployments = [];
|
public Array|Collection $deployments = [];
|
||||||
public int $deployments_count = 0;
|
public int $deployments_count = 0;
|
||||||
public string $current_url;
|
public string $current_url;
|
||||||
public int $skip = 0;
|
public int $skip = 0;
|
||||||
public int $default_take = 8;
|
public int $default_take = 40;
|
||||||
public bool $show_next = false;
|
public bool $show_next = false;
|
||||||
|
public ?string $pull_request_id = null;
|
||||||
|
protected $queryString = ['pull_request_id'];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->current_url = url()->current();
|
$this->current_url = url()->current();
|
||||||
|
$this->show_pull_request_only();
|
||||||
$this->show_more();
|
$this->show_more();
|
||||||
}
|
}
|
||||||
|
private function show_pull_request_only() {
|
||||||
|
if ($this->pull_request_id) {
|
||||||
|
$this->deployments = $this->deployments->where('pull_request_id', $this->pull_request_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
private function show_more()
|
private function show_more()
|
||||||
{
|
{
|
||||||
if (count($this->deployments) !== 0) {
|
if (count($this->deployments) !== 0) {
|
||||||
@@ -47,6 +54,7 @@ class Deployments extends Component
|
|||||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
|
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
|
||||||
$this->deployments = $deployments;
|
$this->deployments = $deployments;
|
||||||
$this->deployments_count = $count;
|
$this->deployments_count = $count;
|
||||||
|
$this->show_pull_request_only();
|
||||||
$this->show_more();
|
$this->show_more();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Models\Application;
|
|||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class General extends Component
|
class General extends Component
|
||||||
{
|
{
|
||||||
@@ -19,20 +20,23 @@ class General extends Component
|
|||||||
public string $git_branch;
|
public string $git_branch;
|
||||||
public ?string $git_commit_sha = null;
|
public ?string $git_commit_sha = null;
|
||||||
public string $build_pack;
|
public string $build_pack;
|
||||||
|
public ?string $ports_exposes = null;
|
||||||
|
|
||||||
public $customLabels;
|
public $customLabels;
|
||||||
public bool $labelsChanged = false;
|
public bool $labelsChanged = false;
|
||||||
public bool $isConfigurationChanged = false;
|
public bool $isConfigurationChanged = false;
|
||||||
|
|
||||||
|
public ?string $initialDockerComposeLocation = null;
|
||||||
|
public ?string $initialDockerComposePrLocation = null;
|
||||||
|
|
||||||
public bool $is_static;
|
public bool $is_static;
|
||||||
public bool $is_git_submodules_enabled;
|
|
||||||
public bool $is_git_lfs_enabled;
|
|
||||||
public bool $is_debug_enabled;
|
|
||||||
public bool $is_preview_deployments_enabled;
|
|
||||||
public bool $is_auto_deploy_enabled;
|
|
||||||
public bool $is_force_https_enabled;
|
|
||||||
|
|
||||||
|
public $parsedServices = [];
|
||||||
|
public $parsedServiceDomains = [];
|
||||||
|
|
||||||
|
protected $listeners = [
|
||||||
|
'resetDefaultLabels'
|
||||||
|
];
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'application.name' => 'required',
|
'application.name' => 'required',
|
||||||
'application.description' => 'nullable',
|
'application.description' => 'nullable',
|
||||||
@@ -53,7 +57,15 @@ class General extends Component
|
|||||||
'application.docker_registry_image_name' => 'nullable',
|
'application.docker_registry_image_name' => 'nullable',
|
||||||
'application.docker_registry_image_tag' => 'nullable',
|
'application.docker_registry_image_tag' => 'nullable',
|
||||||
'application.dockerfile_location' => 'nullable',
|
'application.dockerfile_location' => 'nullable',
|
||||||
|
'application.docker_compose_location' => 'nullable',
|
||||||
|
'application.docker_compose_pr_location' => 'nullable',
|
||||||
|
'application.docker_compose' => 'nullable',
|
||||||
|
'application.docker_compose_pr' => 'nullable',
|
||||||
|
'application.docker_compose_raw' => 'nullable',
|
||||||
|
'application.docker_compose_pr_raw' => 'nullable',
|
||||||
'application.custom_labels' => 'nullable',
|
'application.custom_labels' => 'nullable',
|
||||||
|
'application.dockerfile_target_build' => 'nullable',
|
||||||
|
'application.settings.is_static' => 'boolean|required',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'application.name' => 'name',
|
'application.name' => 'name',
|
||||||
@@ -75,11 +87,27 @@ class General extends Component
|
|||||||
'application.docker_registry_image_name' => 'Docker registry image name',
|
'application.docker_registry_image_name' => 'Docker registry image name',
|
||||||
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
||||||
'application.dockerfile_location' => 'Dockerfile location',
|
'application.dockerfile_location' => 'Dockerfile location',
|
||||||
|
'application.docker_compose_location' => 'Docker compose location',
|
||||||
|
'application.docker_compose_pr_location' => 'Docker compose location',
|
||||||
|
'application.docker_compose' => 'Docker compose',
|
||||||
|
'application.docker_compose_pr' => 'Docker compose',
|
||||||
|
'application.docker_compose_raw' => 'Docker compose raw',
|
||||||
|
'application.docker_compose_pr_raw' => 'Docker compose raw',
|
||||||
'application.custom_labels' => 'Custom labels',
|
'application.custom_labels' => 'Custom labels',
|
||||||
|
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||||
|
'application.settings.is_static' => 'Is static',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
$this->parsedServices = $this->application->parseCompose();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->emit('error', $e->getMessage());
|
||||||
|
}
|
||||||
|
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||||
|
|
||||||
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
|
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
|
||||||
$this->application->isConfigurationChanged(true);
|
$this->application->isConfigurationChanged(true);
|
||||||
}
|
}
|
||||||
@@ -89,23 +117,52 @@ class General extends Component
|
|||||||
} else {
|
} else {
|
||||||
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
|
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
|
||||||
}
|
}
|
||||||
if (data_get($this->application, 'settings')) {
|
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
|
||||||
$this->is_static = $this->application->settings->is_static;
|
|
||||||
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
|
||||||
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
|
||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
|
||||||
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
|
||||||
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
|
||||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
|
||||||
}
|
|
||||||
$this->checkLabelUpdates();
|
$this->checkLabelUpdates();
|
||||||
}
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
$this->application->settings->save();
|
||||||
|
$this->emit('success', 'Settings saved.');
|
||||||
|
}
|
||||||
|
public function loadComposeFile($isInit = false)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($isInit && $this->application->docker_compose_raw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
|
||||||
|
$this->emit('success', 'Docker compose file loaded.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
||||||
|
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
|
||||||
|
$this->application->save();
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function generateDomain(string $serviceName)
|
||||||
|
{
|
||||||
|
$domain = $this->parsedServiceDomains[$serviceName]['domain'] ?? null;
|
||||||
|
if (!$domain) {
|
||||||
|
$uuid = new Cuid2(7);
|
||||||
|
$domain = generateFqdn($this->application->destination->server, $uuid);
|
||||||
|
$this->parsedServiceDomains[$serviceName]['domain'] = $domain;
|
||||||
|
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||||
|
$this->application->save();
|
||||||
|
$this->emit('success', 'Domain generated.');
|
||||||
|
}
|
||||||
|
return $domain;
|
||||||
|
}
|
||||||
public function updatedApplicationBuildPack()
|
public function updatedApplicationBuildPack()
|
||||||
{
|
{
|
||||||
if ($this->application->build_pack !== 'nixpacks') {
|
if ($this->application->build_pack !== 'nixpacks') {
|
||||||
$this->application->settings->is_static = $this->is_static = false;
|
$this->application->settings->is_static = $this->is_static = false;
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
}
|
}
|
||||||
|
if ($this->application->build_pack === 'dockercompose') {
|
||||||
|
$this->application->fqdn = null;
|
||||||
|
$this->application->settings->save();
|
||||||
|
}
|
||||||
$this->submit();
|
$this->submit();
|
||||||
}
|
}
|
||||||
public function checkLabelUpdates()
|
public function checkLabelUpdates()
|
||||||
@@ -116,32 +173,6 @@ class General extends Component
|
|||||||
$this->labelsChanged = false;
|
$this->labelsChanged = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
// @TODO: find another way - if possible
|
|
||||||
$force_https = $this->application->settings->is_force_https_enabled;
|
|
||||||
$this->application->settings->is_static = $this->is_static;
|
|
||||||
if ($this->is_static) {
|
|
||||||
$this->application->ports_exposes = 80;
|
|
||||||
} else {
|
|
||||||
$this->application->ports_exposes = 3000;
|
|
||||||
}
|
|
||||||
$this->application->settings->is_git_submodules_enabled = $this->is_git_submodules_enabled;
|
|
||||||
$this->application->settings->is_git_lfs_enabled = $this->is_git_lfs_enabled;
|
|
||||||
$this->application->settings->is_debug_enabled = $this->is_debug_enabled;
|
|
||||||
$this->application->settings->is_preview_deployments_enabled = $this->is_preview_deployments_enabled;
|
|
||||||
$this->application->settings->is_auto_deploy_enabled = $this->is_auto_deploy_enabled;
|
|
||||||
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
|
|
||||||
$this->application->settings->save();
|
|
||||||
$this->application->save();
|
|
||||||
$this->application->refresh();
|
|
||||||
$this->emit('success', 'Application settings updated!');
|
|
||||||
$this->checkLabelUpdates();
|
|
||||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
|
||||||
if ($force_https !== $this->is_force_https_enabled) {
|
|
||||||
$this->resetDefaultLabels(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getWildcardDomain()
|
public function getWildcardDomain()
|
||||||
{
|
{
|
||||||
@@ -150,12 +181,13 @@ class General extends Component
|
|||||||
$fqdn = generateFqdn($server, $this->application->uuid);
|
$fqdn = generateFqdn($server, $this->application->uuid);
|
||||||
$this->application->fqdn = $fqdn;
|
$this->application->fqdn = $fqdn;
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->emit('success', 'Application settings updated!');
|
$this->updatedApplicationFqdn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function resetDefaultLabels($showToaster = true)
|
public function resetDefaultLabels($showToaster = true)
|
||||||
{
|
{
|
||||||
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
||||||
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
$this->submit($showToaster);
|
$this->submit($showToaster);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +199,13 @@ class General extends Component
|
|||||||
public function submit($showToaster = true)
|
public function submit($showToaster = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
if ($this->application->build_pack === 'dockercompose' && ($this->initialDockerComposeLocation !== $this->application->docker_compose_location || $this->initialDockerComposePrLocation !== $this->application->docker_compose_pr_location)) {
|
||||||
|
$this->loadComposeFile();
|
||||||
|
}
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
if ($this->ports_exposes !== $this->application->ports_exposes) {
|
||||||
|
$this->resetDefaultLabels(false);
|
||||||
|
}
|
||||||
if (data_get($this->application, 'build_pack') === 'dockerimage') {
|
if (data_get($this->application, 'build_pack') === 'dockerimage') {
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'application.docker_registry_image_name' => 'required',
|
'application.docker_registry_image_name' => 'required',
|
||||||
@@ -196,6 +234,10 @@ class General extends Component
|
|||||||
$this->customLabels = str($this->customLabels)->replace(',', "\n");
|
$this->customLabels = str($this->customLabels)->replace(',', "\n");
|
||||||
}
|
}
|
||||||
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
|
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
|
||||||
|
if ($this->application->build_pack === 'dockercompose') {
|
||||||
|
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||||
|
$this->parsedServices = $this->application->parseCompose();
|
||||||
|
}
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$showToaster && $this->emit('success', 'Application settings updated!');
|
$showToaster && $this->emit('success', 'Application settings updated!');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Http\Livewire\Project\Application;
|
|||||||
|
|
||||||
use App\Actions\Application\StopApplication;
|
use App\Actions\Application\StopApplication;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
|
use App\Jobs\ServerStatusJob;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
@@ -28,6 +29,8 @@ class Heading extends Component
|
|||||||
$this->application->previews->each(function ($preview) {
|
$this->application->previews->each(function ($preview) {
|
||||||
$preview->refresh();
|
$preview->refresh();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
dispatch(new ServerStatusJob($this->application->destination->server));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +41,10 @@ class Heading extends Component
|
|||||||
|
|
||||||
public function deploy(bool $force_rebuild = false)
|
public function deploy(bool $force_rebuild = false)
|
||||||
{
|
{
|
||||||
|
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
|
||||||
|
$this->emit('error', 'Please load a Compose file first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->setDeploymentUuid();
|
$this->setDeploymentUuid();
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application_id: $this->application->id,
|
application_id: $this->application->id,
|
||||||
@@ -65,4 +72,19 @@ class Heading extends Component
|
|||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->application->refresh();
|
$this->application->refresh();
|
||||||
}
|
}
|
||||||
|
public function restart()
|
||||||
|
{
|
||||||
|
$this->setDeploymentUuid();
|
||||||
|
queue_application_deployment(
|
||||||
|
application_id: $this->application->id,
|
||||||
|
deployment_uuid: $this->deploymentUuid,
|
||||||
|
restart_only: true,
|
||||||
|
);
|
||||||
|
return redirect()->route('project.application.deployment', [
|
||||||
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
|
'application_uuid' => $this->parameters['application_uuid'],
|
||||||
|
'deployment_uuid' => $this->deploymentUuid,
|
||||||
|
'environment_name' => $this->parameters['environment_name'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,10 +72,12 @@ class Previews extends Component
|
|||||||
public function stop(int $pull_request_id)
|
public function stop(int $pull_request_id)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$container_name = generateApplicationContainerName($this->application, $pull_request_id);
|
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
|
||||||
|
foreach ($containers as $container) {
|
||||||
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
|
$name = str_replace('/', '', $container['Names']);
|
||||||
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
|
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
|
||||||
|
}
|
||||||
|
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
|
||||||
$this->application->refresh();
|
$this->application->refresh();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -38,32 +38,36 @@ class Rollback extends Component
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadImages()
|
public function loadImages($showToast = false)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$image = $this->application->uuid;
|
$image = $this->application->docker_registry_image_name ?? $this->application->uuid;
|
||||||
$output = instant_remote_process([
|
if ($this->application->destination->server->isFunctional()) {
|
||||||
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
|
$output = instant_remote_process([
|
||||||
], $this->application->destination->server, throwError: false);
|
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
|
||||||
$current_tag = Str::of($output)->trim()->explode(":");
|
], $this->application->destination->server, throwError: false);
|
||||||
$this->current = data_get($current_tag, 1);
|
$current_tag = Str::of($output)->trim()->explode(":");
|
||||||
|
$this->current = data_get($current_tag, 1);
|
||||||
|
|
||||||
$output = instant_remote_process([
|
$output = instant_remote_process([
|
||||||
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
|
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
|
||||||
], $this->application->destination->server);
|
], $this->application->destination->server);
|
||||||
$this->images = Str::of($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
|
$this->images = Str::of($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
|
||||||
return Str::of($item)->contains($image);
|
return Str::of($item)->contains($image);
|
||||||
})->map(function ($item) {
|
})->map(function ($item) {
|
||||||
$item = Str::of($item)->explode('#');
|
$item = Str::of($item)->explode('#');
|
||||||
if ($item[1] === $this->current) {
|
if ($item[1] === $this->current) {
|
||||||
// $is_current = true;
|
// $is_current = true;
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
'tag' => $item[1],
|
'tag' => $item[1],
|
||||||
'created_at' => $item[2],
|
'created_at' => $item[2],
|
||||||
'is_current' => $is_current ?? null,
|
'is_current' => $is_current ?? null,
|
||||||
];
|
];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
}
|
||||||
|
$showToast && $this->emit('success', 'Images loaded.');
|
||||||
|
return [];
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
159
app/Http/Livewire/Project/CloneProject.php
Normal file
159
app/Http/Livewire/Project/CloneProject.php
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project;
|
||||||
|
|
||||||
|
use App\Models\Environment;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class CloneProject extends Component
|
||||||
|
{
|
||||||
|
public string $project_uuid;
|
||||||
|
public string $environment_name;
|
||||||
|
public int $project_id;
|
||||||
|
|
||||||
|
public Project $project;
|
||||||
|
public $environments;
|
||||||
|
public $servers;
|
||||||
|
public ?Environment $environment = null;
|
||||||
|
public ?int $selectedServer = null;
|
||||||
|
public ?Server $server = null;
|
||||||
|
public $resources = [];
|
||||||
|
public string $newProjectName = '';
|
||||||
|
|
||||||
|
protected $messages = [
|
||||||
|
'selectedServer' => 'Please select a server.',
|
||||||
|
'newProjectName' => 'Please enter a name for the new project.',
|
||||||
|
];
|
||||||
|
public function mount($project_uuid)
|
||||||
|
{
|
||||||
|
$this->project_uuid = $project_uuid;
|
||||||
|
$this->project = Project::where('uuid', $project_uuid)->firstOrFail();
|
||||||
|
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
||||||
|
$this->project_id = $this->project->id;
|
||||||
|
$this->servers = currentTeam()->servers;
|
||||||
|
$this->newProjectName = $this->project->name . ' (clone)';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.clone-project');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selectServer($server_id)
|
||||||
|
{
|
||||||
|
$this->selectedServer = $server_id;
|
||||||
|
$this->server = $this->servers->where('id', $server_id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clone()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'selectedServer' => 'required',
|
||||||
|
'newProjectName' => 'required',
|
||||||
|
]);
|
||||||
|
$foundProject = Project::where('name', $this->newProjectName)->first();
|
||||||
|
if ($foundProject) {
|
||||||
|
throw new \Exception('Project with the same name already exists.');
|
||||||
|
}
|
||||||
|
$newProject = Project::create([
|
||||||
|
'name' => $this->newProjectName,
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
'description' => $this->project->description . ' (clone)',
|
||||||
|
]);
|
||||||
|
if ($this->environment->name !== 'production') {
|
||||||
|
$newProject->environments()->create([
|
||||||
|
'name' => $this->environment->name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$newEnvironment = $newProject->environments->where('name', $this->environment->name)->first();
|
||||||
|
// Clone Applications
|
||||||
|
$applications = $this->environment->applications;
|
||||||
|
$databases = $this->environment->databases();
|
||||||
|
$services = $this->environment->services;
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$uuid = (string)new Cuid2(7);
|
||||||
|
$newApplication = $application->replicate()->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'fqdn' => generateFqdn($this->server, $uuid),
|
||||||
|
'status' => 'exited',
|
||||||
|
'environment_id' => $newEnvironment->id,
|
||||||
|
'destination_id' => $this->selectedServer,
|
||||||
|
]);
|
||||||
|
$newApplication->save();
|
||||||
|
$environmentVaribles = $application->environment_variables()->get();
|
||||||
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
|
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
||||||
|
'application_id' => $newApplication->id,
|
||||||
|
]);
|
||||||
|
$newEnvironmentVariable->save();
|
||||||
|
}
|
||||||
|
$persistentVolumes = $application->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newPersistentVolume = $volume->replicate()->fill([
|
||||||
|
'name' => $newApplication->uuid . '-' . str($volume->name)->afterLast('-'),
|
||||||
|
'resource_id' => $newApplication->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($databases as $database) {
|
||||||
|
$uuid = (string)new Cuid2(7);
|
||||||
|
$newDatabase = $database->replicate()->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'status' => 'exited',
|
||||||
|
'started_at' => null,
|
||||||
|
'environment_id' => $newEnvironment->id,
|
||||||
|
'destination_id' => $this->selectedServer,
|
||||||
|
]);
|
||||||
|
$newDatabase->save();
|
||||||
|
$environmentVaribles = $database->environment_variables()->get();
|
||||||
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
|
$payload = [];
|
||||||
|
if ($database->type() === 'standalone-postgresql') {
|
||||||
|
$payload['standalone_postgresql_id'] = $newDatabase->id;
|
||||||
|
} else if ($database->type() === 'standalone-redis') {
|
||||||
|
$payload['standalone_redis_id'] = $newDatabase->id;
|
||||||
|
} else if ($database->type() === 'standalone-mongodb') {
|
||||||
|
$payload['standalone_mongodb_id'] = $newDatabase->id;
|
||||||
|
} else if ($database->type() === 'standalone-mysql') {
|
||||||
|
$payload['standalone_mysql_id'] = $newDatabase->id;
|
||||||
|
} else if ($database->type() === 'standalone-mariadb') {
|
||||||
|
$payload['standalone_mariadb_id'] = $newDatabase->id;
|
||||||
|
}
|
||||||
|
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
|
||||||
|
$newEnvironmentVariable->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$uuid = (string)new Cuid2(7);
|
||||||
|
$newService = $service->replicate()->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'environment_id' => $newEnvironment->id,
|
||||||
|
'destination_id' => $this->selectedServer,
|
||||||
|
]);
|
||||||
|
$newService->save();
|
||||||
|
foreach ($newService->applications() as $application) {
|
||||||
|
$application->update([
|
||||||
|
'status' => 'exited',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
foreach ($newService->databases() as $database) {
|
||||||
|
$database->update([
|
||||||
|
'status' => 'exited',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$newService->parse();
|
||||||
|
}
|
||||||
|
return redirect()->route('project.resources', [
|
||||||
|
'project_uuid' => $newProject->uuid,
|
||||||
|
'environment_name' => $newEnvironment->name,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Livewire\Project\Database;
|
namespace App\Http\Livewire\Project\Database;
|
||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Spatie\Url\Url;
|
||||||
|
|
||||||
class BackupEdit extends Component
|
class BackupEdit extends Component
|
||||||
{
|
{
|
||||||
@@ -43,14 +44,23 @@ class BackupEdit extends Component
|
|||||||
{
|
{
|
||||||
// TODO: Delete backup from server and add a confirmation modal
|
// TODO: Delete backup from server and add a confirmation modal
|
||||||
$this->backup->delete();
|
$this->backup->delete();
|
||||||
redirect()->route('project.database.backups.all', $this->parameters);
|
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$previousUrl = url()->previous();
|
||||||
|
$url = Url::fromString($previousUrl);
|
||||||
|
$url = $url->withoutQueryParameter('selectedBackupId');
|
||||||
|
$url = $url->withFragment('backups');
|
||||||
|
$url = $url->getPath() . "#{$url->getFragment()}";
|
||||||
|
return redirect()->to($url);
|
||||||
|
} else {
|
||||||
|
redirect()->route('project.database.backups.all', $this->parameters);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->custom_validate();
|
$this->custom_validate();
|
||||||
|
|
||||||
$this->backup->save();
|
$this->backup->save();
|
||||||
$this->backup->refresh();
|
$this->backup->refresh();
|
||||||
$this->emit('success', 'Backup updated successfully');
|
$this->emit('success', 'Backup updated successfully');
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database;
|
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class BackupExecution extends Component
|
|
||||||
{
|
|
||||||
public ScheduledDatabaseBackupExecution $execution;
|
|
||||||
|
|
||||||
public function download()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(): void
|
|
||||||
{
|
|
||||||
delete_backup_locally($this->execution->filename, $this->execution->scheduledDatabaseBackup->database->destination->server);
|
|
||||||
$this->execution->delete();
|
|
||||||
$this->emit('success', 'Backup deleted successfully.');
|
|
||||||
$this->emit('refreshBackupExecutions');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,14 +2,59 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database;
|
namespace App\Http\Livewire\Project\Database;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class BackupExecutions extends Component
|
class BackupExecutions extends Component
|
||||||
{
|
{
|
||||||
public $backup;
|
public $backup;
|
||||||
public $executions;
|
public $executions;
|
||||||
protected $listeners = ['refreshBackupExecutions'];
|
public $setDeletableBackup;
|
||||||
|
protected $listeners = ['refreshBackupExecutions', 'deleteBackup'];
|
||||||
|
|
||||||
|
public function deleteBackup($exeuctionId)
|
||||||
|
{
|
||||||
|
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
|
||||||
|
if (is_null($execution)) {
|
||||||
|
$this->emit('error', 'Backup execution not found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->service->destination->server);
|
||||||
|
} else {
|
||||||
|
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
|
||||||
|
}
|
||||||
|
$execution->delete();
|
||||||
|
$this->emit('success', 'Backup deleted successfully.');
|
||||||
|
$this->emit('refreshBackupExecutions');
|
||||||
|
}
|
||||||
|
public function download($exeuctionId)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
|
||||||
|
if (is_null($execution)) {
|
||||||
|
$this->emit('error', 'Backup execution not found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = data_get($execution, 'filename');
|
||||||
|
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$server = $execution->scheduledDatabaseBackup->database->service->destination->server;
|
||||||
|
} else {
|
||||||
|
$server = $execution->scheduledDatabaseBackup->database->destination->server;
|
||||||
|
}
|
||||||
|
$privateKeyLocation = savePrivateKeyToFs($server);
|
||||||
|
$disk = Storage::build([
|
||||||
|
'driver' => 'sftp',
|
||||||
|
'host' => $server->ip,
|
||||||
|
'port' => $server->port,
|
||||||
|
'username' => $server->user,
|
||||||
|
'privateKey' => $privateKeyLocation,
|
||||||
|
]);
|
||||||
|
return $disk->download($filename);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function refreshBackupExecutions(): void
|
public function refreshBackupExecutions(): void
|
||||||
{
|
{
|
||||||
$this->executions = $this->backup->executions;
|
$this->executions = $this->backup->executions;
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ class CreateScheduledBackup extends Component
|
|||||||
'frequency' => 'Backup Frequency',
|
'frequency' => 'Backup Frequency',
|
||||||
'save_s3' => 'Save to S3',
|
'save_s3' => 'Save to S3',
|
||||||
];
|
];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
if ($this->s3s->count() > 0) {
|
||||||
|
$this->s3_storage_id = $this->s3s->first()->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function submit(): void
|
public function submit(): void
|
||||||
{
|
{
|
||||||
@@ -43,9 +49,18 @@ class CreateScheduledBackup extends Component
|
|||||||
];
|
];
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
$payload['databases_to_backup'] = $this->database->postgres_db;
|
$payload['databases_to_backup'] = $this->database->postgres_db;
|
||||||
|
} else if ($this->database->type() === 'standalone-mysql') {
|
||||||
|
$payload['databases_to_backup'] = $this->database->mysql_database;
|
||||||
|
} else if ($this->database->type() === 'standalone-mariadb') {
|
||||||
|
$payload['databases_to_backup'] = $this->database->mariadb_database;
|
||||||
|
}
|
||||||
|
|
||||||
|
$databaseBackup = ScheduledDatabaseBackup::create($payload);
|
||||||
|
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$this->emit('refreshScheduledBackups', $databaseBackup->id);
|
||||||
|
} else {
|
||||||
|
$this->emit('refreshScheduledBackups');
|
||||||
}
|
}
|
||||||
ScheduledDatabaseBackup::create($payload);
|
|
||||||
$this->emit('refreshScheduledBackups');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
handleError($e, $this);
|
handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database;
|
namespace App\Http\Livewire\Project\Database;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartMariadb;
|
||||||
|
use App\Actions\Database\StartMongodb;
|
||||||
|
use App\Actions\Database\StartMysql;
|
||||||
use App\Actions\Database\StartPostgresql;
|
use App\Actions\Database\StartPostgresql;
|
||||||
use App\Actions\Database\StartRedis;
|
use App\Actions\Database\StartRedis;
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
@@ -46,11 +49,19 @@ class Heading extends Component
|
|||||||
public function start()
|
public function start()
|
||||||
{
|
{
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
$activity = StartPostgresql::run($this->database->destination->server, $this->database);
|
$activity = StartPostgresql::run($this->database);
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
}
|
} else if ($this->database->type() === 'standalone-redis') {
|
||||||
if ($this->database->type() === 'standalone-redis') {
|
$activity = StartRedis::run($this->database);
|
||||||
$activity = StartRedis::run($this->database->destination->server, $this->database);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
|
} else if ($this->database->type() === 'standalone-mongodb') {
|
||||||
|
$activity = StartMongodb::run($this->database);
|
||||||
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
|
} else if ($this->database->type() === 'standalone-mysql') {
|
||||||
|
$activity = StartMysql::run($this->database);
|
||||||
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
|
} else if ($this->database->type() === 'standalone-mariadb') {
|
||||||
|
$activity = StartMariadb::run($this->database);
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
118
app/Http/Livewire/Project/Database/Mariadb/General.php
Normal file
118
app/Http/Livewire/Project/Database/Mariadb/General.php
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Database\Mariadb;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use Exception;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class General extends Component
|
||||||
|
{
|
||||||
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
|
public StandaloneMariadb $database;
|
||||||
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'database.name' => 'required',
|
||||||
|
'database.description' => 'nullable',
|
||||||
|
'database.mariadb_root_password' => 'required',
|
||||||
|
'database.mariadb_user' => 'required',
|
||||||
|
'database.mariadb_password' => 'required',
|
||||||
|
'database.mariadb_database' => 'required',
|
||||||
|
'database.mariadb_conf' => 'nullable',
|
||||||
|
'database.image' => 'required',
|
||||||
|
'database.ports_mappings' => 'nullable',
|
||||||
|
'database.is_public' => 'nullable|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'database.name' => 'Name',
|
||||||
|
'database.description' => 'Description',
|
||||||
|
'database.mariadb_root_password' => 'Root Password',
|
||||||
|
'database.mariadb_user' => 'User',
|
||||||
|
'database.mariadb_password' => 'Password',
|
||||||
|
'database.mariadb_database' => 'Database',
|
||||||
|
'database.mariadb_conf' => 'MariaDB Configuration',
|
||||||
|
'database.image' => 'Image',
|
||||||
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
|
'database.is_public' => 'Is Public',
|
||||||
|
'database.public_port' => 'Public Port',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSaveAdvanced() {
|
||||||
|
try {
|
||||||
|
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||||
|
$this->database->is_log_drain_enabled = false;
|
||||||
|
$this->emit('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
$this->emit('success', 'You need to restart the service for the changes to take effect.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (str($this->database->public_port)->isEmpty()) {
|
||||||
|
$this->database->public_port = null;
|
||||||
|
}
|
||||||
|
$this->validate();
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
|
$this->emit('error', 'Public port is required.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->database->save();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function refresh(): void
|
||||||
|
{
|
||||||
|
$this->database->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.database.mariadb.general');
|
||||||
|
}
|
||||||
|
}
|
||||||
120
app/Http/Livewire/Project/Database/Mongodb/General.php
Normal file
120
app/Http/Livewire/Project/Database/Mongodb/General.php
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Database\Mongodb;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use Exception;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class General extends Component
|
||||||
|
{
|
||||||
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
|
public StandaloneMongodb $database;
|
||||||
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'database.name' => 'required',
|
||||||
|
'database.description' => 'nullable',
|
||||||
|
'database.mongo_conf' => 'nullable',
|
||||||
|
'database.mongo_initdb_root_username' => 'required',
|
||||||
|
'database.mongo_initdb_root_password' => 'required',
|
||||||
|
'database.mongo_initdb_database' => 'required',
|
||||||
|
'database.image' => 'required',
|
||||||
|
'database.ports_mappings' => 'nullable',
|
||||||
|
'database.is_public' => 'nullable|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'database.name' => 'Name',
|
||||||
|
'database.description' => 'Description',
|
||||||
|
'database.mongo_conf' => 'Mongo Configuration',
|
||||||
|
'database.mongo_initdb_root_username' => 'Root Username',
|
||||||
|
'database.mongo_initdb_root_password' => 'Root Password',
|
||||||
|
'database.mongo_initdb_database' => 'Database',
|
||||||
|
'database.image' => 'Image',
|
||||||
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
|
'database.is_public' => 'Is Public',
|
||||||
|
'database.public_port' => 'Public Port',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSaveAdvanced()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||||
|
$this->database->is_log_drain_enabled = false;
|
||||||
|
$this->emit('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
$this->emit('success', 'You need to restart the service for the changes to take effect.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (str($this->database->public_port)->isEmpty()) {
|
||||||
|
$this->database->public_port = null;
|
||||||
|
}
|
||||||
|
if (str($this->database->mongo_conf)->isEmpty()) {
|
||||||
|
$this->database->mongo_conf = null;
|
||||||
|
}
|
||||||
|
$this->validate();
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
|
$this->emit('error', 'Public port is required.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->database->save();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function refresh(): void
|
||||||
|
{
|
||||||
|
$this->database->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.database.mongodb.general');
|
||||||
|
}
|
||||||
|
}
|
||||||
119
app/Http/Livewire/Project/Database/Mysql/General.php
Normal file
119
app/Http/Livewire/Project/Database/Mysql/General.php
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Database\Mysql;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use Exception;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class General extends Component
|
||||||
|
{
|
||||||
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
|
public StandaloneMysql $database;
|
||||||
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'database.name' => 'required',
|
||||||
|
'database.description' => 'nullable',
|
||||||
|
'database.mysql_root_password' => 'required',
|
||||||
|
'database.mysql_user' => 'required',
|
||||||
|
'database.mysql_password' => 'required',
|
||||||
|
'database.mysql_database' => 'required',
|
||||||
|
'database.mysql_conf' => 'nullable',
|
||||||
|
'database.image' => 'required',
|
||||||
|
'database.ports_mappings' => 'nullable',
|
||||||
|
'database.is_public' => 'nullable|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'database.name' => 'Name',
|
||||||
|
'database.description' => 'Description',
|
||||||
|
'database.mysql_root_password' => 'Root Password',
|
||||||
|
'database.mysql_user' => 'User',
|
||||||
|
'database.mysql_password' => 'Password',
|
||||||
|
'database.mysql_database' => 'Database',
|
||||||
|
'database.mysql_conf' => 'MySQL Configuration',
|
||||||
|
'database.image' => 'Image',
|
||||||
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
|
'database.is_public' => 'Is Public',
|
||||||
|
'database.public_port' => 'Public Port',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSaveAdvanced()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||||
|
$this->database->is_log_drain_enabled = false;
|
||||||
|
$this->emit('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
$this->emit('success', 'You need to restart the service for the changes to take effect.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (str($this->database->public_port)->isEmpty()) {
|
||||||
|
$this->database->public_port = null;
|
||||||
|
}
|
||||||
|
$this->validate();
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
|
$this->emit('error', 'Public port is required.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->database->save();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function refresh(): void
|
||||||
|
{
|
||||||
|
$this->database->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.database.mysql.general');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,8 @@ class General extends Component
|
|||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql $database;
|
||||||
public string $new_filename;
|
public string $new_filename;
|
||||||
public string $new_content;
|
public string $new_content;
|
||||||
public string $db_url;
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
||||||
|
|
||||||
@@ -27,11 +28,13 @@ class General extends Component
|
|||||||
'database.postgres_db' => 'required',
|
'database.postgres_db' => 'required',
|
||||||
'database.postgres_initdb_args' => 'nullable',
|
'database.postgres_initdb_args' => 'nullable',
|
||||||
'database.postgres_host_auth_method' => 'nullable',
|
'database.postgres_host_auth_method' => 'nullable',
|
||||||
|
'database.postgres_conf' => 'nullable',
|
||||||
'database.init_scripts' => 'nullable',
|
'database.init_scripts' => 'nullable',
|
||||||
'database.image' => 'required',
|
'database.image' => 'required',
|
||||||
'database.ports_mappings' => 'nullable',
|
'database.ports_mappings' => 'nullable',
|
||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'database.name' => 'Name',
|
'database.name' => 'Name',
|
||||||
@@ -41,6 +44,7 @@ class General extends Component
|
|||||||
'database.postgres_db' => 'Postgres DB',
|
'database.postgres_db' => 'Postgres DB',
|
||||||
'database.postgres_initdb_args' => 'Postgres Initdb Args',
|
'database.postgres_initdb_args' => 'Postgres Initdb Args',
|
||||||
'database.postgres_host_auth_method' => 'Postgres Host Auth Method',
|
'database.postgres_host_auth_method' => 'Postgres Host Auth Method',
|
||||||
|
'database.postgres_conf' => 'Postgres Configuration',
|
||||||
'database.init_scripts' => 'Init Scripts',
|
'database.init_scripts' => 'Init Scripts',
|
||||||
'database.image' => 'Image',
|
'database.image' => 'Image',
|
||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
@@ -49,14 +53,23 @@ class General extends Component
|
|||||||
];
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->getDbUrl();
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
}
|
|
||||||
public function getDbUrl() {
|
|
||||||
|
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/{$this->database->postgres_db}";
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
} else {
|
}
|
||||||
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
|
}
|
||||||
|
public function instantSaveAdvanced() {
|
||||||
|
try {
|
||||||
|
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||||
|
$this->database->is_log_drain_enabled = false;
|
||||||
|
$this->emit('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
$this->emit('success', 'You need to restart the service for the changes to take effect.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
@@ -68,20 +81,24 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
$this->emit('success', 'Starting TCP proxy...');
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->getDbUrl();
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch(\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = !$this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public function save_init_script($script)
|
public function save_init_script($script)
|
||||||
{
|
{
|
||||||
@@ -96,7 +113,6 @@ class General extends Component
|
|||||||
$collection = collect($this->database->init_scripts);
|
$collection = collect($this->database->init_scripts);
|
||||||
$found = $collection->firstWhere('filename', $script['filename']);
|
$found = $collection->firstWhere('filename', $script['filename']);
|
||||||
if ($found) {
|
if ($found) {
|
||||||
ray($collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray());
|
|
||||||
$this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray();
|
$this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray();
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->refresh();
|
$this->refresh();
|
||||||
@@ -140,6 +156,9 @@ class General extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
if (str($this->database->public_port)->isEmpty()) {
|
||||||
|
$this->database->public_port = null;
|
||||||
|
}
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->emit('success', 'Database updated successfully.');
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class General extends Component
|
|||||||
protected $listeners = ['refresh'];
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
public StandaloneRedis $database;
|
public StandaloneRedis $database;
|
||||||
public string $db_url;
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'database.name' => 'required',
|
'database.name' => 'required',
|
||||||
@@ -24,6 +25,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'nullable',
|
'database.ports_mappings' => 'nullable',
|
||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'database.name' => 'Name',
|
'database.name' => 'Name',
|
||||||
@@ -35,7 +37,29 @@ class General extends Component
|
|||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
];
|
];
|
||||||
public function submit() {
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSaveAdvanced() {
|
||||||
|
try {
|
||||||
|
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||||
|
$this->database->is_log_drain_enabled = false;
|
||||||
|
$this->emit('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
$this->emit('success', 'You need to restart the service for the changes to take effect.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
if ($this->database->redis_conf === "") {
|
if ($this->database->redis_conf === "") {
|
||||||
@@ -56,16 +80,21 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
$this->emit('success', 'Starting TCP proxy...');
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->getDbUrl();
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch(\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = !$this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -75,18 +104,6 @@ class General extends Component
|
|||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->getDbUrl();
|
|
||||||
}
|
|
||||||
public function getDbUrl() {
|
|
||||||
|
|
||||||
if ($this->database->is_public) {
|
|
||||||
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/0";
|
|
||||||
} else {
|
|
||||||
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->uuid}:6379/0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.database.redis.general');
|
return view('livewire.project.database.redis.general');
|
||||||
|
|||||||
@@ -8,13 +8,33 @@ class ScheduledBackups extends Component
|
|||||||
{
|
{
|
||||||
public $database;
|
public $database;
|
||||||
public $parameters;
|
public $parameters;
|
||||||
|
public $type;
|
||||||
|
public $selectedBackup;
|
||||||
|
public $selectedBackupId;
|
||||||
|
public $s3s;
|
||||||
protected $listeners = ['refreshScheduledBackups'];
|
protected $listeners = ['refreshScheduledBackups'];
|
||||||
|
protected $queryString = ['selectedBackupId'];
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
|
if ($this->selectedBackupId) {
|
||||||
|
$this->setSelectedBackup($this->selectedBackupId);
|
||||||
|
}
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
|
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$this->type = 'service-database';
|
||||||
|
} else {
|
||||||
|
$this->type = 'database';
|
||||||
|
}
|
||||||
|
$this->s3s = currentTeam()->s3s;
|
||||||
|
}
|
||||||
|
public function setSelectedBackup($backupId) {
|
||||||
|
$this->selectedBackupId = $backupId;
|
||||||
|
$this->selectedBackup = $this->database->scheduledBackups->find($this->selectedBackupId);
|
||||||
|
if (is_null($this->selectedBackup)) {
|
||||||
|
$this->selectedBackupId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete($scheduled_backup_id): void
|
public function delete($scheduled_backup_id): void
|
||||||
{
|
{
|
||||||
$this->database->scheduledBackups->find($scheduled_backup_id)->delete();
|
$this->database->scheduledBackups->find($scheduled_backup_id)->delete();
|
||||||
@@ -22,9 +42,11 @@ class ScheduledBackups extends Component
|
|||||||
$this->refreshScheduledBackups();
|
$this->refreshScheduledBackups();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function refreshScheduledBackups(): void
|
public function refreshScheduledBackups(?int $id = null): void
|
||||||
{
|
{
|
||||||
ray('refreshScheduledBackups');
|
|
||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
|
if ($id) {
|
||||||
|
$this->setSelectedBackup($id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ class DeleteEnvironment extends Component
|
|||||||
'environment_id' => 'required|int',
|
'environment_id' => 'required|int',
|
||||||
]);
|
]);
|
||||||
$environment = Environment::findOrFail($this->environment_id);
|
$environment = Environment::findOrFail($this->environment_id);
|
||||||
if ($environment->applications->count() > 0) {
|
if ($environment->isEmpty()) {
|
||||||
return $this->emit('error', 'Environment has resources defined, please delete them first.');
|
$environment->delete();
|
||||||
|
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
|
||||||
}
|
}
|
||||||
$environment->delete();
|
return $this->emit('error', 'Environment has defined resources, please delete them first.');
|
||||||
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,79 +22,19 @@ class DockerCompose extends Component
|
|||||||
$this->query = request()->query();
|
$this->query = request()->query();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$this->dockerComposeRaw = 'services:
|
$this->dockerComposeRaw = 'services:
|
||||||
ghost:
|
appsmith:
|
||||||
image: ghost:5
|
build:
|
||||||
volumes:
|
context: .
|
||||||
- ~/configs:/etc/configs/:ro
|
dockerfile_inline: |
|
||||||
- ./var/lib/ghost/content:/tmp/ghost2/content:ro
|
FROM nginx
|
||||||
- /var/lib/ghost/content:/tmp/ghost/content:rw
|
ARG GIT_COMMIT
|
||||||
- ghost-content-data:/var/lib/ghost/content
|
ARG GIT_BRANCH
|
||||||
- type: volume
|
RUN echo "Hello World ${GIT_COMMIT} ${GIT_BRANCH}"
|
||||||
source: mydata
|
args:
|
||||||
target: /data
|
- GIT_COMMIT=cdc3b19
|
||||||
- type: bind
|
- GIT_BRANCH=${GIT_BRANCH}
|
||||||
source: ./var/lib/ghost/data
|
|
||||||
target: /data
|
|
||||||
- type: bind
|
|
||||||
source: /tmp
|
|
||||||
target: /tmp
|
|
||||||
labels:
|
|
||||||
- "test.label=true"
|
|
||||||
ports:
|
|
||||||
- "3000"
|
|
||||||
- "3000-3005"
|
|
||||||
- "8000:8000"
|
|
||||||
- "9090-9091:8080-8081"
|
|
||||||
- "49100:22"
|
|
||||||
- "127.0.0.1:8001:8001"
|
|
||||||
- "127.0.0.1:5000-5010:5000-5010"
|
|
||||||
- "127.0.0.1::5000"
|
|
||||||
- "6060:6060/udp"
|
|
||||||
- "12400-12500:1240"
|
|
||||||
- target: 80
|
|
||||||
published: 8080
|
|
||||||
protocol: tcp
|
|
||||||
mode: host
|
|
||||||
networks:
|
|
||||||
- some-network
|
|
||||||
- other-network
|
|
||||||
environment:
|
environment:
|
||||||
- database__client=${DATABASE_CLIENT:-mysql}
|
- APPSMITH_MAIL_ENABLED=${APPSMITH_MAIL_ENABLED}
|
||||||
- database__connection__database=${MYSQL_DATABASE:-ghost}
|
|
||||||
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
|
|
||||||
- test=${TEST:?true}
|
|
||||||
- url=$SERVICE_FQDN_GHOST
|
|
||||||
- database__connection__user=$SERVICE_USER_MYSQL
|
|
||||||
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
|
||||||
depends_on:
|
|
||||||
- mysql
|
|
||||||
mysql:
|
|
||||||
image: mysql:8.0
|
|
||||||
volumes:
|
|
||||||
- ghost-mysql-data:/var/lib/mysql
|
|
||||||
environment:
|
|
||||||
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
|
||||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
|
||||||
- MYSQL_DATABASE=$MYSQL_DATABASE
|
|
||||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
|
|
||||||
- SESSION_SECRET
|
|
||||||
minio:
|
|
||||||
image: minio/minio
|
|
||||||
environment:
|
|
||||||
RACK_ENV: development
|
|
||||||
A: $A
|
|
||||||
SHOW: ${SHOW}
|
|
||||||
SHOW1: ${SHOW2-show1}
|
|
||||||
SHOW2: ${SHOW3:-show2}
|
|
||||||
SHOW3: ${SHOW4?show3}
|
|
||||||
SHOW4: ${SHOW5:?show4}
|
|
||||||
SHOW5: ${SERVICE_USER_MINIO}
|
|
||||||
SHOW6: ${SERVICE_PASSWORD_MINIO}
|
|
||||||
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
|
|
||||||
SHOW8: ${SERVICE_BASE64_64_MINIO}
|
|
||||||
SHOW9: ${SERVICE_BASE64_128_MINIO}
|
|
||||||
SHOW10: ${SERVICE_BASE64_MINIO}
|
|
||||||
SHOW11:
|
|
||||||
';
|
';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +69,7 @@ class DockerCompose extends Component
|
|||||||
|
|
||||||
$service->parse(isNew: true);
|
$service->parse(isNew: true);
|
||||||
|
|
||||||
return redirect()->route('project.service', [
|
return redirect()->route('project.service.configuration', [
|
||||||
'service_uuid' => $service->uuid,
|
'service_uuid' => $service->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use App\Traits\SaveFromRedirect;
|
|||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
|
||||||
|
|
||||||
class GithubPrivateRepository extends Component
|
class GithubPrivateRepository extends Component
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,14 +21,18 @@ class Select extends Component
|
|||||||
public Collection|array $swarmDockers = [];
|
public Collection|array $swarmDockers = [];
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public Collection|array $services = [];
|
public Collection|array $services = [];
|
||||||
|
public Collection|array $allServices = [];
|
||||||
|
|
||||||
public bool $loadingServices = true;
|
public bool $loadingServices = true;
|
||||||
public bool $loading = false;
|
public bool $loading = false;
|
||||||
public $environments = [];
|
public $environments = [];
|
||||||
public ?string $selectedEnvironment = null;
|
public ?string $selectedEnvironment = null;
|
||||||
public ?string $existingPostgresqlUrl = null;
|
public ?string $existingPostgresqlUrl = null;
|
||||||
|
|
||||||
|
public ?string $search = null;
|
||||||
protected $queryString = [
|
protected $queryString = [
|
||||||
'server',
|
'server',
|
||||||
|
'search'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -41,6 +45,11 @@ class Select extends Component
|
|||||||
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
|
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
|
||||||
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
|
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
|
||||||
}
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
if ($this->search) $this->loadServices();
|
||||||
|
return view('livewire.project.new.select');
|
||||||
|
}
|
||||||
|
|
||||||
public function updatedSelectedEnvironment()
|
public function updatedSelectedEnvironment()
|
||||||
{
|
{
|
||||||
@@ -49,6 +58,7 @@ class Select extends Component
|
|||||||
'environment_name' => $this->selectedEnvironment,
|
'environment_name' => $this->selectedEnvironment,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function addExistingPostgresql()
|
// public function addExistingPostgresql()
|
||||||
// {
|
// {
|
||||||
// try {
|
// try {
|
||||||
@@ -59,19 +69,28 @@ class Select extends Component
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
public function loadThings()
|
public function loadServices()
|
||||||
{
|
|
||||||
$this->loadServices();
|
|
||||||
$this->loadServers();
|
|
||||||
}
|
|
||||||
public function loadServices(bool $forceReload = false)
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($forceReload) {
|
if (count($this->allServices) > 0) {
|
||||||
Cache::forget('services');
|
if (!$this->search) {
|
||||||
|
$this->services = $this->allServices;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->services = $this->allServices->filter(function ($service, $key) {
|
||||||
|
$tags = collect(data_get($service, 'tags', []));
|
||||||
|
return str_contains(strtolower($key), strtolower($this->search)) || $tags->contains(function ($tag) {
|
||||||
|
return str_contains(strtolower($tag), strtolower($this->search));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$this->search = null;
|
||||||
|
$this->allServices = getServiceTemplates();
|
||||||
|
$this->services = $this->allServices->filter(function ($service, $key) {
|
||||||
|
return str_contains(strtolower($key), strtolower($this->search));
|
||||||
|
});;
|
||||||
|
$this->emit('success', 'Successfully loaded services.');
|
||||||
}
|
}
|
||||||
$this->services = getServiceTemplates();
|
|
||||||
$this->emit('success', 'Successfully loaded services.');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class Application extends Component
|
|||||||
'application.image' => 'required',
|
'application.image' => 'required',
|
||||||
'application.exclude_from_status' => 'required|boolean',
|
'application.exclude_from_status' => 'required|boolean',
|
||||||
'application.required_fqdn' => 'required|boolean',
|
'application.required_fqdn' => 'required|boolean',
|
||||||
|
'application.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
];
|
];
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
@@ -25,13 +26,22 @@ class Application extends Component
|
|||||||
{
|
{
|
||||||
$this->submit();
|
$this->submit();
|
||||||
}
|
}
|
||||||
|
public function instantSaveAdvanced()
|
||||||
|
{
|
||||||
|
if (!$this->application->service->destination->server->isLogDrainEnabled()) {
|
||||||
|
$this->application->is_log_drain_enabled = false;
|
||||||
|
$this->emit('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->application->save();
|
||||||
|
$this->emit('success', 'You need to restart the service for the changes to take effect.');
|
||||||
|
}
|
||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->application->delete();
|
$this->application->delete();
|
||||||
$this->emit('success', 'Application deleted successfully.');
|
$this->emit('success', 'Application deleted successfully.');
|
||||||
return redirect()->route('project.service', $this->parameters);
|
return redirect()->route('project.service.configuration', $this->parameters);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class ComposeModal extends Component
|
class ComposeModal extends Component
|
||||||
{
|
{
|
||||||
public string $raw;
|
public ?string $raw = null;
|
||||||
public string $actual;
|
public ?string $actual = null;
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.compose-modal');
|
return view('livewire.project.service.compose-modal');
|
||||||
|
|||||||
@@ -2,28 +2,69 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Service;
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Database extends Component
|
class Database extends Component
|
||||||
{
|
{
|
||||||
public ServiceDatabase $database;
|
public ServiceDatabase $database;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
public $fileStorages;
|
public $fileStorages;
|
||||||
|
|
||||||
protected $listeners = ["refreshFileStorages"];
|
protected $listeners = ["refreshFileStorages"];
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'database.human_name' => 'nullable',
|
'database.human_name' => 'nullable',
|
||||||
'database.description' => 'nullable',
|
'database.description' => 'nullable',
|
||||||
'database.image' => 'required',
|
'database.image' => 'required',
|
||||||
'database.exclude_from_status' => 'required|boolean',
|
'database.exclude_from_status' => 'required|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
|
'database.is_public' => 'required|boolean',
|
||||||
|
'database.is_log_drain_enabled' => 'required|boolean',
|
||||||
];
|
];
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.database');
|
return view('livewire.project.service.database');
|
||||||
}
|
}
|
||||||
public function mount() {
|
public function mount()
|
||||||
|
{
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getServiceDatabaseUrl();
|
||||||
|
}
|
||||||
$this->refreshFileStorages();
|
$this->refreshFileStorages();
|
||||||
}
|
}
|
||||||
public function instantSave() {
|
public function instantSaveAdvanced()
|
||||||
|
{
|
||||||
|
if (!$this->database->service->destination->server->isLogDrainEnabled()) {
|
||||||
|
$this->database->is_log_drain_enabled = false;
|
||||||
|
$this->emit('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->submit();
|
||||||
|
$this->emit('success', 'You need to restart the service for the changes to take effect.');
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
|
$this->emit('error', 'Public port is required.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getServiceDatabaseUrl();
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
$this->submit();
|
$this->submit();
|
||||||
}
|
}
|
||||||
public function refreshFileStorages()
|
public function refreshFileStorages()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Index extends Component
|
|||||||
public $databases;
|
public $databases;
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public array $query;
|
public array $query;
|
||||||
protected $listeners = ["refreshStacks","checkStatus"];
|
protected $listeners = ["refreshStacks", "checkStatus"];
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.index');
|
return view('livewire.project.service.index');
|
||||||
@@ -28,7 +28,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
public function checkStatus()
|
public function checkStatus()
|
||||||
{
|
{
|
||||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
dispatch(new ContainerStatusJob($this->service->server));
|
||||||
$this->refreshStacks();
|
$this->refreshStacks();
|
||||||
}
|
}
|
||||||
public function refreshStacks()
|
public function refreshStacks()
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Http\Livewire\Project\Service;
|
|||||||
|
|
||||||
use App\Actions\Service\StartService;
|
use App\Actions\Service\StartService;
|
||||||
use App\Actions\Service\StopService;
|
use App\Actions\Service\StopService;
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -13,15 +12,14 @@ class Navbar extends Component
|
|||||||
public Service $service;
|
public Service $service;
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public array $query;
|
public array $query;
|
||||||
|
protected $listeners = ["checkStatus"];
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.navbar');
|
return view('livewire.project.service.navbar');
|
||||||
}
|
}
|
||||||
|
public function checkStatus() {
|
||||||
public function checkStatus()
|
$this->service->refresh();
|
||||||
{
|
|
||||||
$this->emit('checkStatus');
|
|
||||||
}
|
}
|
||||||
public function deploy()
|
public function deploy()
|
||||||
{
|
{
|
||||||
@@ -29,11 +27,15 @@ class Navbar extends Component
|
|||||||
$activity = StartService::run($this->service);
|
$activity = StartService::run($this->service);
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
}
|
}
|
||||||
public function stop()
|
public function stop(bool $forceCleanup = false)
|
||||||
{
|
{
|
||||||
StopService::run($this->service);
|
StopService::run($this->service);
|
||||||
$this->service->refresh();
|
$this->service->refresh();
|
||||||
$this->emit('success', 'Service stopped successfully.');
|
if ($forceCleanup) {
|
||||||
$this->checkStatus();
|
$this->emit('success', 'Force cleanup service successfully.');
|
||||||
|
} else {
|
||||||
|
$this->emit('success', 'Service stopped successfully.');
|
||||||
|
}
|
||||||
|
$this->emit('checkStatus');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class Show extends Component
|
|||||||
public array $parameters;
|
public array $parameters;
|
||||||
public array $query;
|
public array $query;
|
||||||
public Collection $services;
|
public Collection $services;
|
||||||
|
public $s3s;
|
||||||
|
|
||||||
protected $listeners = ['generateDockerCompose'];
|
protected $listeners = ['generateDockerCompose'];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -33,6 +35,7 @@ class Show extends Component
|
|||||||
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
|
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
|
||||||
$this->serviceDatabase->getFilesFromServer();
|
$this->serviceDatabase->getFilesFromServer();
|
||||||
}
|
}
|
||||||
|
$this->s3s = currentTeam()->s3s;
|
||||||
} catch(\Throwable $e) {
|
} catch(\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,40 @@ use Livewire\Component;
|
|||||||
class StackForm extends Component
|
class StackForm extends Component
|
||||||
{
|
{
|
||||||
public $service;
|
public $service;
|
||||||
|
public $fields = [];
|
||||||
protected $listeners = ["saveCompose"];
|
protected $listeners = ["saveCompose"];
|
||||||
protected $rules = [
|
public $rules = [
|
||||||
'service.docker_compose_raw' => 'required',
|
'service.docker_compose_raw' => 'required',
|
||||||
'service.docker_compose' => 'required',
|
'service.docker_compose' => 'required',
|
||||||
'service.name' => 'required',
|
'service.name' => 'required',
|
||||||
'service.description' => 'nullable',
|
'service.description' => 'nullable',
|
||||||
];
|
];
|
||||||
|
public $validationAttributes = [];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$extraFields = $this->service->extraFields();
|
||||||
|
foreach ($extraFields as $serviceName => $fields) {
|
||||||
|
foreach ($fields as $fieldKey => $field) {
|
||||||
|
$key = data_get($field, 'key');
|
||||||
|
$value = data_get($field, 'value');
|
||||||
|
$rules = data_get($field, 'rules', 'nullable');
|
||||||
|
$isPassword = data_get($field, 'isPassword');
|
||||||
|
$this->fields[$key] = [
|
||||||
|
"serviceName" => $serviceName,
|
||||||
|
"key" => $key,
|
||||||
|
"name" => $fieldKey,
|
||||||
|
"value" => $value,
|
||||||
|
"isPassword" => $isPassword,
|
||||||
|
"rules" => $rules
|
||||||
|
];
|
||||||
|
$this->rules["fields.$key.value"] = $rules;
|
||||||
|
$this->validationAttributes["fields.$key.value"] = $fieldKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public function saveCompose($raw)
|
public function saveCompose($raw)
|
||||||
{
|
{
|
||||||
|
|
||||||
$this->service->docker_compose_raw = $raw;
|
$this->service->docker_compose_raw = $raw;
|
||||||
$this->submit();
|
$this->submit();
|
||||||
}
|
}
|
||||||
@@ -25,6 +50,7 @@ class StackForm extends Component
|
|||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->service->save();
|
$this->service->save();
|
||||||
|
$this->service->saveExtraFields($this->fields);
|
||||||
$this->service->parse();
|
$this->service->parse();
|
||||||
$this->service->refresh();
|
$this->service->refresh();
|
||||||
$this->service->saveComposeConfigs();
|
$this->service->saveComposeConfigs();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Shared;
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
use App\Jobs\StopResourceJob;
|
use App\Jobs\DeleteResourceJob;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class Danger extends Component
|
|||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
StopResourceJob::dispatchSync($this->resource);
|
DeleteResourceJob::dispatchSync($this->resource);
|
||||||
return redirect()->route('project.resources', [
|
return redirect()->route('project.resources', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'environment_name' => $this->parameters['environment_name']
|
'environment_name' => $this->parameters['environment_name']
|
||||||
|
|||||||
@@ -6,5 +6,7 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Destination extends Component
|
class Destination extends Component
|
||||||
{
|
{
|
||||||
public $destination;
|
public $resource;
|
||||||
|
public $servers = [];
|
||||||
|
public $additionalServers = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,17 @@ class All extends Component
|
|||||||
public function getDevView()
|
public function getDevView()
|
||||||
{
|
{
|
||||||
$this->variables = $this->resource->environment_variables->map(function ($item) {
|
$this->variables = $this->resource->environment_variables->map(function ($item) {
|
||||||
|
if ($item->is_shown_once) {
|
||||||
|
return "$item->key=(locked secret)";
|
||||||
|
}
|
||||||
return "$item->key=$item->value";
|
return "$item->key=$item->value";
|
||||||
})->sort()->join('
|
})->sort()->join('
|
||||||
');
|
');
|
||||||
if ($this->showPreview) {
|
if ($this->showPreview) {
|
||||||
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
|
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
|
||||||
|
if ($item->is_shown_once) {
|
||||||
|
return "$item->key=(locked secret)";
|
||||||
|
}
|
||||||
return "$item->key=$item->value";
|
return "$item->key=$item->value";
|
||||||
})->sort()->join('
|
})->sort()->join('
|
||||||
');
|
');
|
||||||
@@ -49,16 +55,21 @@ class All extends Component
|
|||||||
{
|
{
|
||||||
if ($isPreview) {
|
if ($isPreview) {
|
||||||
$variables = parseEnvFormatToArray($this->variablesPreview);
|
$variables = parseEnvFormatToArray($this->variablesPreview);
|
||||||
$existingVariables = $this->resource->environment_variables_preview();
|
$this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete();
|
||||||
$this->resource->environment_variables_preview()->delete();
|
|
||||||
} else {
|
} else {
|
||||||
$variables = parseEnvFormatToArray($this->variables);
|
$variables = parseEnvFormatToArray($this->variables);
|
||||||
$existingVariables = $this->resource->environment_variables();
|
$this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete();
|
||||||
$this->resource->environment_variables()->delete();
|
|
||||||
}
|
}
|
||||||
foreach ($variables as $key => $variable) {
|
foreach ($variables as $key => $variable) {
|
||||||
$found = $existingVariables->where('key', $key)->first();
|
if ($isPreview) {
|
||||||
|
$found = $this->resource->environment_variables_preview()->where('key', $key)->first();
|
||||||
|
} else {
|
||||||
|
$found = $this->resource->environment_variables()->where('key', $key)->first();
|
||||||
|
}
|
||||||
if ($found) {
|
if ($found) {
|
||||||
|
if ($found->is_shown_once) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$found->value = $variable;
|
$found->value = $variable;
|
||||||
$found->save();
|
$found->save();
|
||||||
continue;
|
continue;
|
||||||
@@ -78,6 +89,15 @@ class All extends Component
|
|||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
$environment->standalone_redis_id = $this->resource->id;
|
$environment->standalone_redis_id = $this->resource->id;
|
||||||
break;
|
break;
|
||||||
|
case 'standalone-mongodb':
|
||||||
|
$environment->standalone_mongodb_id = $this->resource->id;
|
||||||
|
break;
|
||||||
|
case 'standalone-mysql':
|
||||||
|
$environment->standalone_mysql_id = $this->resource->id;
|
||||||
|
break;
|
||||||
|
case 'standalone-mariadb':
|
||||||
|
$environment->standalone_mariadb_id = $this->resource->id;
|
||||||
|
break;
|
||||||
case 'service':
|
case 'service':
|
||||||
$environment->service_id = $this->resource->id;
|
$environment->service_id = $this->resource->id;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
|
|||||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
{
|
{
|
||||||
@@ -13,29 +12,45 @@ class Show extends Component
|
|||||||
public ModelsEnvironmentVariable $env;
|
public ModelsEnvironmentVariable $env;
|
||||||
public ?string $modalId = null;
|
public ?string $modalId = null;
|
||||||
public bool $isDisabled = false;
|
public bool $isDisabled = false;
|
||||||
|
public bool $isLocked = false;
|
||||||
public string $type;
|
public string $type;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'env.key' => 'required|string',
|
'env.key' => 'required|string',
|
||||||
'env.value' => 'nullable',
|
'env.value' => 'nullable',
|
||||||
'env.is_build_time' => 'required|boolean',
|
'env.is_build_time' => 'required|boolean',
|
||||||
|
'env.is_shown_once' => 'required|boolean',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'key' => 'key',
|
'key' => 'Key',
|
||||||
'value' => 'value',
|
'value' => 'Value',
|
||||||
'is_build_time' => 'build',
|
'is_build_time' => 'Build Time',
|
||||||
|
'is_shown_once' => 'Shown Once',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->isDisabled = false;
|
|
||||||
if (Str::of($this->env->key)->startsWith('SERVICE_FQDN') || Str::of($this->env->key)->startsWith('SERVICE_URL')) {
|
|
||||||
$this->isDisabled = true;
|
|
||||||
}
|
|
||||||
$this->modalId = new Cuid2(7);
|
$this->modalId = new Cuid2(7);
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
|
$this->checkEnvs();
|
||||||
|
}
|
||||||
|
public function checkEnvs()
|
||||||
|
{
|
||||||
|
$this->isDisabled = false;
|
||||||
|
if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) {
|
||||||
|
$this->isDisabled = true;
|
||||||
|
}
|
||||||
|
if ($this->env->is_shown_once) {
|
||||||
|
$this->isLocked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function lock()
|
||||||
|
{
|
||||||
|
$this->env->is_shown_once = true;
|
||||||
|
$this->env->save();
|
||||||
|
$this->checkEnvs();
|
||||||
|
$this->emit('refreshEnvs');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
$this->submit();
|
$this->submit();
|
||||||
|
|||||||
@@ -2,7 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Shared;
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -10,27 +19,66 @@ class GetLogs extends Component
|
|||||||
{
|
{
|
||||||
public string $outputs = '';
|
public string $outputs = '';
|
||||||
public string $errors = '';
|
public string $errors = '';
|
||||||
|
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
|
||||||
|
public ServiceApplication|ServiceDatabase|null $servicesubtype = null;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public ?string $container = null;
|
public ?string $container = null;
|
||||||
public ?bool $streamLogs = false;
|
public ?bool $streamLogs = false;
|
||||||
|
public ?bool $showTimeStamps = true;
|
||||||
public int $numberOfLines = 100;
|
public int $numberOfLines = 100;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
if ($this->resource->getMorphClass() === 'App\Models\Application') {
|
||||||
|
$this->showTimeStamps = $this->resource->settings->is_include_timestamps;
|
||||||
|
} else {
|
||||||
|
if ($this->servicesubtype) {
|
||||||
|
$this->showTimeStamps = $this->servicesubtype->is_include_timestamps;
|
||||||
|
} else {
|
||||||
|
$this->showTimeStamps = $this->resource->is_include_timestamps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public function doSomethingWithThisChunkOfOutput($output)
|
public function doSomethingWithThisChunkOfOutput($output)
|
||||||
{
|
{
|
||||||
$this->outputs .= $output;
|
$this->outputs .= removeAnsiColors($output);
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
|
if ($this->resource->getMorphClass() === 'App\Models\Application') {
|
||||||
|
$this->resource->settings->is_include_timestamps = $this->showTimeStamps;
|
||||||
|
$this->resource->settings->save();
|
||||||
|
} else {
|
||||||
|
if ($this->servicesubtype) {
|
||||||
|
$this->servicesubtype->is_include_timestamps = $this->showTimeStamps;
|
||||||
|
$this->servicesubtype->save();
|
||||||
|
} else {
|
||||||
|
$this->resource->is_include_timestamps = $this->showTimeStamps;
|
||||||
|
$this->resource->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function getLogs($refresh = false)
|
public function getLogs($refresh = false)
|
||||||
{
|
{
|
||||||
if ($this->container) {
|
if ($this->container) {
|
||||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
if ($this->showTimeStamps) {
|
||||||
|
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
||||||
|
} else {
|
||||||
|
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
|
||||||
|
}
|
||||||
if ($refresh) {
|
if ($refresh) {
|
||||||
$this->outputs = '';
|
$this->outputs = '';
|
||||||
}
|
}
|
||||||
Process::run($sshCommand, function (string $type, string $output) {
|
Process::run($sshCommand, function (string $type, string $output) {
|
||||||
$this->doSomethingWithThisChunkOfOutput($output);
|
$this->doSomethingWithThisChunkOfOutput($output);
|
||||||
});
|
});
|
||||||
|
if ($this->showTimeStamps) {
|
||||||
|
$this->outputs = str($this->outputs)->split('/\n/')->sort(function ($a, $b) {
|
||||||
|
$a = explode(' ', $a);
|
||||||
|
$b = explode(' ', $b);
|
||||||
|
return $a[0] <=> $b[0];
|
||||||
|
})->join("\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ namespace App\Http\Livewire\Project\Shared;
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -12,15 +15,18 @@ use Livewire\Component;
|
|||||||
class Logs extends Component
|
class Logs extends Component
|
||||||
{
|
{
|
||||||
public ?string $type = null;
|
public ?string $type = null;
|
||||||
public Application|StandalonePostgresql|Service|StandaloneRedis $resource;
|
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public ?string $container = null;
|
public $container = [];
|
||||||
|
public $containers;
|
||||||
public $parameters;
|
public $parameters;
|
||||||
public $query;
|
public $query;
|
||||||
public $status;
|
public $status;
|
||||||
|
public $serviceSubType;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->containers = collect();
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->query = request()->query();
|
$this->query = request()->query();
|
||||||
if (data_get($this->parameters, 'application_uuid')) {
|
if (data_get($this->parameters, 'application_uuid')) {
|
||||||
@@ -28,9 +34,11 @@ class Logs extends Component
|
|||||||
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
||||||
$this->status = $this->resource->status;
|
$this->status = $this->resource->status;
|
||||||
$this->server = $this->resource->destination->server;
|
$this->server = $this->resource->destination->server;
|
||||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id);
|
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
|
||||||
if ($containers->count() > 0) {
|
if ($containers->count() > 0) {
|
||||||
$this->container = data_get($containers[0], 'Names');
|
$containers->each(function ($container) {
|
||||||
|
$this->containers->push(str_replace('/', '', $container['Names']));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (data_get($this->parameters, 'database_uuid')) {
|
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||||
$this->type = 'database';
|
$this->type = 'database';
|
||||||
@@ -38,7 +46,16 @@ class Logs extends Component
|
|||||||
if (is_null($resource)) {
|
if (is_null($resource)) {
|
||||||
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
if (is_null($resource)) {
|
if (is_null($resource)) {
|
||||||
abort(404);
|
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
|
if (is_null($resource)) {
|
||||||
|
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
|
if (is_null($resource)) {
|
||||||
|
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
|
if (is_null($resource)) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->resource = $resource;
|
$this->resource = $resource;
|
||||||
@@ -48,6 +65,11 @@ class Logs extends Component
|
|||||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||||
$this->type = 'service';
|
$this->type = 'service';
|
||||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||||
|
$service_name = data_get($this->parameters, 'service_name');
|
||||||
|
$this->serviceSubType = $this->resource->applications()->where('name', $service_name)->first();
|
||||||
|
if (!$this->serviceSubType) {
|
||||||
|
$this->serviceSubType = $this->resource->databases()->where('name', $service_name)->first();
|
||||||
|
}
|
||||||
$this->status = $this->resource->status;
|
$this->status = $this->resource->status;
|
||||||
$this->server = $this->resource->server;
|
$this->server = $this->resource->server;
|
||||||
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
|
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
|
||||||
|
|||||||
37
app/Http/Livewire/Project/Shared/Webhooks.php
Normal file
37
app/Http/Livewire/Project/Shared/Webhooks.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Webhooks extends Component
|
||||||
|
{
|
||||||
|
public $resource;
|
||||||
|
public ?string $deploywebhook = null;
|
||||||
|
public ?string $githubManualWebhook = null;
|
||||||
|
public ?string $gitlabManualWebhook = null;
|
||||||
|
protected $rules = [
|
||||||
|
'resource.manual_webhook_secret_github' => 'nullable|string',
|
||||||
|
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
|
||||||
|
];
|
||||||
|
public function saveSecret()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->resource->save();
|
||||||
|
$this->emit('success','Secret Saved.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->deploywebhook = generateDeployWebhook($this->resource);
|
||||||
|
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
||||||
|
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.shared.webhooks');
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/Http/Livewire/Security/ApiTokens.php
Normal file
38
app/Http/Livewire/Security/ApiTokens.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Security;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class ApiTokens extends Component
|
||||||
|
{
|
||||||
|
public ?string $description = null;
|
||||||
|
public $tokens = [];
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.security.api-tokens');
|
||||||
|
}
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->tokens = auth()->user()->tokens;
|
||||||
|
}
|
||||||
|
public function addNewToken()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'description' => 'required|min:3|max:255',
|
||||||
|
]);
|
||||||
|
$token = auth()->user()->createToken($this->description);
|
||||||
|
$this->tokens = auth()->user()->tokens;
|
||||||
|
session()->flash('token', $token->plainTextToken);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function revoke(int $id)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->tokens()->where('id', $id)->first();
|
||||||
|
$token->delete();
|
||||||
|
$this->tokens = auth()->user()->tokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Http/Livewire/Server/Delete.php
Normal file
31
app/Http/Livewire/Server/Delete.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Delete extends Component
|
||||||
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
|
public $server;
|
||||||
|
public function delete()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->authorize('delete', $this->server);
|
||||||
|
if ($this->server->hasDefinedResources()) {
|
||||||
|
$this->emit('error', 'Server has defined resources. Please delete them first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->server->delete();
|
||||||
|
return redirect()->route('server.all');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.delete');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,10 @@ namespace App\Http\Livewire\Server;
|
|||||||
|
|
||||||
use App\Actions\Server\InstallDocker;
|
use App\Actions\Server\InstallDocker;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Form extends Component
|
class Form extends Component
|
||||||
{
|
{
|
||||||
use AuthorizesRequests;
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public bool $isValidConnection = false;
|
public bool $isValidConnection = false;
|
||||||
public bool $isValidDocker = false;
|
public bool $isValidDocker = false;
|
||||||
@@ -19,25 +17,27 @@ class Form extends Component
|
|||||||
protected $listeners = ['serverRefresh'];
|
protected $listeners = ['serverRefresh'];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'server.name' => 'required|min:6',
|
'server.name' => 'required',
|
||||||
'server.description' => 'nullable',
|
'server.description' => 'nullable',
|
||||||
'server.ip' => 'required',
|
'server.ip' => 'required',
|
||||||
'server.user' => 'required',
|
'server.user' => 'required',
|
||||||
'server.port' => 'required',
|
'server.port' => 'required',
|
||||||
'server.settings.is_cloudflare_tunnel' => 'required',
|
'server.settings.is_cloudflare_tunnel' => 'required|boolean',
|
||||||
'server.settings.is_reachable' => 'required',
|
'server.settings.is_reachable' => 'required',
|
||||||
'server.settings.is_part_of_swarm' => 'required',
|
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||||
|
// 'server.settings.is_swarm_worker' => 'required|boolean',
|
||||||
'wildcard_domain' => 'nullable|url',
|
'wildcard_domain' => 'nullable|url',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'server.name' => 'Name',
|
'server.name' => 'Name',
|
||||||
'server.description' => 'Description',
|
'server.description' => 'Description',
|
||||||
'server.ip' => 'IP address',
|
'server.ip' => 'IP address/Domain',
|
||||||
'server.user' => 'User',
|
'server.user' => 'User',
|
||||||
'server.port' => 'Port',
|
'server.port' => 'Port',
|
||||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||||
'server.settings.is_reachable' => 'is reachable',
|
'server.settings.is_reachable' => 'Is reachable',
|
||||||
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
'server.settings.is_swarm_manager' => 'Swarm Manager',
|
||||||
|
// 'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -45,15 +45,20 @@ class Form extends Component
|
|||||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||||
}
|
}
|
||||||
public function serverRefresh()
|
public function serverRefresh($install = true)
|
||||||
{
|
{
|
||||||
$this->validateServer();
|
$this->validateServer($install);
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
refresh_server_connection($this->server->privateKey);
|
try {
|
||||||
$this->validateServer();
|
refresh_server_connection($this->server->privateKey);
|
||||||
$this->server->settings->save();
|
$this->validateServer(false);
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->emit('success', 'Server updated successfully.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function installDocker()
|
public function installDocker()
|
||||||
{
|
{
|
||||||
@@ -79,12 +84,15 @@ class Form extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$uptime = $this->server->validateConnection();
|
$uptime = $this->server->validateConnection();
|
||||||
if ($uptime) {
|
if (!$uptime) {
|
||||||
$install && $this->emit('success', 'Server is reachable.');
|
|
||||||
} else {
|
|
||||||
$install && $this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
|
$install && $this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$supported_os_type = $this->server->validateOS();
|
||||||
|
if (!$supported_os_type) {
|
||||||
|
$install && $this->emit('error', 'Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/servers#install-docker-engine-manually">documentation</a>.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
$dockerInstalled = $this->server->validateDockerEngine();
|
$dockerInstalled = $this->server->validateDockerEngine();
|
||||||
if ($dockerInstalled) {
|
if ($dockerInstalled) {
|
||||||
$install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
|
$install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
|
||||||
@@ -94,11 +102,17 @@ class Form extends Component
|
|||||||
}
|
}
|
||||||
$dockerVersion = $this->server->validateDockerEngineVersion();
|
$dockerVersion = $this->server->validateDockerEngineVersion();
|
||||||
if ($dockerVersion) {
|
if ($dockerVersion) {
|
||||||
$install && $this->emit('success', 'Docker Engine version is 23+.');
|
$install && $this->emit('success', 'Docker Engine version is 22+.');
|
||||||
} else {
|
} else {
|
||||||
$install && $this->installDocker();
|
$install && $this->installDocker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$swarmInstalled = $this->server->validateDockerSwarm();
|
||||||
|
if ($swarmInstalled) {
|
||||||
|
$install && $this->emit('success', 'Docker Swarm is initiated.');
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -106,26 +120,12 @@ class Form extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->authorize('delete', $this->server);
|
|
||||||
if (!$this->server->isEmpty()) {
|
|
||||||
$this->emit('error', 'Server has defined resources. Please delete them first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->server->delete();
|
|
||||||
return redirect()->route('server.all');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
if(isCloud() && !isDev()) {
|
if (isCloud() && !isDev()) {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'server.ip' => 'required|ip',
|
'server.ip' => 'required',
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|||||||
157
app/Http/Livewire/Server/LogDrains.php
Normal file
157
app/Http/Livewire/Server/LogDrains.php
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Actions\Server\InstallLogDrain;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class LogDrains extends Component
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
public $parameters = [];
|
||||||
|
protected $rules = [
|
||||||
|
'server.settings.is_logdrain_newrelic_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_newrelic_license_key' => 'required|string',
|
||||||
|
'server.settings.logdrain_newrelic_base_uri' => 'required|string',
|
||||||
|
'server.settings.is_logdrain_highlight_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_highlight_project_id' => 'required|string',
|
||||||
|
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_axiom_dataset_name' => 'required|string',
|
||||||
|
'server.settings.logdrain_axiom_api_key' => 'required|string',
|
||||||
|
'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_custom_config' => 'required|string',
|
||||||
|
'server.settings.logdrain_custom_config_parser' => 'nullable',
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'server.settings.is_logdrain_newrelic_enabled' => 'New Relic log drain',
|
||||||
|
'server.settings.logdrain_newrelic_license_key' => 'New Relic license key',
|
||||||
|
'server.settings.logdrain_newrelic_base_uri' => 'New Relic base URI',
|
||||||
|
'server.settings.is_logdrain_highlight_enabled' => 'Highlight log drain',
|
||||||
|
'server.settings.logdrain_highlight_project_id' => 'Highlight project ID',
|
||||||
|
'server.settings.is_logdrain_axiom_enabled' => 'Axiom log drain',
|
||||||
|
'server.settings.logdrain_axiom_dataset_name' => 'Axiom dataset name',
|
||||||
|
'server.settings.logdrain_axiom_api_key' => 'Axiom API key',
|
||||||
|
'server.settings.is_logdrain_custom_enabled' => 'Custom log drain',
|
||||||
|
'server.settings.logdrain_custom_config' => 'Custom log drain configuration',
|
||||||
|
'server.settings.logdrain_custom_config_parser' => 'Custom log drain configuration parser',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
try {
|
||||||
|
$server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||||
|
if (is_null($server)) {
|
||||||
|
return redirect()->route('server.all');
|
||||||
|
}
|
||||||
|
$this->server = $server;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function configureLogDrain()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
InstallLogDrain::run($this->server);
|
||||||
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
|
$this->emit('serverRefresh');
|
||||||
|
$this->emit('success', 'Log drain service stopped.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->emit('serverRefresh');
|
||||||
|
$this->emit('success', 'Log drain service started successfully.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSave(string $type)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$ok = $this->submit($type);
|
||||||
|
if (!$ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->configureLogDrain();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function submit(string $type)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->resetErrorBag();
|
||||||
|
if ($type === 'newrelic') {
|
||||||
|
$this->validate([
|
||||||
|
'server.settings.is_logdrain_newrelic_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_newrelic_license_key' => 'required|string',
|
||||||
|
'server.settings.logdrain_newrelic_base_uri' => 'required|string',
|
||||||
|
]);
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_highlight_enabled' => false,
|
||||||
|
'is_logdrain_axiom_enabled' => false,
|
||||||
|
'is_logdrain_custom_enabled' => false,
|
||||||
|
]);
|
||||||
|
} else if ($type === 'highlight') {
|
||||||
|
$this->validate([
|
||||||
|
'server.settings.is_logdrain_highlight_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_highlight_project_id' => 'required|string',
|
||||||
|
]);
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_newrelic_enabled' => false,
|
||||||
|
'is_logdrain_axiom_enabled' => false,
|
||||||
|
'is_logdrain_custom_enabled' => false,
|
||||||
|
]);
|
||||||
|
} else if ($type === 'axiom') {
|
||||||
|
$this->validate([
|
||||||
|
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_axiom_dataset_name' => 'required|string',
|
||||||
|
'server.settings.logdrain_axiom_api_key' => 'required|string',
|
||||||
|
]);
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_newrelic_enabled' => false,
|
||||||
|
'is_logdrain_highlight_enabled' => false,
|
||||||
|
'is_logdrain_custom_enabled' => false,
|
||||||
|
]);
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
$this->validate([
|
||||||
|
'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
|
||||||
|
'server.settings.logdrain_custom_config' => 'required|string',
|
||||||
|
'server.settings.logdrain_custom_config_parser' => 'nullable',
|
||||||
|
]);
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_newrelic_enabled' => false,
|
||||||
|
'is_logdrain_highlight_enabled' => false,
|
||||||
|
'is_logdrain_axiom_enabled' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
|
return true;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
if ($type === 'newrelic') {
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_newrelic_enabled' => false,
|
||||||
|
]);
|
||||||
|
} else if ($type === 'highlight') {
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_highlight_enabled' => false,
|
||||||
|
]);
|
||||||
|
} else if ($type === 'axiom') {
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_axiom_enabled' => false,
|
||||||
|
]);
|
||||||
|
} else if ($type === 'custom') {
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_logdrain_custom_enabled' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
handleError($e, $this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.log-drains');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,21 +21,23 @@ class ByIp extends Component
|
|||||||
public string $ip;
|
public string $ip;
|
||||||
public string $user = 'root';
|
public string $user = 'root';
|
||||||
public int $port = 22;
|
public int $port = 22;
|
||||||
public bool $is_part_of_swarm = false;
|
public bool $is_swarm_manager = false;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'name' => 'required|string',
|
'name' => 'required|string',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
'ip' => 'required|ip',
|
'ip' => 'required',
|
||||||
'user' => 'required|string',
|
'user' => 'required|string',
|
||||||
'port' => 'required|integer',
|
'port' => 'required|integer',
|
||||||
|
'is_swarm_manager' => 'required|boolean',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'name' => 'Name',
|
'name' => 'Name',
|
||||||
'description' => 'Description',
|
'description' => 'Description',
|
||||||
'ip' => 'IP Address',
|
'ip' => 'IP Address/Domain',
|
||||||
'user' => 'User',
|
'user' => 'User',
|
||||||
'port' => 'Port',
|
'port' => 'Port',
|
||||||
|
'is_swarm_manager' => 'Swarm Manager',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -72,11 +74,11 @@ class ByIp extends Component
|
|||||||
'proxy' => [
|
'proxy' => [
|
||||||
"type" => ProxyTypes::TRAEFIK_V2->value,
|
"type" => ProxyTypes::TRAEFIK_V2->value,
|
||||||
"status" => ProxyStatus::EXITED->value,
|
"status" => ProxyStatus::EXITED->value,
|
||||||
]
|
],
|
||||||
|
|
||||||
]);
|
]);
|
||||||
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
|
$server->settings->is_swarm_manager = $this->is_swarm_manager;
|
||||||
$server->settings->save();
|
$server->settings->save();
|
||||||
|
$server->addInitialNetwork();
|
||||||
return redirect()->route('server.show', $server->uuid);
|
return redirect()->route('server.show', $server->uuid);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -58,11 +58,25 @@ class Deploy extends Component
|
|||||||
|
|
||||||
public function stop()
|
public function stop()
|
||||||
{
|
{
|
||||||
instant_remote_process([
|
try {
|
||||||
"docker rm -f coolify-proxy",
|
if ($this->server->isSwarm()) {
|
||||||
], $this->server);
|
instant_remote_process([
|
||||||
$this->server->proxy->status = 'exited';
|
"docker service rm coolify-proxy_traefik",
|
||||||
$this->server->save();
|
], $this->server);
|
||||||
$this->emit('proxyStatusUpdated');
|
$this->server->proxy->status = 'exited';
|
||||||
|
$this->server->save();
|
||||||
|
$this->emit('proxyStatusUpdated');
|
||||||
|
} else {
|
||||||
|
instant_remote_process([
|
||||||
|
"docker rm -f coolify-proxy",
|
||||||
|
], $this->server);
|
||||||
|
$this->server->proxy->status = 'exited';
|
||||||
|
$this->server->save();
|
||||||
|
$this->emit('proxyStatusUpdated');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ class Status extends Component
|
|||||||
public int $numberOfPolls = 0;
|
public int $numberOfPolls = 0;
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||||
|
public function mount() {
|
||||||
|
$this->checkProxy();
|
||||||
|
}
|
||||||
public function startProxyPolling()
|
public function startProxyPolling()
|
||||||
{
|
{
|
||||||
$this->polling = true;
|
$this->checkProxy();
|
||||||
}
|
}
|
||||||
public function proxyStatusUpdated()
|
public function proxyStatusUpdated()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ class Show extends Component
|
|||||||
if (is_null($this->server)) {
|
if (is_null($this->server)) {
|
||||||
return redirect()->route('server.all');
|
return redirect()->route('server.all');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->emit('serverRefresh');
|
$this->emit('serverRefresh',false);
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ class Backup extends Component
|
|||||||
]);
|
]);
|
||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
$this->backup->refresh();
|
$this->backup->refresh();
|
||||||
ray($this->backup);
|
|
||||||
$this->s3s = S3Storage::whereTeamId(0)->get();
|
$this->s3s = S3Storage::whereTeamId(0)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,18 @@
|
|||||||
namespace App\Http\Livewire\Source\Github;
|
namespace App\Http\Livewire\Source\Github;
|
||||||
|
|
||||||
use App\Models\GithubApp;
|
use App\Models\GithubApp;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Change extends Component
|
class Change extends Component
|
||||||
{
|
{
|
||||||
public string $webhook_endpoint;
|
public string $webhook_endpoint;
|
||||||
public string|null $ipv4;
|
public ?string $ipv4;
|
||||||
public string|null $ipv6;
|
public ?string $ipv6;
|
||||||
public string|null $fqdn;
|
public ?string $fqdn;
|
||||||
|
|
||||||
public bool|null $default_permissions = true;
|
public ?bool $default_permissions = true;
|
||||||
public bool|null $preview_deployment_permissions = true;
|
public ?bool $preview_deployment_permissions = true;
|
||||||
|
|
||||||
public $parameters;
|
public $parameters;
|
||||||
public GithubApp $github_app;
|
public GithubApp $github_app;
|
||||||
@@ -28,29 +29,68 @@ class Change extends Component
|
|||||||
'github_app.custom_user' => 'required|string',
|
'github_app.custom_user' => 'required|string',
|
||||||
'github_app.custom_port' => 'required|int',
|
'github_app.custom_port' => 'required|int',
|
||||||
'github_app.app_id' => 'required|int',
|
'github_app.app_id' => 'required|int',
|
||||||
'github_app.installation_id' => 'nullable',
|
'github_app.installation_id' => 'required|int',
|
||||||
'github_app.client_id' => 'nullable',
|
'github_app.client_id' => 'required|string',
|
||||||
'github_app.client_secret' => 'nullable',
|
'github_app.client_secret' => 'required|string',
|
||||||
'github_app.webhook_secret' => 'nullable',
|
'github_app.webhook_secret' => 'required|string',
|
||||||
'github_app.is_system_wide' => 'required|bool',
|
'github_app.is_system_wide' => 'required|bool',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$github_app_uuid = request()->github_app_uuid;
|
||||||
|
$this->github_app = GithubApp::where('uuid', $github_app_uuid)->first();
|
||||||
|
if (!$this->github_app) {
|
||||||
|
return redirect()->route('source.all');
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||||
|
|
||||||
|
$this->name = str($this->github_app->name)->kebab();
|
||||||
|
$this->fqdn = $settings->fqdn;
|
||||||
|
|
||||||
|
if ($settings->public_ipv4) {
|
||||||
|
$this->ipv4 = 'http://' . $settings->public_ipv4 . ':' . config('app.port');
|
||||||
|
}
|
||||||
|
if ($settings->public_ipv6) {
|
||||||
|
$this->ipv6 = 'http://' . $settings->public_ipv6 . ':' . config('app.port');
|
||||||
|
}
|
||||||
|
if ($this->github_app->installation_id && session('from')) {
|
||||||
|
$source_id = data_get(session('from'), 'source_id');
|
||||||
|
if (!$source_id || $this->github_app->id !== $source_id) {
|
||||||
|
session()->forget('from');
|
||||||
|
} else {
|
||||||
|
$parameters = data_get(session('from'), 'parameters');
|
||||||
|
$back = data_get(session('from'), 'back');
|
||||||
|
$environment_name = data_get($parameters, 'environment_name');
|
||||||
|
$project_uuid = data_get($parameters, 'project_uuid');
|
||||||
|
$type = data_get($parameters, 'type');
|
||||||
|
$destination = data_get($parameters, 'destination');
|
||||||
|
session()->forget('from');
|
||||||
|
return redirect()->route($back, [
|
||||||
|
'environment_name' => $environment_name,
|
||||||
|
'project_uuid' => $project_uuid,
|
||||||
|
'type' => $type,
|
||||||
|
'destination' => $destination,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
if (isCloud() && !isDev()) {
|
if (isCloud() && !isDev()) {
|
||||||
$this->webhook_endpoint = config('app.url');
|
$this->webhook_endpoint = config('app.url');
|
||||||
} else {
|
} else {
|
||||||
$this->webhook_endpoint = $this->ipv4;
|
$this->webhook_endpoint = $this->ipv4;
|
||||||
$this->is_system_wide = $this->github_app->is_system_wide;
|
$this->is_system_wide = $this->github_app->is_system_wide;
|
||||||
}
|
}
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->github_app->save();
|
$this->github_app->save();
|
||||||
|
$this->emit('success', 'Github App updated successfully.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -58,6 +98,7 @@ class Change extends Component
|
|||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
|
$this->submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
|
|||||||
17
app/Http/Livewire/Sponsorship.php
Normal file
17
app/Http/Livewire/Sponsorship.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Sponsorship extends Component
|
||||||
|
{
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
auth()->user()->update(['is_notification_sponsorship_enabled' => false]);
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.sponsorship');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@ class PricingPlans extends Component
|
|||||||
public bool $isTrial = false;
|
public bool $isTrial = false;
|
||||||
public function mount() {
|
public function mount() {
|
||||||
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
|
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
|
||||||
|
if (config('constants.limits.trial_period') == 0) {
|
||||||
|
$this->isTrial = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function subscribeStripe($type)
|
public function subscribeStripe($type)
|
||||||
{
|
{
|
||||||
@@ -63,6 +66,7 @@ class PricingPlans extends Component
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
|
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
|
||||||
|
if (config('constants.limits.trial_period') > 0) {
|
||||||
$payload['subscription_data'] = [
|
$payload['subscription_data'] = [
|
||||||
'trial_period_days' => config('constants.limits.trial_period'),
|
'trial_period_days' => config('constants.limits.trial_period'),
|
||||||
'trial_settings' => [
|
'trial_settings' => [
|
||||||
@@ -71,6 +75,7 @@ class PricingPlans extends Component
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
}
|
||||||
$payload['payment_method_collection'] = 'if_required';
|
$payload['payment_method_collection'] = 'if_required';
|
||||||
}
|
}
|
||||||
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
||||||
|
|||||||
@@ -64,21 +64,10 @@ class Create extends Component
|
|||||||
}
|
}
|
||||||
$this->storage->team_id = currentTeam()->id;
|
$this->storage->team_id = currentTeam()->id;
|
||||||
$this->storage->testConnection();
|
$this->storage->testConnection();
|
||||||
$this->storage->is_usable = true;
|
|
||||||
$this->storage->save();
|
$this->storage->save();
|
||||||
return redirect()->route('team.storages.show', $this->storage->uuid);
|
return redirect()->route('team.storages.show', $this->storage->uuid);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function test_s3_connection()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->storage->testConnection();
|
|
||||||
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class Form extends Component
|
|||||||
public function test_s3_connection()
|
public function test_s3_connection()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->storage->testConnection();
|
$this->storage->testConnection(shouldSave: true);
|
||||||
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -53,10 +53,7 @@ class Form extends Component
|
|||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
try {
|
try {
|
||||||
$this->storage->testConnection();
|
$this->test_s3_connection();
|
||||||
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
|
||||||
$this->storage->save();
|
|
||||||
$this->emit('success', 'Storage settings saved.');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class DecideWhatToDoWithUser
|
|||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||||
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||||
return redirect('boarding');
|
return redirect('boarding');
|
||||||
}
|
}
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
28
app/Jobs/ApplicationRestartJob.php
Normal file
28
app/Jobs/ApplicationRestartJob.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Traits\ExecuteRemoteCommand;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationRestartJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
|
||||||
|
|
||||||
|
public $timeout = 3600;
|
||||||
|
public $tries = 1;
|
||||||
|
public string $applicationDeploymentQueueId;
|
||||||
|
public function __construct(string $applicationDeploymentQueueId)
|
||||||
|
{
|
||||||
|
$this->applicationDeploymentQueueId = $applicationDeploymentQueueId;
|
||||||
|
}
|
||||||
|
public function handle() {
|
||||||
|
ray('Restarting application');
|
||||||
|
}
|
||||||
|
}
|
||||||
88
app/Jobs/CheckLogDrainContainerJob.php
Normal file
88
app/Jobs/CheckLogDrainContainerJob.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Server\InstallLogDrain;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
|
use App\Notifications\Container\ContainerStopped;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Sleep;
|
||||||
|
|
||||||
|
class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uniqueId(): int
|
||||||
|
{
|
||||||
|
return $this->server->id;
|
||||||
|
}
|
||||||
|
public function healthcheck()
|
||||||
|
{
|
||||||
|
$status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false);
|
||||||
|
if (str($status)->contains('running')) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// ray("checking log drain statuses for {$this->server->id}");
|
||||||
|
try {
|
||||||
|
if (!$this->server->isServerReady()) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||||
|
if (!$containers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
|
||||||
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
|
||||||
|
$foundLogDrainContainer = $containers->filter(function ($value, $key) {
|
||||||
|
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||||
|
})->first();
|
||||||
|
if (!$foundLogDrainContainer || !$this->healthcheck()) {
|
||||||
|
ray('Log drain container not found or unhealthy. Restarting...');
|
||||||
|
InstallLogDrain::run($this->server);
|
||||||
|
Sleep::for(10)->seconds();
|
||||||
|
if ($this->healthcheck()) {
|
||||||
|
if ($this->server->log_drain_notification_sent) {
|
||||||
|
$this->server->team->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||||
|
$this->server->update(['log_drain_notification_sent' => false]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$this->server->log_drain_notification_sent) {
|
||||||
|
ray('Log drain container still unhealthy. Sending notification...');
|
||||||
|
$this->server->team->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
|
||||||
|
$this->server->update(['log_drain_notification_sent' => true]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($this->server->log_drain_notification_sent) {
|
||||||
|
$this->server->team->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||||
|
$this->server->update(['log_drain_notification_sent' => false]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
resolve(CheckResaleLicense::class)();
|
CheckResaleLicense::run();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('CheckResaleLicenseJob failed with: ' . $e->getMessage());
|
send_internal_notification('CheckResaleLicenseJob failed with: ' . $e->getMessage());
|
||||||
ray($e);
|
ray($e);
|
||||||
|
|||||||
40
app/Jobs/CleanupHelperContainersJob.php
Normal file
40
app/Jobs/CleanupHelperContainersJob.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CleanupHelperContainersJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ray('Cleaning up helper containers on ' . $this->server->name);
|
||||||
|
$containers = instant_remote_process(['docker container ps --filter "ancestor=ghcr.io/coollabsio/coolify-helper:next" --filter "ancestor=ghcr.io/coollabsio/coolify-helper:latest" --format \'{{json .}}\''], $this->server, false);
|
||||||
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
if ($containers->count() > 0) {
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$containerId = data_get($container,'ID');
|
||||||
|
ray('Removing container ' . $containerId);
|
||||||
|
instant_remote_process(['docker container rm -f ' . $containerId], $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification('CleanupHelperContainersJob failed with error: ' . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,153 +2,113 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
use App\Actions\Proxy\CheckProxy;
|
use App\Actions\Proxy\CheckProxy;
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Container\ContainerRestarted;
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
use App\Notifications\Container\ContainerStopped;
|
use App\Notifications\Container\ContainerStopped;
|
||||||
use App\Notifications\Server\Revived;
|
|
||||||
use App\Notifications\Server\Unreachable;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $tries = 1;
|
|
||||||
public $timeout = 120;
|
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function uniqueId(): string
|
public function uniqueId(): int
|
||||||
{
|
{
|
||||||
return $this->server->uuid;
|
return $this->server->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server)
|
||||||
{
|
{
|
||||||
$this->handle();
|
if (isDev()) $this->handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
// ray("checking container statuses for {$this->server->id}");
|
||||||
try {
|
try {
|
||||||
// ray("checking server status for {$this->server->id}");
|
if (!$this->server->isServerReady()) {
|
||||||
// ray()->clearAll();
|
|
||||||
$serverUptimeCheckNumber = $this->server->unreachable_count;
|
|
||||||
$serverUptimeCheckNumberMax = 3;
|
|
||||||
|
|
||||||
// ray('checking # ' . $serverUptimeCheckNumber);
|
|
||||||
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
|
||||||
if ($this->server->unreachable_email_sent === false) {
|
|
||||||
ray('Server unreachable, sending notification...');
|
|
||||||
$this->server->team->notify(new Unreachable($this->server));
|
|
||||||
$this->server->update(['unreachable_email_sent' => true]);
|
|
||||||
}
|
|
||||||
$this->server->settings()->update([
|
|
||||||
'is_reachable' => false,
|
|
||||||
]);
|
|
||||||
$this->server->update([
|
|
||||||
'unreachable_count' => 0,
|
|
||||||
]);
|
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
$result = $this->server->validateConnection();
|
if ($this->server->isSwarm()) {
|
||||||
if ($result) {
|
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
$this->server->settings()->update([
|
$containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||||
'is_reachable' => true,
|
|
||||||
]);
|
|
||||||
$this->server->update([
|
|
||||||
'unreachable_count' => 0,
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
$serverUptimeCheckNumber++;
|
// Precheck for containers
|
||||||
$this->server->settings()->update([
|
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||||
'is_reachable' => false,
|
if (!$containers) {
|
||||||
]);
|
return;
|
||||||
$this->server->update([
|
}
|
||||||
'unreachable_count' => $serverUptimeCheckNumber,
|
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
]);
|
$containerReplicase = null;
|
||||||
|
}
|
||||||
|
if (is_null($containers)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data_get($this->server, 'unreachable_email_sent') === true) {
|
|
||||||
ray('Server is reachable again, sending notification...');
|
|
||||||
$this->server->team->notify(new Revived($this->server));
|
|
||||||
$this->server->update(['unreachable_email_sent' => false]);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
data_get($this->server, 'settings.is_reachable') === false ||
|
|
||||||
data_get($this->server, 'settings.is_usable') === false
|
|
||||||
) {
|
|
||||||
$this->server->settings()->update([
|
|
||||||
'is_reachable' => true,
|
|
||||||
'is_usable' => true
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
// $this->server->validateDockerEngine(true);
|
|
||||||
$containers = instant_remote_process(["docker container ls -q"], $this->server);
|
|
||||||
if (!$containers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
|
|
||||||
$containers = format_docker_command_output_to_json($containers);
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
if ($containerReplicase) {
|
||||||
|
$containerReplicase = format_docker_command_output_to_json($containerReplicase);
|
||||||
|
foreach ($containerReplicase as $containerReplica) {
|
||||||
|
$name = data_get($containerReplica, 'Name');
|
||||||
|
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
|
if (data_get($container, 'Spec.Name') === $name) {
|
||||||
|
$replicas = data_get($containerReplica, 'Replicas');
|
||||||
|
$running = str($replicas)->explode('/')[0];
|
||||||
|
$total = str($replicas)->explode('/')[1];
|
||||||
|
if ($running === $total) {
|
||||||
|
data_set($container, 'State.Status', 'running');
|
||||||
|
data_set($container, 'State.Health.Status', 'healthy');
|
||||||
|
} else {
|
||||||
|
data_set($container, 'State.Status', 'starting');
|
||||||
|
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $container;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
$applications = $this->server->applications();
|
$applications = $this->server->applications();
|
||||||
$databases = $this->server->databases();
|
$databases = $this->server->databases();
|
||||||
$services = $this->server->services()->get();
|
$services = $this->server->services()->get();
|
||||||
$previews = $this->server->previews();
|
$previews = $this->server->previews();
|
||||||
$this->server->proxyType();
|
|
||||||
/// Check if proxy is running
|
|
||||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
|
||||||
return data_get($value, 'Name') === '/coolify-proxy';
|
|
||||||
})->first();
|
|
||||||
if (!$foundProxyContainer) {
|
|
||||||
try {
|
|
||||||
$shouldStart = CheckProxy::run($this->server);
|
|
||||||
if ($shouldStart) {
|
|
||||||
StartProxy::run($this->server, false);
|
|
||||||
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
|
||||||
} else {
|
|
||||||
ray('Proxy could not be started.');
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
|
||||||
$this->server->save();
|
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
|
||||||
}
|
|
||||||
$foundApplications = [];
|
$foundApplications = [];
|
||||||
$foundApplicationPreviews = [];
|
$foundApplicationPreviews = [];
|
||||||
$foundDatabases = [];
|
$foundDatabases = [];
|
||||||
$foundServices = [];
|
$foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$labels = data_get($container, 'Spec.Labels');
|
||||||
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
|
} else {
|
||||||
|
$labels = data_get($container, 'Config.Labels');
|
||||||
|
}
|
||||||
$containerStatus = data_get($container, 'State.Status');
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
$containerStatus = "$containerStatus ($containerHealth)";
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
$labels = data_get($container, 'Config.Labels');
|
|
||||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
$labelId = data_get($labels, 'coolify.applicationId');
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
if ($labelId) {
|
if ($applicationId) {
|
||||||
if (str_contains($labelId, '-pr-')) {
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
if ($pullRequestId) {
|
||||||
$applicationId = (int) Str::before($labelId, '-pr-');
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
if ($preview) {
|
if ($preview) {
|
||||||
$foundApplicationPreviews[] = $preview->id;
|
$foundApplicationPreviews[] = $preview->id;
|
||||||
@@ -160,7 +120,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
//Notify user that this container should not be there.
|
//Notify user that this container should not be there.
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$application = $applications->where('id', $labelId)->first();
|
$application = $applications->where('id', $applicationId)->first();
|
||||||
if ($application) {
|
if ($application) {
|
||||||
$foundApplications[] = $application->id;
|
$foundApplications[] = $application->id;
|
||||||
$statusFromDb = $application->status;
|
$statusFromDb = $application->status;
|
||||||
@@ -176,11 +136,25 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($uuid) {
|
if ($uuid) {
|
||||||
$database = $databases->where('uuid', $uuid)->first();
|
$database = $databases->where('uuid', $uuid)->first();
|
||||||
if ($database) {
|
if ($database) {
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
$foundDatabases[] = $database->id;
|
$foundDatabases[] = $database->id;
|
||||||
$statusFromDb = $database->status;
|
$statusFromDb = $database->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$database->update(['status' => $containerStatus]);
|
$database->update(['status' => $containerStatus]);
|
||||||
}
|
}
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Notify user that this container should not be there.
|
// Notify user that this container should not be there.
|
||||||
}
|
}
|
||||||
@@ -236,11 +210,16 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$name = data_get($exitedService, 'name');
|
$name = data_get($exitedService, 'name');
|
||||||
$fqdn = data_get($exitedService, 'fqdn');
|
$fqdn = data_get($exitedService, 'fqdn');
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
$project = data_get($service, 'environment.project');
|
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
$environment = data_get($service, 'environment');
|
$serviceUuid = data_get($service, 'uuid');
|
||||||
|
$environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/service/" . $service->uuid;
|
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
$exitedService->update(['status' => 'exited']);
|
$exitedService->update(['status' => 'exited']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,12 +236,17 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$project = data_get($application, 'environment.project');
|
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
$environment = data_get($application, 'environment');
|
$applicationUuid = data_get($application, 'uuid');
|
||||||
|
$environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $application->uuid;
|
if ($projectUuid && $applicationUuid && $environment) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
}
|
||||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
@@ -277,11 +261,17 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$project = data_get($preview, 'application.environment.project');
|
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
$environment = data_get($preview, 'application.environment');
|
$environmentName = data_get($preview, 'application.environment.name');
|
||||||
|
$applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $preview->application->uuid;
|
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
}
|
||||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
foreach ($notRunningDatabases as $database) {
|
foreach ($notRunningDatabases as $database) {
|
||||||
@@ -296,14 +286,45 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
$containerName = $name;
|
$containerName = $name;
|
||||||
|
|
||||||
$project = data_get($database, 'environment.project');
|
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
$environment = data_get($database, 'environment');
|
$environmentName = data_get($database, 'environment.name');
|
||||||
|
$databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/database/" . $database->uuid;
|
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if proxy is running
|
||||||
|
$this->server->proxyType();
|
||||||
|
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
|
send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Database\StopDatabase;
|
||||||
use App\Models\S3Storage;
|
use App\Models\S3Storage;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
use App\Models\ScheduledDatabaseBackupExecution;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Database\BackupFailed;
|
use App\Notifications\Database\BackupFailed;
|
||||||
@@ -19,6 +24,7 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
@@ -27,9 +33,10 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public ?Team $team = null;
|
public ?Team $team = null;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public ScheduledDatabaseBackup $backup;
|
public ScheduledDatabaseBackup $backup;
|
||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
|
||||||
|
|
||||||
public ?string $container_name = null;
|
public ?string $container_name = null;
|
||||||
|
public ?string $directory_name = null;
|
||||||
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
||||||
public string $backup_status = 'failed';
|
public string $backup_status = 'failed';
|
||||||
public ?string $backup_location = null;
|
public ?string $backup_location = null;
|
||||||
@@ -43,9 +50,15 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
$this->backup = $backup;
|
$this->backup = $backup;
|
||||||
$this->team = Team::find($backup->team_id);
|
$this->team = Team::find($backup->team_id);
|
||||||
$this->database = data_get($this->backup, 'database');
|
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||||
$this->server = $this->database->destination->server;
|
$this->database = data_get($this->backup, 'database');
|
||||||
$this->s3 = $this->backup->s3;
|
$this->server = $this->database->service->server;
|
||||||
|
$this->s3 = $this->backup->s3;
|
||||||
|
} else {
|
||||||
|
$this->database = data_get($this->backup, 'database');
|
||||||
|
$this->server = $this->database->destination->server;
|
||||||
|
$this->s3 = $this->backup->s3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
@@ -61,30 +74,155 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
// Check if team is exists
|
||||||
|
if (is_null($this->team)) {
|
||||||
|
$this->backup->update(['status' => 'failed']);
|
||||||
|
StopDatabase::run($this->database);
|
||||||
|
$this->database->delete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
$status = Str::of(data_get($this->database, 'status'));
|
$status = Str::of(data_get($this->database, 'status'));
|
||||||
if (!$status->startsWith('running') && $this->database->id !== 0) {
|
if (!$status->startsWith('running') && $this->database->id !== 0) {
|
||||||
ray('database not running');
|
ray('database not running');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$databaseType = $this->database->type();
|
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||||
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
$databaseType = $this->database->databaseType();
|
||||||
|
$serviceUuid = $this->database->service->uuid;
|
||||||
|
$serviceName = str($this->database->service->name)->slug();
|
||||||
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
|
$this->directory_name = $serviceName . '-' . $this->container_name;
|
||||||
|
$commands[] = "docker exec $this->container_name env | grep POSTGRES_";
|
||||||
|
$envs = instant_remote_process($commands, $this->server);
|
||||||
|
$envs = str($envs)->explode("\n");
|
||||||
|
|
||||||
|
$user = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('POSTGRES_USER=');
|
||||||
|
})->first();
|
||||||
|
if ($user) {
|
||||||
|
$this->database->postgres_user = str($user)->after('POSTGRES_USER=')->value();
|
||||||
|
} else {
|
||||||
|
$this->database->postgres_user = 'postgres';
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('POSTGRES_DB=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('POSTGRES_DB=')->value();
|
||||||
|
} else {
|
||||||
|
$databasesToBackup = $this->database->postgres_user;
|
||||||
|
}
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
|
$this->directory_name = $serviceName . '-' . $this->container_name;
|
||||||
|
$commands[] = "docker exec $this->container_name env | grep MYSQL_";
|
||||||
|
$envs = instant_remote_process($commands, $this->server);
|
||||||
|
$envs = str($envs)->explode("\n");
|
||||||
|
|
||||||
|
$rootPassword = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_ROOT_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($rootPassword) {
|
||||||
|
$this->database->mysql_root_password = str($rootPassword)->after('MYSQL_ROOT_PASSWORD=')->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_DATABASE=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('MYSQL_DATABASE=')->value();
|
||||||
|
} else {
|
||||||
|
throw new \Exception('MYSQL_DATABASE not found');
|
||||||
|
}
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
|
$this->directory_name = $serviceName . '-' . $this->container_name;
|
||||||
|
$commands[] = "docker exec $this->container_name env";
|
||||||
|
$envs = instant_remote_process($commands, $this->server);
|
||||||
|
$envs = str($envs)->explode("\n");
|
||||||
|
$rootPassword = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MARIADB_ROOT_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($rootPassword) {
|
||||||
|
$this->database->mariadb_root_password = str($rootPassword)->after('MARIADB_ROOT_PASSWORD=')->value();
|
||||||
|
} else {
|
||||||
|
$rootPassword = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_ROOT_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($rootPassword) {
|
||||||
|
$this->database->mariadb_root_password = str($rootPassword)->after('MYSQL_ROOT_PASSWORD=')->value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MARIADB_DATABASE=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('MARIADB_DATABASE=')->value();
|
||||||
|
} else {
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_DATABASE=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('MYSQL_DATABASE=')->value();
|
||||||
|
} else {
|
||||||
|
throw new \Exception('MARIADB_DATABASE or MYSQL_DATABASE not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$databaseName = str($this->database->name)->slug()->value();
|
||||||
|
$this->container_name = $this->database->uuid;
|
||||||
|
$this->directory_name = $databaseName . '-' . $this->container_name;
|
||||||
|
$databaseType = $this->database->type();
|
||||||
|
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
||||||
|
}
|
||||||
|
|
||||||
if (is_null($databasesToBackup)) {
|
if (is_null($databasesToBackup)) {
|
||||||
if ($databaseType === 'standalone-postgresql') {
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
$databasesToBackup = [$this->database->postgres_db];
|
$databasesToBackup = [$this->database->postgres_db];
|
||||||
|
} else if ($databaseType === 'standalone-mongodb') {
|
||||||
|
$databasesToBackup = ['*'];
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
$databasesToBackup = [$this->database->mysql_database];
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
$databasesToBackup = [$this->database->mariadb_database];
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$databasesToBackup = explode(',', $databasesToBackup);
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
$databasesToBackup = array_map('trim', $databasesToBackup);
|
// Format: db1,db2,db3
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
} else if ($databaseType === 'standalone-mongodb') {
|
||||||
|
// Format: db1:collection1,collection2|db2:collection3,collection4
|
||||||
|
$databasesToBackup = explode('|', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
ray($databasesToBackup);
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
// Format: db1,db2,db3
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
// Format: db1,db2,db3
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->container_name = $this->database->uuid;
|
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->directory_name;
|
||||||
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->container_name;
|
|
||||||
|
|
||||||
if ($this->database->name === 'coolify-db') {
|
if ($this->database->name === 'coolify-db') {
|
||||||
$databasesToBackup = ['coolify'];
|
$databasesToBackup = ['coolify'];
|
||||||
$this->container_name = "coolify-db";
|
$this->directory_name = $this->container_name = "coolify-db";
|
||||||
$ip = Str::slug($this->server->ip);
|
$ip = Str::slug($this->server->ip);
|
||||||
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
||||||
}
|
}
|
||||||
@@ -92,15 +230,54 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$size = 0;
|
$size = 0;
|
||||||
ray('Backing up ' . $database);
|
ray('Backing up ' . $database);
|
||||||
try {
|
try {
|
||||||
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
|
||||||
$this->backup_location = $this->backup_dir . $this->backup_file;
|
|
||||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
|
||||||
'database_name' => $database,
|
|
||||||
'filename' => $this->backup_location,
|
|
||||||
'scheduled_database_backup_id' => $this->backup->id,
|
|
||||||
]);
|
|
||||||
if ($databaseType === 'standalone-postgresql') {
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
|
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
||||||
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
|
'database_name' => $database,
|
||||||
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
$this->backup_standalone_postgresql($database);
|
$this->backup_standalone_postgresql($database);
|
||||||
|
} else if ($databaseType === 'standalone-mongodb') {
|
||||||
|
if ($database === '*') {
|
||||||
|
$database = 'all';
|
||||||
|
$databaseName = 'all';
|
||||||
|
} else {
|
||||||
|
if (str($database)->contains(':')) {
|
||||||
|
$databaseName = str($database)->before(':');
|
||||||
|
} else {
|
||||||
|
$databaseName = $database;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz";
|
||||||
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
|
'database_name' => $databaseName,
|
||||||
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
|
$this->backup_standalone_mongodb($database);
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
$this->backup_file = "/mysql-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
||||||
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
|
'database_name' => $database,
|
||||||
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
|
$this->backup_standalone_mysql($database);
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
$this->backup_file = "/mariadb-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
||||||
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
|
'database_name' => $database,
|
||||||
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
|
$this->backup_standalone_mariadb($database);
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unsupported database type');
|
||||||
}
|
}
|
||||||
$size = $this->calculate_size();
|
$size = $this->calculate_size();
|
||||||
$this->remove_old_backups();
|
$this->remove_old_backups();
|
||||||
@@ -114,12 +291,14 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
'size' => $size,
|
'size' => $size,
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->backup_log->update([
|
if ($this->backup_log) {
|
||||||
'status' => 'failed',
|
$this->backup_log->update([
|
||||||
'message' => $this->backup_output,
|
'status' => 'failed',
|
||||||
'size' => $size,
|
'message' => $this->backup_output,
|
||||||
'filename' => null
|
'size' => $size,
|
||||||
]);
|
'filename' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||||
throw $e;
|
throw $e;
|
||||||
@@ -130,11 +309,43 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$url = $this->database->getDbUrl(useInternal: true);
|
||||||
|
if ($databaseWithCollections === 'all') {
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
||||||
|
} else {
|
||||||
|
if (str($databaseWithCollections)->contains(':')) {
|
||||||
|
$databaseName = str($databaseWithCollections)->before(':');
|
||||||
|
$collectionsToExclude = str($databaseWithCollections)->after(':')->explode(',');
|
||||||
|
} else {
|
||||||
|
$databaseName = $databaseWithCollections;
|
||||||
|
$collectionsToExclude = collect();
|
||||||
|
}
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
if ($collectionsToExclude->count() === 0) {
|
||||||
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
|
||||||
|
} else {
|
||||||
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
|
$this->backup_output = trim($this->backup_output);
|
||||||
|
if ($this->backup_output === '') {
|
||||||
|
$this->backup_output = null;
|
||||||
|
}
|
||||||
|
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->add_to_backup_output($e->getMessage());
|
||||||
|
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
private function backup_standalone_postgresql(string $database): void
|
private function backup_standalone_postgresql(string $database): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ray($this->backup_dir);
|
|
||||||
$commands[] = "mkdir -p " . $this->backup_dir;
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
@@ -149,7 +360,42 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function backup_standalone_mysql(string $database): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
|
||||||
|
ray($commands);
|
||||||
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
|
$this->backup_output = trim($this->backup_output);
|
||||||
|
if ($this->backup_output === '') {
|
||||||
|
$this->backup_output = null;
|
||||||
|
}
|
||||||
|
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->add_to_backup_output($e->getMessage());
|
||||||
|
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function backup_standalone_mariadb(string $database): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
|
||||||
|
ray($commands);
|
||||||
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
|
$this->backup_output = trim($this->backup_output);
|
||||||
|
if ($this->backup_output === '') {
|
||||||
|
$this->backup_output = null;
|
||||||
|
}
|
||||||
|
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->add_to_backup_output($e->getMessage());
|
||||||
|
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
private function add_to_backup_output($output): void
|
private function add_to_backup_output($output): void
|
||||||
{
|
{
|
||||||
if ($this->backup_output) {
|
if ($this->backup_output) {
|
||||||
@@ -169,7 +415,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($this->backup->number_of_backups_locally === 0) {
|
if ($this->backup->number_of_backups_locally === 0) {
|
||||||
$deletable = $this->backup->executions()->where('status', 'success');
|
$deletable = $this->backup->executions()->where('status', 'success');
|
||||||
} else {
|
} else {
|
||||||
$deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally);
|
$deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally - 1);
|
||||||
}
|
}
|
||||||
foreach ($deletable->get() as $execution) {
|
foreach ($deletable->get() as $execution) {
|
||||||
delete_backup_locally($execution->filename, $this->server);
|
delete_backup_locally($execution->filename, $this->server);
|
||||||
@@ -188,13 +434,13 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// $region = $this->s3->region;
|
// $region = $this->s3->region;
|
||||||
$bucket = $this->s3->bucket;
|
$bucket = $this->s3->bucket;
|
||||||
$endpoint = $this->s3->endpoint;
|
$endpoint = $this->s3->endpoint;
|
||||||
$this->s3->testConnection();
|
$this->s3->testConnection(shouldSave: true);
|
||||||
if (isDev()) {
|
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||||
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v coolify_coolify-data-dev:/data/coolify:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
|
$network = $this->database->service->destination->network;
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
|
$network = $this->database->destination->network;
|
||||||
}
|
}
|
||||||
|
$commands[] = "docker run --pull=always -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
|
||||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
||||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
||||||
instant_remote_process($commands, $this->server);
|
instant_remote_process($commands, $this->server);
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ use App\Actions\Database\StopDatabase;
|
|||||||
use App\Actions\Service\StopService;
|
use App\Actions\Service\StopService;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
@@ -16,11 +19,11 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis $resource)
|
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +32,7 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
try {
|
try {
|
||||||
$server = $this->resource->destination->server;
|
$server = $this->resource->destination->server;
|
||||||
if (!$server->isFunctional()) {
|
if (!$server->isFunctional()) {
|
||||||
|
$this->resource->delete();
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
switch ($this->resource->type()) {
|
switch ($this->resource->type()) {
|
||||||
@@ -41,15 +45,23 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
StopDatabase::run($this->resource);
|
StopDatabase::run($this->resource);
|
||||||
break;
|
break;
|
||||||
|
case 'standalone-mongodb':
|
||||||
|
StopDatabase::run($this->resource);
|
||||||
|
break;
|
||||||
|
case 'standalone-mysql':
|
||||||
|
StopDatabase::run($this->resource);
|
||||||
|
break;
|
||||||
|
case 'standalone-mariadb':
|
||||||
|
StopDatabase::run($this->resource);
|
||||||
|
break;
|
||||||
case 'service':
|
case 'service':
|
||||||
StopService::run($this->resource);
|
StopService::run($this->resource);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
$this->resource->delete();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
|
||||||
$this->resource->delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
@@ -11,68 +10,58 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $timeout = 1000;
|
public $timeout = 300;
|
||||||
public ?string $dockerRootFilesystem = null;
|
|
||||||
public ?int $usageBefore = null;
|
public ?int $usageBefore = null;
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): string
|
|
||||||
{
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$queuedCount = 0;
|
|
||||||
$this->server->applications()->each(function ($application) use ($queuedCount) {
|
|
||||||
$count = data_get($application->deployments(), 'count', 0);
|
|
||||||
$queuedCount += $count;
|
|
||||||
});
|
|
||||||
if ($queuedCount > 0) {
|
|
||||||
ray('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping')->color('orange');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
|
$isInprogress = false;
|
||||||
|
$this->server->applications()->each(function ($application) use (&$isInprogress) {
|
||||||
|
if ($application->isDeploymentInprogress()) {
|
||||||
|
$isInprogress = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if ($isInprogress) {
|
||||||
|
throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
||||||
|
}
|
||||||
if (!$this->server->isFunctional()) {
|
if (!$this->server->isFunctional()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->dockerRootFilesystem = "/";
|
$this->usageBefore = $this->server->getDiskUsage();
|
||||||
$this->usageBefore = $this->getFilesystemUsage();
|
ray('Usage before: ' . $this->usageBefore);
|
||||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
||||||
ray('Cleaning up ' . $this->server->name)->color('orange');
|
ray('Cleaning up ' . $this->server->name);
|
||||||
instant_remote_process(['docker image prune -af'], $this->server);
|
instant_remote_process(['docker image prune -af'], $this->server, false);
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server);
|
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server, false);
|
||||||
instant_remote_process(['docker builder prune -af'], $this->server);
|
instant_remote_process(['docker builder prune -af'], $this->server, false);
|
||||||
$usageAfter = $this->getFilesystemUsage();
|
$usageAfter = $this->server->getDiskUsage();
|
||||||
if ($usageAfter < $this->usageBefore) {
|
if ($usageAfter < $this->usageBefore) {
|
||||||
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name)->color('orange');
|
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||||
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||||
|
Log::info('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||||
} else {
|
} else {
|
||||||
ray('DockerCleanupJob failed to save disk space on ' . $this->server->name)->color('orange');
|
Log::info('DockerCleanupJob failed to save disk space on ' . $this->server->name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ray('No need to clean up ' . $this->server->name)->color('orange');
|
ray('No need to clean up ' . $this->server->name);
|
||||||
|
Log::info('No need to clean up ' . $this->server->name);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
|
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
|
||||||
ray($e->getMessage())->color('orange');
|
ray($e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getFilesystemUsage()
|
|
||||||
{
|
|
||||||
return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this->server, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
45
app/Jobs/PullHelperImageJob.php
Normal file
45
app/Jobs/PullHelperImageJob.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 1000;
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uniqueId(): string
|
||||||
|
{
|
||||||
|
return $this->server->uuid;
|
||||||
|
}
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$helperImage = config('coolify.helper_image');
|
||||||
|
ray("Pulling {$helperImage}");
|
||||||
|
instant_remote_process(["docker pull -q {$helperImage}"], $this->server, false);
|
||||||
|
ray('PullHelperImageJob done');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification('PullHelperImageJob failed with: ' . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user