mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 20:59:24 +00:00
Compare commits
321 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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).
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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. 🪄️
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ 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,9 +48,12 @@ 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>
|
||||||
|
|
||||||
|
<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
|
## 💰 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)]
|
||||||
|
|||||||
@@ -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,7 +11,7 @@ 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);
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
if ($containers->count() > 0) {
|
if ($containers->count() > 0) {
|
||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
$containerName = data_get($container, 'Names');
|
$containerName = data_get($container, 'Names');
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
160
app/Actions/Database/StartMariadb.php
Normal file
160
app/Actions/Database/StartMariadb.php
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<?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 (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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
180
app/Actions/Database/StartMongodb.php
Normal file
180
app/Actions/Database/StartMongodb.php
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<?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 (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";
|
||||||
|
}
|
||||||
|
}
|
||||||
160
app/Actions/Database/StartMysql.php
Normal file
160
app/Actions/Database/StartMysql.php
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<?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 (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,7 +16,7 @@ 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;
|
||||||
@@ -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,
|
||||||
]
|
]
|
||||||
@@ -97,14 +98,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 +161,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 +188,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;
|
||||||
|
|
||||||
@@ -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,
|
||||||
]
|
]
|
||||||
@@ -102,9 +101,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()
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ class StartService
|
|||||||
$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 || 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";
|
||||||
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
|
$commands[] = "docker network connect $service->uuid coolify-proxy || 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,50 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
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\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()
|
||||||
{
|
{
|
||||||
|
ray()->clearAll();
|
||||||
|
$cleanup = $this->option('cleanup');
|
||||||
|
if ($cleanup) {
|
||||||
|
$this->cleanup_stucked_resources();
|
||||||
|
$this->cleanup_ssh();
|
||||||
|
}
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function cleanup_ssh()
|
||||||
|
{
|
||||||
|
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 +61,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')) {
|
||||||
|
ray('Application without environment', $application->name);
|
||||||
|
$application->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($application, 'destination.server')) {
|
||||||
|
ray('Application without server', $application->name);
|
||||||
|
$application->delete();
|
||||||
|
}
|
||||||
|
if (!$application->destination()) {
|
||||||
|
ray('Application without destination', $application->name);
|
||||||
|
$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')) {
|
||||||
|
ray('Postgresql without environment', $postgresql->name);
|
||||||
|
$postgresql->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($postgresql, 'destination.server')) {
|
||||||
|
ray('Postgresql without server', $postgresql->name);
|
||||||
|
$postgresql->delete();
|
||||||
|
}
|
||||||
|
if (!$postgresql->destination()) {
|
||||||
|
ray('Postgresql without destination', $postgresql->name);
|
||||||
|
$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')) {
|
||||||
|
ray('Redis without environment', $redis->name);
|
||||||
|
$redis->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($redis, 'destination.server')) {
|
||||||
|
ray('Redis without server', $redis->name);
|
||||||
|
$redis->delete();
|
||||||
|
}
|
||||||
|
if (!$redis->destination()) {
|
||||||
|
ray('Redis without destination', $redis->name);
|
||||||
|
$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')) {
|
||||||
|
ray('Mongodb without environment', $mongodb->name);
|
||||||
|
$mongodb->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($mongodb, 'destination.server')) {
|
||||||
|
ray('Mongodb without server', $mongodb->name);
|
||||||
|
$mongodb->delete();
|
||||||
|
}
|
||||||
|
if (!$mongodb->destination()) {
|
||||||
|
ray('Mongodb without destination', $mongodb->name);
|
||||||
|
$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')) {
|
||||||
|
ray('Mysql without environment', $mysql->name);
|
||||||
|
$mysql->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($mysql, 'destination.server')) {
|
||||||
|
ray('Mysql without server', $mysql->name);
|
||||||
|
$mysql->delete();
|
||||||
|
}
|
||||||
|
if (!$mysql->destination()) {
|
||||||
|
ray('Mysql without destination', $mysql->name);
|
||||||
|
$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')) {
|
||||||
|
ray('Mariadb without environment', $mariadb->name);
|
||||||
|
$mariadb->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($mariadb, 'destination.server')) {
|
||||||
|
ray('Mariadb without server', $mariadb->name);
|
||||||
|
$mariadb->delete();
|
||||||
|
}
|
||||||
|
if (!$mariadb->destination()) {
|
||||||
|
ray('Mariadb without destination', $mariadb->name);
|
||||||
|
$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')) {
|
||||||
|
ray('Service without environment', $service->name);
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
if (!data_get($service, 'server')) {
|
||||||
|
ray('Service without server', $service->name);
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
if (!$service->destination()) {
|
||||||
|
ray('Service without destination', $service->name);
|
||||||
|
$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')) {
|
||||||
|
ray('ServiceApplication without service', $service->name);
|
||||||
|
$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')) {
|
||||||
|
ray('ServiceDatabase without service', $service->name);
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use App\Jobs\DatabaseBackupJob;
|
|||||||
use App\Jobs\DockerCleanupJob;
|
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\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -19,24 +20,35 @@ 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_scheduled_backups($schedule);
|
|
||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->cleanup_servers($schedule);
|
$this->cleanup_servers($schedule);
|
||||||
$this->check_scheduled_backups($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->cleanup_servers($schedule);
|
||||||
|
$this->pull_helper_image($schedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function pull_helper_image($schedule)
|
||||||
|
{
|
||||||
|
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function cleanup_servers($schedule)
|
private function cleanup_servers($schedule)
|
||||||
@@ -69,7 +81,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,6 +4,7 @@ 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 Sentry\Laravel\Integration;
|
use Sentry\Laravel\Integration;
|
||||||
use Sentry\State\Scope;
|
use Sentry\State\Scope;
|
||||||
@@ -40,6 +41,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 +55,7 @@ class Handler extends ExceptionHandler
|
|||||||
{
|
{
|
||||||
$this->reportable(function (Throwable $e) {
|
$this->reportable(function (Throwable $e) {
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
ray($e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = InstanceSettings::get();
|
||||||
|
|||||||
@@ -41,7 +41,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();
|
||||||
|
|||||||
@@ -164,7 +164,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',
|
||||||
]);
|
]);
|
||||||
@@ -213,7 +213,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
]);
|
]);
|
||||||
$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, customErrorMessage: $customErrorMessage, livewire: $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ 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;
|
||||||
@@ -54,6 +55,7 @@ class General extends Component
|
|||||||
'application.docker_registry_image_tag' => 'nullable',
|
'application.docker_registry_image_tag' => 'nullable',
|
||||||
'application.dockerfile_location' => 'nullable',
|
'application.dockerfile_location' => 'nullable',
|
||||||
'application.custom_labels' => 'nullable',
|
'application.custom_labels' => 'nullable',
|
||||||
|
'application.dockerfile_target_build' => 'nullable',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'application.name' => 'name',
|
'application.name' => 'name',
|
||||||
@@ -76,10 +78,12 @@ class General extends Component
|
|||||||
'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.custom_labels' => 'Custom labels',
|
'application.custom_labels' => 'Custom labels',
|
||||||
|
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$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);
|
||||||
}
|
}
|
||||||
@@ -150,12 +154,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +173,9 @@ class General extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$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',
|
||||||
|
|||||||
@@ -65,4 +65,18 @@ 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'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
103
app/Http/Livewire/Project/Database/Mariadb/General.php
Normal file
103
app/Http/Livewire/Project/Database/Mariadb/General.php
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?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',
|
||||||
|
];
|
||||||
|
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 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
105
app/Http/Livewire/Project/Database/Mongodb/General.php
Normal file
105
app/Http/Livewire/Project/Database/Mongodb/General.php
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<?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',
|
||||||
|
];
|
||||||
|
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 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
103
app/Http/Livewire/Project/Database/Mysql/General.php
Normal file
103
app/Http/Livewire/Project/Database/Mysql/General.php
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?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',
|
||||||
|
];
|
||||||
|
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 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,6 +28,7 @@ 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',
|
||||||
@@ -41,6 +43,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 +52,9 @@ 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 instantSave()
|
public function instantSave()
|
||||||
@@ -68,20 +66,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 +98,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 +141,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',
|
||||||
@@ -35,7 +36,15 @@ 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 submit()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
if ($this->database->redis_conf === "") {
|
if ($this->database->redis_conf === "") {
|
||||||
@@ -56,16 +65,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 +89,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']]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
{
|
||||||
|
$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(bool $force = false)
|
||||||
{
|
|
||||||
$this->loadServices();
|
|
||||||
$this->loadServers();
|
|
||||||
}
|
|
||||||
public function loadServices(bool $forceReload = false)
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($forceReload) {
|
if (count($this->allServices) > 0 && !$force) {
|
||||||
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 {
|
||||||
|
|||||||
@@ -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,56 @@
|
|||||||
|
|
||||||
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',
|
||||||
];
|
];
|
||||||
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 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');
|
||||||
|
|||||||
@@ -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,39 @@ 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');
|
||||||
|
$isPassword = data_get($field, 'isPassword');
|
||||||
|
$this->fields[$key] = [
|
||||||
|
"serviceName" => $serviceName,
|
||||||
|
"key" => $key,
|
||||||
|
"name" => $fieldKey,
|
||||||
|
"value" => $value,
|
||||||
|
"isPassword" => $isPassword,
|
||||||
|
];
|
||||||
|
$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 +49,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']
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ class GetLogs extends Component
|
|||||||
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 doSomethingWithThisChunkOfOutput($output)
|
public function doSomethingWithThisChunkOfOutput($output)
|
||||||
{
|
{
|
||||||
$this->outputs .= $output;
|
$this->outputs .= removeAnsiColors($output);
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
@@ -24,13 +25,24 @@ class GetLogs extends Component
|
|||||||
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,7 +15,7 @@ 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 ?string $container = null;
|
||||||
public $parameters;
|
public $parameters;
|
||||||
@@ -28,7 +31,7 @@ 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');
|
$this->container = data_get($containers[0], 'Names');
|
||||||
}
|
}
|
||||||
@@ -38,7 +41,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;
|
||||||
|
|||||||
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;
|
||||||
@@ -32,7 +30,7 @@ class Form extends Component
|
|||||||
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',
|
||||||
@@ -106,26 +104,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();
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ class ByIp extends Component
|
|||||||
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',
|
||||||
];
|
];
|
||||||
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',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private int $pull_request_id;
|
private int $pull_request_id;
|
||||||
private string $commit;
|
private string $commit;
|
||||||
private bool $force_rebuild;
|
private bool $force_rebuild;
|
||||||
|
private bool $restart_only;
|
||||||
|
|
||||||
private ?string $dockerImage = null;
|
private ?string $dockerImage = null;
|
||||||
private ?string $dockerImageTag = null;
|
private ?string $dockerImageTag = null;
|
||||||
@@ -51,7 +52,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private GithubApp|GitlabApp|string $source = 'other';
|
private GithubApp|GitlabApp|string $source = 'other';
|
||||||
private StandaloneDocker|SwarmDocker $destination;
|
private StandaloneDocker|SwarmDocker $destination;
|
||||||
private Server $server;
|
private Server $server;
|
||||||
private ApplicationPreview|null $preview = null;
|
private ?ApplicationPreview $preview = null;
|
||||||
|
private ?string $git_type = null;
|
||||||
|
|
||||||
private string $container_name;
|
private string $container_name;
|
||||||
private ?string $currently_running_container_name = null;
|
private ?string $currently_running_container_name = null;
|
||||||
@@ -68,6 +70,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private $docker_compose_base64;
|
private $docker_compose_base64;
|
||||||
private string $dockerfile_location = '/Dockerfile';
|
private string $dockerfile_location = '/Dockerfile';
|
||||||
private ?string $addHosts = null;
|
private ?string $addHosts = null;
|
||||||
|
private ?string $buildTarget = null;
|
||||||
private $log_model;
|
private $log_model;
|
||||||
private Collection $saved_outputs;
|
private Collection $saved_outputs;
|
||||||
|
|
||||||
@@ -75,6 +78,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private string $serverUserHomeDir = '/root';
|
private string $serverUserHomeDir = '/root';
|
||||||
private string $dockerConfigFileExists = 'NOK';
|
private string $dockerConfigFileExists = 'NOK';
|
||||||
|
|
||||||
|
private int $customPort = 22;
|
||||||
|
private ?string $customRepository = null;
|
||||||
|
|
||||||
|
private ?string $fullRepoUrl = null;
|
||||||
|
private ?string $branch = null;
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
{
|
{
|
||||||
@@ -89,6 +98,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
||||||
$this->commit = $this->application_deployment_queue->commit;
|
$this->commit = $this->application_deployment_queue->commit;
|
||||||
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
||||||
|
$this->restart_only = $this->application_deployment_queue->restart_only;
|
||||||
|
|
||||||
|
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
|
||||||
|
|
||||||
$source = data_get($this->application, 'source');
|
$source = data_get($this->application, 'source');
|
||||||
if ($source) {
|
if ($source) {
|
||||||
@@ -110,11 +122,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||||
if ($this->application->fqdn) {
|
if ($this->application->fqdn) {
|
||||||
if (data_get($this->preview, 'fqdn')) {
|
if (str($this->application->fqdn)->contains(',')) {
|
||||||
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
|
$url = Url::fromString(str($this->application->fqdn)->explode(',')[0]);
|
||||||
|
$preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]);
|
||||||
|
} else {
|
||||||
|
$url = Url::fromString($this->application->fqdn);
|
||||||
|
if (data_get($this->preview, 'fqdn')) {
|
||||||
|
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$template = $this->application->preview_url_template;
|
$template = $this->application->preview_url_template;
|
||||||
$url = Url::fromString($this->application->fqdn);
|
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
$schema = $url->getScheme();
|
$schema = $url->getScheme();
|
||||||
$random = new Cuid2(7);
|
$random = new Cuid2(7);
|
||||||
@@ -130,14 +147,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
// ray()->measure();
|
|
||||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id);
|
|
||||||
if ($containers->count() > 0) {
|
|
||||||
$this->currently_running_container_name = data_get($containers[0], 'Names');
|
|
||||||
}
|
|
||||||
if ($this->pull_request_id !== 0 && $this->pull_request_id !== null) {
|
|
||||||
$this->currently_running_container_name = $this->container_name;
|
|
||||||
}
|
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
]);
|
]);
|
||||||
@@ -164,16 +173,35 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return "--add-host $name:$ip";
|
return "--add-host $name:$ip";
|
||||||
})->implode(' ');
|
})->implode(' ');
|
||||||
|
|
||||||
|
if ($this->application->dockerfile_target_build) {
|
||||||
|
$this->buildTarget = " --target {$this->application->dockerfile_target_build} ";
|
||||||
|
}
|
||||||
|
|
||||||
// Get user home directory
|
// Get user home directory
|
||||||
$this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server);
|
$this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server);
|
||||||
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
|
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
|
||||||
|
|
||||||
|
// Check custom port
|
||||||
|
preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches);
|
||||||
|
if (count($matches) === 1) {
|
||||||
|
$this->customPort = $matches[0];
|
||||||
|
$gitHost = str($this->application->git_repository)->before(':');
|
||||||
|
$gitRepo = str($this->application->git_repository)->after('/');
|
||||||
|
$this->customRepository = "$gitHost:$gitRepo";
|
||||||
|
} else {
|
||||||
|
$this->customRepository = $this->application->git_repository;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if ($this->application->dockerfile) {
|
if ($this->restart_only) {
|
||||||
|
$this->just_restart();
|
||||||
|
} else if ($this->application->dockerfile) {
|
||||||
$this->deploy_simple_dockerfile();
|
$this->deploy_simple_dockerfile();
|
||||||
} else if ($this->application->build_pack === 'dockerimage') {
|
} else if ($this->application->build_pack === 'dockerimage') {
|
||||||
$this->deploy_dockerimage_buildpack();
|
$this->deploy_dockerimage_buildpack();
|
||||||
} else if ($this->application->build_pack === 'dockerfile') {
|
} else if ($this->application->build_pack === 'dockerfile') {
|
||||||
$this->deploy_dockerfile_buildpack();
|
$this->deploy_dockerfile_buildpack();
|
||||||
|
} else if ($this->application->build_pack === 'static') {
|
||||||
|
$this->deploy_static_buildpack();
|
||||||
} else {
|
} else {
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$this->deploy_pull_request();
|
$this->deploy_pull_request();
|
||||||
@@ -193,12 +221,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
} finally {
|
} finally {
|
||||||
if (isset($this->docker_compose_base64)) {
|
if (isset($this->docker_compose_base64)) {
|
||||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||||
|
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||||
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"mkdir -p $this->configuration_dir"
|
"mkdir -p $this->configuration_dir"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"echo '{$this->docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml",
|
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"echo '{$readme}' > $this->configuration_dir/README.md",
|
"echo '{$readme}' > $this->configuration_dir/README.md",
|
||||||
@@ -209,6 +241,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
[
|
[
|
||||||
"docker rm -f {$this->deployment_uuid} >/dev/null 2>&1",
|
"docker rm -f {$this->deployment_uuid} >/dev/null 2>&1",
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
|
"ignore_errors" => true,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"docker image prune -f >/dev/null 2>&1",
|
||||||
|
"hidden" => true,
|
||||||
|
"ignore_errors" => true,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -228,7 +268,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
// executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
||||||
// ],
|
// ],
|
||||||
// );
|
// );
|
||||||
// $this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
// $this->build_image_name = Str::lower("{$this->customRepository}:build");
|
||||||
// $this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
// $this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||||
// $this->save_environment_variables();
|
// $this->save_environment_variables();
|
||||||
// $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
// $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||||
@@ -250,6 +290,48 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
// [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
private function generate_image_names()
|
||||||
|
{
|
||||||
|
if ($this->application->dockerfile) {
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->uuid}:build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||||
|
} else if ($this->application->build_pack === 'dockerimage') {
|
||||||
|
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
||||||
|
} else if ($this->pull_request_id !== 0) {
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||||
|
} else {
|
||||||
|
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
||||||
|
if (strlen($tag) > 128) {
|
||||||
|
$tag = $tag->substr(0, 128);
|
||||||
|
}
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->uuid}:{$tag}-build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function just_restart()
|
||||||
|
{
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$this->prepare_builder_image();
|
||||||
|
$this->check_git_if_build_needed();
|
||||||
|
$this->set_base_dir();
|
||||||
|
$this->generate_image_names();
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||||
|
]);
|
||||||
|
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||||
|
$this->generate_compose_file();
|
||||||
|
$this->rolling_update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"echo 'Cannot find image {$this->production_image_name} locally. Please redeploy the application.'",
|
||||||
|
]);
|
||||||
|
}
|
||||||
private function save_environment_variables()
|
private function save_environment_variables()
|
||||||
{
|
{
|
||||||
$envs = collect([]);
|
$envs = collect([]);
|
||||||
@@ -274,12 +356,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
|
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir$this->dockerfile_location")
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
$this->generate_image_names();
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
|
||||||
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->generate_build_env_variables();
|
$this->generate_build_env_variables();
|
||||||
$this->add_build_env_variables_to_dockerfile();
|
$this->add_build_env_variables_to_dockerfile();
|
||||||
@@ -297,7 +377,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
"echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"
|
"echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
$this->generate_image_names();
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
@@ -310,20 +390,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
|
$this->check_git_if_build_needed();
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
$this->set_base_dir();
|
$this->set_base_dir();
|
||||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
$this->generate_image_names();
|
||||||
if (strlen($tag) > 128) {
|
|
||||||
$tag = $tag->substr(0, 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
|
||||||
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->generate_build_env_variables();
|
$this->generate_build_env_variables();
|
||||||
@@ -335,21 +409,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->clone_repository();
|
$this->check_git_if_build_needed();
|
||||||
$this->set_base_dir();
|
$this->set_base_dir();
|
||||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
$this->generate_image_names();
|
||||||
if (strlen($tag) > 128) {
|
|
||||||
$tag = $tag->substr(0, 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
|
||||||
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
|
||||||
|
|
||||||
if (!$this->force_rebuild) {
|
if (!$this->force_rebuild) {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||||
@@ -368,6 +434,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$this->clone_repository();
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
$this->generate_nixpacks_confs();
|
$this->generate_nixpacks_confs();
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
@@ -376,12 +443,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->build_image();
|
$this->build_image();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
}
|
}
|
||||||
|
private function deploy_static_buildpack()
|
||||||
|
{
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$this->prepare_builder_image();
|
||||||
|
$this->check_git_if_build_needed();
|
||||||
|
$this->set_base_dir();
|
||||||
|
$this->generate_image_names();
|
||||||
|
$this->clone_repository();
|
||||||
|
$this->cleanup_git();
|
||||||
|
$this->build_image();
|
||||||
|
$this->generate_compose_file();
|
||||||
|
$this->rolling_update();
|
||||||
|
}
|
||||||
|
|
||||||
private function rolling_update()
|
private function rolling_update()
|
||||||
{
|
{
|
||||||
if (count($this->application->ports_mappings_array) > 0) {
|
if (count($this->application->ports_mappings_array) > 0) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"],
|
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
|
||||||
);
|
);
|
||||||
$this->stop_running_container(force: true);
|
$this->stop_running_container(force: true);
|
||||||
$this->start_by_compose_file();
|
$this->start_by_compose_file();
|
||||||
@@ -442,11 +526,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
private function deploy_pull_request()
|
private function deploy_pull_request()
|
||||||
{
|
{
|
||||||
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
$this->newVersionIsHealthy = true;
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
$this->generate_image_names();
|
||||||
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
|
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.'",
|
||||||
]);
|
]);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
@@ -469,17 +552,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function prepare_builder_image()
|
private function prepare_builder_image()
|
||||||
{
|
{
|
||||||
$pull = "--pull=always";
|
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('coolify.helper_image');
|
||||||
if ($this->dockerConfigFileExists === 'OK') {
|
if ($this->dockerConfigFileExists === 'OK') {
|
||||||
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
$runCommand = "docker run -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||||
} else {
|
} else {
|
||||||
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
$runCommand = "docker run -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n 'Pulling helper image from $helperImage.'",
|
"echo -n 'Preparing container with helper image: $helperImage.'",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
$runCommand,
|
$runCommand,
|
||||||
@@ -499,27 +581,58 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
private function check_git_if_build_needed()
|
||||||
|
{
|
||||||
|
$this->generate_git_import_commands();
|
||||||
|
$private_key = data_get($this->application, 'private_key.private_key');
|
||||||
|
if ($private_key) {
|
||||||
|
$private_key = base64_encode($private_key);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->fullRepoUrl} {$this->branch}"),
|
||||||
|
"hidden" => true,
|
||||||
|
"save" => "git_commit_sha"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->fullRepoUrl} {$this->branch}"),
|
||||||
|
"hidden" => true,
|
||||||
|
"save" => "git_commit_sha"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->saved_outputs->get('git_commit_sha')) {
|
||||||
|
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
|
||||||
|
}
|
||||||
|
}
|
||||||
private function clone_repository()
|
private function clone_repository()
|
||||||
{
|
{
|
||||||
|
$importCommands = $this->generate_git_import_commands();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
|
"echo -n 'Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
$this->importing_git_repository(), "hidden" => true
|
$importCommands, "hidden" => true
|
||||||
],
|
]
|
||||||
[
|
|
||||||
executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git rev-parse HEAD"),
|
|
||||||
"hidden" => true,
|
|
||||||
"save" => "git_commit_sha"
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
$this->commit = $this->saved_outputs->get('git_commit_sha');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function importing_git_repository()
|
private function generate_git_import_commands()
|
||||||
{
|
{
|
||||||
|
$this->branch = $this->application->git_branch;
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
$git_clone_command = "git clone -q -b {$this->application->git_branch}";
|
$git_clone_command = "git clone -q -b {$this->application->git_branch}";
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
@@ -534,42 +647,59 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
|
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
|
||||||
if ($this->source->is_public) {
|
if ($this->source->is_public) {
|
||||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->basedir}";
|
$this->fullRepoUrl = "{$this->source->html_url}/{$this->customRepository}";
|
||||||
|
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->customRepository} {$this->basedir}";
|
||||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||||
|
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
||||||
} else {
|
} else {
|
||||||
$github_access_token = generate_github_installation_token($this->source);
|
$github_access_token = generate_github_installation_token($this->source);
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->basedir}"));
|
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->customRepository}.git {$this->basedir}"));
|
||||||
|
$this->fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->customRepository}.git";
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
|
$this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name";
|
||||||
|
$commands->push(executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git fetch origin $this->branch && git checkout $pr_branch_name"));
|
||||||
}
|
}
|
||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->application->deploymentType() === 'deploy_key') {
|
if ($this->application->deploymentType() === 'deploy_key') {
|
||||||
$port = 22;
|
$this->fullRepoUrl = $this->customRepository;
|
||||||
preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches);
|
$private_key = data_get($this->application, 'private_key.private_key');
|
||||||
if (count($matches) === 1) {
|
if (is_null($private_key)) {
|
||||||
$port = $matches[0];
|
throw new Exception('Private key not found. Please add a private key to the application and try again.');
|
||||||
}
|
}
|
||||||
$private_key = base64_encode($this->application->private_key->private_key);
|
$private_key = base64_encode($private_key);
|
||||||
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -p $port -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_repository} {$this->basedir}";
|
$git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->customRepository} {$this->basedir}";
|
||||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
$git_clone_command = $this->set_git_import_settings($git_clone_command_base);
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
|
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
|
||||||
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
|
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
|
||||||
executeInDocker($this->deployment_uuid, $git_clone_command)
|
|
||||||
]);
|
]);
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
ray($this->git_type);
|
||||||
|
if ($this->git_type === 'gitlab') {
|
||||||
|
$this->branch = "merge-requests/{$this->pull_request_id}/head:$pr_branch_name";
|
||||||
|
$commands->push(executeInDocker($this->deployment_uuid, "echo 'Checking out $this->branch'"));
|
||||||
|
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $this->branch && git checkout $pr_branch_name";
|
||||||
|
}
|
||||||
|
if ($this->git_type === 'github') {
|
||||||
|
$this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name";
|
||||||
|
$commands->push(executeInDocker($this->deployment_uuid, "echo 'Checking out $this->branch'"));
|
||||||
|
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $this->branch && git checkout $pr_branch_name";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
if ($this->application->deploymentType() === 'other') {
|
if ($this->application->deploymentType() === 'other') {
|
||||||
$git_clone_command = "{$git_clone_command} {$this->application->git_repository} {$this->basedir}";
|
$this->fullRepoUrl = $this->customRepository;
|
||||||
|
$git_clone_command = "{$git_clone_command} {$this->customRepository} {$this->basedir}";
|
||||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
||||||
ray($commands);
|
|
||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,7 +747,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private function nixpacks_build_cmd()
|
private function nixpacks_build_cmd()
|
||||||
{
|
{
|
||||||
$this->generate_env_variables();
|
$this->generate_env_variables();
|
||||||
$nixpacks_command = "nixpacks build --no-cache -o {$this->workdir} {$this->env_args} --no-error-without-start";
|
$nixpacks_command = "nixpacks build --cache-key '{$this->application->uuid}' -o {$this->workdir} {$this->env_args} --no-error-without-start";
|
||||||
if ($this->application->build_command) {
|
if ($this->application->build_command) {
|
||||||
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
|
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
|
||||||
}
|
}
|
||||||
@@ -655,10 +785,35 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$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($ports);
|
$environment_variables = $this->generate_environment_variables($ports);
|
||||||
|
|
||||||
$labels = generateLabelsApplication($this->application, $this->preview);
|
|
||||||
if (data_get($this->application, 'custom_labels')) {
|
if (data_get($this->application, 'custom_labels')) {
|
||||||
$labels = str($this->application->custom_labels)->explode(',')->toArray();
|
$labels = collect(str($this->application->custom_labels)->explode(','));
|
||||||
|
$labels = $labels->filter(function ($value, $key) {
|
||||||
|
return !Str::startsWith($value, 'coolify.');
|
||||||
|
});
|
||||||
|
$this->application->custom_labels = $labels->implode(',');
|
||||||
|
$this->application->save();
|
||||||
|
} else {
|
||||||
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
}
|
}
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
|
|
||||||
|
// $newHostLabel = $newLabels->filter(function ($label) {
|
||||||
|
// return str($label)->contains('Host');
|
||||||
|
// });
|
||||||
|
// $labels = $labels->reject(function ($label) {
|
||||||
|
// return str($label)->contains('Host');
|
||||||
|
// });
|
||||||
|
// ray($labels,$newLabels);
|
||||||
|
// $labels = $labels->map(function ($label) {
|
||||||
|
// $pattern = '/([a-zA-Z0-9]+)-(\d+)-(http|https)/';
|
||||||
|
// $replacement = "$1-pr-{$this->pull_request_id}-$2-$3";
|
||||||
|
// $newLabel = preg_replace($pattern, $replacement, $label);
|
||||||
|
// return $newLabel;
|
||||||
|
// });
|
||||||
|
// $labels = $labels->merge($newHostLabel);
|
||||||
|
}
|
||||||
|
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
'version' => '3.8',
|
||||||
'services' => [
|
'services' => [
|
||||||
@@ -686,7 +841,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
'memswap_limit' => $this->application->limits_memory_swap,
|
'memswap_limit' => $this->application->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->application->limits_memory_swappiness,
|
'mem_swappiness' => $this->application->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->application->limits_memory_reservation,
|
'mem_reservation' => $this->application->limits_memory_reservation,
|
||||||
'cpus' => $this->application->limits_cpus,
|
'cpus' => (int) $this->application->limits_cpus,
|
||||||
'cpuset' => $this->application->limits_cpuset,
|
'cpuset' => $this->application->limits_cpuset,
|
||||||
'cpu_shares' => $this->application->limits_cpu_shares,
|
'cpu_shares' => $this->application->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
@@ -765,11 +920,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
foreach ($this->application->runtime_environment_variables as $env) {
|
foreach ($this->application->runtime_environment_variables as $env) {
|
||||||
$environment_variables->push("$env->key=$env->value");
|
$environment_variables->push("$env->key=$env->value");
|
||||||
}
|
}
|
||||||
|
foreach ($this->application->nixpacks_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// ray($this->application->runtime_environment_variables_preview)->green();
|
// ray($this->application->runtime_environment_variables_preview)->green();
|
||||||
foreach ($this->application->runtime_environment_variables_preview as $env) {
|
foreach ($this->application->runtime_environment_variables_preview as $env) {
|
||||||
$environment_variables->push("$env->key=$env->value");
|
$environment_variables->push("$env->key=$env->value");
|
||||||
}
|
}
|
||||||
|
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Add PORT if not exists, use the first port as default
|
// Add PORT if not exists, use the first port as default
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
||||||
@@ -800,25 +961,41 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
return implode(' ', $generated_healthchecks_commands);
|
return implode(' ', $generated_healthchecks_commands);
|
||||||
}
|
}
|
||||||
|
private function pull_latest_image($image)
|
||||||
|
{
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["echo -n 'Pulling latest image ($image) from the registry.'"],
|
||||||
|
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "docker pull {$image}"), "hidden" => true
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
private function build_image()
|
private function build_image()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command([
|
if ($this->application->build_pack === 'static') {
|
||||||
"echo -n 'Building docker image for your application. To check the current progress, click on Show Debug Logs.'",
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($this->application->settings->is_static) {
|
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
"echo -n 'Static deployment. Copying static assets to the image.'",
|
||||||
]);
|
]);
|
||||||
|
} else {
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"echo -n 'Building docker image for your application. To check the current progress, click on Show Debug Logs.'",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
if ($this->application->settings->is_static || $this->application->build_pack === 'static') {
|
||||||
|
if ($this->application->static_image) {
|
||||||
|
$this->pull_latest_image($this->application->static_image);
|
||||||
|
}
|
||||||
|
if ($this->application->build_pack === 'static') {
|
||||||
|
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||||
WORKDIR /usr/share/nginx/html/
|
WORKDIR /usr/share/nginx/html/
|
||||||
LABEL coolify.deploymentId={$this->deployment_uuid}
|
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||||
COPY --from=$this->build_image_name /app/{$this->application->publish_directory} .
|
COPY . .
|
||||||
|
RUN rm -f /usr/share/nginx/html/nginx.conf
|
||||||
|
RUN rm -f /usr/share/nginx/html/Dockerfile
|
||||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||||
|
$nginx_config = base64_encode("server {
|
||||||
$nginx_config = base64_encode("server {
|
|
||||||
listen 80;
|
listen 80;
|
||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
@@ -834,43 +1011,95 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
} else {
|
||||||
|
$this->execute_remote_command([
|
||||||
|
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||||
|
WORKDIR /usr/share/nginx/html/
|
||||||
|
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||||
|
COPY --from=$this->build_image_name /app/{$this->application->publish_directory} .
|
||||||
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||||
|
|
||||||
|
$nginx_config = base64_encode("server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
try_files \$uri \$uri.html \$uri/index.html \$uri/ /index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod")
|
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile")
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// Pure Dockerfile based deployment
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
executeInDocker($this->deployment_uuid, "docker build --pull $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function stop_running_container(bool $force = false)
|
private function stop_running_container(bool $force = false)
|
||||||
{
|
{
|
||||||
if ($this->currently_running_container_name) {
|
$this->execute_remote_command(["echo -n 'Removing old version of your application.'"]);
|
||||||
if ($this->newVersionIsHealthy || $force) {
|
|
||||||
$this->execute_remote_command(
|
if ($this->newVersionIsHealthy || $force) {
|
||||||
["echo -n 'Removing old version of your application.'"],
|
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
if ($this->pull_request_id !== 0) {
|
||||||
);
|
$containers = $containers->filter(function ($container) {
|
||||||
|
return data_get($container, 'Names') === $this->container_name;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$containers = $containers->filter(function ($container) {
|
||||||
["echo -n 'New version is not healthy, rolling back to the old version.'"],
|
return data_get($container, 'Names') !== $this->container_name;
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true],
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
$containers->each(function ($container) {
|
||||||
|
$containerName = data_get($container, 'Names');
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["echo -n 'New version is not healthy, rolling back to the old version.'"],
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function start_by_compose_file()
|
private function start_by_compose_file()
|
||||||
{
|
{
|
||||||
|
if (
|
||||||
|
!$this->application->dockerfile &&
|
||||||
|
(
|
||||||
|
$this->application->build_pack === 'dockerimage' ||
|
||||||
|
$this->application->build_pack === 'dockerfile')
|
||||||
|
) {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["echo -n 'Pulling latest images from the registry.'"],
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir}"), "hidden" => true],
|
||||||
|
);
|
||||||
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'Starting application (could take a while).'"],
|
["echo -n 'Starting application (could take a while).'"],
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
||||||
@@ -879,14 +1108,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
|
|
||||||
private function generate_build_env_variables()
|
private function generate_build_env_variables()
|
||||||
{
|
{
|
||||||
$this->build_args = collect(["--build-arg SOURCE_COMMIT={$this->commit}"]);
|
$this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]);
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
foreach ($this->application->build_environment_variables as $env) {
|
foreach ($this->application->build_environment_variables as $env) {
|
||||||
$this->build_args->push("--build-arg {$env->key}={$env->value}");
|
$this->build_args->push("--build-arg {$env->key}=\"{$env->value}\"");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||||
$this->build_args->push("--build-arg {$env->key}={$env->value}");
|
$this->build_args->push("--build-arg {$env->key}=\"{$env->value}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,7 +1125,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
private function add_build_env_variables_to_dockerfile()
|
private function add_build_env_variables_to_dockerfile()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile'
|
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile'
|
||||||
]);
|
]);
|
||||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||||
|
|
||||||
@@ -905,7 +1134,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
}
|
}
|
||||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/{$this->dockerfile_location}"),
|
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"),
|
||||||
"hidden" => true
|
"hidden" => true
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -931,8 +1160,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
{
|
{
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo 'Oops something is not okay, are you okay? 😢'"],
|
["echo 'Oops something is not okay, are you okay? 😢'"],
|
||||||
["echo '{$exception->getMessage()}'"]
|
["echo '{$exception->getMessage()}'"],
|
||||||
|
["echo -n 'Deployment failed. Removing the new version of your application.'"],
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -12,41 +12,35 @@ use App\Notifications\Server\Revived;
|
|||||||
use App\Notifications\Server\Unreachable;
|
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
|
|
||||||
{
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
$this->handle();
|
$this->handle();
|
||||||
}
|
}
|
||||||
|
public function middleware(): array
|
||||||
public function handle()
|
|
||||||
{
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uniqueId(): int
|
||||||
|
{
|
||||||
|
return $this->server->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// ray("checking server status for {$this->server->id}");
|
||||||
try {
|
try {
|
||||||
// ray("checking server status for {$this->server->id}");
|
|
||||||
// ray()->clearAll();
|
// ray()->clearAll();
|
||||||
$serverUptimeCheckNumber = $this->server->unreachable_count;
|
$serverUptimeCheckNumber = $this->server->unreachable_count;
|
||||||
$serverUptimeCheckNumberMax = 3;
|
$serverUptimeCheckNumberMax = 3;
|
||||||
@@ -64,6 +58,23 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->server->update([
|
$this->server->update([
|
||||||
'unreachable_count' => 0,
|
'unreachable_count' => 0,
|
||||||
]);
|
]);
|
||||||
|
// Update all applications, databases and services to exited
|
||||||
|
foreach ($this->server->applications() as $application) {
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->server->databases() as $database) {
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->server->services() as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
$app->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
$db->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$result = $this->server->validateConnection();
|
$result = $this->server->validateConnection();
|
||||||
@@ -144,11 +155,13 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$containerStatus = "$containerStatus ($containerHealth)";
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
$labels = data_get($container, 'Config.Labels');
|
$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 +173,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;
|
||||||
@@ -236,10 +249,15 @@ 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) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
$exitedService->update(['status' => 'exited']);
|
$exitedService->update(['status' => 'exited']);
|
||||||
}
|
}
|
||||||
@@ -257,10 +275,15 @@ 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));
|
||||||
}
|
}
|
||||||
@@ -277,10 +300,16 @@ 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');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $preview->application->uuid;
|
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
}
|
||||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
@@ -296,16 +325,21 @@ 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) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
|
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
return handleError($e);
|
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);
|
||||||
@@ -189,12 +435,12 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$bucket = $this->s3->bucket;
|
$bucket = $this->s3->bucket;
|
||||||
$endpoint = $this->s3->endpoint;
|
$endpoint = $this->s3->endpoint;
|
||||||
$this->s3->testConnection();
|
$this->s3->testConnection();
|
||||||
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,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Exception;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -11,18 +11,19 @@ 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;
|
||||||
|
|
||||||
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 ?string $dockerRootFilesystem = null;
|
||||||
public ?int $usageBefore = null;
|
public ?int $usageBefore = null;
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function uniqueId(): string
|
public function uniqueId(): string
|
||||||
@@ -34,14 +35,15 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$queuedCount = 0;
|
$isInprogress = false;
|
||||||
$this->server->applications()->each(function ($application) use ($queuedCount) {
|
$this->server->applications()->each(function ($application) use (&$isInprogress) {
|
||||||
$count = data_get($application->deployments(), 'count', 0);
|
if ($application->isDeploymentInprogress()) {
|
||||||
$queuedCount += $count;
|
$isInprogress = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if ($queuedCount > 0) {
|
if ($isInprogress) {
|
||||||
ray('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping')->color('orange');
|
throw new Exception('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!$this->server->isFunctional()) {
|
if (!$this->server->isFunctional()) {
|
||||||
@@ -50,23 +52,25 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->dockerRootFilesystem = "/";
|
$this->dockerRootFilesystem = "/";
|
||||||
$this->usageBefore = $this->getFilesystemUsage();
|
$this->usageBefore = $this->getFilesystemUsage();
|
||||||
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);
|
||||||
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);
|
||||||
instant_remote_process(['docker builder prune -af'], $this->server);
|
instant_remote_process(['docker builder prune -af'], $this->server);
|
||||||
$usageAfter = $this->getFilesystemUsage();
|
$usageAfter = $this->getFilesystemUsage();
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,11 +46,12 @@ class SendMessageToTelegramJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (!empty($this->buttons)) {
|
if (!empty($this->buttons)) {
|
||||||
foreach ($this->buttons as $button) {
|
foreach ($this->buttons as $button) {
|
||||||
$buttonUrl = data_get($button, 'url');
|
$buttonUrl = data_get($button, 'url');
|
||||||
|
$text = data_get($button, 'text', 'Click here');
|
||||||
if ($buttonUrl && Str::contains($buttonUrl, 'http://localhost')) {
|
if ($buttonUrl && Str::contains($buttonUrl, 'http://localhost')) {
|
||||||
$buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl);
|
$buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl);
|
||||||
}
|
}
|
||||||
$inlineButtons[] = [
|
$inlineButtons[] = [
|
||||||
'text' => $button['text'],
|
'text' => $text,
|
||||||
'url' => $buttonUrl,
|
'url' => $buttonUrl,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,11 @@ class Application extends BaseModel
|
|||||||
static::deleting(function ($application) {
|
static::deleting(function ($application) {
|
||||||
$application->settings()->delete();
|
$application->settings()->delete();
|
||||||
$storages = $application->persistentStorages()->get();
|
$storages = $application->persistentStorages()->get();
|
||||||
foreach ($storages as $storage) {
|
$server = data_get($application, 'destination.server');
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $application->destination->server, false);
|
if ($server) {
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$application->persistentStorages()->delete();
|
$application->persistentStorages()->delete();
|
||||||
$application->environment_variables()->delete();
|
$application->environment_variables()->delete();
|
||||||
@@ -82,6 +85,18 @@ class Application extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function gitWebhook(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function () {
|
||||||
|
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||||
|
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
|
||||||
|
}
|
||||||
|
return $this->git_repository;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function gitCommits(): Attribute
|
public function gitCommits(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -210,6 +225,14 @@ class Application extends BaseModel
|
|||||||
return $this->morphTo();
|
return $this->morphTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isDeploymentInprogress() {
|
||||||
|
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'in_progress')->count();
|
||||||
|
if ($deployments > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public function deployments(int $skip = 0, int $take = 10)
|
public function deployments(int $skip = 0, int $take = 10)
|
||||||
{
|
{
|
||||||
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->orderBy('created_at', 'desc');
|
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->orderBy('created_at', 'desc');
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class Environment extends Model
|
class Environment extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $guarded = [];
|
||||||
'name',
|
public function isEmpty()
|
||||||
'project_id',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function can_delete_environment()
|
|
||||||
{
|
{
|
||||||
return $this->applications()->count() == 0 && $this->redis()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
|
return $this->applications()->count() == 0 &&
|
||||||
|
$this->redis()->count() == 0 &&
|
||||||
|
$this->postgresqls()->count() == 0 &&
|
||||||
|
$this->mongodbs()->count() == 0 &&
|
||||||
|
$this->services()->count() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function applications()
|
public function applications()
|
||||||
@@ -30,12 +30,27 @@ class Environment extends Model
|
|||||||
{
|
{
|
||||||
return $this->hasMany(StandaloneRedis::class);
|
return $this->hasMany(StandaloneRedis::class);
|
||||||
}
|
}
|
||||||
|
public function mongodbs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMongodb::class);
|
||||||
|
}
|
||||||
|
public function mysqls()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMysql::class);
|
||||||
|
}
|
||||||
|
public function mariadbs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMariadb::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function databases()
|
public function databases()
|
||||||
{
|
{
|
||||||
$postgresqls = $this->postgresqls;
|
$postgresqls = $this->postgresqls;
|
||||||
$redis = $this->redis;
|
$redis = $this->redis;
|
||||||
return $postgresqls->concat($redis);
|
$mongodbs = $this->mongodbs;
|
||||||
|
$mysqls = $this->mysqls;
|
||||||
|
$mariadbs = $this->mariadbs;
|
||||||
|
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function project()
|
public function project()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class EnvironmentVariable extends Model
|
|||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
"key" => 'string',
|
'key' => 'string',
|
||||||
'value' => 'encrypted',
|
'value' => 'encrypted',
|
||||||
'is_build_time' => 'boolean',
|
'is_build_time' => 'boolean',
|
||||||
];
|
];
|
||||||
@@ -21,6 +21,10 @@ class EnvironmentVariable extends Model
|
|||||||
static::created(function ($environment_variable) {
|
static::created(function ($environment_variable) {
|
||||||
if ($environment_variable->application_id && !$environment_variable->is_preview) {
|
if ($environment_variable->application_id && !$environment_variable->is_preview) {
|
||||||
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first();
|
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first();
|
||||||
|
$application = Application::find($environment_variable->application_id);
|
||||||
|
if ($application->build_pack === 'dockerfile') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!$found) {
|
if (!$found) {
|
||||||
ModelsEnvironmentVariable::create([
|
ModelsEnvironmentVariable::create([
|
||||||
'key' => $environment_variable->key,
|
'key' => $environment_variable->key,
|
||||||
@@ -33,7 +37,8 @@ class EnvironmentVariable extends Model
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public function service() {
|
public function service()
|
||||||
|
{
|
||||||
return $this->belongsTo(Service::class);
|
return $this->belongsTo(Service::class);
|
||||||
}
|
}
|
||||||
protected function value(): Attribute
|
protected function value(): Attribute
|
||||||
@@ -55,9 +60,9 @@ class EnvironmentVariable extends Model
|
|||||||
$variable = Str::after($environment_variable, 'global.');
|
$variable = Str::after($environment_variable, 'global.');
|
||||||
$variable = Str::before($variable, '}}');
|
$variable = Str::before($variable, '}}');
|
||||||
$variable = Str::of($variable)->trim()->value;
|
$variable = Str::of($variable)->trim()->value;
|
||||||
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
|
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
|
||||||
ray('global env variable');
|
ray('global env variable');
|
||||||
return $environment_variable;
|
return $environment_variable;
|
||||||
}
|
}
|
||||||
return $environment_variable;
|
return $environment_variable;
|
||||||
}
|
}
|
||||||
@@ -77,5 +82,4 @@ class EnvironmentVariable extends Model
|
|||||||
set: fn (string $value) => Str::of($value)->trim(),
|
set: fn (string $value) => Str::of($value)->trim(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
15
app/Models/PersonalAccessToken.php
Normal file
15
app/Models/PersonalAccessToken.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
|
||||||
|
|
||||||
|
class PersonalAccessToken extends SanctumPersonalAccessToken
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'token',
|
||||||
|
'abilities',
|
||||||
|
'expires_at',
|
||||||
|
'team_id',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ class Project extends BaseModel
|
|||||||
'project_id' => $project->id,
|
'project_id' => $project->id,
|
||||||
]);
|
]);
|
||||||
Environment::create([
|
Environment::create([
|
||||||
'name' => 'Production',
|
'name' => 'production',
|
||||||
'project_id' => $project->id,
|
'project_id' => $project->id,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -56,4 +56,16 @@ class Project extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->hasManyThrough(StandaloneRedis::class, Environment::class);
|
return $this->hasManyThrough(StandaloneRedis::class, Environment::class);
|
||||||
}
|
}
|
||||||
|
public function mongodbs()
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(StandaloneMongodb::class, Environment::class);
|
||||||
|
}
|
||||||
|
public function mysqls()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMysql::class, Environment::class);
|
||||||
|
}
|
||||||
|
public function mariadbs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMariadb::class, Environment::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,11 +109,12 @@ class Server extends BaseModel
|
|||||||
return $this->proxy->modelScope();
|
return $this->proxy->modelScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isEmpty()
|
public function hasDefinedResources()
|
||||||
{
|
{
|
||||||
$applications = $this->applications()->count() === 0;
|
$applications = $this->applications()->count() === 0;
|
||||||
$databases = $this->databases()->count() === 0;
|
$databases = $this->databases()->count() === 0;
|
||||||
if ($applications && $databases) {
|
$services = $this->services()->count() === 0;
|
||||||
|
if ($applications || $databases || $services) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -122,9 +123,12 @@ class Server extends BaseModel
|
|||||||
public function databases()
|
public function databases()
|
||||||
{
|
{
|
||||||
return $this->destinations()->map(function ($standaloneDocker) {
|
return $this->destinations()->map(function ($standaloneDocker) {
|
||||||
$postgresqls = $standaloneDocker->postgresqls;
|
$postgresqls = data_get($standaloneDocker, 'postgresqls', collect([]));
|
||||||
$redis = $standaloneDocker->redis;
|
$redis = data_get($standaloneDocker, 'redis', collect([]));
|
||||||
return $postgresqls->concat($redis);
|
$mongodbs = data_get($standaloneDocker, 'mongodbs', collect([]));
|
||||||
|
$mysqls = data_get($standaloneDocker, 'mysqls', collect([]));
|
||||||
|
$mariadbs = data_get($standaloneDocker, 'mariadbs', collect([]));
|
||||||
|
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||||
})->flatten();
|
})->flatten();
|
||||||
}
|
}
|
||||||
public function applications()
|
public function applications()
|
||||||
@@ -257,7 +261,8 @@ class Server extends BaseModel
|
|||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public function validateCoolifyNetwork() {
|
public function validateCoolifyNetwork()
|
||||||
|
{
|
||||||
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class ServerSetting extends Model
|
class ServerSetting extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $guarded = [];
|
||||||
'server_id',
|
|
||||||
'is_usable',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
@@ -23,34 +22,254 @@ class Service extends BaseModel
|
|||||||
foreach ($storages as $storage) {
|
foreach ($storages as $storage) {
|
||||||
$storagesToDelete->push($storage);
|
$storagesToDelete->push($storage);
|
||||||
}
|
}
|
||||||
$application->persistentStorages()->delete();
|
|
||||||
}
|
}
|
||||||
foreach ($service->databases()->get() as $database) {
|
foreach ($service->databases()->get() as $database) {
|
||||||
$storages = $database->persistentStorages()->get();
|
$storages = $database->persistentStorages()->get();
|
||||||
foreach ($storages as $storage) {
|
foreach ($storages as $storage) {
|
||||||
$storagesToDelete->push($storage);
|
$storagesToDelete->push($storage);
|
||||||
}
|
}
|
||||||
$database->persistentStorages()->delete();
|
|
||||||
}
|
}
|
||||||
$service->environment_variables()->delete();
|
$service->environment_variables()->delete();
|
||||||
$service->applications()->delete();
|
$service->applications()->delete();
|
||||||
$service->databases()->delete();
|
$service->databases()->delete();
|
||||||
if ($storagesToDelete->count() > 0) {
|
|
||||||
$storagesToDelete->each(function ($storage) use ($service) {
|
$server = data_get($service, 'server');
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $service->server, false);
|
if ($server && $storagesToDelete->count() > 0) {
|
||||||
|
$storagesToDelete->each(function ($storage) use ($server) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
instant_remote_process(["docker network rm {$service->uuid}"], $service->server, false);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public function type()
|
public function type()
|
||||||
{
|
{
|
||||||
return 'service';
|
return 'service';
|
||||||
}
|
}
|
||||||
|
public function extraFields()
|
||||||
|
{
|
||||||
|
$fields = collect([]);
|
||||||
|
$applications = $this->applications()->get();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$image = str($application->image)->before(':')->value();
|
||||||
|
switch ($image) {
|
||||||
|
case str($image)->contains('minio'):
|
||||||
|
$console_url = $this->environment_variables()->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first();
|
||||||
|
$s3_api_url = $this->environment_variables()->where('key', 'MINIO_SERVER_URL')->first();
|
||||||
|
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_MINIO')->first();
|
||||||
|
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_MINIO')->first();
|
||||||
|
$fields->put('MinIO', [
|
||||||
|
'Console URL' => [
|
||||||
|
'key' => data_get($console_url, 'key'),
|
||||||
|
'value' => data_get($console_url, 'value'),
|
||||||
|
'rules' => 'required|url',
|
||||||
|
],
|
||||||
|
'S3 API URL' => [
|
||||||
|
'key' => data_get($s3_api_url, 'key'),
|
||||||
|
'value' => data_get($s3_api_url, 'value'),
|
||||||
|
'rules' => 'required|url',
|
||||||
|
],
|
||||||
|
'Admin User' => [
|
||||||
|
'key' => data_get($admin_user, 'key'),
|
||||||
|
'value' => data_get($admin_user, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
'Admin Password' => [
|
||||||
|
'key' => data_get($admin_password, 'key'),
|
||||||
|
'value' => data_get($admin_password, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case str($image)->contains('weblate'):
|
||||||
|
$admin_email = $this->environment_variables()->where('key', 'WEBLATE_ADMIN_EMAIL')->first();
|
||||||
|
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_WEBLATE')->first();
|
||||||
|
$fields->put('Weblate', [
|
||||||
|
'Admin Email' => [
|
||||||
|
'key' => data_get($admin_email, 'key'),
|
||||||
|
'value' => data_get($admin_email, 'value'),
|
||||||
|
'rules' => 'required|email',
|
||||||
|
],
|
||||||
|
'Admin Password' => [
|
||||||
|
'key' => data_get($admin_password, 'key'),
|
||||||
|
'value' => data_get($admin_password, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$databases = $this->databases()->get();
|
||||||
|
|
||||||
|
foreach ($databases as $database) {
|
||||||
|
$image = str($database->image)->before(':')->value();
|
||||||
|
switch ($image) {
|
||||||
|
case str($image)->contains('postgres'):
|
||||||
|
$userVariables = ['SERVICE_USER_POSTGRES', 'SERVICE_USER_POSTGRESQL'];
|
||||||
|
$passwordVariables = ['SERVICE_PASSWORD_POSTGRES', 'SERVICE_PASSWORD_POSTGRESQL'];
|
||||||
|
$dbNameVariables = ['POSTGRESQL_DATABASE', 'POSTGRES_DB'];
|
||||||
|
$postgres_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
|
||||||
|
$postgres_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
|
||||||
|
$postgres_db_name = $this->environment_variables()->whereIn('key', $dbNameVariables)->first();
|
||||||
|
$data = collect([]);
|
||||||
|
if ($postgres_user) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'User' => [
|
||||||
|
'key' => data_get($postgres_user, 'key'),
|
||||||
|
'value' => data_get($postgres_user, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($postgres_password) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Password' => [
|
||||||
|
'key' => data_get($postgres_password, 'key'),
|
||||||
|
'value' => data_get($postgres_password, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($postgres_db_name) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Database Name' => [
|
||||||
|
'key' => data_get($postgres_db_name, 'key'),
|
||||||
|
'value' => data_get($postgres_db_name, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$fields->put('PostgreSQL', $data->toArray());
|
||||||
|
break;
|
||||||
|
case str($image)->contains('mysql'):
|
||||||
|
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS'];
|
||||||
|
$passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS'];
|
||||||
|
$rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT'];
|
||||||
|
$dbNameVariables = ['MYSQL_DATABASE'];
|
||||||
|
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
|
||||||
|
$mysql_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
|
||||||
|
$mysql_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first();
|
||||||
|
$mysql_db_name = $this->environment_variables()->whereIn('key', $dbNameVariables)->first();
|
||||||
|
$data = collect([]);
|
||||||
|
if ($mysql_user) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'User' => [
|
||||||
|
'key' => data_get($mysql_user, 'key'),
|
||||||
|
'value' => data_get($mysql_user, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($mysql_password) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Password' => [
|
||||||
|
'key' => data_get($mysql_password, 'key'),
|
||||||
|
'value' => data_get($mysql_password, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($mysql_root_password) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Root Password' => [
|
||||||
|
'key' => data_get($mysql_root_password, 'key'),
|
||||||
|
'value' => data_get($mysql_root_password, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($mysql_db_name) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Database Name' => [
|
||||||
|
'key' => data_get($mysql_db_name, 'key'),
|
||||||
|
'value' => data_get($mysql_db_name, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$fields->put('MySQL', $data->toArray());
|
||||||
|
break;
|
||||||
|
case str($image)->contains('mariadb'):
|
||||||
|
$userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER'];
|
||||||
|
$passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS'];
|
||||||
|
$rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS'];
|
||||||
|
$dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA'];
|
||||||
|
$mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
|
||||||
|
$mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
|
||||||
|
$mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first();
|
||||||
|
$mariadb_db_name = $this->environment_variables()->whereIn('key', $dbNameVariables)->first();
|
||||||
|
$data = collect([]);
|
||||||
|
|
||||||
|
if ($mariadb_user) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'User' => [
|
||||||
|
'key' => data_get($mariadb_user, 'key'),
|
||||||
|
'value' => data_get($mariadb_user, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($mariadb_password) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Password' => [
|
||||||
|
'key' => data_get($mariadb_password, 'key'),
|
||||||
|
'value' => data_get($mariadb_password, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($mariadb_root_password) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Root Password' => [
|
||||||
|
'key' => data_get($mariadb_root_password, 'key'),
|
||||||
|
'value' => data_get($mariadb_root_password, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($mariadb_db_name) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Database Name' => [
|
||||||
|
'key' => data_get($mariadb_db_name, 'key'),
|
||||||
|
'value' => data_get($mariadb_db_name, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$fields->put('MariaDB', $data->toArray());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
public function saveExtraFields($fields)
|
||||||
|
{
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
$key = data_get($field, 'key');
|
||||||
|
$value = data_get($field, 'value');
|
||||||
|
$found = $this->environment_variables()->where('key', $key)->first();
|
||||||
|
if ($found) {
|
||||||
|
$found->value = $value;
|
||||||
|
$found->save();
|
||||||
|
} else {
|
||||||
|
$this->environment_variables()->create([
|
||||||
|
'key' => $key,
|
||||||
|
'value' => $value,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'service_id' => $this->id,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public function documentation()
|
public function documentation()
|
||||||
{
|
{
|
||||||
$services = Cache::get('services', []);
|
$services = getServiceTemplates();
|
||||||
$service = data_get($services, Str::of($this->name)->beforeLast('-')->value, []);
|
$service = data_get($services, Str::of($this->name)->beforeLast('-')->value, []);
|
||||||
return data_get($service, 'documentation', config('constants.docs.base_url'));
|
return data_get($service, 'documentation', config('constants.docs.base_url'));
|
||||||
}
|
}
|
||||||
@@ -90,6 +309,10 @@ class Service extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
|
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
|
||||||
}
|
}
|
||||||
|
public function environment_variables_preview(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc');
|
||||||
|
}
|
||||||
public function workdir()
|
public function workdir()
|
||||||
{
|
{
|
||||||
return service_configuration_dir() . "/{$this->uuid}";
|
return service_configuration_dir() . "/{$this->uuid}";
|
||||||
@@ -115,7 +338,7 @@ class Service extends BaseModel
|
|||||||
|
|
||||||
public function parse(bool $isNew = false): Collection
|
public function parse(bool $isNew = false): Collection
|
||||||
{
|
{
|
||||||
ray()->clearAll();
|
// ray()->clearAll();
|
||||||
if ($this->docker_compose_raw) {
|
if ($this->docker_compose_raw) {
|
||||||
try {
|
try {
|
||||||
$yaml = Yaml::parse($this->docker_compose_raw);
|
$yaml = Yaml::parse($this->docker_compose_raw);
|
||||||
@@ -254,13 +477,25 @@ class Service extends BaseModel
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$networks = $serviceNetworks->toArray();
|
$networks = collect();
|
||||||
foreach ($definedNetwork as $key => $network) {
|
foreach ($serviceNetworks as $key => $serviceNetwork) {
|
||||||
$networks = array_merge($networks, [
|
if (gettype($serviceNetwork) === 'string') {
|
||||||
$network
|
// networks:
|
||||||
]);
|
// - appwrite
|
||||||
|
$networks->put($serviceNetwork, null);
|
||||||
|
} else if (gettype($serviceNetwork) === 'array') {
|
||||||
|
// networks:
|
||||||
|
// default:
|
||||||
|
// ipv4_address: 192.168.203.254
|
||||||
|
// $networks->put($serviceNetwork, null);
|
||||||
|
ray($key);
|
||||||
|
$networks->put($key, $serviceNetwork);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
data_set($service, 'networks', $networks);
|
foreach ($definedNetwork as $key => $network) {
|
||||||
|
$networks->put($network, null);
|
||||||
|
}
|
||||||
|
data_set($service, 'networks', $networks->toArray());
|
||||||
|
|
||||||
// Collect/create/update volumes
|
// Collect/create/update volumes
|
||||||
if ($serviceVolumes->count() > 0) {
|
if ($serviceVolumes->count() > 0) {
|
||||||
@@ -381,6 +616,7 @@ class Service extends BaseModel
|
|||||||
$key = Str::of($variableName);
|
$key = Str::of($variableName);
|
||||||
$value = Str::of($variable);
|
$value = Str::of($variable);
|
||||||
}
|
}
|
||||||
|
// TODO: here is the problem
|
||||||
if ($key->startsWith('SERVICE_FQDN')) {
|
if ($key->startsWith('SERVICE_FQDN')) {
|
||||||
if ($isNew || $savedService->fqdn === null) {
|
if ($isNew || $savedService->fqdn === null) {
|
||||||
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
|
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
|
||||||
@@ -438,15 +674,31 @@ class Service extends BaseModel
|
|||||||
'service_id' => $this->id,
|
'service_id' => $this->id,
|
||||||
])->first();
|
])->first();
|
||||||
if ($value->startsWith('SERVICE_')) {
|
if ($value->startsWith('SERVICE_')) {
|
||||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
// Count _ in $value
|
||||||
$forService = $value->afterLast('_');
|
$count = substr_count($value->value(), '_');
|
||||||
$generatedValue = null;
|
if ($count === 2) {
|
||||||
|
// SERVICE_FQDN_UMAMI
|
||||||
|
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||||
|
$forService = $value->afterLast('_');
|
||||||
|
$generatedValue = null;
|
||||||
|
$port = null;
|
||||||
|
}
|
||||||
|
if ($count === 3) {
|
||||||
|
// SERVICE_FQDN_UMAMI_1000
|
||||||
|
$command = $value->after('SERVICE_')->before('_');
|
||||||
|
$forService = $value->after('SERVICE_')->after('_')->before('_');
|
||||||
|
$generatedValue = null;
|
||||||
|
$port = $value->afterLast('_');
|
||||||
|
}
|
||||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
||||||
if (Str::lower($forService) === $serviceName) {
|
if (Str::lower($forService) === $serviceName) {
|
||||||
$fqdn = generateFqdn($this->server, $containerName);
|
$fqdn = generateFqdn($this->server, $containerName);
|
||||||
} else {
|
} else {
|
||||||
$fqdn = generateFqdn($this->server, Str::lower($forService) . '-' . $this->uuid);
|
$fqdn = generateFqdn($this->server, Str::lower($forService) . '-' . $this->uuid);
|
||||||
}
|
}
|
||||||
|
if ($port) {
|
||||||
|
$fqdn = "$fqdn:$port";
|
||||||
|
}
|
||||||
if ($foundEnv) {
|
if ($foundEnv) {
|
||||||
$fqdn = data_get($foundEnv, 'value');
|
$fqdn = data_get($foundEnv, 'value');
|
||||||
} else {
|
} else {
|
||||||
@@ -462,7 +714,7 @@ class Service extends BaseModel
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (!$isDatabase) {
|
if (!$isDatabase) {
|
||||||
if ($command->value() === 'FQDN') {
|
if ($command->value() === 'FQDN' && is_null($savedService->fqdn)) {
|
||||||
$savedService->fqdn = $fqdn;
|
$savedService->fqdn = $fqdn;
|
||||||
$savedService->save();
|
$savedService->save();
|
||||||
}
|
}
|
||||||
@@ -533,7 +785,11 @@ class Service extends BaseModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add labels to the service
|
// Add labels to the service
|
||||||
$fqdns = collect(data_get($savedService, 'fqdns'));
|
if ($savedService->serviceType()) {
|
||||||
|
$fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true);
|
||||||
|
} else {
|
||||||
|
$fqdns = collect(data_get($savedService, 'fqdns'));
|
||||||
|
}
|
||||||
$defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
|
$defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
|
||||||
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
||||||
if (!$isDatabase && $fqdns->count() > 0) {
|
if (!$isDatabase && $fqdns->count() > 0) {
|
||||||
|
|||||||
@@ -11,10 +11,27 @@ class ServiceApplication extends BaseModel
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::deleting(function ($service) {
|
||||||
|
$service->persistentStorages()->delete();
|
||||||
|
$service->fileStorages()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
public function type()
|
public function type()
|
||||||
{
|
{
|
||||||
return 'service';
|
return 'service';
|
||||||
}
|
}
|
||||||
|
public function serviceType()
|
||||||
|
{
|
||||||
|
$found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) {
|
||||||
|
return str($this->image)->before(':')->value() === $service;
|
||||||
|
})->first());
|
||||||
|
if ($found->isNotEmpty()) {
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
public function service()
|
public function service()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Service::class);
|
return $this->belongsTo(Service::class);
|
||||||
|
|||||||
@@ -9,10 +9,39 @@ class ServiceDatabase extends BaseModel
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::deleting(function ($service) {
|
||||||
|
$service->persistentStorages()->delete();
|
||||||
|
$service->fileStorages()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
public function type()
|
public function type()
|
||||||
{
|
{
|
||||||
return 'service';
|
return 'service';
|
||||||
}
|
}
|
||||||
|
public function serviceType()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public function databaseType()
|
||||||
|
{
|
||||||
|
$image = str($this->image)->before(':');
|
||||||
|
if ($image->value() === 'postgres') {
|
||||||
|
$image = 'postgresql';
|
||||||
|
}
|
||||||
|
return "standalone-$image";
|
||||||
|
}
|
||||||
|
public function getServiceDatabaseUrl()
|
||||||
|
{
|
||||||
|
$port = $this->public_port;
|
||||||
|
$realIp = $this->service->server->ip;
|
||||||
|
if ($realIp === 'host.docker.internal' || isDev()) {
|
||||||
|
$realIp = base_ip();
|
||||||
|
}
|
||||||
|
$url = "{$realIp}:{$port}";
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
public function service()
|
public function service()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Service::class);
|
return $this->belongsTo(Service::class);
|
||||||
@@ -29,4 +58,8 @@ class ServiceDatabase extends BaseModel
|
|||||||
{
|
{
|
||||||
getFilesystemVolumesFromServer($this, $isInit);
|
getFilesystemVolumesFromServer($this, $isInit);
|
||||||
}
|
}
|
||||||
|
public function scheduledBackups()
|
||||||
|
{
|
||||||
|
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,23 @@ class StandaloneDocker extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function redis()
|
public function redis()
|
||||||
{
|
{
|
||||||
return $this->morphMany(StandaloneRedis::class, 'destination');
|
return $this->morphMany(StandaloneRedis::class, 'destination');
|
||||||
}
|
}
|
||||||
|
public function mongodbs()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneMongodb::class, 'destination');
|
||||||
|
}
|
||||||
|
public function mysqls()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneMysql::class, 'destination');
|
||||||
|
}
|
||||||
|
public function mariadbs()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneMariadb::class, 'destination');
|
||||||
|
}
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
@@ -30,8 +43,18 @@ class StandaloneDocker extends BaseModel
|
|||||||
return $this->morphMany(Service::class, 'destination');
|
return $this->morphMany(Service::class, 'destination');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databases()
|
||||||
|
{
|
||||||
|
$postgresqls = $this->postgresqls;
|
||||||
|
$redis = $this->redis;
|
||||||
|
$mongodbs = $this->mongodbs;
|
||||||
|
$mysqls = $this->mysqls;
|
||||||
|
$mariadbs = $this->mariadbs;
|
||||||
|
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||||
|
}
|
||||||
|
|
||||||
public function attachedTo()
|
public function attachedTo()
|
||||||
{
|
{
|
||||||
return $this->applications?->count() > 0 || $this->databases?->count() > 0;
|
return $this->applications?->count() > 0 || $this->databases()->count() > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
app/Models/StandaloneMariadb.php
Normal file
109
app/Models/StandaloneMariadb.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class StandaloneMariadb extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
protected $casts = [
|
||||||
|
'mariadb_password' => 'encrypted',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::created(function ($database) {
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => 'mariadb-data-' . $database->uuid,
|
||||||
|
'mount_path' => '/var/lib/mysql',
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
'is_readonly' => true
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
static::deleting(function ($database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
$server = data_get($database, 'destination.server');
|
||||||
|
if ($server) {
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
|
$database->persistentStorages()->delete();
|
||||||
|
$database->environment_variables()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return 'standalone-mariadb';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappings(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: fn ($value) => $value === "" ? null : $value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappingsArray(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => is_null($this->ports_mappings)
|
||||||
|
? []
|
||||||
|
: explode(',', $this->ports_mappings),
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDbUrl(bool $useInternal = false): string
|
||||||
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
|
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
|
||||||
|
} else {
|
||||||
|
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Environment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destination()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runtime_environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function persistentStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scheduledBackups()
|
||||||
|
{
|
||||||
|
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||||
|
}
|
||||||
|
}
|
||||||
125
app/Models/StandaloneMongodb.php
Normal file
125
app/Models/StandaloneMongodb.php
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class StandaloneMongodb extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::created(function ($database) {
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => 'mongodb-configdb-' . $database->uuid,
|
||||||
|
'mount_path' => '/data/configdb',
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
'is_readonly' => true
|
||||||
|
]);
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => 'mongodb-db-' . $database->uuid,
|
||||||
|
'mount_path' => '/data/db',
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
'is_readonly' => true
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
static::deleting(function ($database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
$server = data_get($database, 'destination.server');
|
||||||
|
if ($server) {
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
|
$database->persistentStorages()->delete();
|
||||||
|
$database->environment_variables()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mongoInitdbRootPassword(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function ($value) {
|
||||||
|
try {
|
||||||
|
return decrypt($value);
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
$this->mongo_initdb_root_password = encrypt($value);
|
||||||
|
$this->save();
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function portsMappings(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: fn ($value) => $value === "" ? null : $value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappingsArray(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => is_null($this->ports_mappings)
|
||||||
|
? []
|
||||||
|
: explode(',', $this->ports_mappings),
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return 'standalone-mongodb';
|
||||||
|
}
|
||||||
|
public function getDbUrl(bool $useInternal = false)
|
||||||
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
|
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
|
||||||
|
} else {
|
||||||
|
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function environment()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Environment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destination()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runtime_environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function persistentStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scheduledBackups()
|
||||||
|
{
|
||||||
|
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||||
|
}
|
||||||
|
}
|
||||||
109
app/Models/StandaloneMysql.php
Normal file
109
app/Models/StandaloneMysql.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class StandaloneMysql extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
protected $casts = [
|
||||||
|
'mysql_password' => 'encrypted',
|
||||||
|
'mysql_root_password' => 'encrypted',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::created(function ($database) {
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => 'mysql-data-' . $database->uuid,
|
||||||
|
'mount_path' => '/var/lib/mysql',
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
'is_readonly' => true
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
static::deleting(function ($database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
$server = data_get($database, 'destination.server');
|
||||||
|
if ($server) {
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
|
$database->persistentStorages()->delete();
|
||||||
|
$database->environment_variables()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return 'standalone-mysql';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappings(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: fn ($value) => $value === "" ? null : $value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappingsArray(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => is_null($this->ports_mappings)
|
||||||
|
? []
|
||||||
|
: explode(',', $this->ports_mappings),
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDbUrl(bool $useInternal = false): string
|
||||||
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
|
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
|
||||||
|
} else {
|
||||||
|
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Environment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destination()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runtime_environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function persistentStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scheduledBackups()
|
||||||
|
{
|
||||||
|
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,8 +30,11 @@ class StandalonePostgresql extends BaseModel
|
|||||||
});
|
});
|
||||||
static::deleting(function ($database) {
|
static::deleting(function ($database) {
|
||||||
$storages = $database->persistentStorages()->get();
|
$storages = $database->persistentStorages()->get();
|
||||||
foreach ($storages as $storage) {
|
$server = data_get($database, 'destination.server');
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
|
if ($server) {
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
@@ -46,8 +49,6 @@ class StandalonePostgresql extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal Deployments
|
|
||||||
|
|
||||||
public function portsMappingsArray(): Attribute
|
public function portsMappingsArray(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -62,6 +63,14 @@ class StandalonePostgresql extends BaseModel
|
|||||||
{
|
{
|
||||||
return 'standalone-postgresql';
|
return 'standalone-postgresql';
|
||||||
}
|
}
|
||||||
|
public function getDbUrl(bool $useInternal = false): string
|
||||||
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
|
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
|
||||||
|
} else {
|
||||||
|
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ class StandaloneRedis extends BaseModel
|
|||||||
static::deleting(function ($database) {
|
static::deleting(function ($database) {
|
||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$storages = $database->persistentStorages()->get();
|
$storages = $database->persistentStorages()->get();
|
||||||
foreach ($storages as $storage) {
|
$server = data_get($database, 'destination.server');
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
|
if ($server) {
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
@@ -41,8 +44,6 @@ class StandaloneRedis extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal Deployments
|
|
||||||
|
|
||||||
public function portsMappingsArray(): Attribute
|
public function portsMappingsArray(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -57,6 +58,14 @@ class StandaloneRedis extends BaseModel
|
|||||||
{
|
{
|
||||||
return 'standalone-redis';
|
return 'standalone-redis';
|
||||||
}
|
}
|
||||||
|
public function getDbUrl(bool $useInternal = false): string
|
||||||
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
|
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||||
|
} else {
|
||||||
|
return "redis://:{$this->redis_password}@{$this->uuid}:6379/0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,14 +39,18 @@ class Subscription extends Model
|
|||||||
if (!$subscription) {
|
if (!$subscription) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$subscriptionPlanId = data_get($subscription,'stripe_plan_id');
|
$subscriptionPlanId = data_get($subscription, 'stripe_plan_id');
|
||||||
if (!$subscriptionPlanId) {
|
if (!$subscriptionPlanId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
$subscriptionInvoicePaid = data_get($subscription, 'stripe_invoice_paid');
|
||||||
|
if (!$subscriptionInvoicePaid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
$subscriptionConfigs = collect(config('subscription'));
|
$subscriptionConfigs = collect(config('subscription'));
|
||||||
$stripePlanId = null;
|
$stripePlanId = null;
|
||||||
$subscriptionConfigs->map(function ($value, $key) use ($subscriptionPlanId, &$stripePlanId) {
|
$subscriptionConfigs->map(function ($value, $key) use ($subscriptionPlanId, &$stripePlanId) {
|
||||||
if ($value === $subscriptionPlanId){
|
if ($value === $subscriptionPlanId) {
|
||||||
$stripePlanId = $key;
|
$stripePlanId = $key;
|
||||||
};
|
};
|
||||||
})->first();
|
})->first();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
use App\Notifications\Channels\SendsEmail;
|
use App\Notifications\Channels\SendsEmail;
|
||||||
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
||||||
|
use DateTimeInterface;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
@@ -14,6 +15,8 @@ use Illuminate\Support\Facades\Config;
|
|||||||
use Illuminate\Support\Facades\URL;
|
use Illuminate\Support\Facades\URL;
|
||||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
use Laravel\Sanctum\NewAccessToken;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class User extends Authenticatable implements SendsEmail
|
class User extends Authenticatable implements SendsEmail
|
||||||
{
|
{
|
||||||
@@ -47,7 +50,26 @@ class User extends Authenticatable implements SendsEmail
|
|||||||
$user->teams()->attach($new_team, ['role' => 'owner']);
|
$user->teams()->attach($new_team, ['role' => 'owner']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public function createToken(string $name, array $abilities = ['*'], DateTimeInterface $expiresAt = null)
|
||||||
|
{
|
||||||
|
ray('asd');
|
||||||
|
$plainTextToken = sprintf(
|
||||||
|
'%s%s%s',
|
||||||
|
config('sanctum.token_prefix', ''),
|
||||||
|
$tokenEntropy = Str::random(40),
|
||||||
|
hash('crc32b', $tokenEntropy)
|
||||||
|
);
|
||||||
|
|
||||||
|
$token = $this->tokens()->create([
|
||||||
|
'name' => $name,
|
||||||
|
'token' => hash('sha256', $plainTextToken),
|
||||||
|
'abilities' => $abilities,
|
||||||
|
'expires_at' => $expiresAt,
|
||||||
|
'team_id' => session('currentTeam')->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken);
|
||||||
|
}
|
||||||
public function teams()
|
public function teams()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Team::class)->withPivot('role');
|
return $this->belongsToMany(Team::class)->withPivot('role');
|
||||||
|
|||||||
@@ -84,11 +84,14 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
||||||
}
|
}
|
||||||
|
$buttons[] = [
|
||||||
|
"text" => "Deployment logs",
|
||||||
|
"url" => $this->deployment_url
|
||||||
|
];
|
||||||
return [
|
return [
|
||||||
"message" => $message,
|
"message" => $message,
|
||||||
"buttons" => [
|
"buttons" => [
|
||||||
"text" => "View Deployment Logs",
|
...$buttons
|
||||||
"url" => $this->deployment_url
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,18 @@ class EmailChannel
|
|||||||
->html((string)$mailMessage->render())
|
->html((string)$mailMessage->render())
|
||||||
);
|
);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
$error = $e->getMessage();
|
||||||
|
if ($error === 'No email settings found.') {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
$message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:";
|
$message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:";
|
||||||
if (isset($recepients)) {
|
if (isset($recepients)) {
|
||||||
$message .= implode(', ', $recepients);
|
$message .= implode(', ', $recepients);
|
||||||
}
|
}
|
||||||
$message .= " with subject: {$mailMessage->subject}";
|
if (isset($mailMessage)) {
|
||||||
|
$message .= " with subject: {$mailMessage->subject}";
|
||||||
|
}
|
||||||
send_internal_notification($message);
|
send_internal_notification($message);
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
@@ -49,8 +55,8 @@ class EmailChannel
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address'));
|
config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address', 'test@example.com'));
|
||||||
config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name'));
|
config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name', 'Test'));
|
||||||
if (data_get($notifiable, 'resend_enabled')) {
|
if (data_get($notifiable, 'resend_enabled')) {
|
||||||
config()->set('mail.default', 'resend');
|
config()->set('mail.default', 'resend');
|
||||||
config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
|
config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ namespace App\Providers;
|
|||||||
|
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
use App\Models\PersonalAccessToken;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -13,6 +15,8 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
|
||||||
|
|
||||||
Http::macro('github', function (string $api_url, string|null $github_access_token = null) {
|
Http::macro('github', function (string $api_url, string|null $github_access_token = null) {
|
||||||
if ($github_access_token) {
|
if ($github_access_token) {
|
||||||
return Http::withHeaders([
|
return Http::withHeaders([
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
RateLimiter::for('api', function (Request $request) {
|
RateLimiter::for('api', function (Request $request) {
|
||||||
if ($request->path() === 'api/health') {
|
if ($request->path() === 'api/health') {
|
||||||
return Limit::perMinute(5000)->by($request->user()?->id ?: $request->ip());
|
return Limit::perMinute(1000)->by($request->user()?->id ?: $request->ip());
|
||||||
}
|
}
|
||||||
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
return Limit::perMinute(200)->by($request->user()?->id ?: $request->ip());
|
||||||
});
|
});
|
||||||
RateLimiter::for('5', function (Request $request) {
|
RateLimiter::for('5', function (Request $request) {
|
||||||
return Limit::perMinute(5)->by($request->user()?->id ?: $request->ip());
|
return Limit::perMinute(5)->by($request->user()?->id ?: $request->ip());
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Input extends Component
|
|||||||
public bool $readonly = false,
|
public bool $readonly = false,
|
||||||
public string|null $helper = null,
|
public string|null $helper = null,
|
||||||
public bool $allowToPeak = true,
|
public bool $allowToPeak = true,
|
||||||
public string $defaultClass = "input input-sm bg-coolgray-200 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
public string $defaultClass = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user