mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 20:59:24 +00:00
Compare commits
276 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59d6818f70 | ||
|
|
7678cd47df | ||
|
|
b101fbacd4 | ||
|
|
a61a86dc3b | ||
|
|
0b3cde44c3 | ||
|
|
6b302ab786 | ||
|
|
3c623f13e2 | ||
|
|
da54c24e8d | ||
|
|
1e39c3d5ab | ||
|
|
6071412986 | ||
|
|
ba7148206a | ||
|
|
59c5b22e6c | ||
|
|
be7f2ad9c4 | ||
|
|
62295ef573 | ||
|
|
ceb9fcf3b6 | ||
|
|
60282f7b6c | ||
|
|
f14b0a3411 | ||
|
|
30af317bd9 | ||
|
|
95faa1c3ad | ||
|
|
fb280afe41 | ||
|
|
fd488a561a | ||
|
|
ab57a5d8ef | ||
|
|
f5ae222a6e | ||
|
|
5d95d8b79a | ||
|
|
fbb5f2ca2e | ||
|
|
16cbca36c1 | ||
|
|
24a578bedb | ||
|
|
36dc479772 | ||
|
|
83d6e488e4 | ||
|
|
a4d358d512 | ||
|
|
c0c197101d | ||
|
|
62e39ccc7f | ||
|
|
a88a016137 | ||
|
|
f4c8986ab3 | ||
|
|
e286eae53b | ||
|
|
bc3e59e4ef | ||
|
|
17ebc650c9 | ||
|
|
0ef386b4a8 | ||
|
|
26fce85bb0 | ||
|
|
2a079e3365 | ||
|
|
5fb5ed75c4 | ||
|
|
a2008fe9d1 | ||
|
|
3c96485e3d | ||
|
|
1c97d47ea0 | ||
|
|
8f8f5878dd | ||
|
|
6e6f39dc1f | ||
|
|
d2d1f984e1 | ||
|
|
d635e5dbae | ||
|
|
49c56524e1 | ||
|
|
6ced607f2a | ||
|
|
aaa2febef4 | ||
|
|
10e6eddcfe | ||
|
|
2639bf92ad | ||
|
|
59eae3a44e | ||
|
|
5aa8ccfcf4 | ||
|
|
aea7cc9638 | ||
|
|
c970907c73 | ||
|
|
38c6c1ee40 | ||
|
|
a6118f5daf | ||
|
|
b196c138d9 | ||
|
|
8f9949160c | ||
|
|
beae0b545f | ||
|
|
b8dd7704b3 | ||
|
|
d913be66e6 | ||
|
|
63de538879 | ||
|
|
1d733b2282 | ||
|
|
1d0ad51fdf | ||
|
|
83d00bbe3c | ||
|
|
c422b4dbcf | ||
|
|
767fd334dd | ||
|
|
972223f01b | ||
|
|
9318cac189 | ||
|
|
7aa991fd7c | ||
|
|
5c27f43b3d | ||
|
|
a2f4d4ed6d | ||
|
|
6aca2740fb | ||
|
|
cd13b5b83e | ||
|
|
758dbafbf1 | ||
|
|
f6663661df | ||
|
|
9666099408 | ||
|
|
d382af6860 | ||
|
|
4905454269 | ||
|
|
ed8bd37230 | ||
|
|
ec1a7aa893 | ||
|
|
62adf2c5dc | ||
|
|
3e4538de98 | ||
|
|
5a7b16ea5f | ||
|
|
aa7bc40f85 | ||
|
|
4fd83dc727 | ||
|
|
0e451f87a9 | ||
|
|
0b0ae55f0b | ||
|
|
40ec3d9753 | ||
|
|
9535c8df29 | ||
|
|
6ca1d36d5d | ||
|
|
f5d16c46cb | ||
|
|
725c3fd547 | ||
|
|
dcfcee1db6 | ||
|
|
9f8c44d96b | ||
|
|
5b74fd34f5 | ||
|
|
5a4c9422b2 | ||
|
|
81b916724e | ||
|
|
9540f60fa2 | ||
|
|
8eb1686125 | ||
|
|
daf3710a5e | ||
|
|
1067f37e4d | ||
|
|
3b3c0b94e5 | ||
|
|
8b0a0d67da | ||
|
|
5541c135df | ||
|
|
2552cb2208 | ||
|
|
f001e9bc34 | ||
|
|
f943fdc5be | ||
|
|
a4f1fcba58 | ||
|
|
68091b44fc | ||
|
|
9f8caac91c | ||
|
|
8082dc1a01 | ||
|
|
a71cf5bc66 | ||
|
|
0775074509 | ||
|
|
242d2fb283 | ||
|
|
5646818965 | ||
|
|
ffc5320940 | ||
|
|
7c10c55b1c | ||
|
|
7c96b6207a | ||
|
|
0be8ffbdc9 | ||
|
|
24fa56762e | ||
|
|
84d8e35411 | ||
|
|
3d3ccc435c | ||
|
|
be3b01472e | ||
|
|
de6f5b1105 | ||
|
|
14d9c06dcd | ||
|
|
8abfaa1967 | ||
|
|
46f7ae9588 | ||
|
|
f2c32b9aeb | ||
|
|
3dab1eb92e | ||
|
|
9c22e01716 | ||
|
|
d1c47a4062 | ||
|
|
6c3f97d9ae | ||
|
|
ebd8e2ce40 | ||
|
|
b650f3f754 | ||
|
|
f33ba40478 | ||
|
|
5cea9c4603 | ||
|
|
d32832fabc | ||
|
|
165f0a3d4a | ||
|
|
f14995200b | ||
|
|
12bb2ecc4a | ||
|
|
933ec5741d | ||
|
|
8004a40139 | ||
|
|
a6209fbe5c | ||
|
|
b47c327b55 | ||
|
|
25434a7acd | ||
|
|
0de042dbac | ||
|
|
eb9e2203b0 | ||
|
|
dcaa7a6ad7 | ||
|
|
5b584a6c6d | ||
|
|
c40ea6f1da | ||
|
|
61a7b9ac94 | ||
|
|
9e81416fef | ||
|
|
c58706e3e4 | ||
|
|
45bca8649b | ||
|
|
b095b88281 | ||
|
|
cb41584137 | ||
|
|
6659153804 | ||
|
|
44c429a224 | ||
|
|
fe9c501c1d | ||
|
|
4e94b4a0c1 | ||
|
|
2f4d7c0e43 | ||
|
|
e443fc394a | ||
|
|
5b56c50f03 | ||
|
|
3adeb2f73f | ||
|
|
9eaa13a08a | ||
|
|
df5a4a9667 | ||
|
|
26048339d6 | ||
|
|
d85af3fefc | ||
|
|
575338609b | ||
|
|
d32e43ef37 | ||
|
|
a96ef1bfab | ||
|
|
1bfedf69f2 | ||
|
|
208fe7d87b | ||
|
|
a6d58b5d72 | ||
|
|
277b4276e6 | ||
|
|
f6adc9285a | ||
|
|
f03bbe0e95 | ||
|
|
535375193c | ||
|
|
d79c063fd6 | ||
|
|
35f45492e3 | ||
|
|
ab8a7893d9 | ||
|
|
76b8d048d4 | ||
|
|
0e583334e7 | ||
|
|
0ad8ca224f | ||
|
|
050e56f69a | ||
|
|
6099ac11d9 | ||
|
|
adac728a60 | ||
|
|
e2e64e36a0 | ||
|
|
762af66cbf | ||
|
|
b08f525bd4 | ||
|
|
5ae16b195c | ||
|
|
91db1953ff | ||
|
|
1c8f92d3b7 | ||
|
|
4075572dbc | ||
|
|
2971e360d7 | ||
|
|
32bb2780f2 | ||
|
|
af69575b29 | ||
|
|
d4a7d0d25f | ||
|
|
45f9def0f6 | ||
|
|
5a90eed7ef | ||
|
|
38e1f17edf | ||
|
|
1651845e20 | ||
|
|
38e96548b5 | ||
|
|
47e4126dca | ||
|
|
e0b175ab07 | ||
|
|
93ec785f4f | ||
|
|
e849addab8 | ||
|
|
a5e6975dac | ||
|
|
4ac8e1cc67 | ||
|
|
1a5e3a7836 | ||
|
|
4498d1ed4b | ||
|
|
c8b974820b | ||
|
|
527373e297 | ||
|
|
a84be8dc33 | ||
|
|
bd856f7f67 | ||
|
|
8ff216e5fb | ||
|
|
5255311a2e | ||
|
|
774a245e84 | ||
|
|
194675c838 | ||
|
|
75862ca8de | ||
|
|
5580a4e704 | ||
|
|
51e601a303 | ||
|
|
734e9fd68d | ||
|
|
09fc950ae8 | ||
|
|
cf6caa279d | ||
|
|
68c976ab70 | ||
|
|
1560ab2a50 | ||
|
|
e3a6458506 | ||
|
|
1768b9374f | ||
|
|
51d0a30a6c | ||
|
|
9701c65297 | ||
|
|
58e3bb2571 | ||
|
|
31cbd1602d | ||
|
|
dd5723d596 | ||
|
|
620f26a6f1 | ||
|
|
540717e809 | ||
|
|
d446cd4103 | ||
|
|
7d1a76570c | ||
|
|
e18766ec21 | ||
|
|
3d0354cf7e | ||
|
|
af5b9fced1 | ||
|
|
ab5202515e | ||
|
|
f863db7ea5 | ||
|
|
3adc0bdd6e | ||
|
|
97027875bf | ||
|
|
fd9c13009f | ||
|
|
46a72fac47 | ||
|
|
5d6ee04991 | ||
|
|
d523becb29 | ||
|
|
41672f75d0 | ||
|
|
acd8541e68 | ||
|
|
ed6af777a4 | ||
|
|
05f162f4e8 | ||
|
|
5e0adc3777 | ||
|
|
7d06fc4403 | ||
|
|
0e1bcceb8e | ||
|
|
a922f2fedf | ||
|
|
1e0226c8ed | ||
|
|
5c45908087 | ||
|
|
aefdc76805 | ||
|
|
b3c8c881b7 | ||
|
|
9ab5a1f7bd | ||
|
|
23968e7886 | ||
|
|
390d24b6d7 | ||
|
|
e4296345b3 | ||
|
|
bcffbe418b | ||
|
|
bfbee4e78f | ||
|
|
2bdb44dac3 | ||
|
|
4daa1b8c16 | ||
|
|
febc399568 | ||
|
|
9b9d4f9941 | ||
|
|
f49b87870c |
@@ -1,11 +1,3 @@
|
|||||||
############################################################################################################
|
|
||||||
# Development Environment
|
|
||||||
|
|
||||||
# User and group id for the user that will run the application inside the container
|
|
||||||
# Run in your terminal: `id -u` and `id -g` and that's the results
|
|
||||||
USERID=
|
|
||||||
GROUPID=
|
|
||||||
############################################################################################################
|
|
||||||
APP_NAME=Coolify-localhost
|
APP_NAME=Coolify-localhost
|
||||||
APP_ID=development
|
APP_ID=development
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
@@ -13,6 +5,7 @@ APP_KEY=
|
|||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
|
MUX_ENABLED=false
|
||||||
|
|
||||||
DUSK_DRIVER_URL=http://selenium:4444
|
DUSK_DRIVER_URL=http://selenium:4444
|
||||||
|
|
||||||
|
|||||||
31
CONTRIBUTION.md
Normal file
31
CONTRIBUTION.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
> "First, thanks for considering to contribute to my project.
|
||||||
|
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
|
||||||
|
|
||||||
|
You can ask for guidance anytime on our
|
||||||
|
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
||||||
|
|
||||||
|
|
||||||
|
## 1) Setup your development environment
|
||||||
|
|
||||||
|
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
||||||
|
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
||||||
|
|
||||||
|
## 2) Set your environment variables
|
||||||
|
|
||||||
|
- Copy [.env.development.example](./.env.development.example) to .env.
|
||||||
|
|
||||||
|
## 3) Start & setup Coolify
|
||||||
|
|
||||||
|
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
|
||||||
|
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
|
||||||
|
|
||||||
|
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
|
||||||
|
|
||||||
|
## 4) Start development
|
||||||
|
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
||||||
|
|
||||||
|
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
||||||
|
|
||||||
|
Mails are caught by Mailpit: `localhost:8025`
|
||||||
@@ -36,7 +36,7 @@ You can find the installation script [here](./scripts/install.sh).
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
Contact us [here](https://docs.coollabs.io/contact).
|
Contact us [here](https://coolify.io/docs/contact).
|
||||||
|
|
||||||
## Recognitions
|
## Recognitions
|
||||||
|
|
||||||
|
|||||||
30
app/Actions/Application/StopApplication.php
Normal file
30
app/Actions/Application/StopApplication.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Notifications\Application\StatusChanged;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopApplication
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Application $application)
|
||||||
|
{
|
||||||
|
$server = $application->destination->server;
|
||||||
|
$containers = getCurrentApplicationContainerStatus($server, $application->id);
|
||||||
|
if ($containers->count() > 0) {
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$containerName = data_get($container, 'Names');
|
||||||
|
if ($containerName) {
|
||||||
|
instant_remote_process(
|
||||||
|
["docker rm -f {$containerName}"],
|
||||||
|
$server
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: make notification for application
|
||||||
|
// $application->environment->project->team->notify(new StatusChanged($application));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
app/Actions/Database/StartDatabaseProxy.php
Normal file
93
app/Actions/Database/StartDatabaseProxy.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class StartDatabaseProxy
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
||||||
|
{
|
||||||
|
$internalPort = null;
|
||||||
|
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
|
||||||
|
$internalPort = 6379;
|
||||||
|
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
|
||||||
|
$internalPort = 5432;
|
||||||
|
}
|
||||||
|
$containerName = "{$database->uuid}-proxy";
|
||||||
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
|
$nginxconf = <<<EOF
|
||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
stream {
|
||||||
|
server {
|
||||||
|
listen $database->public_port;
|
||||||
|
proxy_pass $database->uuid:$internalPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF;
|
||||||
|
$dockerfile = <<< EOF
|
||||||
|
FROM nginx:stable-alpine
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
EOF;
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$containerName => [
|
||||||
|
'build' => [
|
||||||
|
'context' => $configuration_dir,
|
||||||
|
'dockerfile' => 'Dockerfile',
|
||||||
|
],
|
||||||
|
'image' => "nginx:stable-alpine",
|
||||||
|
'container_name' => $containerName,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'ports' => [
|
||||||
|
"$database->public_port:$database->public_port",
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$database->destination->network,
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => [
|
||||||
|
'CMD-SHELL',
|
||||||
|
'stat /etc/nginx/nginx.conf || exit 1',
|
||||||
|
],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '5s',
|
||||||
|
'retries' => 3,
|
||||||
|
'start_period' => '1s'
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
|
||||||
|
$nginxconf_base64 = base64_encode($nginxconf);
|
||||||
|
$dockerfile_base64 = base64_encode($dockerfile);
|
||||||
|
instant_remote_process([
|
||||||
|
"mkdir -p $configuration_dir",
|
||||||
|
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
||||||
|
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||||
|
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||||
|
"docker compose --project-directory {$configuration_dir} up --build -d",
|
||||||
|
], $database->destination->server);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,15 +6,18 @@ 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;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StartPostgresql
|
class StartPostgresql
|
||||||
{
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql $database;
|
||||||
public array $commands = [];
|
public array $commands = [];
|
||||||
public array $init_scripts = [];
|
public array $init_scripts = [];
|
||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
public function __invoke(Server $server, StandalonePostgresql $database)
|
public function handle(Server $server, StandalonePostgresql $database)
|
||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
@@ -41,6 +44,9 @@ class StartPostgresql
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
'CMD-SHELL',
|
'CMD-SHELL',
|
||||||
|
|||||||
160
app/Actions/Database/StartRedis.php
Normal file
160
app/Actions/Database/StartRedis.php
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartRedis
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneRedis $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
|
||||||
|
public function handle(Server $server, StandaloneRedis $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
|
|
||||||
|
$container_name = $this->database->uuid;
|
||||||
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
|
$this->commands = [
|
||||||
|
"echo '####### Starting {$database->name}.'",
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
];
|
||||||
|
|
||||||
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
$this->add_custom_redis();
|
||||||
|
|
||||||
|
$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',
|
||||||
|
'redis-cli',
|
||||||
|
'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' => $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->redis_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/redis.conf',
|
||||||
|
'target' => '/usr/local/etc/redis/redis.conf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
|
||||||
|
}
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
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('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_redis()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->redis_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'redis.conf';
|
||||||
|
$content = $this->database->redis_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Actions/Database/StopDatabase.php
Normal file
27
app/Actions/Database/StopDatabase.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use App\Notifications\Application\StatusChanged;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
instant_remote_process(
|
||||||
|
["docker rm -f {$database->uuid}"],
|
||||||
|
$server
|
||||||
|
);
|
||||||
|
if ($database->is_public) {
|
||||||
|
StopDatabaseProxy::run($database);
|
||||||
|
}
|
||||||
|
// TODO: make notification for services
|
||||||
|
// $database->environment->project->team->notify(new StatusChanged($database));
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Actions/Database/StopDatabaseProxy.php
Normal file
19
app/Actions/Database/StopDatabaseProxy.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopDatabaseProxy
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
||||||
|
{
|
||||||
|
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
||||||
|
$database->is_public = false;
|
||||||
|
$database->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,6 +58,11 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
'password' => Hash::make($input['password']),
|
'password' => Hash::make($input['password']),
|
||||||
]);
|
]);
|
||||||
$team = $user->teams()->first();
|
$team = $user->teams()->first();
|
||||||
|
if (isCloud()) {
|
||||||
|
$user->sendVerificationEmail();
|
||||||
|
} else {
|
||||||
|
$user->markEmailAsVerified();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Set session variable
|
// Set session variable
|
||||||
session(['currentTeam' => $user->currentTeam = $team]);
|
session(['currentTeam' => $user->currentTeam = $team]);
|
||||||
|
|||||||
51
app/Actions/Proxy/CheckProxy.php
Normal file
51
app/Actions/Proxy/CheckProxy.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class CheckProxy
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Server $server, $fromUI = false)
|
||||||
|
{
|
||||||
|
if (!$server->isProxyShouldRun()) {
|
||||||
|
if ($fromUI) {
|
||||||
|
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$status = getContainerStatus($server, 'coolify-proxy');
|
||||||
|
if ($status === 'running') {
|
||||||
|
$server->proxy->set('status', 'running');
|
||||||
|
$server->save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$ip = $server->ip;
|
||||||
|
if ($server->id === 0) {
|
||||||
|
$ip = 'host.docker.internal';
|
||||||
|
}
|
||||||
|
|
||||||
|
$connection80 = @fsockopen($ip, '80');
|
||||||
|
$connection443 = @fsockopen($ip, '443');
|
||||||
|
$port80 = is_resource($connection80) && fclose($connection80);
|
||||||
|
$port443 = is_resource($connection443) && fclose($connection443);
|
||||||
|
if ($port80) {
|
||||||
|
if ($fromUI) {
|
||||||
|
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($port443) {
|
||||||
|
if ($fromUI) {
|
||||||
|
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,56 +10,47 @@ use Spatie\Activitylog\Models\Activity;
|
|||||||
class StartProxy
|
class StartProxy
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Server $server, bool $async = true): Activity|string
|
public function handle(Server $server, bool $async = true): string|Activity
|
||||||
{
|
{
|
||||||
$commands = collect([]);
|
try {
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
if ($proxyType === 'none') {
|
$commands = collect([]);
|
||||||
return 'OK';
|
$proxy_path = get_proxy_path();
|
||||||
}
|
$configuration = CheckConfiguration::run($server);
|
||||||
$proxy_path = get_proxy_path();
|
if (!$configuration) {
|
||||||
$configuration = CheckConfiguration::run($server);
|
throw new \Exception("Configuration is not synced");
|
||||||
if (!$configuration) {
|
}
|
||||||
throw new \Exception("Configuration is not synced");
|
SaveConfiguration::run($server, $configuration);
|
||||||
}
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
SaveConfiguration::run($server, $configuration);
|
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
|
||||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
|
||||||
$server->save();
|
|
||||||
|
|
||||||
$commands = $commands->merge([
|
|
||||||
"apt-get update > /dev/null 2>&1 || true",
|
|
||||||
"command -v lsof >/dev/null || echo '####### Installing lsof.'",
|
|
||||||
"command -v lsof >/dev/null || apt install -y lsof",
|
|
||||||
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
|
|
||||||
"mkdir -p $proxy_path && cd $proxy_path",
|
|
||||||
"echo '####### Creating Docker Compose file.'",
|
|
||||||
"echo '####### Pulling docker image.'",
|
|
||||||
'docker compose pull',
|
|
||||||
"echo '####### Stopping existing coolify-proxy.'",
|
|
||||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
|
||||||
"command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
|
|
||||||
"command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
|
|
||||||
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
|
|
||||||
"command -v fuser >/dev/null && fuser -k 80/tcp || true",
|
|
||||||
"command -v fuser >/dev/null && fuser -k 443/tcp || true",
|
|
||||||
"systemctl disable nginx > /dev/null 2>&1 || true",
|
|
||||||
"systemctl disable apache2 > /dev/null 2>&1 || true",
|
|
||||||
"systemctl disable apache > /dev/null 2>&1 || true",
|
|
||||||
"echo '####### Starting coolify-proxy.'",
|
|
||||||
'docker compose up -d --remove-orphans',
|
|
||||||
"echo '####### Proxy installed successfully.'"
|
|
||||||
]);
|
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
|
||||||
if ($async) {
|
|
||||||
$activity = remote_process($commands, $server);
|
|
||||||
return $activity;
|
|
||||||
} else {
|
|
||||||
instant_remote_process($commands, $server);
|
|
||||||
$server->proxy->set('status', 'running');
|
|
||||||
$server->proxy->set('type', $proxyType);
|
|
||||||
$server->save();
|
$server->save();
|
||||||
return 'OK';
|
$commands = $commands->merge([
|
||||||
|
"mkdir -p $proxy_path && cd $proxy_path",
|
||||||
|
"echo 'Creating required Docker Compose file.'",
|
||||||
|
"echo 'Pulling docker image.'",
|
||||||
|
'docker compose pull',
|
||||||
|
"echo 'Stopping existing coolify-proxy.'",
|
||||||
|
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||||
|
"echo 'Starting coolify-proxy.'",
|
||||||
|
'docker compose up -d --remove-orphans',
|
||||||
|
"echo 'Proxy started successfully.'"
|
||||||
|
]);
|
||||||
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
|
if ($async) {
|
||||||
|
$activity = remote_process($commands, $server);
|
||||||
|
return $activity;
|
||||||
|
} else {
|
||||||
|
instant_remote_process($commands, $server);
|
||||||
|
$server->proxy->set('status', 'running');
|
||||||
|
$server->proxy->set('type', $proxyType);
|
||||||
|
$server->save();
|
||||||
|
return 'OK';
|
||||||
|
}
|
||||||
|
} catch(\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
|
|
||||||
class InstallDocker
|
class InstallDocker
|
||||||
{
|
{
|
||||||
public function __invoke(Server $server)
|
use AsAction;
|
||||||
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
$dockerVersion = '24.0';
|
$dockerVersion = '24.0';
|
||||||
$config = base64_encode('{
|
$config = base64_encode('{
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
|
||||||
class UpdateCoolify
|
class UpdateCoolify
|
||||||
{
|
{
|
||||||
|
use AsAction;
|
||||||
public ?Server $server = null;
|
public ?Server $server = null;
|
||||||
public ?string $latestVersion = null;
|
public ?string $latestVersion = null;
|
||||||
public ?string $currentVersion = null;
|
public ?string $currentVersion = null;
|
||||||
|
|
||||||
public function __invoke(bool $force)
|
public function handle(bool $force)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ namespace App\Actions\Service;
|
|||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class StartService
|
class StartService
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
|
$network = $service->destination->network;
|
||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = "cd " . $service->workdir();
|
$commands[] = "cd " . $service->workdir();
|
||||||
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
|
||||||
@@ -21,6 +23,11 @@ class StartService
|
|||||||
$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 2>/dev/null || true";
|
||||||
|
$compose = data_get($service,'docker_compose',[]);
|
||||||
|
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
|
||||||
|
foreach($serviceNames as $serviceName => $serviceConfig){
|
||||||
|
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} 2>/dev/null || true";
|
||||||
|
}
|
||||||
$activity = remote_process($commands, $service->server);
|
$activity = remote_process($commands, $service->server);
|
||||||
return $activity;
|
return $activity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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
|
||||||
{
|
{
|
||||||
@@ -22,5 +23,7 @@ class StopService
|
|||||||
}
|
}
|
||||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
||||||
|
// TODO: make notification for databases
|
||||||
|
// $service->environment->project->team->notify(new StatusChanged($service));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
app/Console/Commands/Cloud.php
Normal file
33
app/Console/Commands/Cloud.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class Cloud extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'cloud:unused-servers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Get Unused Servers from Cloud';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
|
||||||
|
$this->info($server->name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
139
app/Console/Commands/ResourcesDelete.php
Normal file
139
app/Console/Commands/ResourcesDelete.php
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\confirm;
|
||||||
|
use function Laravel\Prompts\multiselect;
|
||||||
|
use function Laravel\Prompts\select;
|
||||||
|
|
||||||
|
class ResourcesDelete extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'resources:delete';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Delete a resource from the database';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$resource = select(
|
||||||
|
'What resource do you want to delete?',
|
||||||
|
['Application', 'Database', 'Service', 'Server'],
|
||||||
|
);
|
||||||
|
if ($resource === 'Application') {
|
||||||
|
$this->deleteApplication();
|
||||||
|
} elseif ($resource === 'Database') {
|
||||||
|
$this->deleteDatabase();
|
||||||
|
} elseif ($resource === 'Service') {
|
||||||
|
$this->deleteService();
|
||||||
|
} elseif ($resource === 'Server') {
|
||||||
|
$this->deleteServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function deleteServer()
|
||||||
|
{
|
||||||
|
$servers = Server::all();
|
||||||
|
if ($servers->count() === 0) {
|
||||||
|
$this->error('There are no applications to delete.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$serversToDelete = multiselect(
|
||||||
|
label: 'What server do you want to delete?',
|
||||||
|
options: $servers->pluck('name', 'id')->sortKeys(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($serversToDelete as $server) {
|
||||||
|
$toDelete = $servers->where('id', $server)->first();
|
||||||
|
$this->info($toDelete);
|
||||||
|
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||||
|
if (!$confirmed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$toDelete->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function deleteApplication()
|
||||||
|
{
|
||||||
|
$applications = Application::all();
|
||||||
|
if ($applications->count() === 0) {
|
||||||
|
$this->error('There are no applications to delete.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$applicationsToDelete = multiselect(
|
||||||
|
'What application do you want to delete?',
|
||||||
|
$applications->pluck('name', 'id')->sortKeys(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($applicationsToDelete as $application) {
|
||||||
|
ray($application);
|
||||||
|
$toDelete = $applications->where('id', $application)->first();
|
||||||
|
$this->info($toDelete);
|
||||||
|
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
||||||
|
if (!$confirmed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$toDelete->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function deleteDatabase()
|
||||||
|
{
|
||||||
|
$databases = StandalonePostgresql::all();
|
||||||
|
if ($databases->count() === 0) {
|
||||||
|
$this->error('There are no databases to delete.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$databasesToDelete = multiselect(
|
||||||
|
'What database do you want to delete?',
|
||||||
|
$databases->pluck('name', 'id')->sortKeys(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($databasesToDelete as $database) {
|
||||||
|
$toDelete = $databases->where('id', $database)->first();
|
||||||
|
$this->info($toDelete);
|
||||||
|
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||||
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$toDelete->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function deleteService()
|
||||||
|
{
|
||||||
|
$services = Service::all();
|
||||||
|
if ($services->count() === 0) {
|
||||||
|
$this->error('There are no services to delete.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$servicesToDelete = multiselect(
|
||||||
|
'What service do you want to delete?',
|
||||||
|
$services->pluck('name', 'id')->sortKeys(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($servicesToDelete as $service) {
|
||||||
|
$toDelete = $services->where('id', $service)->first();
|
||||||
|
$this->info($toDelete);
|
||||||
|
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||||
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$toDelete->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
app/Console/Commands/UsersResetRoot.php
Normal file
49
app/Console/Commands/UsersResetRoot.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\password;
|
||||||
|
|
||||||
|
class UsersResetRoot extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'users:reset-root';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Reset Root Password';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
$this->info('You are about to reset the root password.');
|
||||||
|
$password = password('Give me a new password for root user: ');
|
||||||
|
$passwordAgain = password('Again');
|
||||||
|
if ($password != $passwordAgain) {
|
||||||
|
$this->error('Passwords do not match.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->info('Updating root password...');
|
||||||
|
try {
|
||||||
|
User::find(0)->update(['password' => Hash::make($password)]);
|
||||||
|
$this->info('Root password updated successfully.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error('Failed to update root password.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,11 +28,11 @@ class Kernel extends ConsoleKernel
|
|||||||
// $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);
|
||||||
} else {
|
} else {
|
||||||
$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();
|
||||||
// $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
|
||||||
$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);
|
||||||
@@ -48,7 +48,11 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
private function check_resources($schedule)
|
private function check_resources($schedule)
|
||||||
{
|
{
|
||||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
|
if (isCloud()) {
|
||||||
|
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
|
||||||
|
} else {
|
||||||
|
$servers = Server::all();
|
||||||
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,15 +46,6 @@ class Controller extends BaseController
|
|||||||
}
|
}
|
||||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||||
}
|
}
|
||||||
public function subscription()
|
|
||||||
{
|
|
||||||
if (!isCloud()) {
|
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
return view('subscription.index', [
|
|
||||||
'settings' => InstanceSettings::get(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function license()
|
public function license()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class DatabaseController extends Controller
|
|||||||
if (!$environment) {
|
if (!$environment) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
||||||
if (!$database) {
|
if (!$database) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ class DatabaseController extends Controller
|
|||||||
if (!$environment) {
|
if (!$environment) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
||||||
if (!$database) {
|
if (!$database) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
@@ -64,10 +64,18 @@ class DatabaseController extends Controller
|
|||||||
if (!$environment) {
|
if (!$environment) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
||||||
if (!$database) {
|
if (!$database) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
// No backups for redis
|
||||||
|
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
|
||||||
|
return redirect()->route('project.database.configuration', [
|
||||||
|
'project_uuid' => $project->uuid,
|
||||||
|
'environment_name' => $environment->name,
|
||||||
|
'database_uuid' => $database->uuid,
|
||||||
|
]);
|
||||||
|
}
|
||||||
return view('project.database.backups.all', [
|
return view('project.database.backups.all', [
|
||||||
'database' => $database,
|
'database' => $database,
|
||||||
's3s' => currentTeam()->s3s,
|
's3s' => currentTeam()->s3s,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use App\Models\EnvironmentVariable;
|
|||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use App\Models\StandaloneDocker;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ProjectController extends Controller
|
class ProjectController extends Controller
|
||||||
@@ -59,27 +59,33 @@ class ProjectController extends Controller
|
|||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
if (in_array($type, DATABASE_TYPES)) {
|
if (in_array($type, DATABASE_TYPES)) {
|
||||||
$standalone_postgresql = create_standalone_postgresql($environment->id, $destination_uuid);
|
if ($type->value() === "postgresql") {
|
||||||
|
$database = create_standalone_postgresql($environment->id, $destination_uuid);
|
||||||
|
} else if ($type->value() === 'redis') {
|
||||||
|
$database = create_standalone_redis($environment->id, $destination_uuid);
|
||||||
|
}
|
||||||
return redirect()->route('project.database.configuration', [
|
return redirect()->route('project.database.configuration', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
'database_uuid' => $standalone_postgresql->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");
|
||||||
ray($oneClickServiceName);
|
|
||||||
$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/');
|
||||||
}
|
}
|
||||||
if ($oneClickService) {
|
if ($oneClickService) {
|
||||||
|
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
|
||||||
$service = Service::create([
|
$service = Service::create([
|
||||||
'name' => "$oneClickServiceName-" . Str::random(10),
|
'name' => "$oneClickServiceName-" . Str::random(10),
|
||||||
'docker_compose_raw' => base64_decode($oneClickService),
|
'docker_compose_raw' => base64_decode($oneClickService),
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
'server_id' => (int) $server_id,
|
'server_id' => (int) $server_id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
$service->name = "$oneClickServiceName-" . $service->uuid;
|
$service->name = "$oneClickServiceName-" . $service->uuid;
|
||||||
$service->save();
|
$service->save();
|
||||||
@@ -90,6 +96,7 @@ class ProjectController extends Controller
|
|||||||
$generatedValue = $value;
|
$generatedValue = $value;
|
||||||
if ($value->contains('SERVICE_')) {
|
if ($value->contains('SERVICE_')) {
|
||||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||||
|
// TODO: make it shared with Service.php
|
||||||
switch ($command->value()) {
|
switch ($command->value()) {
|
||||||
case 'PASSWORD':
|
case 'PASSWORD':
|
||||||
$generatedValue = Str::password(symbols: false);
|
$generatedValue = Str::password(symbols: false);
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Models\PrivateKey;
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
|
||||||
|
|
||||||
class ServerController extends Controller
|
|
||||||
{
|
|
||||||
use AuthorizesRequests, ValidatesRequests;
|
|
||||||
|
|
||||||
public function new_server()
|
|
||||||
{
|
|
||||||
$privateKeys = PrivateKey::ownedByCurrentTeam()->get();
|
|
||||||
if (!isCloud()) {
|
|
||||||
return view('server.create', [
|
|
||||||
'limit_reached' => false,
|
|
||||||
'private_keys' => $privateKeys,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$team = currentTeam();
|
|
||||||
$servers = $team->servers->count();
|
|
||||||
['serverLimit' => $serverLimit] = $team->limits;
|
|
||||||
$limit_reached = $servers >= $serverLimit;
|
|
||||||
|
|
||||||
return view('server.create', [
|
|
||||||
'limit_reached' => $limit_reached,
|
|
||||||
'private_keys' => $privateKeys,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,8 +38,7 @@ class Kernel extends HttpKernel
|
|||||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
\App\Http\Middleware\CheckForcePasswordReset::class,
|
\App\Http\Middleware\CheckForcePasswordReset::class,
|
||||||
\App\Http\Middleware\IsSubscriptionValid::class,
|
\App\Http\Middleware\DecideWhatToDoWithUser::class,
|
||||||
\App\Http\Middleware\IsBoardingFlow::class,
|
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
Team::find(currentTeam()->id)->update([
|
Team::find(currentTeam()->id)->update([
|
||||||
'show_boarding' => false
|
'show_boarding' => false
|
||||||
]);
|
]);
|
||||||
ray(currentTeam());
|
|
||||||
refreshSession();
|
refreshSession();
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
@@ -165,7 +164,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
{
|
{
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'remoteServerName' => 'required',
|
'remoteServerName' => 'required',
|
||||||
'remoteServerHost' => 'required',
|
'remoteServerHost' => 'required|ip',
|
||||||
'remoteServerPort' => 'required|integer',
|
'remoteServerPort' => 'required|integer',
|
||||||
'remoteServerUser' => 'required',
|
'remoteServerUser' => 'required',
|
||||||
]);
|
]);
|
||||||
@@ -221,7 +220,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
public function installDocker()
|
public function installDocker()
|
||||||
{
|
{
|
||||||
$this->dockerInstallationStarted = true;
|
$this->dockerInstallationStarted = true;
|
||||||
$activity = resolve(InstallDocker::class)($this->createdServer);
|
$activity = InstallDocker::run($this->createdServer);
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
}
|
}
|
||||||
public function dockerInstalledOrSkipped()
|
public function dockerInstalledOrSkipped()
|
||||||
|
|||||||
@@ -9,21 +9,13 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Dashboard extends Component
|
class Dashboard extends Component
|
||||||
{
|
{
|
||||||
public int $projects = 0;
|
public $projects = [];
|
||||||
public int $servers = 0;
|
public $servers = [];
|
||||||
public int $s3s = 0;
|
|
||||||
public int $resources = 0;
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->servers = Server::ownedByCurrentTeam()->get()->count();
|
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||||
$this->s3s = S3Storage::ownedByCurrentTeam()->get()->count();
|
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||||
$projects = Project::ownedByCurrentTeam()->get();
|
|
||||||
foreach ($projects as $project) {
|
|
||||||
$this->resources += $project->applications->count();
|
|
||||||
$this->resources += $project->postgresqls->count();
|
|
||||||
}
|
|
||||||
$this->projects = $projects->count();
|
|
||||||
}
|
}
|
||||||
// public function getIptables()
|
// public function getIptables()
|
||||||
// {
|
// {
|
||||||
|
|||||||
28
app/Http/Livewire/Dev/Compose.php
Normal file
28
app/Http/Livewire/Dev/Compose.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Dev;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Compose extends Component
|
||||||
|
{
|
||||||
|
public string $compose = '';
|
||||||
|
public string $base64 = '';
|
||||||
|
public $services;
|
||||||
|
public function mount() {
|
||||||
|
$this->services = getServiceTemplates();
|
||||||
|
}
|
||||||
|
public function setService(string $selected) {
|
||||||
|
$this->base64 = data_get($this->services, $selected . '.compose');
|
||||||
|
if ($this->base64) {
|
||||||
|
$this->compose = base64_decode($this->base64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function updatedCompose($value) {
|
||||||
|
$this->base64 = base64_encode($value);
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.dev.compose');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,12 +60,16 @@ class DeploymentNavbar extends Component
|
|||||||
$previous_logs[] = $new_log_entry;
|
$previous_logs[] = $new_log_entry;
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
|
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
|
||||||
'current_process_id' => null,
|
|
||||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
} finally {
|
||||||
|
$this->application_deployment_queue->update([
|
||||||
|
'current_process_id' => null,
|
||||||
|
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||||
|
]);
|
||||||
|
queue_next_deployment($this->application);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
namespace App\Http\Livewire\Project\Application;
|
namespace App\Http\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
|
|
||||||
class General extends Component
|
class General extends Component
|
||||||
{
|
{
|
||||||
@@ -17,12 +14,16 @@ class General extends Component
|
|||||||
public Application $application;
|
public Application $application;
|
||||||
public Collection $services;
|
public Collection $services;
|
||||||
public string $name;
|
public string $name;
|
||||||
public string|null $fqdn;
|
public ?string $fqdn = null;
|
||||||
public string $git_repository;
|
public string $git_repository;
|
||||||
public string $git_branch;
|
public string $git_branch;
|
||||||
public string|null $git_commit_sha;
|
public ?string $git_commit_sha = null;
|
||||||
public string $build_pack;
|
public string $build_pack;
|
||||||
|
|
||||||
|
public $customLabels;
|
||||||
|
public bool $labelsChanged = false;
|
||||||
|
public bool $isConfigurationChanged = false;
|
||||||
|
|
||||||
public bool $is_static;
|
public bool $is_static;
|
||||||
public bool $is_git_submodules_enabled;
|
public bool $is_git_submodules_enabled;
|
||||||
public bool $is_git_lfs_enabled;
|
public bool $is_git_lfs_enabled;
|
||||||
@@ -49,6 +50,10 @@ class General extends Component
|
|||||||
'application.ports_exposes' => 'required',
|
'application.ports_exposes' => 'required',
|
||||||
'application.ports_mappings' => 'nullable',
|
'application.ports_mappings' => 'nullable',
|
||||||
'application.dockerfile' => 'nullable',
|
'application.dockerfile' => 'nullable',
|
||||||
|
'application.docker_registry_image_name' => 'nullable',
|
||||||
|
'application.docker_registry_image_tag' => 'nullable',
|
||||||
|
'application.dockerfile_location' => 'nullable',
|
||||||
|
'application.custom_labels' => 'nullable',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'application.name' => 'name',
|
'application.name' => 'name',
|
||||||
@@ -67,9 +72,50 @@ class General extends Component
|
|||||||
'application.ports_exposes' => 'Ports exposes',
|
'application.ports_exposes' => 'Ports exposes',
|
||||||
'application.ports_mappings' => 'Ports mappings',
|
'application.ports_mappings' => 'Ports mappings',
|
||||||
'application.dockerfile' => 'Dockerfile',
|
'application.dockerfile' => 'Dockerfile',
|
||||||
|
'application.docker_registry_image_name' => 'Docker registry image name',
|
||||||
|
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
||||||
|
'application.dockerfile_location' => 'Dockerfile location',
|
||||||
|
'application.custom_labels' => 'Custom labels',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
|
||||||
|
$this->application->isConfigurationChanged(true);
|
||||||
|
}
|
||||||
|
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||||
|
if (is_null(data_get($this->application, 'custom_labels'))) {
|
||||||
|
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
||||||
|
} else {
|
||||||
|
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
|
||||||
|
}
|
||||||
|
if (data_get($this->application, 'settings')) {
|
||||||
|
$this->is_static = $this->application->settings->is_static;
|
||||||
|
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
||||||
|
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
||||||
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
|
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
||||||
|
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
||||||
|
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||||
|
}
|
||||||
|
$this->checkLabelUpdates();
|
||||||
|
}
|
||||||
|
public function updatedApplicationBuildPack()
|
||||||
|
{
|
||||||
|
if ($this->application->build_pack !== 'nixpacks') {
|
||||||
|
$this->application->settings->is_static = $this->is_static = false;
|
||||||
|
$this->application->settings->save();
|
||||||
|
}
|
||||||
|
$this->submit();
|
||||||
|
}
|
||||||
|
public function checkLabelUpdates()
|
||||||
|
{
|
||||||
|
if (md5($this->application->custom_labels) !== md5(implode(",", generateLabelsApplication($this->application)))) {
|
||||||
|
$this->labelsChanged = true;
|
||||||
|
} else {
|
||||||
|
$this->labelsChanged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
// @TODO: find another way - if possible
|
// @TODO: find another way - if possible
|
||||||
@@ -89,34 +135,41 @@ class General extends Component
|
|||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->application->refresh();
|
$this->application->refresh();
|
||||||
$this->emit('success', 'Application settings updated!');
|
$this->emit('success', 'Application settings updated!');
|
||||||
|
$this->checkLabelUpdates();
|
||||||
|
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWildcardDomain() {
|
public function getWildcardDomain()
|
||||||
|
{
|
||||||
$server = data_get($this->application, 'destination.server');
|
$server = data_get($this->application, 'destination.server');
|
||||||
if ($server) {
|
if ($server) {
|
||||||
$fqdn = generateFqdn($server, $this->application->uuid);
|
$fqdn = generateFqdn($server, $this->application->uuid);
|
||||||
ray($fqdn);
|
|
||||||
$this->application->fqdn = $fqdn;
|
$this->application->fqdn = $fqdn;
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->emit('success', 'Application settings updated!');
|
$this->emit('success', 'Application settings updated!');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public function mount()
|
public function resetDefaultLabels($showToaster = true)
|
||||||
{
|
{
|
||||||
$this->is_static = $this->application->settings->is_static;
|
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
||||||
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
$this->submit($showToaster);
|
||||||
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
|
||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
|
||||||
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
|
||||||
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
|
||||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function updatedApplicationFqdn()
|
||||||
|
{
|
||||||
|
$this->resetDefaultLabels(false);
|
||||||
|
$this->emit('success', 'Labels reseted to default!');
|
||||||
|
}
|
||||||
|
public function submit($showToaster = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
if (data_get($this->application, 'build_pack') === 'dockerimage') {
|
||||||
|
$this->validate([
|
||||||
|
'application.docker_registry_image_name' => 'required',
|
||||||
|
'application.docker_registry_image_tag' => 'required',
|
||||||
|
]);
|
||||||
|
}
|
||||||
if (data_get($this->application, 'fqdn')) {
|
if (data_get($this->application, 'fqdn')) {
|
||||||
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||||
return Str::of($domain)->trim()->lower();
|
return Str::of($domain)->trim()->lower();
|
||||||
@@ -125,7 +178,7 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
if (data_get($this->application, 'dockerfile')) {
|
if (data_get($this->application, 'dockerfile')) {
|
||||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
$port = get_port_from_dockerfile($this->application->dockerfile);
|
||||||
if ($port) {
|
if ($port && !$this->application->ports_exposes) {
|
||||||
$this->application->ports_exposes = $port;
|
$this->application->ports_exposes = $port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,10 +188,17 @@ class General extends Component
|
|||||||
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
|
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
|
||||||
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
|
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
|
||||||
}
|
}
|
||||||
|
if (gettype($this->customLabels) === 'string') {
|
||||||
|
$this->customLabels = str($this->customLabels)->replace(',', "\n");
|
||||||
|
}
|
||||||
|
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->emit('success', 'Application settings updated!');
|
$showToaster && $this->emit('success', 'Application settings updated!');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
} finally {
|
||||||
|
$this->checkLabelUpdates();
|
||||||
|
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Application;
|
namespace App\Http\Livewire\Project\Application;
|
||||||
|
|
||||||
|
use App\Actions\Application\StopApplication;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -21,11 +22,13 @@ class Heading extends Component
|
|||||||
|
|
||||||
public function check_status()
|
public function check_status()
|
||||||
{
|
{
|
||||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
if ($this->application->destination->server->isFunctional()) {
|
||||||
$this->application->refresh();
|
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||||
$this->application->previews->each(function ($preview) {
|
$this->application->refresh();
|
||||||
$preview->refresh();
|
$this->application->previews->each(function ($preview) {
|
||||||
});
|
$preview->refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function force_deploy_without_cache()
|
public function force_deploy_without_cache()
|
||||||
@@ -57,22 +60,9 @@ class Heading extends Component
|
|||||||
|
|
||||||
public function stop()
|
public function stop()
|
||||||
{
|
{
|
||||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
StopApplication::run($this->application);
|
||||||
if ($containers->count() === 0) {
|
$this->application->status = 'exited';
|
||||||
return;
|
$this->application->save();
|
||||||
}
|
|
||||||
foreach ($containers as $container) {
|
|
||||||
$containerName = data_get($container, 'Names');
|
|
||||||
if ($containerName) {
|
|
||||||
instant_remote_process(
|
|
||||||
["docker rm -f {$containerName}"],
|
|
||||||
$this->application->destination->server
|
|
||||||
);
|
|
||||||
$this->application->status = 'exited';
|
|
||||||
$this->application->save();
|
|
||||||
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->application->refresh();
|
$this->application->refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ class Form extends Component
|
|||||||
public function generate_real_url()
|
public function generate_real_url()
|
||||||
{
|
{
|
||||||
if (data_get($this->application, 'fqdn')) {
|
if (data_get($this->application, 'fqdn')) {
|
||||||
$url = Url::fromString($this->application->fqdn);
|
$firstFqdn = Str::of($this->application->fqdn)->before(',');
|
||||||
|
$url = Url::fromString($firstFqdn);
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
$this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host);
|
$this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class Previews extends Component
|
|||||||
public function load_prs()
|
public function load_prs()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
|
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
|
||||||
$this->rate_limit_remaining = $rate_limit_remaining;
|
$this->rate_limit_remaining = $rate_limit_remaining;
|
||||||
$this->pull_requests = $data->sortBy('number')->values();
|
$this->pull_requests = $data->sortBy('number')->values();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -72,8 +72,7 @@ class Previews extends Component
|
|||||||
public function stop(int $pull_request_id)
|
public function stop(int $pull_request_id)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$container_name = generateApplicationContainerName($this->application);
|
$container_name = generateApplicationContainerName($this->application, $pull_request_id);
|
||||||
ray('Stopping container: ' . $container_name);
|
|
||||||
|
|
||||||
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
|
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
|
||||||
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
|
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class BackupEdit extends Component
|
|||||||
{
|
{
|
||||||
public $backup;
|
public $backup;
|
||||||
public $s3s;
|
public $s3s;
|
||||||
|
public ?string $status = null;
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
@@ -16,6 +17,7 @@ class BackupEdit extends Component
|
|||||||
'backup.number_of_backups_locally' => 'required|integer|min:1',
|
'backup.number_of_backups_locally' => 'required|integer|min:1',
|
||||||
'backup.save_s3' => 'required|boolean',
|
'backup.save_s3' => 'required|boolean',
|
||||||
'backup.s3_storage_id' => 'nullable|integer',
|
'backup.s3_storage_id' => 'nullable|integer',
|
||||||
|
'backup.databases_to_backup' => 'nullable',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'backup.enabled' => 'Enabled',
|
'backup.enabled' => 'Enabled',
|
||||||
@@ -23,6 +25,7 @@ class BackupEdit extends Component
|
|||||||
'backup.number_of_backups_locally' => 'Number of Backups Locally',
|
'backup.number_of_backups_locally' => 'Number of Backups Locally',
|
||||||
'backup.save_s3' => 'Save to S3',
|
'backup.save_s3' => 'Save to S3',
|
||||||
'backup.s3_storage_id' => 'S3 Storage',
|
'backup.s3_storage_id' => 'S3 Storage',
|
||||||
|
'backup.databases_to_backup' => 'Databases to Backup',
|
||||||
];
|
];
|
||||||
protected $messages = [
|
protected $messages = [
|
||||||
'backup.s3_storage_id' => 'Select a S3 Storage',
|
'backup.s3_storage_id' => 'Select a S3 Storage',
|
||||||
@@ -36,7 +39,6 @@ class BackupEdit extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
// TODO: Delete backup from server and add a confirmation modal
|
// TODO: Delete backup from server and add a confirmation modal
|
||||||
@@ -48,6 +50,7 @@ class BackupEdit extends Component
|
|||||||
{
|
{
|
||||||
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');
|
||||||
@@ -70,9 +73,11 @@ class BackupEdit extends Component
|
|||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
ray($this->backup->s3_storage_id);
|
|
||||||
try {
|
try {
|
||||||
$this->custom_validate();
|
$this->custom_validate();
|
||||||
|
if ($this->backup->databases_to_backup == '' || $this->backup->databases_to_backup === null) {
|
||||||
|
$this->backup->databases_to_backup = null;
|
||||||
|
}
|
||||||
$this->backup->save();
|
$this->backup->save();
|
||||||
$this->backup->refresh();
|
$this->backup->refresh();
|
||||||
$this->emit('success', 'Backup updated successfully');
|
$this->emit('success', 'Backup updated successfully');
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ class BackupNow extends Component
|
|||||||
dispatch(new DatabaseBackupJob(
|
dispatch(new DatabaseBackupJob(
|
||||||
backup: $this->backup
|
backup: $this->backup
|
||||||
));
|
));
|
||||||
$this->emit('success', 'Backup queued. It will be available in a few minutes');
|
$this->emit('success', 'Backup queued. It will be available in a few minutes.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class CreateScheduledBackup extends Component
|
|||||||
public $database;
|
public $database;
|
||||||
public $frequency;
|
public $frequency;
|
||||||
public bool $enabled = true;
|
public bool $enabled = true;
|
||||||
public bool $save_s3 = true;
|
public bool $save_s3 = false;
|
||||||
public $s3_storage_id;
|
public $s3_storage_id;
|
||||||
public $s3s;
|
public $s3s;
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class CreateScheduledBackup extends Component
|
|||||||
$this->emit('error', 'Invalid Cron / Human expression.');
|
$this->emit('error', 'Invalid Cron / Human expression.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ScheduledDatabaseBackup::create([
|
$payload = [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'frequency' => $this->frequency,
|
'frequency' => $this->frequency,
|
||||||
'save_s3' => $this->save_s3,
|
'save_s3' => $this->save_s3,
|
||||||
@@ -40,7 +40,11 @@ class CreateScheduledBackup extends Component
|
|||||||
'database_id' => $this->database->id,
|
'database_id' => $this->database->id,
|
||||||
'database_type' => $this->database->getMorphClass(),
|
'database_type' => $this->database->getMorphClass(),
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
];
|
||||||
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
|
$payload['databases_to_backup'] = $this->database->postgres_db;
|
||||||
|
}
|
||||||
|
ScheduledDatabaseBackup::create($payload);
|
||||||
$this->emit('refreshScheduledBackups');
|
$this->emit('refreshScheduledBackups');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
handleError($e, $this);
|
handleError($e, $this);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Http\Livewire\Project\Database;
|
namespace App\Http\Livewire\Project\Database;
|
||||||
|
|
||||||
use App\Actions\Database\StartPostgresql;
|
use App\Actions\Database\StartPostgresql;
|
||||||
|
use App\Actions\Database\StartRedis;
|
||||||
|
use App\Actions\Database\StopDatabase;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -35,24 +37,20 @@ class Heading extends Component
|
|||||||
|
|
||||||
public function stop()
|
public function stop()
|
||||||
{
|
{
|
||||||
instant_remote_process(
|
StopDatabase::run($this->database);
|
||||||
["docker rm -f {$this->database->uuid}"],
|
|
||||||
$this->database->destination->server
|
|
||||||
);
|
|
||||||
if ($this->database->is_public) {
|
|
||||||
stopPostgresProxy($this->database);
|
|
||||||
$this->database->is_public = false;
|
|
||||||
}
|
|
||||||
$this->database->status = 'exited';
|
$this->database->status = 'exited';
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->check_status();
|
$this->check_status();
|
||||||
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function start()
|
public function start()
|
||||||
{
|
{
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database);
|
$activity = StartPostgresql::run($this->database->destination->server, $this->database);
|
||||||
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
|
}
|
||||||
|
if ($this->database->type() === 'standalone-redis') {
|
||||||
|
$activity = StartRedis::run($this->database->destination->server, $this->database);
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database\Postgresql;
|
namespace App\Http\Livewire\Project\Database\Postgresql;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -50,8 +52,9 @@ class General extends Component
|
|||||||
$this->getDbUrl();
|
$this->getDbUrl();
|
||||||
}
|
}
|
||||||
public function getDbUrl() {
|
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->ip}:{$this->database->public_port}/{$this->database->postgres_db}";
|
$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}";
|
||||||
} else {
|
} else {
|
||||||
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
|
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
|
||||||
}
|
}
|
||||||
@@ -66,10 +69,10 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
$this->emit('success', 'Starting TCP proxy...');
|
$this->emit('success', 'Starting TCP proxy...');
|
||||||
startPostgresProxy($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
stopPostgresProxy($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->getDbUrl();
|
$this->getDbUrl();
|
||||||
|
|||||||
94
app/Http/Livewire/Project/Database/Redis/General.php
Normal file
94
app/Http/Livewire/Project/Database/Redis/General.php
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Database\Redis;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Exception;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class General extends Component
|
||||||
|
{
|
||||||
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
|
public StandaloneRedis $database;
|
||||||
|
public string $db_url;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'database.name' => 'required',
|
||||||
|
'database.description' => 'nullable',
|
||||||
|
'database.redis_conf' => 'nullable',
|
||||||
|
'database.redis_password' => '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.redis_conf' => 'Redis Configuration',
|
||||||
|
'database.redis_password' => 'Redis Password',
|
||||||
|
'database.image' => 'Image',
|
||||||
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
|
'database.is_public' => 'Is Public',
|
||||||
|
'database.public_port' => 'Public Port',
|
||||||
|
];
|
||||||
|
public function submit() {
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
if ($this->database->redis_conf === "") {
|
||||||
|
$this->database->redis_conf = null;
|
||||||
|
}
|
||||||
|
$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) {
|
||||||
|
$this->emit('success', 'Starting TCP proxy...');
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->getDbUrl();
|
||||||
|
$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 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()
|
||||||
|
{
|
||||||
|
return view('livewire.project.database.redis.general');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,8 +32,6 @@ class DockerCompose extends Component
|
|||||||
- type: volume
|
- type: volume
|
||||||
source: mydata
|
source: mydata
|
||||||
target: /data
|
target: /data
|
||||||
volume:
|
|
||||||
nocopy: true
|
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ./var/lib/ghost/data
|
source: ./var/lib/ghost/data
|
||||||
target: /data
|
target: /data
|
||||||
|
|||||||
78
app/Http/Livewire/Project/New/DockerImage.php
Normal file
78
app/Http/Livewire/Project/New/DockerImage.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\New;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Models\SwarmDocker;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class DockerImage extends Component
|
||||||
|
{
|
||||||
|
public string $dockerImage = '';
|
||||||
|
public array $parameters;
|
||||||
|
public array $query;
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
$this->query = request()->query();
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'dockerImage' => 'required'
|
||||||
|
]);
|
||||||
|
$image = Str::of($this->dockerImage)->before(':');
|
||||||
|
if (Str::of($this->dockerImage)->contains(':')) {
|
||||||
|
$tag = Str::of($this->dockerImage)->after(':');
|
||||||
|
} else {
|
||||||
|
$tag = 'latest';
|
||||||
|
}
|
||||||
|
$destination_uuid = $this->query['destination'];
|
||||||
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
|
if (!$destination) {
|
||||||
|
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
|
||||||
|
}
|
||||||
|
if (!$destination) {
|
||||||
|
throw new \Exception('Destination not found. What?!');
|
||||||
|
}
|
||||||
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
|
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||||
|
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||||
|
ray($image,$tag);
|
||||||
|
$application = Application::create([
|
||||||
|
'name' => 'docker-image-' . new Cuid2(7),
|
||||||
|
'repository_project_id' => 0,
|
||||||
|
'git_repository' => "coollabsio/coolify",
|
||||||
|
'git_branch' => 'main',
|
||||||
|
'build_pack' => 'dockerimage',
|
||||||
|
'ports_exposes' => 80,
|
||||||
|
'docker_registry_image_name' => $image,
|
||||||
|
'docker_registry_image_tag' => $tag,
|
||||||
|
'environment_id' => $environment->id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination_class,
|
||||||
|
'health_check_enabled' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||||
|
$application->update([
|
||||||
|
'name' => 'docker-image-' . $application->uuid,
|
||||||
|
'fqdn' => $fqdn
|
||||||
|
]);
|
||||||
|
|
||||||
|
redirect()->route('project.application.configuration', [
|
||||||
|
'application_uuid' => $application->uuid,
|
||||||
|
'environment_name' => $environment->name,
|
||||||
|
'project_uuid' => $project->uuid,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.new.docker-image');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ use App\Models\StandaloneDocker;
|
|||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class GithubPrivateRepositoryDeployKey extends Component
|
class GithubPrivateRepositoryDeployKey extends Component
|
||||||
{
|
{
|
||||||
@@ -29,7 +30,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
public string $repository_url;
|
public string $repository_url;
|
||||||
public string $branch;
|
public string $branch;
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'repository_url' => 'required|url',
|
'repository_url' => 'required',
|
||||||
'branch' => 'required|string',
|
'branch' => 'required|string',
|
||||||
'port' => 'required|numeric',
|
'port' => 'required|numeric',
|
||||||
'is_static' => 'required|boolean',
|
'is_static' => 'required|boolean',
|
||||||
@@ -43,8 +44,8 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
'publish_directory' => 'Publish directory',
|
'publish_directory' => 'Publish directory',
|
||||||
];
|
];
|
||||||
private object $repository_url_parsed;
|
private object $repository_url_parsed;
|
||||||
private GithubApp|GitlabApp|null $git_source = null;
|
private GithubApp|GitlabApp|string $git_source = 'other';
|
||||||
private string $git_host;
|
private ?string $git_host = null;
|
||||||
private string $git_repository;
|
private string $git_repository;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -92,21 +93,36 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
|
|
||||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||||
$application_init = [
|
if ($this->git_source === 'other') {
|
||||||
'name' => generate_random_name(),
|
$application_init = [
|
||||||
'git_repository' => $this->git_repository,
|
'name' => generate_random_name(),
|
||||||
'git_branch' => $this->branch,
|
'git_repository' => $this->git_repository,
|
||||||
'git_full_url' => "git@$this->git_host:$this->git_repository.git",
|
'git_branch' => $this->branch,
|
||||||
'build_pack' => 'nixpacks',
|
'build_pack' => 'nixpacks',
|
||||||
'ports_exposes' => $this->port,
|
'ports_exposes' => $this->port,
|
||||||
'publish_directory' => $this->publish_directory,
|
'publish_directory' => $this->publish_directory,
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_id' => $destination->id,
|
'destination_id' => $destination->id,
|
||||||
'destination_type' => $destination_class,
|
'destination_type' => $destination_class,
|
||||||
'private_key_id' => $this->private_key_id,
|
'private_key_id' => $this->private_key_id,
|
||||||
'source_id' => $this->git_source->id,
|
];
|
||||||
'source_type' => $this->git_source->getMorphClass()
|
} else {
|
||||||
];
|
$application_init = [
|
||||||
|
'name' => generate_random_name(),
|
||||||
|
'git_repository' => $this->git_repository,
|
||||||
|
'git_branch' => $this->branch,
|
||||||
|
'build_pack' => 'nixpacks',
|
||||||
|
'ports_exposes' => $this->port,
|
||||||
|
'publish_directory' => $this->publish_directory,
|
||||||
|
'environment_id' => $environment->id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination_class,
|
||||||
|
'private_key_id' => $this->private_key_id,
|
||||||
|
'source_id' => $this->git_source->id,
|
||||||
|
'source_type' => $this->git_source->getMorphClass()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$application = Application::create($application_init);
|
$application = Application::create($application_init);
|
||||||
$application->settings->is_static = $this->is_static;
|
$application->settings->is_static = $this->is_static;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
@@ -134,10 +150,15 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
|
|
||||||
if ($this->git_host == 'github.com') {
|
if ($this->git_host == 'github.com') {
|
||||||
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
|
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
|
||||||
} elseif ($this->git_host == 'gitlab.com') {
|
return;
|
||||||
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
|
|
||||||
} elseif ($this->git_host == 'bitbucket.org') {
|
|
||||||
// Not supported yet
|
|
||||||
}
|
}
|
||||||
|
if (Str::of($this->repository_url)->startsWith('http')) {
|
||||||
|
$this->git_host = $this->repository_url_parsed->getHost();
|
||||||
|
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
|
||||||
|
$this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git');
|
||||||
|
} else {
|
||||||
|
$this->git_repository = $this->repository_url;
|
||||||
|
}
|
||||||
|
$this->git_source = 'other';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ class PublicGitRepository extends Component
|
|||||||
public string $git_branch = 'main';
|
public string $git_branch = 'main';
|
||||||
public int $rate_limit_remaining = 0;
|
public int $rate_limit_remaining = 0;
|
||||||
public $rate_limit_reset = 0;
|
public $rate_limit_reset = 0;
|
||||||
|
private object $repository_url_parsed;
|
||||||
|
public GithubApp|GitlabApp|string $git_source = 'other';
|
||||||
|
public string $git_host;
|
||||||
|
public string $git_repository;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'repository_url' => 'required|url',
|
'repository_url' => 'required|url',
|
||||||
'port' => 'required|numeric',
|
'port' => 'required|numeric',
|
||||||
@@ -38,10 +43,6 @@ class PublicGitRepository extends Component
|
|||||||
'is_static' => 'static',
|
'is_static' => 'static',
|
||||||
'publish_directory' => 'publish directory',
|
'publish_directory' => 'publish directory',
|
||||||
];
|
];
|
||||||
private object $repository_url_parsed;
|
|
||||||
private GithubApp|GitlabApp|null $git_source = null;
|
|
||||||
private string $git_host;
|
|
||||||
private string $git_repository;
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
@@ -64,18 +65,26 @@ class PublicGitRepository extends Component
|
|||||||
}
|
}
|
||||||
$this->emit('success', 'Application settings updated!');
|
$this->emit('success', 'Application settings updated!');
|
||||||
}
|
}
|
||||||
|
public function load_any_git()
|
||||||
|
{
|
||||||
|
$this->branch_found = true;
|
||||||
|
}
|
||||||
public function load_branch()
|
public function load_branch()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->branch_found = false;
|
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'repository_url' => 'required|url'
|
'repository_url' => 'required|url'
|
||||||
]);
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$this->branch_found = false;
|
||||||
$this->get_git_source();
|
$this->get_git_source();
|
||||||
$this->get_branch();
|
$this->get_branch();
|
||||||
$this->selected_branch = $this->git_branch;
|
$this->selected_branch = $this->git_branch;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
if (!$this->branch_found && $this->git_branch == 'main') {
|
if (!$this->branch_found && $this->git_branch == 'main') {
|
||||||
try {
|
try {
|
||||||
$this->git_branch = 'master';
|
$this->git_branch = 'master';
|
||||||
@@ -98,21 +107,23 @@ class PublicGitRepository extends Component
|
|||||||
|
|
||||||
if ($this->git_host == 'github.com') {
|
if ($this->git_host == 'github.com') {
|
||||||
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
|
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
|
||||||
} elseif ($this->git_host == 'gitlab.com') {
|
return;
|
||||||
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
|
|
||||||
} elseif ($this->git_host == 'bitbucket.org') {
|
|
||||||
// Not supported yet
|
|
||||||
}
|
|
||||||
if (is_null($this->git_source)) {
|
|
||||||
throw new \Exception('Git source not found. What?!');
|
|
||||||
}
|
}
|
||||||
|
$this->git_repository = $this->repository_url;
|
||||||
|
$this->git_source = 'other';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_branch()
|
private function get_branch()
|
||||||
{
|
{
|
||||||
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
|
if ($this->git_source === 'other') {
|
||||||
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
|
$this->branch_found = true;
|
||||||
$this->branch_found = true;
|
return;
|
||||||
|
}
|
||||||
|
if ($this->git_source->getMorphClass() === 'App\Models\GithubApp') {
|
||||||
|
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
|
||||||
|
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
|
||||||
|
$this->branch_found = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
@@ -123,9 +134,6 @@ class PublicGitRepository extends Component
|
|||||||
$project_uuid = $this->parameters['project_uuid'];
|
$project_uuid = $this->parameters['project_uuid'];
|
||||||
$environment_name = $this->parameters['environment_name'];
|
$environment_name = $this->parameters['environment_name'];
|
||||||
|
|
||||||
$this->get_git_source();
|
|
||||||
$this->git_branch = $this->selected_branch ?? $this->git_branch;
|
|
||||||
|
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (!$destination) {
|
if (!$destination) {
|
||||||
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
|
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
|
||||||
@@ -138,19 +146,34 @@ class PublicGitRepository extends Component
|
|||||||
$project = Project::where('uuid', $project_uuid)->first();
|
$project = Project::where('uuid', $project_uuid)->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
|
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
|
||||||
|
|
||||||
$application_init = [
|
if ($this->git_source === 'other') {
|
||||||
'name' => generate_application_name($this->git_repository, $this->git_branch),
|
$application_init = [
|
||||||
'git_repository' => $this->git_repository,
|
'name' => generate_random_name(),
|
||||||
'git_branch' => $this->git_branch,
|
'git_repository' => $this->git_repository,
|
||||||
'build_pack' => 'nixpacks',
|
'git_branch' => $this->git_branch,
|
||||||
'ports_exposes' => $this->port,
|
'build_pack' => 'nixpacks',
|
||||||
'publish_directory' => $this->publish_directory,
|
'ports_exposes' => $this->port,
|
||||||
'environment_id' => $environment->id,
|
'publish_directory' => $this->publish_directory,
|
||||||
'destination_id' => $destination->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_type' => $destination_class,
|
'destination_id' => $destination->id,
|
||||||
'source_id' => $this->git_source->id,
|
'destination_type' => $destination_class,
|
||||||
'source_type' => $this->git_source->getMorphClass()
|
];
|
||||||
];
|
} else {
|
||||||
|
$application_init = [
|
||||||
|
'name' => generate_application_name($this->git_repository, $this->git_branch),
|
||||||
|
'git_repository' => $this->git_repository,
|
||||||
|
'git_branch' => $this->git_branch,
|
||||||
|
'build_pack' => 'nixpacks',
|
||||||
|
'ports_exposes' => $this->port,
|
||||||
|
'publish_directory' => $this->publish_directory,
|
||||||
|
'environment_id' => $environment->id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination_class,
|
||||||
|
'source_id' => $this->git_source->id,
|
||||||
|
'source_type' => $this->git_source->getMorphClass()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$application = Application::create($application_init);
|
$application = Application::create($application_init);
|
||||||
|
|
||||||
@@ -159,7 +182,6 @@ class PublicGitRepository extends Component
|
|||||||
|
|
||||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||||
$application->fqdn = $fqdn;
|
$application->fqdn = $fqdn;
|
||||||
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
|
|
||||||
$application->save();
|
$application->save();
|
||||||
|
|
||||||
return redirect()->route('project.application.configuration', [
|
return redirect()->route('project.application.configuration', [
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\New;
|
namespace App\Http\Livewire\Project\New;
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Countable;
|
use Countable;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\File;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Select extends Component
|
class Select extends Component
|
||||||
@@ -24,7 +23,8 @@ class Select extends Component
|
|||||||
public Collection|array $services = [];
|
public Collection|array $services = [];
|
||||||
public bool $loadingServices = true;
|
public bool $loadingServices = true;
|
||||||
public bool $loading = false;
|
public bool $loading = false;
|
||||||
|
public $environments = [];
|
||||||
|
public ?string $selectedEnvironment = null;
|
||||||
public ?string $existingPostgresqlUrl = null;
|
public ?string $existingPostgresqlUrl = null;
|
||||||
|
|
||||||
protected $queryString = [
|
protected $queryString = [
|
||||||
@@ -37,8 +37,18 @@ class Select extends Component
|
|||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$this->existingPostgresqlUrl = 'postgres://coolify:password@coolify-db:5432';
|
$this->existingPostgresqlUrl = 'postgres://coolify:password@coolify-db:5432';
|
||||||
}
|
}
|
||||||
|
$projectUuid = data_get($this->parameters, 'project_uuid');
|
||||||
|
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
|
||||||
|
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatedSelectedEnvironment()
|
||||||
|
{
|
||||||
|
return redirect()->route('project.resources.new', [
|
||||||
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
|
'environment_name' => $this->selectedEnvironment,
|
||||||
|
]);
|
||||||
|
}
|
||||||
// public function addExistingPostgresql()
|
// public function addExistingPostgresql()
|
||||||
// {
|
// {
|
||||||
// try {
|
// try {
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ CMD ["nginx", "-g", "daemon off;"]
|
|||||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||||
|
|
||||||
$port = get_port_from_dockerfile($this->dockerfile);
|
$port = get_port_from_dockerfile($this->dockerfile);
|
||||||
|
if (!$port) {
|
||||||
|
$port = 80;
|
||||||
|
}
|
||||||
$application = Application::create([
|
$application = Application::create([
|
||||||
'name' => 'dockerfile-' . new Cuid2(7),
|
'name' => 'dockerfile-' . new Cuid2(7),
|
||||||
'repository_project_id' => 0,
|
'repository_project_id' => 0,
|
||||||
@@ -56,6 +59,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_id' => $destination->id,
|
'destination_id' => $destination->id,
|
||||||
'destination_type' => $destination_class,
|
'destination_type' => $destination_class,
|
||||||
|
'health_check_enabled' => false,
|
||||||
'source_id' => 0,
|
'source_id' => 0,
|
||||||
'source_type' => GithubApp::class
|
'source_type' => GithubApp::class
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -13,13 +13,7 @@ class Index extends Component
|
|||||||
public $databases;
|
public $databases;
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public array $query;
|
public array $query;
|
||||||
protected $rules = [
|
protected $listeners = ["refreshStacks","checkStatus"];
|
||||||
'service.docker_compose_raw' => 'required',
|
|
||||||
'service.docker_compose' => 'required',
|
|
||||||
'service.name' => 'required',
|
|
||||||
'service.description' => 'nullable',
|
|
||||||
];
|
|
||||||
protected $listeners = ["saveCompose"];
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.index');
|
return view('livewire.project.service.index');
|
||||||
@@ -29,19 +23,15 @@ class Index extends Component
|
|||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->query = request()->query();
|
$this->query = request()->query();
|
||||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||||
$this->refreshStack();
|
$this->applications = $this->service->applications->sort();
|
||||||
}
|
$this->databases = $this->service->databases->sort();
|
||||||
public function saveCompose($raw)
|
|
||||||
{
|
|
||||||
$this->service->docker_compose_raw = $raw;
|
|
||||||
$this->submit();
|
|
||||||
}
|
}
|
||||||
public function checkStatus()
|
public function checkStatus()
|
||||||
{
|
{
|
||||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||||
$this->refreshStack();
|
$this->refreshStacks();
|
||||||
}
|
}
|
||||||
public function refreshStack()
|
public function refreshStacks()
|
||||||
{
|
{
|
||||||
$this->applications = $this->service->applications->sort();
|
$this->applications = $this->service->applications->sort();
|
||||||
$this->applications->each(function ($application) {
|
$this->applications->each(function ($application) {
|
||||||
@@ -52,21 +42,4 @@ class Index extends Component
|
|||||||
$database->refresh();
|
$database->refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->validate();
|
|
||||||
$this->service->save();
|
|
||||||
$this->service->parse();
|
|
||||||
$this->service->refresh();
|
|
||||||
$this->service->saveComposeConfigs();
|
|
||||||
$this->refreshStack();
|
|
||||||
$this->emit('refreshEnvs');
|
|
||||||
$this->emit('success', 'Service saved successfully.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Modal extends Component
|
class Modal extends Component
|
||||||
{
|
{
|
||||||
public function serviceStatusUpdated() {
|
public function checkStatus() {
|
||||||
$this->emit('serviceStatusUpdated');
|
$this->emit('checkStatus');
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,21 +13,15 @@ class Navbar extends Component
|
|||||||
public Service $service;
|
public Service $service;
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public array $query;
|
public array $query;
|
||||||
protected $listeners = ['serviceStatusUpdated'];
|
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.navbar');
|
return view('livewire.project.service.navbar');
|
||||||
}
|
}
|
||||||
public function serviceStatusUpdated()
|
|
||||||
|
public function checkStatus()
|
||||||
{
|
{
|
||||||
ray('serviceStatusUpdated');
|
$this->emit('checkStatus');
|
||||||
$this->check_status();
|
|
||||||
}
|
|
||||||
public function check_status()
|
|
||||||
{
|
|
||||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
|
||||||
$this->service->refresh();
|
|
||||||
}
|
}
|
||||||
public function deploy()
|
public function deploy()
|
||||||
{
|
{
|
||||||
@@ -40,5 +34,6 @@ class Navbar extends Component
|
|||||||
StopService::run($this->service);
|
StopService::run($this->service);
|
||||||
$this->service->refresh();
|
$this->service->refresh();
|
||||||
$this->emit('success', 'Service stopped successfully.');
|
$this->emit('success', 'Service stopped successfully.');
|
||||||
|
$this->checkStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ 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();
|
||||||
}
|
}
|
||||||
if (is_null($service)) {
|
|
||||||
throw new \Exception("Service not found.");
|
|
||||||
}
|
|
||||||
} catch(\Throwable $e) {
|
} catch(\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
42
app/Http/Livewire/Project/Service/StackForm.php
Normal file
42
app/Http/Livewire/Project/Service/StackForm.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class StackForm extends Component
|
||||||
|
{
|
||||||
|
public $service;
|
||||||
|
protected $listeners = ["saveCompose"];
|
||||||
|
protected $rules = [
|
||||||
|
'service.docker_compose_raw' => 'required',
|
||||||
|
'service.docker_compose' => 'required',
|
||||||
|
'service.name' => 'required',
|
||||||
|
'service.description' => 'nullable',
|
||||||
|
];
|
||||||
|
public function saveCompose($raw)
|
||||||
|
{
|
||||||
|
$this->service->docker_compose_raw = $raw;
|
||||||
|
$this->submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->service->save();
|
||||||
|
$this->service->parse();
|
||||||
|
$this->service->refresh();
|
||||||
|
$this->service->saveComposeConfigs();
|
||||||
|
$this->emit('refreshStacks');
|
||||||
|
$this->emit('refreshEnvs');
|
||||||
|
$this->emit('success', 'Service saved successfully.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.service.stack-form');
|
||||||
|
}
|
||||||
|
}
|
||||||
35
app/Http/Livewire/Project/Service/Storage.php
Normal file
35
app/Http/Livewire/Project/Service/Storage.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Models\LocalPersistentVolume;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Storage extends Component
|
||||||
|
{
|
||||||
|
protected $listeners = ['addNewVolume'];
|
||||||
|
public $resource;
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.service.storage');
|
||||||
|
}
|
||||||
|
public function addNewVolume($data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => $data['name'],
|
||||||
|
'mount_path' => $data['mount_path'],
|
||||||
|
'host_path' => $data['host_path'],
|
||||||
|
'resource_id' => $this->resource->id,
|
||||||
|
'resource_type' => $this->resource->getMorphClass(),
|
||||||
|
]);
|
||||||
|
$this->resource->refresh();
|
||||||
|
$this->emit('success', 'Storage added successfully');
|
||||||
|
$this->emit('clearAddStorage');
|
||||||
|
$this->emit('refreshStorages');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Shared;
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
use App\Actions\Service\StopService;
|
use App\Jobs\StopResourceJob;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ class Danger extends Component
|
|||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public string|null $modalId = null;
|
public ?string $modalId = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
@@ -20,20 +20,8 @@ class Danger extends Component
|
|||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
// Should be queued
|
|
||||||
try {
|
try {
|
||||||
if ($this->resource->type() === 'service') {
|
StopResourceJob::dispatchSync($this->resource);
|
||||||
$server = $this->resource->server;
|
|
||||||
StopService::run($this->resource);
|
|
||||||
} else {
|
|
||||||
$destination = data_get($this->resource, 'destination');
|
|
||||||
if ($destination) {
|
|
||||||
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
|
|
||||||
$server = $destination->server;
|
|
||||||
}
|
|
||||||
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
|
|
||||||
}
|
|
||||||
$this->resource->delete();
|
|
||||||
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']
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ class All extends Component
|
|||||||
case 'standalone-postgresql':
|
case 'standalone-postgresql':
|
||||||
$environment->standalone_postgresql_id = $this->resource->id;
|
$environment->standalone_postgresql_id = $this->resource->id;
|
||||||
break;
|
break;
|
||||||
|
case 'standalone-redis':
|
||||||
|
$environment->standalone_redis_id = $this->resource->id;
|
||||||
|
break;
|
||||||
case 'service':
|
case 'service':
|
||||||
$environment->service_id = $this->resource->id;
|
$environment->service_id = $this->resource->id;
|
||||||
break;
|
break;
|
||||||
|
|||||||
40
app/Http/Livewire/Project/Shared/GetLogs.php
Normal file
40
app/Http/Livewire/Project/Shared/GetLogs.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class GetLogs extends Component
|
||||||
|
{
|
||||||
|
public string $outputs = '';
|
||||||
|
public string $errors = '';
|
||||||
|
public Server $server;
|
||||||
|
public ?string $container = null;
|
||||||
|
public ?bool $streamLogs = false;
|
||||||
|
public int $numberOfLines = 100;
|
||||||
|
public function doSomethingWithThisChunkOfOutput($output)
|
||||||
|
{
|
||||||
|
$this->outputs .= $output;
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function getLogs($refresh = false)
|
||||||
|
{
|
||||||
|
if ($this->container) {
|
||||||
|
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
||||||
|
if ($refresh) {
|
||||||
|
$this->outputs = '';
|
||||||
|
}
|
||||||
|
Process::run($sshCommand, function (string $type, string $output) {
|
||||||
|
$this->doSomethingWithThisChunkOfOutput($output);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.shared.get-logs');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ class HealthChecks extends Component
|
|||||||
|
|
||||||
public $resource;
|
public $resource;
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
|
'resource.health_check_enabled' => 'boolean',
|
||||||
'resource.health_check_path' => 'string',
|
'resource.health_check_path' => 'string',
|
||||||
'resource.health_check_port' => 'nullable|string',
|
'resource.health_check_port' => 'nullable|string',
|
||||||
'resource.health_check_host' => 'string',
|
'resource.health_check_host' => 'string',
|
||||||
@@ -22,12 +23,19 @@ class HealthChecks extends Component
|
|||||||
'resource.health_check_start_period' => 'integer',
|
'resource.health_check_start_period' => 'integer',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
$this->resource->save();
|
||||||
|
$this->emit('success', 'Health check updated.');
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->resource->save();
|
$this->resource->save();
|
||||||
$this->emit('saved');
|
$this->emit('success', 'Health check updated.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
61
app/Http/Livewire/Project/Shared/Logs.php
Normal file
61
app/Http/Livewire/Project/Shared/Logs.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Logs extends Component
|
||||||
|
{
|
||||||
|
public ?string $type = null;
|
||||||
|
public Application|StandalonePostgresql|Service|StandaloneRedis $resource;
|
||||||
|
public Server $server;
|
||||||
|
public ?string $container = null;
|
||||||
|
public $parameters;
|
||||||
|
public $query;
|
||||||
|
public $status;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
$this->query = request()->query();
|
||||||
|
if (data_get($this->parameters, 'application_uuid')) {
|
||||||
|
$this->type = 'application';
|
||||||
|
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
||||||
|
$this->status = $this->resource->status;
|
||||||
|
$this->server = $this->resource->destination->server;
|
||||||
|
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id);
|
||||||
|
if ($containers->count() > 0) {
|
||||||
|
$this->container = data_get($containers[0], 'Names');
|
||||||
|
}
|
||||||
|
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||||
|
$this->type = 'database';
|
||||||
|
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
|
if (is_null($resource)) {
|
||||||
|
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
|
if (is_null($resource)) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->resource = $resource;
|
||||||
|
$this->status = $this->resource->status;
|
||||||
|
$this->server = $this->resource->destination->server;
|
||||||
|
$this->container = $this->resource->uuid;
|
||||||
|
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||||
|
$this->type = 'service';
|
||||||
|
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||||
|
$this->status = $this->resource->status;
|
||||||
|
$this->server = $this->resource->server;
|
||||||
|
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.shared.logs');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Add extends Component
|
class Add extends Component
|
||||||
{
|
{
|
||||||
|
public $uuid;
|
||||||
public $parameters;
|
public $parameters;
|
||||||
public string $name;
|
public string $name;
|
||||||
public string $mount_path;
|
public string $mount_path;
|
||||||
@@ -31,8 +32,9 @@ class Add extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->emitUp('submit', [
|
$name = $this->uuid . '-' . $this->name;
|
||||||
'name' => $this->name,
|
$this->emit('addNewVolume', [
|
||||||
|
'name' => $name,
|
||||||
'mount_path' => $this->mount_path,
|
'mount_path' => $this->mount_path,
|
||||||
'host_path' => $this->host_path,
|
'host_path' => $this->host_path,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -8,28 +8,10 @@ use Livewire\Component;
|
|||||||
class All extends Component
|
class All extends Component
|
||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
protected $listeners = ['refreshStorages', 'submit'];
|
protected $listeners = ['refreshStorages'];
|
||||||
|
|
||||||
public function refreshStorages()
|
public function refreshStorages()
|
||||||
{
|
{
|
||||||
$this->resource->refresh();
|
$this->resource->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit($data)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
LocalPersistentVolume::create([
|
|
||||||
'name' => $data['name'],
|
|
||||||
'mount_path' => $data['mount_path'],
|
|
||||||
'host_path' => $data['host_path'],
|
|
||||||
'resource_id' => $this->resource->id,
|
|
||||||
'resource_type' => $this->resource->getMorphClass(),
|
|
||||||
]);
|
|
||||||
$this->resource->refresh();
|
|
||||||
$this->emit('success', 'Storage added successfully');
|
|
||||||
$this->emit('clearAddStorage');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class Show extends Component
|
|||||||
public LocalPersistentVolume $storage;
|
public LocalPersistentVolume $storage;
|
||||||
public bool $isReadOnly = false;
|
public bool $isReadOnly = false;
|
||||||
public ?string $modalId = null;
|
public ?string $modalId = null;
|
||||||
public ?string $realName = null;
|
public bool $isFirst = true;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'storage.name' => 'required|string',
|
'storage.name' => 'required|string',
|
||||||
@@ -26,11 +26,6 @@ class Show extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if ($this->storage->resource_type === 'App\Models\ServiceApplication' || $this->storage->resource_type === 'App\Models\ServiceDatabase') {
|
|
||||||
$this->realName = "{$this->storage->service->service->uuid}_{$this->storage->name}";
|
|
||||||
} else {
|
|
||||||
$this->realName = $this->storage->name;
|
|
||||||
}
|
|
||||||
$this->modalId = new Cuid2(7);
|
$this->modalId = new Cuid2(7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
app/Http/Livewire/Server/Create.php
Normal file
29
app/Http/Livewire/Server/Create.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Create extends Component
|
||||||
|
{
|
||||||
|
public $private_keys = [];
|
||||||
|
public bool $limit_reached = false;
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
|
||||||
|
if (!isCloud()) {
|
||||||
|
$this->limit_reached = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$team = currentTeam();
|
||||||
|
$servers = $team->servers->count();
|
||||||
|
['serverLimit' => $serverLimit] = $team->limits;
|
||||||
|
|
||||||
|
$this->limit_reached = $servers >= $serverLimit;
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.create');
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Http/Livewire/Server/Destination/Show.php
Normal file
28
app/Http/Livewire/Server/Destination/Show.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server\Destination;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public ?Server $server = null;
|
||||||
|
public $parameters = [];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
try {
|
||||||
|
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||||
|
if (is_null($this->server)) {
|
||||||
|
return redirect()->route('server.all');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.destination.show');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,11 +11,12 @@ class Form extends Component
|
|||||||
{
|
{
|
||||||
use AuthorizesRequests;
|
use AuthorizesRequests;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public $uptime;
|
public bool $isValidConnection = false;
|
||||||
public $dockerVersion;
|
public bool $isValidDocker = false;
|
||||||
public string|null $wildcard_domain = null;
|
public ?string $wildcard_domain = null;
|
||||||
public int $cleanup_after_percentage;
|
public int $cleanup_after_percentage;
|
||||||
public bool $dockerInstallationStarted = false;
|
public bool $dockerInstallationStarted = false;
|
||||||
|
protected $listeners = ['serverRefresh'];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'server.name' => 'required|min:6',
|
'server.name' => 'required|min:6',
|
||||||
@@ -29,11 +30,11 @@ class Form extends Component
|
|||||||
'wildcard_domain' => 'nullable|url',
|
'wildcard_domain' => 'nullable|url',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'server.name' => 'name',
|
'server.name' => 'Name',
|
||||||
'server.description' => 'description',
|
'server.description' => 'Description',
|
||||||
'server.ip' => 'ip',
|
'server.ip' => 'IP address',
|
||||||
'server.user' => 'user',
|
'server.user' => 'User',
|
||||||
'server.port' => 'port',
|
'server.port' => 'Port',
|
||||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||||
'server.settings.is_reachable' => 'is reachable',
|
'server.settings.is_reachable' => 'is reachable',
|
||||||
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
||||||
@@ -44,37 +45,62 @@ class Form extends Component
|
|||||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||||
}
|
}
|
||||||
public function instantSave() {
|
public function serverRefresh()
|
||||||
|
{
|
||||||
|
$this->validateServer();
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
refresh_server_connection($this->server->privateKey);
|
refresh_server_connection($this->server->privateKey);
|
||||||
$this->validateServer();
|
$this->validateServer();
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
}
|
}
|
||||||
public function installDocker()
|
public function installDocker()
|
||||||
{
|
{
|
||||||
|
$this->emit('installDocker');
|
||||||
$this->dockerInstallationStarted = true;
|
$this->dockerInstallationStarted = true;
|
||||||
$activity = resolve(InstallDocker::class)($this->server);
|
$activity = InstallDocker::run($this->server);
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
}
|
}
|
||||||
|
public function checkLocalhostConnection()
|
||||||
public function validateServer()
|
{
|
||||||
|
$uptime = $this->server->validateConnection();
|
||||||
|
if ($uptime) {
|
||||||
|
$this->emit('success', 'Server is reachable.');
|
||||||
|
$this->server->settings->is_reachable = true;
|
||||||
|
$this->server->settings->is_usable = true;
|
||||||
|
$this->server->settings->save();
|
||||||
|
} else {
|
||||||
|
$this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function validateServer($install = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
|
$uptime = $this->server->validateConnection();
|
||||||
if ($uptime) {
|
if ($uptime) {
|
||||||
$this->uptime = $uptime;
|
$install && $this->emit('success', 'Server is reachable.');
|
||||||
$this->emit('success', 'Server is reachable.');
|
|
||||||
} else {
|
} else {
|
||||||
$this->emit('error', 'Server is not reachable.');
|
$install && $this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($dockerVersion) {
|
$dockerInstalled = $this->server->validateDockerEngine();
|
||||||
$this->dockerVersion = $dockerVersion;
|
if ($dockerInstalled) {
|
||||||
$this->emit('success', 'Docker Engine 23+ is installed!');
|
$install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
|
||||||
} else {
|
} else {
|
||||||
$this->emit('error', 'No Docker Engine or older than 23 version installed.');
|
$install && $this->installDocker();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$dockerVersion = $this->server->validateDockerEngineVersion();
|
||||||
|
if ($dockerVersion) {
|
||||||
|
$install && $this->emit('success', 'Docker Engine version is 23+.');
|
||||||
|
} else {
|
||||||
|
$install && $this->installDocker();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
$this->emit('proxyStatusUpdated');
|
$this->emit('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
@@ -96,7 +122,14 @@ class Form extends Component
|
|||||||
}
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
if(isCloud() && !isDev()) {
|
||||||
|
$this->validate();
|
||||||
|
$this->validate([
|
||||||
|
'server.ip' => 'required|ip',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->validate();
|
||||||
|
}
|
||||||
$uniqueIPs = Server::all()->reject(function (Server $server) {
|
$uniqueIPs = Server::all()->reject(function (Server $server) {
|
||||||
return $server->id === $this->server->id;
|
return $server->id === $this->server->id;
|
||||||
})->pluck('ip')->toArray();
|
})->pluck('ip')->toArray();
|
||||||
@@ -104,6 +137,7 @@ class Form extends Component
|
|||||||
$this->emit('error', 'IP address is already in use by another team.');
|
$this->emit('error', 'IP address is already in use by another team.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
refresh_server_connection($this->server->privateKey);
|
||||||
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
||||||
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
|
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ class ByIp extends Component
|
|||||||
{
|
{
|
||||||
public $private_keys;
|
public $private_keys;
|
||||||
public $limit_reached;
|
public $limit_reached;
|
||||||
public int|null $private_key_id = null;
|
public ?int $private_key_id = null;
|
||||||
public $new_private_key_name;
|
public $new_private_key_name;
|
||||||
public $new_private_key_description;
|
public $new_private_key_description;
|
||||||
public $new_private_key_value;
|
public $new_private_key_value;
|
||||||
|
|
||||||
public string $name;
|
public string $name;
|
||||||
public string|null $description = null;
|
public ?string $description = null;
|
||||||
public string $ip;
|
public string $ip;
|
||||||
public string $user = 'root';
|
public string $user = 'root';
|
||||||
public int $port = 22;
|
public int $port = 22;
|
||||||
@@ -26,16 +26,16 @@ class ByIp extends Component
|
|||||||
protected $rules = [
|
protected $rules = [
|
||||||
'name' => 'required|string',
|
'name' => 'required|string',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
'ip' => 'required',
|
'ip' => 'required|ip',
|
||||||
'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',
|
'ip' => 'IP Address',
|
||||||
'user' => 'user',
|
'user' => 'User',
|
||||||
'port' => 'port',
|
'port' => 'Port',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
|
|||||||
31
app/Http/Livewire/Server/PrivateKey/Show.php
Normal file
31
app/Http/Livewire/Server/PrivateKey/Show.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server\PrivateKey;
|
||||||
|
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public ?Server $server = null;
|
||||||
|
public $privateKeys = [];
|
||||||
|
public $parameters = [];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
try {
|
||||||
|
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||||
|
if (is_null($this->server)) {
|
||||||
|
return redirect()->route('server.all');
|
||||||
|
}
|
||||||
|
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.private-key.show');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Server\Proxy;
|
namespace App\Http\Livewire\Server\Proxy;
|
||||||
|
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -11,18 +12,40 @@ class Deploy extends Component
|
|||||||
public Server $server;
|
public Server $server;
|
||||||
public bool $traefikDashboardAvailable = false;
|
public bool $traefikDashboardAvailable = false;
|
||||||
public ?string $currentRoute = null;
|
public ?string $currentRoute = null;
|
||||||
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable'];
|
public ?string $serverIp = null;
|
||||||
|
|
||||||
public function mount() {
|
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated', "checkProxy", "startProxy"];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
if ($this->server->id === 0) {
|
||||||
|
$this->serverIp = base_ip();
|
||||||
|
} else {
|
||||||
|
$this->serverIp = $this->server->ip;
|
||||||
|
}
|
||||||
$this->currentRoute = request()->route()->getName();
|
$this->currentRoute = request()->route()->getName();
|
||||||
}
|
}
|
||||||
public function traefikDashboardAvailable(bool $data) {
|
public function traefikDashboardAvailable(bool $data)
|
||||||
|
{
|
||||||
$this->traefikDashboardAvailable = $data;
|
$this->traefikDashboardAvailable = $data;
|
||||||
}
|
}
|
||||||
public function proxyStatusUpdated()
|
public function proxyStatusUpdated()
|
||||||
{
|
{
|
||||||
$this->server->refresh();
|
$this->server->refresh();
|
||||||
}
|
}
|
||||||
|
public function ip()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function checkProxy()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
CheckProxy::run($this->server, true);
|
||||||
|
$this->emit('startProxyPolling');
|
||||||
|
$this->emit('proxyChecked');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function startProxy()
|
public function startProxy()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
28
app/Http/Livewire/Server/Proxy/Logs.php
Normal file
28
app/Http/Livewire/Server/Proxy/Logs.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server\Proxy;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Logs extends Component
|
||||||
|
{
|
||||||
|
public ?Server $server = null;
|
||||||
|
public $parameters = [];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
try {
|
||||||
|
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||||
|
if (is_null($this->server)) {
|
||||||
|
return redirect()->route('server.all');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.proxy.logs');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,6 @@ class Modal extends Component
|
|||||||
|
|
||||||
public function proxyStatusUpdated()
|
public function proxyStatusUpdated()
|
||||||
{
|
{
|
||||||
$this->server->proxy->set('status', 'running');
|
|
||||||
$this->server->save();
|
|
||||||
$this->emit('proxyStatusUpdated');
|
$this->emit('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
app/Http/Livewire/Server/Proxy/Show.php
Normal file
33
app/Http/Livewire/Server/Proxy/Show.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server\Proxy;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public ?Server $server = null;
|
||||||
|
public $parameters = [];
|
||||||
|
protected $listeners = ['proxyStatusUpdated'];
|
||||||
|
public function proxyStatusUpdated()
|
||||||
|
{
|
||||||
|
$this->server->refresh();
|
||||||
|
}
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
try {
|
||||||
|
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||||
|
if (is_null($this->server)) {
|
||||||
|
return redirect()->route('server.all');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.proxy.show');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Server\Proxy;
|
namespace App\Http\Livewire\Server\Proxy;
|
||||||
|
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -9,12 +10,42 @@ use Livewire\Component;
|
|||||||
class Status extends Component
|
class Status extends Component
|
||||||
{
|
{
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
public bool $polling = false;
|
||||||
|
public int $numberOfPolls = 0;
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated'];
|
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||||
|
public function startProxyPolling()
|
||||||
|
{
|
||||||
|
$this->polling = true;
|
||||||
|
}
|
||||||
public function proxyStatusUpdated()
|
public function proxyStatusUpdated()
|
||||||
{
|
{
|
||||||
$this->server->refresh();
|
$this->server->refresh();
|
||||||
}
|
}
|
||||||
|
public function checkProxy(bool $notification = false)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->polling) {
|
||||||
|
if ($this->numberOfPolls >= 10) {
|
||||||
|
$this->polling = false;
|
||||||
|
$this->numberOfPolls = 0;
|
||||||
|
$notification && $this->emit('error', 'Proxy is not running.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->numberOfPolls++;
|
||||||
|
}
|
||||||
|
CheckProxy::run($this->server, true);
|
||||||
|
$this->emit('proxyStatusUpdated');
|
||||||
|
if ($this->server->proxy->status === 'running') {
|
||||||
|
$this->polling = false;
|
||||||
|
$notification && $this->emit('success', 'Proxy is running.');
|
||||||
|
} else {
|
||||||
|
$notification && $this->emit('error', 'Proxy is not running.');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function getProxyStatus()
|
public function getProxyStatus()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -24,9 +55,4 @@ class Status extends Component
|
|||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function getProxyStatusWithNoti()
|
|
||||||
{
|
|
||||||
$this->emit('success', 'Refreshed proxy status.');
|
|
||||||
$this->getProxyStatus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ class Show extends Component
|
|||||||
{
|
{
|
||||||
use AuthorizesRequests;
|
use AuthorizesRequests;
|
||||||
public ?Server $server = null;
|
public ?Server $server = null;
|
||||||
|
public $parameters = [];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
try {
|
try {
|
||||||
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||||
if (is_null($this->server)) {
|
if (is_null($this->server)) {
|
||||||
@@ -21,6 +23,10 @@ class Show extends Component
|
|||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
$this->emit('serverRefresh');
|
||||||
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.server.show');
|
return view('livewire.server.show');
|
||||||
|
|||||||
@@ -35,31 +35,13 @@ class ShowPrivateKey extends Component
|
|||||||
public function checkConnection()
|
public function checkConnection()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
|
$uptime = $this->server->validateConnection();
|
||||||
if ($uptime) {
|
if ($uptime) {
|
||||||
$this->server->settings->update([
|
$this->emit('success', 'Server is reachable.');
|
||||||
'is_reachable' => true
|
|
||||||
]);
|
|
||||||
$this->emit('success', 'Server is reachable with this private key.');
|
|
||||||
} else {
|
} else {
|
||||||
$this->server->settings->update([
|
$this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.');
|
||||||
'is_reachable' => false,
|
|
||||||
'is_usable' => false
|
|
||||||
]);
|
|
||||||
$this->emit('error', 'Server is not reachable with this private key.');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($dockerVersion) {
|
|
||||||
$this->server->settings->update([
|
|
||||||
'is_usable' => true
|
|
||||||
]);
|
|
||||||
$this->emit('success', 'Server is usable for Coolify.');
|
|
||||||
} else {
|
|
||||||
$this->server->settings->update([
|
|
||||||
'is_usable' => false
|
|
||||||
]);
|
|
||||||
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,10 +78,10 @@ class Backup extends Component
|
|||||||
dispatch(new DatabaseBackupJob(
|
dispatch(new DatabaseBackupJob(
|
||||||
backup: $this->backup
|
backup: $this->backup
|
||||||
));
|
));
|
||||||
$this->emit('success', 'Backup queued. It will be available in a few minutes');
|
$this->emit('success', 'Backup queued. It will be available in a few minutes.');
|
||||||
}
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->emit('success', 'Backup updated successfully');
|
$this->emit('success', 'Backup updated successfully.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
app/Http/Livewire/Subscription/Show.php
Normal file
30
app/Http/Livewire/Subscription/Show.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Subscription;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public InstanceSettings $settings;
|
||||||
|
public bool $alreadySubscribed = false;
|
||||||
|
public function mount() {
|
||||||
|
if (!isCloud()) {
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
$this->settings = InstanceSettings::get();
|
||||||
|
$this->alreadySubscribed = currentTeam()->subscription()->exists();
|
||||||
|
}
|
||||||
|
public function stripeCustomerPortal() {
|
||||||
|
$session = getStripeCustomerPortalSession(currentTeam());
|
||||||
|
if (is_null($session)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return redirect($session->url);
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.subscription.show')->layout('layouts.subscription');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ class Create extends Component
|
|||||||
}
|
}
|
||||||
$this->storage->team_id = currentTeam()->id;
|
$this->storage->team_id = currentTeam()->id;
|
||||||
$this->storage->testConnection();
|
$this->storage->testConnection();
|
||||||
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
$this->storage->is_usable = true;
|
||||||
$this->storage->save();
|
$this->storage->save();
|
||||||
return redirect()->route('team.storages.show', $this->storage->uuid);
|
return redirect()->route('team.storages.show', $this->storage->uuid);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class Form extends Component
|
|||||||
{
|
{
|
||||||
public S3Storage $storage;
|
public S3Storage $storage;
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
|
'storage.is_usable' => 'nullable|boolean',
|
||||||
'storage.name' => 'nullable|min:3|max:255',
|
'storage.name' => 'nullable|min:3|max:255',
|
||||||
'storage.description' => 'nullable|min:3|max:255',
|
'storage.description' => 'nullable|min:3|max:255',
|
||||||
'storage.region' => 'required|max:255',
|
'storage.region' => 'required|max:255',
|
||||||
@@ -18,6 +19,7 @@ class Form extends Component
|
|||||||
'storage.endpoint' => 'required|url|max:255',
|
'storage.endpoint' => 'required|url|max:255',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
|
'storage.is_usable' => 'Is Usable',
|
||||||
'storage.name' => 'Name',
|
'storage.name' => 'Name',
|
||||||
'storage.description' => 'Description',
|
'storage.description' => 'Description',
|
||||||
'storage.region' => 'Region',
|
'storage.region' => 'Region',
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ namespace App\Http\Livewire;
|
|||||||
use App\Actions\Server\UpdateCoolify;
|
use App\Actions\Server\UpdateCoolify;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Masmerise\Toaster\Toaster;
|
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||||
|
|
||||||
class Upgrade extends Component
|
class Upgrade extends Component
|
||||||
{
|
{
|
||||||
|
use WithRateLimiting;
|
||||||
public bool $showProgress = false;
|
public bool $showProgress = false;
|
||||||
public bool $isUpgradeAvailable = false;
|
public bool $isUpgradeAvailable = false;
|
||||||
public string $latestVersion = '';
|
public string $latestVersion = '';
|
||||||
@@ -31,11 +32,12 @@ class Upgrade extends Component
|
|||||||
public function upgrade()
|
public function upgrade()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->rateLimit(1, 30);
|
||||||
if ($this->showProgress) {
|
if ($this->showProgress) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->showProgress = true;
|
$this->showProgress = true;
|
||||||
resolve(UpdateCoolify::class)(true);
|
UpdateCoolify::run(true);
|
||||||
$this->emit('success', "Upgrading to {$this->latestVersion} version...");
|
$this->emit('success', "Upgrading to {$this->latestVersion} version...");
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
26
app/Http/Livewire/VerifyEmail.php
Normal file
26
app/Http/Livewire/VerifyEmail.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||||
|
|
||||||
|
class VerifyEmail extends Component
|
||||||
|
{
|
||||||
|
use WithRateLimiting;
|
||||||
|
public function again() {
|
||||||
|
try {
|
||||||
|
$this->rateLimit(1, 300);
|
||||||
|
auth()->user()->sendVerificationEmail();
|
||||||
|
$this->emit('success', 'Email verification link sent!');
|
||||||
|
|
||||||
|
} catch(\Exception $e) {
|
||||||
|
ray($e);
|
||||||
|
return handleError($e,$this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.verify-email');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,9 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
if (config('coolify.waitlist') == false) {
|
||||||
|
return redirect()->route('register');
|
||||||
|
}
|
||||||
$this->waitingInLine = Waitlist::whereVerified(true)->count();
|
$this->waitingInLine = Waitlist::whereVerified(true)->count();
|
||||||
$this->users = User::count();
|
$this->users = User::count();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
|||||||
48
app/Http/Middleware/DecideWhatToDoWithUser.php
Normal file
48
app/Http/Middleware/DecideWhatToDoWithUser.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class DecideWhatToDoWithUser
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||||
|
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||||
|
return redirect('boarding');
|
||||||
|
}
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
if (!auth()->user()->hasVerifiedEmail()) {
|
||||||
|
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
return redirect('/verify');
|
||||||
|
}
|
||||||
|
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||||
|
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||||
|
if (Str::startsWith($request->path(), 'invitations')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
return redirect('subscription');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||||
|
if (Str::startsWith($request->path(), 'invitations')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
return redirect('boarding');
|
||||||
|
}
|
||||||
|
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
if (isSubscriptionActive() && $request->path() === 'subscription') {
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,16 +45,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private string $commit;
|
private string $commit;
|
||||||
private bool $force_rebuild;
|
private bool $force_rebuild;
|
||||||
|
|
||||||
private GithubApp|GitlabApp $source;
|
private ?string $dockerImage = null;
|
||||||
|
private ?string $dockerImageTag = null;
|
||||||
|
|
||||||
|
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|null $preview = null;
|
||||||
|
|
||||||
private string $container_name;
|
private string $container_name;
|
||||||
private string|null $currently_running_container_name = null;
|
private ?string $currently_running_container_name = null;
|
||||||
|
private string $basedir;
|
||||||
private string $workdir;
|
private string $workdir;
|
||||||
|
private ?string $build_pack = null;
|
||||||
private string $configuration_dir;
|
private string $configuration_dir;
|
||||||
private string $build_workdir;
|
|
||||||
private string $build_image_name;
|
private string $build_image_name;
|
||||||
private string $production_image_name;
|
private string $production_image_name;
|
||||||
private bool $is_debug_enabled;
|
private bool $is_debug_enabled;
|
||||||
@@ -62,17 +66,23 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private $env_args;
|
private $env_args;
|
||||||
private $docker_compose;
|
private $docker_compose;
|
||||||
private $docker_compose_base64;
|
private $docker_compose_base64;
|
||||||
|
private string $dockerfile_location = '/Dockerfile';
|
||||||
|
private ?string $addHosts = null;
|
||||||
private $log_model;
|
private $log_model;
|
||||||
private Collection $saved_outputs;
|
private Collection $saved_outputs;
|
||||||
|
|
||||||
|
private string $serverUser = 'root';
|
||||||
|
private string $serverUserHomeDir = '/root';
|
||||||
|
private string $dockerConfigFileExists = 'NOK';
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
{
|
{
|
||||||
ray()->clearScreen();
|
// ray()->clearScreen();
|
||||||
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
|
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
|
||||||
$this->log_model = $this->application_deployment_queue;
|
$this->log_model = $this->application_deployment_queue;
|
||||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
$this->application = Application::find($this->application_deployment_queue->application_id);
|
||||||
|
$this->build_pack = data_get($this->application, 'build_pack');
|
||||||
|
|
||||||
$this->application_deployment_queue_id = $application_deployment_queue_id;
|
$this->application_deployment_queue_id = $application_deployment_queue_id;
|
||||||
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
|
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
|
||||||
@@ -80,16 +90,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$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->source = $this->application->source->getMorphClass()::where('id', $this->application->source->id)->first();
|
$source = data_get($this->application, 'source');
|
||||||
|
if ($source) {
|
||||||
|
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
|
||||||
|
}
|
||||||
$this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
|
$this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
|
||||||
$this->server = $this->destination->server;
|
$this->server = $this->destination->server;
|
||||||
|
$this->serverUser = $this->server->user;
|
||||||
$this->workdir = "/artifacts/{$this->deployment_uuid}";
|
$this->basedir = "/artifacts/{$this->deployment_uuid}";
|
||||||
|
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
|
||||||
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
|
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
|
||||||
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
|
|
||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
|
|
||||||
$this->container_name = generateApplicationContainerName($this->application);
|
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||||
savePrivateKeyToFs($this->server);
|
savePrivateKeyToFs($this->server);
|
||||||
$this->saved_outputs = collect();
|
$this->saved_outputs = collect();
|
||||||
|
|
||||||
@@ -97,7 +110,9 @@ 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) {
|
||||||
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, '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);
|
$url = Url::fromString($this->application->fqdn);
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
@@ -126,20 +141,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Generate custom host<->ip mapping
|
||||||
|
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
||||||
|
$allContainers = format_docker_command_output_to_json($allContainers);
|
||||||
|
$ips = collect([]);
|
||||||
|
if (count($allContainers) > 0) {
|
||||||
|
$allContainers = $allContainers[0];
|
||||||
|
foreach ($allContainers as $container) {
|
||||||
|
$containerName = data_get($container, 'Name');
|
||||||
|
if ($containerName === 'coolify-proxy') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$containerIp = data_get($container, 'IPv4Address');
|
||||||
|
if ($containerName && $containerIp) {
|
||||||
|
$containerIp = str($containerIp)->before('/');
|
||||||
|
$ips->put($containerName, $containerIp->value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->addHosts = $ips->map(function ($ip, $name) {
|
||||||
|
return "--add-host $name:$ip";
|
||||||
|
})->implode(' ');
|
||||||
|
|
||||||
|
// Get user home directory
|
||||||
|
$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);
|
||||||
try {
|
try {
|
||||||
if ($this->application->dockerfile) {
|
if ($this->application->dockerfile) {
|
||||||
$this->deploy_simple_dockerfile();
|
$this->deploy_simple_dockerfile();
|
||||||
|
} else if ($this->application->build_pack === 'dockerimage') {
|
||||||
|
$this->deploy_dockerimage_buildpack();
|
||||||
|
} else if ($this->application->build_pack === 'dockerfile') {
|
||||||
|
$this->deploy_dockerfile_buildpack();
|
||||||
} else {
|
} else {
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$this->deploy_pull_request();
|
$this->deploy_pull_request();
|
||||||
} else {
|
} else {
|
||||||
$this->deploy();
|
$this->deploy_nixpacks_buildpack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->server->isProxyShouldRun()) {
|
if ($this->server->isProxyShouldRun()) {
|
||||||
dispatch(new ContainerStatusJob($this->server));
|
dispatch(new ContainerStatusJob($this->server));
|
||||||
}
|
}
|
||||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||||
|
$this->application->isConfigurationChanged(true);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
$this->fail($e);
|
$this->fail($e);
|
||||||
@@ -167,41 +213,43 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function deploy_docker_compose()
|
|
||||||
{
|
|
||||||
$dockercompose_base64 = base64_encode($this->application->dockercompose);
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[
|
|
||||||
"echo 'Starting deployment of {$this->application->name}.'"
|
|
||||||
],
|
|
||||||
);
|
|
||||||
$this->prepare_builder_image();
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[
|
|
||||||
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->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
|
||||||
$this->save_environment_variables();
|
|
||||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
|
||||||
if ($containers->count() > 0) {
|
|
||||||
foreach ($containers as $container) {
|
|
||||||
$containerName = data_get($container, 'Names');
|
|
||||||
if ($containerName) {
|
|
||||||
instant_remote_process(
|
|
||||||
["docker rm -f {$containerName}"],
|
|
||||||
$this->application->destination->server
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->execute_remote_command(
|
// private function deploy_docker_compose()
|
||||||
["echo -n 'Starting services (could take a while)...'"],
|
// {
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
// $dockercompose_base64 = base64_encode($this->application->dockercompose);
|
||||||
);
|
// $this->execute_remote_command(
|
||||||
}
|
// [
|
||||||
|
// "echo 'Starting deployment of {$this->application->name}.'"
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// $this->prepare_builder_image();
|
||||||
|
// $this->execute_remote_command(
|
||||||
|
// [
|
||||||
|
// 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->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||||
|
// $this->save_environment_variables();
|
||||||
|
// $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||||
|
// ray($containers);
|
||||||
|
// if ($containers->count() > 0) {
|
||||||
|
// foreach ($containers as $container) {
|
||||||
|
// $containerName = data_get($container, 'Names');
|
||||||
|
// if ($containerName) {
|
||||||
|
// instant_remote_process(
|
||||||
|
// ["docker rm -f {$containerName}"],
|
||||||
|
// $this->application->destination->server
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $this->execute_remote_command(
|
||||||
|
// ["echo -n 'Starting services (could take a while)...'"],
|
||||||
|
// [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||||
|
// );
|
||||||
|
// }
|
||||||
private function save_environment_variables()
|
private function save_environment_variables()
|
||||||
{
|
{
|
||||||
$envs = collect([]);
|
$envs = collect([]);
|
||||||
@@ -231,7 +279,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
);
|
);
|
||||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
$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();
|
// 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();
|
||||||
@@ -239,7 +287,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deploy()
|
private function deploy_dockerimage_buildpack()
|
||||||
|
{
|
||||||
|
$this->dockerImage = $this->application->docker_registry_image_name;
|
||||||
|
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||||
|
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'");
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
||||||
|
$this->prepare_builder_image();
|
||||||
|
$this->generate_compose_file();
|
||||||
|
$this->rolling_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deploy_dockerfile_buildpack()
|
||||||
|
{
|
||||||
|
if (data_get($this->application, 'dockerfile_location')) {
|
||||||
|
$this->dockerfile_location = $this->application->dockerfile_location;
|
||||||
|
}
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$this->prepare_builder_image();
|
||||||
|
$this->clone_repository();
|
||||||
|
$this->set_base_dir();
|
||||||
|
$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->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->generate_compose_file();
|
||||||
|
$this->generate_build_env_variables();
|
||||||
|
$this->add_build_env_variables_to_dockerfile();
|
||||||
|
$this->build_image();
|
||||||
|
$this->rolling_update();
|
||||||
|
}
|
||||||
|
private function deploy_nixpacks_buildpack()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
@@ -248,7 +340,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
);
|
);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
|
$this->set_base_dir();
|
||||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
||||||
if (strlen($tag) > 128) {
|
if (strlen($tag) > 128) {
|
||||||
$tag = $tag->substr(0, 128);
|
$tag = $tag->substr(0, 128);
|
||||||
@@ -256,25 +348,28 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
$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();
|
// 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"
|
||||||
]);
|
]);
|
||||||
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo 'Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped...'"
|
"echo 'No configuration changed & Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped.'",
|
||||||
]);
|
]);
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($this->application->isConfigurationChanged()) {
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"echo 'Configuration changed. Rebuilding image.'",
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
if ($this->application->build_pack === 'nixpacks') {
|
$this->generate_nixpacks_confs();
|
||||||
$this->generate_nixpacks_confs();
|
|
||||||
}
|
|
||||||
$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();
|
||||||
@@ -284,7 +379,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
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. Stopping current container.'"],
|
||||||
);
|
);
|
||||||
@@ -301,7 +396,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
private function health_check()
|
private function health_check()
|
||||||
{
|
{
|
||||||
ray('New container name: ', $this->container_name);
|
if ($this->application->isHealthcheckDisabled()) {
|
||||||
|
$this->newVersionIsHealthy = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ray('New container name: ', $this->container_name);
|
||||||
if ($this->container_name) {
|
if ($this->container_name) {
|
||||||
$counter = 0;
|
$counter = 0;
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
@@ -329,9 +428,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
||||||
$this->newVersionIsHealthy = true;
|
$this->newVersionIsHealthy = true;
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
|
||||||
"echo 'New version of your application is healthy.'"
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"echo 'Rolling update completed.'"
|
"echo 'Rolling update completed.'"
|
||||||
],
|
],
|
||||||
@@ -348,12 +444,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
$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}");
|
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
// 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->application->git_repository}:{$this->application->git_branch}.'",
|
||||||
]);
|
]);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
|
$this->set_base_dir();
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
if ($this->application->build_pack === 'nixpacks') {
|
if ($this->application->build_pack === 'nixpacks') {
|
||||||
$this->generate_nixpacks_confs();
|
$this->generate_nixpacks_confs();
|
||||||
@@ -366,18 +463,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->stop_running_container();
|
$this->stop_running_container();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'Starting preview deployment.'"],
|
["echo -n 'Starting preview deployment.'"],
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function prepare_builder_image()
|
private function prepare_builder_image()
|
||||||
{
|
{
|
||||||
$pull = "--pull=always";
|
$pull = "--pull=always";
|
||||||
if (isDev()) {
|
|
||||||
$pull = "--pull=never";
|
|
||||||
}
|
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('coolify.helper_image');
|
||||||
$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}";
|
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}";
|
||||||
|
} 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}";
|
||||||
|
}
|
||||||
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
@@ -388,24 +486,31 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
|
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function set_base_dir()
|
||||||
|
{
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo -n 'Setting base directory to {$this->workdir}.'"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private function clone_repository()
|
private function clone_repository()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '"
|
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
$this->importing_git_repository()
|
$this->importing_git_repository(), "hidden" => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git rev-parse HEAD"),
|
executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git rev-parse HEAD"),
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
"save" => "git_commit_sha"
|
"save" => "git_commit_sha"
|
||||||
],
|
],
|
||||||
@@ -429,23 +534,28 @@ 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->workdir}";
|
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$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->workdir}"));
|
$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}"));
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
|
$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"));
|
||||||
}
|
}
|
||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->application->deploymentType() === 'deploy_key') {
|
if ($this->application->deploymentType() === 'deploy_key') {
|
||||||
|
$port = 22;
|
||||||
|
preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches);
|
||||||
|
if (count($matches) === 1) {
|
||||||
|
$port = $matches[0];
|
||||||
|
}
|
||||||
$private_key = base64_encode($this->application->private_key->private_key);
|
$private_key = base64_encode($this->application->private_key->private_key);
|
||||||
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}";
|
$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 = $this->set_git_import_settings($git_clone_command);
|
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
||||||
@@ -455,18 +565,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
]);
|
]);
|
||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
|
if ($this->application->deploymentType() === 'other') {
|
||||||
|
$git_clone_command = "{$git_clone_command} {$this->application->git_repository} {$this->basedir}";
|
||||||
|
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||||
|
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
||||||
|
ray($commands);
|
||||||
|
return $commands->implode(' && ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function set_git_import_settings($git_clone_command)
|
private function set_git_import_settings($git_clone_command)
|
||||||
{
|
{
|
||||||
if ($this->application->git_commit_sha !== 'HEAD') {
|
if ($this->application->git_commit_sha !== 'HEAD') {
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1";
|
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1";
|
||||||
}
|
}
|
||||||
if ($this->application->settings->is_git_submodules_enabled) {
|
if ($this->application->settings->is_git_submodules_enabled) {
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git submodule update --init --recursive";
|
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git submodule update --init --recursive";
|
||||||
}
|
}
|
||||||
if ($this->application->settings->is_git_lfs_enabled) {
|
if ($this->application->settings->is_git_lfs_enabled) {
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git lfs pull";
|
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git lfs pull";
|
||||||
}
|
}
|
||||||
return $git_clone_command;
|
return $git_clone_command;
|
||||||
}
|
}
|
||||||
@@ -474,17 +591,24 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private function cleanup_git()
|
private function cleanup_git()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "rm -fr {$this->workdir}/.git")],
|
[executeInDocker($this->deployment_uuid, "rm -fr {$this->basedir}/.git")],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_nixpacks_confs()
|
private function generate_nixpacks_confs()
|
||||||
{
|
{
|
||||||
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n 'Generating nixpacks configuration.'",
|
"echo -n 'Generating nixpacks configuration.'",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$nixpacks_command = $this->nixpacks_build_cmd();
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo -n Running: $nixpacks_command",
|
||||||
],
|
],
|
||||||
[$this->nixpacks_build_cmd()],
|
[executeInDocker($this->deployment_uuid, $nixpacks_command)],
|
||||||
[executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
|
[executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
|
||||||
[executeInDocker($this->deployment_uuid, "rm -f {$this->workdir}/.nixpacks/Dockerfile")]
|
[executeInDocker($this->deployment_uuid, "rm -f {$this->workdir}/.nixpacks/Dockerfile")]
|
||||||
);
|
);
|
||||||
@@ -493,7 +617,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 -o {$this->workdir} {$this->env_args} --no-error-without-start";
|
$nixpacks_command = "nixpacks build --no-cache -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}\"";
|
||||||
}
|
}
|
||||||
@@ -504,7 +628,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
|
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
|
||||||
}
|
}
|
||||||
$nixpacks_command .= " {$this->workdir}";
|
$nixpacks_command .= " {$this->workdir}";
|
||||||
return executeInDocker($this->deployment_uuid, $nixpacks_command);
|
return $nixpacks_command;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_env_variables()
|
private function generate_env_variables()
|
||||||
@@ -531,6 +655,10 @@ 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')) {
|
||||||
|
$labels = str($this->application->custom_labels)->explode(',')->toArray();
|
||||||
|
}
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
'version' => '3.8',
|
||||||
'services' => [
|
'services' => [
|
||||||
@@ -539,7 +667,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
'container_name' => $this->container_name,
|
'container_name' => $this->container_name,
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'environment' => $environment_variables,
|
'environment' => $environment_variables,
|
||||||
'labels' => generateLabelsApplication($this->application, $this->preview),
|
'labels' => $labels,
|
||||||
'expose' => $ports,
|
'expose' => $ports,
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->destination->network,
|
$this->destination->network,
|
||||||
@@ -571,6 +699,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
if ($this->application->isHealthcheckDisabled()) {
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck');
|
||||||
|
}
|
||||||
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
||||||
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
|
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
|
||||||
}
|
}
|
||||||
@@ -580,6 +711,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
|
// if ($this->build_pack === 'dockerfile') {
|
||||||
|
// $docker_compose['services'][$this->container_name]['build'] = [
|
||||||
|
// 'context' => $this->workdir,
|
||||||
|
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
||||||
|
// ];
|
||||||
|
// }
|
||||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
||||||
@@ -622,14 +759,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private function generate_environment_variables($ports)
|
private function generate_environment_variables($ports)
|
||||||
{
|
{
|
||||||
$environment_variables = collect();
|
$environment_variables = collect();
|
||||||
ray('Generate Environment Variables')->green();
|
// ray('Generate Environment Variables')->green();
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
ray($this->application->runtime_environment_variables)->green();
|
// ray($this->application->runtime_environment_variables)->green();
|
||||||
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");
|
||||||
}
|
}
|
||||||
} 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");
|
||||||
}
|
}
|
||||||
@@ -643,7 +780,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function generate_healthcheck_commands()
|
private function generate_healthcheck_commands()
|
||||||
{
|
{
|
||||||
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
|
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
||||||
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
||||||
return 'exit 0';
|
return 'exit 0';
|
||||||
}
|
}
|
||||||
@@ -667,12 +804,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private function build_image()
|
private function build_image()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo -n 'Building docker image for your application.'",
|
"echo -n 'Building docker image for your application. To check the current progress, click on Show Debug Logs.'",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($this->application->settings->is_static) {
|
if ($this->application->settings->is_static) {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
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
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||||
@@ -705,12 +842,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
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 --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-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$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}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -736,7 +873,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
{
|
{
|
||||||
$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 -d >/dev/null"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,7 +896,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}/Dockerfile"), "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"));
|
||||||
|
|
||||||
@@ -768,7 +905,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}/Dockerfile"),
|
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/{$this->dockerfile_location}"),
|
||||||
"hidden" => true
|
"hidden" => true
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function update_comment()
|
private function update_comment()
|
||||||
{
|
{
|
||||||
['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [
|
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [
|
||||||
'body' => $this->body,
|
'body' => $this->body,
|
||||||
], throwError: false);
|
], throwError: false);
|
||||||
if (data_get($data, 'message') === 'Not Found') {
|
if (data_get($data, 'message') === 'Not Found') {
|
||||||
@@ -77,7 +77,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function create_comment()
|
private function create_comment()
|
||||||
{
|
{
|
||||||
['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
|
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
|
||||||
'body' => $this->body,
|
'body' => $this->body,
|
||||||
]);
|
]);
|
||||||
$this->preview->pull_request_issue_comment_id = $data['id'];
|
$this->preview->pull_request_issue_comment_id = $data['id'];
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Container\ContainerRestarted;
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
use App\Notifications\Container\ContainerStopped;
|
use App\Notifications\Container\ContainerStopped;
|
||||||
|
use App\Notifications\Server\Revived;
|
||||||
use App\Notifications\Server\Unreachable;
|
use App\Notifications\Server\Unreachable;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
@@ -26,11 +28,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
public $timeout = 120;
|
public $timeout = 120;
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
|
||||||
{
|
|
||||||
$this->handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
@@ -41,32 +38,70 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return $this->server->uuid;
|
return $this->server->uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkServerConnection()
|
public function __construct(public Server $server)
|
||||||
{
|
{
|
||||||
$uptime = instant_remote_process(['uptime'], $this->server, false);
|
if (isDev()) {
|
||||||
if (!is_null($uptime)) {
|
$this->handle();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function handle(): void
|
|
||||||
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
// ray("checking server status for {$this->server->id}");
|
||||||
// ray()->clearAll();
|
// ray()->clearAll();
|
||||||
$serverUptimeCheckNumber = 0;
|
$serverUptimeCheckNumber = $this->server->unreachable_count;
|
||||||
$serverUptimeCheckNumberMax = 3;
|
$serverUptimeCheckNumberMax = 3;
|
||||||
while (true) {
|
|
||||||
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
// ray('checking # ' . $serverUptimeCheckNumber);
|
||||||
$this->server->settings()->update(['is_reachable' => false]);
|
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
||||||
|
if ($this->server->unreachable_email_sent === false) {
|
||||||
|
ray('Server unreachable, sending notification...');
|
||||||
$this->server->team->notify(new Unreachable($this->server));
|
$this->server->team->notify(new Unreachable($this->server));
|
||||||
return;
|
$this->server->update(['unreachable_email_sent' => true]);
|
||||||
}
|
}
|
||||||
$result = $this->checkServerConnection();
|
$this->server->settings()->update([
|
||||||
if ($result) {
|
'is_reachable' => false,
|
||||||
break;
|
]);
|
||||||
}
|
$this->server->update([
|
||||||
$serverUptimeCheckNumber++;
|
'unreachable_count' => 0,
|
||||||
sleep(5);
|
]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
$result = $this->server->validateConnection();
|
||||||
|
if ($result) {
|
||||||
|
$this->server->settings()->update([
|
||||||
|
'is_reachable' => true,
|
||||||
|
]);
|
||||||
|
$this->server->update([
|
||||||
|
'unreachable_count' => 0,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$serverUptimeCheckNumber++;
|
||||||
|
$this->server->settings()->update([
|
||||||
|
'is_reachable' => false,
|
||||||
|
]);
|
||||||
|
$this->server->update([
|
||||||
|
'unreachable_count' => $serverUptimeCheckNumber,
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_get($this->server, 'unreachable_email_sent') === true) {
|
||||||
|
ray('Server is reachable again, sending notification...');
|
||||||
|
$this->server->team->notify(new Revived($this->server));
|
||||||
|
$this->server->update(['unreachable_email_sent' => false]);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
data_get($this->server, 'settings.is_reachable') === false ||
|
||||||
|
data_get($this->server, 'settings.is_usable') === false
|
||||||
|
) {
|
||||||
|
$this->server->settings()->update([
|
||||||
|
'is_reachable' => true,
|
||||||
|
'is_usable' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// $this->server->validateDockerEngine(true);
|
||||||
$containers = instant_remote_process(["docker container ls -q"], $this->server);
|
$containers = instant_remote_process(["docker container ls -q"], $this->server);
|
||||||
if (!$containers) {
|
if (!$containers) {
|
||||||
return;
|
return;
|
||||||
@@ -83,11 +118,18 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return data_get($value, 'Name') === '/coolify-proxy';
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
})->first();
|
})->first();
|
||||||
if (!$foundProxyContainer) {
|
if (!$foundProxyContainer) {
|
||||||
ray('Proxy not found, starting it...');
|
try {
|
||||||
if ($this->server->isProxyShouldRun()) {
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
StartProxy::run($this->server, false);
|
if ($shouldStart) {
|
||||||
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
} else {
|
||||||
|
ray('Proxy could not be started.');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
@@ -108,9 +150,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$labelId = data_get($labels, 'coolify.applicationId');
|
$labelId = data_get($labels, 'coolify.applicationId');
|
||||||
if ($labelId) {
|
if ($labelId) {
|
||||||
if (str_contains($labelId, '-pr-')) {
|
if (str_contains($labelId, '-pr-')) {
|
||||||
$previewId = (int) Str::after($labelId, '-pr-');
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
$applicationId = (int) Str::before($labelId, '-pr-');
|
$applicationId = (int) Str::before($labelId, '-pr-');
|
||||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $previewId)->first();
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
if ($preview) {
|
if ($preview) {
|
||||||
$foundApplicationPreviews[] = $preview->id;
|
$foundApplicationPreviews[] = $preview->id;
|
||||||
$statusFromDb = $preview->status;
|
$statusFromDb = $preview->status;
|
||||||
@@ -266,7 +308,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
} 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());
|
||||||
throw $e;
|
return handleError($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
public ?string $container_name = null;
|
public ?string $container_name = null;
|
||||||
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
||||||
public string $backup_status;
|
public string $backup_status = 'failed';
|
||||||
public ?string $backup_location = null;
|
public ?string $backup_location = null;
|
||||||
public string $backup_dir;
|
public string $backup_dir;
|
||||||
public string $backup_file;
|
public string $backup_file;
|
||||||
@@ -61,70 +61,92 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (data_get($this->database, 'status') !== 'running') {
|
$status = Str::of(data_get($this->database, 'status'));
|
||||||
|
if (!$status->startsWith('running') && $this->database->id !== 0) {
|
||||||
ray('database not running');
|
ray('database not running');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$databaseType = $this->database->type();
|
||||||
|
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
||||||
|
|
||||||
|
if (is_null($databasesToBackup)) {
|
||||||
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
|
$databasesToBackup = [$this->database->postgres_db];
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
}
|
||||||
$this->container_name = $this->database->uuid;
|
$this->container_name = $this->database->uuid;
|
||||||
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->container_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'];
|
||||||
$this->container_name = "coolify-db";
|
$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";
|
||||||
}
|
}
|
||||||
$this->backup_file = "/pg_dump-" . Carbon::now()->timestamp . ".dump";
|
foreach ($databasesToBackup as $database) {
|
||||||
$this->backup_location = $this->backup_dir . $this->backup_file;
|
$size = 0;
|
||||||
|
ray('Backing up ' . $database);
|
||||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
try {
|
||||||
'filename' => $this->backup_location,
|
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
||||||
'scheduled_database_backup_id' => $this->backup->id,
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
]);
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
'database_name' => $database,
|
||||||
$this->backup_standalone_postgresql();
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
|
$this->backup_standalone_postgresql($database);
|
||||||
|
}
|
||||||
|
$size = $this->calculate_size();
|
||||||
|
$this->remove_old_backups();
|
||||||
|
if ($this->backup->save_s3) {
|
||||||
|
$this->upload_to_s3();
|
||||||
|
}
|
||||||
|
$this->team->notify(new BackupSuccess($this->backup, $this->database));
|
||||||
|
$this->backup_log->update([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $this->backup_output,
|
||||||
|
'size' => $size,
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->backup_log->update([
|
||||||
|
'status' => 'failed',
|
||||||
|
'message' => $this->backup_output,
|
||||||
|
'size' => $size,
|
||||||
|
'filename' => null
|
||||||
|
]);
|
||||||
|
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||||
|
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->calculate_size();
|
|
||||||
$this->remove_old_backups();
|
|
||||||
if ($this->backup->save_s3) {
|
|
||||||
$this->upload_to_s3();
|
|
||||||
}
|
|
||||||
$this->save_backup_logs();
|
|
||||||
// TODO: Notify user
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e->getMessage());
|
|
||||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function backup_standalone_postgresql(): void
|
private function backup_standalone_postgresql(string $database): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ray($this->backup_dir);
|
ray($this->backup_dir);
|
||||||
$commands[] = "mkdir -p " . $this->backup_dir;
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
$commands[] = "docker exec $this->container_name pg_dump -Fc -U {$this->database->postgres_user} > $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);
|
||||||
|
|
||||||
$this->backup_output = trim($this->backup_output);
|
$this->backup_output = trim($this->backup_output);
|
||||||
|
|
||||||
if ($this->backup_output === '') {
|
if ($this->backup_output === '') {
|
||||||
$this->backup_output = null;
|
$this->backup_output = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
|
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
|
||||||
|
|
||||||
$this->backup_status = 'success';
|
|
||||||
$this->team->notify(new BackupSuccess($this->backup, $this->database));
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->backup_status = 'failed';
|
|
||||||
$this->add_to_backup_output($e->getMessage());
|
$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());
|
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
throw $e;
|
||||||
} finally {
|
|
||||||
$this->backup_log->update([
|
|
||||||
'status' => $this->backup_status,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,9 +159,9 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calculate_size(): void
|
private function calculate_size()
|
||||||
{
|
{
|
||||||
$this->size = instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server);
|
return instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function remove_old_backups(): void
|
private function remove_old_backups(): void
|
||||||
@@ -163,11 +185,16 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
$key = $this->s3->key;
|
$key = $this->s3->key;
|
||||||
$secret = $this->s3->secret;
|
$secret = $this->s3->secret;
|
||||||
// $region = $this->s3->region;
|
// $region = $this->s3->region;
|
||||||
$bucket = $this->s3->bucket;
|
$bucket = $this->s3->bucket;
|
||||||
$endpoint = $this->s3->endpoint;
|
$endpoint = $this->s3->endpoint;
|
||||||
|
$this->s3->testConnection();
|
||||||
|
if (isDev()) {
|
||||||
|
$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";
|
||||||
|
} 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";
|
||||||
|
}
|
||||||
|
|
||||||
$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";
|
|
||||||
$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);
|
||||||
@@ -175,19 +202,10 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir);
|
ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->add_to_backup_output($e->getMessage());
|
$this->add_to_backup_output($e->getMessage());
|
||||||
ray($e->getMessage());
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
$command = "docker rm -f backup-of-{$this->backup->uuid}";
|
$command = "docker rm -f backup-of-{$this->backup->uuid}";
|
||||||
instant_remote_process([$command], $this->server);
|
instant_remote_process([$command], $this->server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function save_backup_logs(): void
|
|
||||||
{
|
|
||||||
$this->backup_log->update([
|
|
||||||
'status' => $this->backup_status,
|
|
||||||
'message' => $this->backup_output,
|
|
||||||
'size' => $this->size,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,20 +47,7 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (!$this->server->isFunctional()) {
|
if (!$this->server->isFunctional()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDev()) {
|
$this->dockerRootFilesystem = "/";
|
||||||
$this->dockerRootFilesystem = "/";
|
|
||||||
} else {
|
|
||||||
$this->dockerRootFilesystem = instant_remote_process(
|
|
||||||
[
|
|
||||||
"stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')"
|
|
||||||
],
|
|
||||||
$this->server,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!$this->dockerRootFilesystem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$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)->color('orange');
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncr
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
resolve(UpdateCoolify::class)($this->force);
|
UpdateCoolify::run($this->force);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
app/Jobs/StopResourceJob.php
Normal file
55
app/Jobs/StopResourceJob.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Application\StopApplication;
|
||||||
|
use App\Actions\Database\StopDatabase;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis $resource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$server = $this->resource->destination->server;
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
switch ($this->resource->type()) {
|
||||||
|
case 'application':
|
||||||
|
StopApplication::run($this->resource);
|
||||||
|
break;
|
||||||
|
case 'standalone-postgresql':
|
||||||
|
StopDatabase::run($this->resource);
|
||||||
|
break;
|
||||||
|
case 'standalone-redis':
|
||||||
|
StopDatabase::run($this->resource);
|
||||||
|
break;
|
||||||
|
case 'service':
|
||||||
|
StopService::run($this->resource);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
$this->resource->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Application extends BaseModel
|
class Application extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -12,6 +13,19 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
|
static::saving(function ($application) {
|
||||||
|
if ($application->fqdn == '') {
|
||||||
|
$application->fqdn = null;
|
||||||
|
}
|
||||||
|
$application->forceFill([
|
||||||
|
'fqdn' => $application->fqdn,
|
||||||
|
'install_command' => Str::of($application->install_command)->trim(),
|
||||||
|
'build_command' => Str::of($application->build_command)->trim(),
|
||||||
|
'start_command' => Str::of($application->start_command)->trim(),
|
||||||
|
'base_directory' => Str::of($application->base_directory)->trim(),
|
||||||
|
'publish_directory' => Str::of($application->publish_directory)->trim(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
static::created(function ($application) {
|
static::created(function ($application) {
|
||||||
ApplicationSetting::create([
|
ApplicationSetting::create([
|
||||||
'application_id' => $application->id,
|
'application_id' => $application->id,
|
||||||
@@ -19,9 +33,9 @@ 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) {
|
foreach ($storages as $storage) {
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $application->destination->server);
|
instant_remote_process(["docker volume rm -f $storage->name"], $application->destination->server, false);
|
||||||
}
|
}
|
||||||
$application->persistentStorages()->delete();
|
$application->persistentStorages()->delete();
|
||||||
$application->environment_variables()->delete();
|
$application->environment_variables()->delete();
|
||||||
@@ -38,6 +52,10 @@ class Application extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||||
}
|
}
|
||||||
|
public function fileStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
public function type()
|
public function type()
|
||||||
{
|
{
|
||||||
@@ -58,6 +76,7 @@ class Application extends BaseModel
|
|||||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
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}/tree/{$this->git_branch}";
|
return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}";
|
||||||
}
|
}
|
||||||
|
return $this->git_repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -70,10 +89,25 @@ class Application extends BaseModel
|
|||||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
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}/commits/{$this->git_branch}";
|
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
|
||||||
}
|
}
|
||||||
|
return $this->git_repository;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function dockerfileLocation(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: function ($value) {
|
||||||
|
if (is_null($value) || $value === '') {
|
||||||
|
return '/Dockerfile';
|
||||||
|
} else {
|
||||||
|
if ($value !== '/') {
|
||||||
|
return Str::start(Str::replaceEnd('/', '', $value), '/');
|
||||||
|
}
|
||||||
|
return Str::start($value, '/');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function baseDirectory(): Attribute
|
public function baseDirectory(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -214,6 +248,8 @@ class Application extends BaseModel
|
|||||||
return 'deploy_key';
|
return 'deploy_key';
|
||||||
} else if (data_get($this, 'source')) {
|
} else if (data_get($this, 'source')) {
|
||||||
return 'source';
|
return 'source';
|
||||||
|
} else {
|
||||||
|
return 'other';
|
||||||
}
|
}
|
||||||
throw new \Exception('No deployment type found');
|
throw new \Exception('No deployment type found');
|
||||||
}
|
}
|
||||||
@@ -229,6 +265,43 @@ class Application extends BaseModel
|
|||||||
if ($this->dockerfile) {
|
if ($this->dockerfile) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if ($this->build_pack === 'dockerimage') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public function isHealthcheckDisabled(): bool
|
||||||
|
{
|
||||||
|
if (data_get($this, 'health_check_enabled') === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public function isConfigurationChanged($save = false)
|
||||||
|
{
|
||||||
|
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->health_check_path . $this->health_check_port . $this->health_check_host . $this->health_check_method . $this->health_check_return_code . $this->health_check_scheme . $this->health_check_response_text . $this->health_check_interval . $this->health_check_timeout . $this->health_check_retries . $this->health_check_start_period . $this->health_check_enabled . $this->limits_memory . $this->limits_swap . $this->limits_swappiness . $this->limits_reservation . $this->limits_cpus . $this->limits_cpuset . $this->limits_cpu_shares . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
|
||||||
|
if ($this->pull_request_id === 0) {
|
||||||
|
$newConfigHash .= json_encode($this->environment_variables->all());
|
||||||
|
} else {
|
||||||
|
$newConfigHash .= json_encode($this->environment_variables_preview->all());
|
||||||
|
}
|
||||||
|
$newConfigHash = md5($newConfigHash);
|
||||||
|
$oldConfigHash = data_get($this, 'config_hash');
|
||||||
|
if ($oldConfigHash === null) {
|
||||||
|
if ($save) {
|
||||||
|
$this->config_hash = $newConfigHash;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($oldConfigHash === $newConfigHash) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if ($save) {
|
||||||
|
$this->config_hash = $newConfigHash;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class Environment extends Model
|
|||||||
|
|
||||||
public function can_delete_environment()
|
public function can_delete_environment()
|
||||||
{
|
{
|
||||||
return $this->applications()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
|
return $this->applications()->count() == 0 && $this->redis()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function applications()
|
public function applications()
|
||||||
@@ -26,10 +26,16 @@ class Environment extends Model
|
|||||||
{
|
{
|
||||||
return $this->hasMany(StandalonePostgresql::class);
|
return $this->hasMany(StandalonePostgresql::class);
|
||||||
}
|
}
|
||||||
|
public function redis()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneRedis::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function databases()
|
public function databases()
|
||||||
{
|
{
|
||||||
return $this->postgresqls();
|
$postgresqls = $this->postgresqls;
|
||||||
|
$redis = $this->redis;
|
||||||
|
return $postgresqls->concat($redis);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function project()
|
public function project()
|
||||||
|
|||||||
@@ -31,23 +31,18 @@ class LocalFileVolume extends BaseModel
|
|||||||
}
|
}
|
||||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||||
ray($isFile);
|
|
||||||
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
||||||
throw new \Exception("File $path is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
|
throw new \Exception("File $path is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
|
||||||
} else if ($isDir == 'OK' && !$fileVolume->is_directory) {
|
} else if ($isDir == 'OK' && !$fileVolume->is_directory) {
|
||||||
throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory.");
|
throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory.");
|
||||||
}
|
}
|
||||||
if (($isFile == 'NOK' && !$fileVolume->is_directory) || $isFile == 'OK') {
|
if (!$fileVolume->is_directory && $isDir == 'NOK') {
|
||||||
$rootDir = Str::of($path)->dirname();
|
$content = base64_encode($content);
|
||||||
$commands->push("mkdir -p $rootDir > /dev/null 2>&1 || true");
|
$commands->push("echo '$content' | base64 -d > $path");
|
||||||
$commands->push("touch $path > /dev/null 2>&1 || true");
|
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||||
if ($content) {
|
|
||||||
$content = base64_encode($content);
|
|
||||||
$commands->push("echo '$content' | base64 -d > $path");
|
|
||||||
}
|
|
||||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
|
||||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||||
}
|
}
|
||||||
|
ray($commands->toArray());
|
||||||
return instant_remote_process($commands, $server);
|
return instant_remote_process($commands, $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Project extends BaseModel
|
|||||||
'project_id' => $project->id,
|
'project_id' => $project->id,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleted(function ($project) {
|
static::deleting(function ($project) {
|
||||||
$project->environments()->delete();
|
$project->environments()->delete();
|
||||||
$project->settings()->delete();
|
$project->settings()->delete();
|
||||||
});
|
});
|
||||||
@@ -52,4 +52,8 @@ class Project extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->hasManyThrough(StandalonePostgresql::class, Environment::class);
|
return $this->hasManyThrough(StandalonePostgresql::class, Environment::class);
|
||||||
}
|
}
|
||||||
|
public function redis()
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(StandaloneRedis::class, Environment::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class S3Storage extends BaseModel
|
class S3Storage extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -10,6 +12,7 @@ class S3Storage extends BaseModel
|
|||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
'is_usable' => 'boolean',
|
||||||
'key' => 'encrypted',
|
'key' => 'encrypted',
|
||||||
'secret' => 'encrypted',
|
'secret' => 'encrypted',
|
||||||
];
|
];
|
||||||
@@ -19,7 +22,15 @@ class S3Storage extends BaseModel
|
|||||||
$selectArray = collect($select)->concat(['id']);
|
$selectArray = collect($select)->concat(['id']);
|
||||||
return S3Storage::whereTeamId(currentTeam()->id)->select($selectArray->all())->orderBy('name');
|
return S3Storage::whereTeamId(currentTeam()->id)->select($selectArray->all())->orderBy('name');
|
||||||
}
|
}
|
||||||
|
public function isUsable()
|
||||||
|
{
|
||||||
|
return $this->is_usable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function team()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Team::class);
|
||||||
|
}
|
||||||
public function awsUrl()
|
public function awsUrl()
|
||||||
{
|
{
|
||||||
return "{$this->endpoint}/{$this->bucket}";
|
return "{$this->endpoint}/{$this->bucket}";
|
||||||
@@ -27,7 +38,34 @@ class S3Storage extends BaseModel
|
|||||||
|
|
||||||
public function testConnection()
|
public function testConnection()
|
||||||
{
|
{
|
||||||
set_s3_target($this);
|
try {
|
||||||
return \Storage::disk('custom-s3')->files();
|
set_s3_target($this);
|
||||||
|
Storage::disk('custom-s3')->files();
|
||||||
|
$this->unusable_email_sent = false;
|
||||||
|
$this->is_usable = true;
|
||||||
|
return;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->is_usable = false;
|
||||||
|
if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
|
||||||
|
$mail = new MailMessage();
|
||||||
|
$mail->subject('Coolify: S3 Storage Connection Error');
|
||||||
|
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storages.show', ['storage_uuid' => $this->uuid])]);
|
||||||
|
$users = collect([]);
|
||||||
|
$members = $this->team->members()->get();
|
||||||
|
foreach ($members as $user) {
|
||||||
|
if ($user->isAdmin()) {
|
||||||
|
$users->push($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($users as $user) {
|
||||||
|
send_user_an_email($mail, $user->email);
|
||||||
|
}
|
||||||
|
$this->unusable_email_sent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ namespace App\Models;
|
|||||||
use App\Enums\ProxyStatus;
|
use App\Enums\ProxyStatus;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Server extends BaseModel
|
class Server extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -14,6 +16,17 @@ class Server extends BaseModel
|
|||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
|
static::saving(function ($server) {
|
||||||
|
$payload = [];
|
||||||
|
if ($server->user) {
|
||||||
|
$payload['user'] = Str::of($server->user)->trim();
|
||||||
|
}
|
||||||
|
if ($server->ip) {
|
||||||
|
$payload['ip'] = Str::of($server->ip)->trim();
|
||||||
|
}
|
||||||
|
$server->forceFill($payload);
|
||||||
|
});
|
||||||
|
|
||||||
static::created(function ($server) {
|
static::created(function ($server) {
|
||||||
ServerSetting::create([
|
ServerSetting::create([
|
||||||
'server_id' => $server->id,
|
'server_id' => $server->id,
|
||||||
@@ -80,8 +93,11 @@ class Server extends BaseModel
|
|||||||
|
|
||||||
public function proxyType()
|
public function proxyType()
|
||||||
{
|
{
|
||||||
$type = $this->proxy->get('type');
|
$proxyType = $this->proxy->get('type');
|
||||||
if (is_null($type)) {
|
if ($proxyType === ProxyTypes::NONE->value) {
|
||||||
|
return $proxyType;
|
||||||
|
}
|
||||||
|
if (is_null($proxyType)) {
|
||||||
$this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
$this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||||
$this->proxy->status = ProxyStatus::EXITED->value;
|
$this->proxy->status = ProxyStatus::EXITED->value;
|
||||||
$this->save();
|
$this->save();
|
||||||
@@ -107,7 +123,8 @@ class Server extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->destinations()->map(function ($standaloneDocker) {
|
return $this->destinations()->map(function ($standaloneDocker) {
|
||||||
$postgresqls = $standaloneDocker->postgresqls;
|
$postgresqls = $standaloneDocker->postgresqls;
|
||||||
return $postgresqls?->concat([]) ?? collect([]);
|
$redis = $standaloneDocker->redis;
|
||||||
|
return $postgresqls->concat($redis);
|
||||||
})->flatten();
|
})->flatten();
|
||||||
}
|
}
|
||||||
public function applications()
|
public function applications()
|
||||||
@@ -120,7 +137,20 @@ class Server extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->hasMany(Service::class);
|
return $this->hasMany(Service::class);
|
||||||
}
|
}
|
||||||
|
public function getIp(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function () {
|
||||||
|
if (isDev()) {
|
||||||
|
return '127.0.0.1';
|
||||||
|
}
|
||||||
|
if ($this->ip === 'host.docker.internal') {
|
||||||
|
return base_ip();
|
||||||
|
}
|
||||||
|
return $this->ip;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
public function previews()
|
public function previews()
|
||||||
{
|
{
|
||||||
return $this->destinations()->map(function ($standaloneDocker) {
|
return $this->destinations()->map(function ($standaloneDocker) {
|
||||||
@@ -163,26 +193,71 @@ class Server extends BaseModel
|
|||||||
}
|
}
|
||||||
public function isProxyShouldRun()
|
public function isProxyShouldRun()
|
||||||
{
|
{
|
||||||
$shouldRun = false;
|
|
||||||
if ($this->proxyType() === ProxyTypes::NONE->value) {
|
if ($this->proxyType() === ProxyTypes::NONE->value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
foreach ($this->applications() as $application) {
|
// foreach ($this->applications() as $application) {
|
||||||
if (data_get($application, 'fqdn')) {
|
// if (data_get($application, 'fqdn')) {
|
||||||
$shouldRun = true;
|
// $shouldRun = true;
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if ($this->id === 0) {
|
// ray($this->services()->get());
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
if (data_get($settings, 'fqdn')) {
|
// if ($this->id === 0) {
|
||||||
$shouldRun = true;
|
// $settings = InstanceSettings::get();
|
||||||
}
|
// if (data_get($settings, 'fqdn')) {
|
||||||
}
|
// $shouldRun = true;
|
||||||
return $shouldRun;
|
// }
|
||||||
|
// }
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
public function isFunctional()
|
public function isFunctional()
|
||||||
{
|
{
|
||||||
return $this->settings->is_reachable && $this->settings->is_usable;
|
return $this->settings->is_reachable && $this->settings->is_usable;
|
||||||
}
|
}
|
||||||
|
public function validateConnection()
|
||||||
|
{
|
||||||
|
$uptime = instant_remote_process(['uptime'], $this, false);
|
||||||
|
if (!$uptime) {
|
||||||
|
$this->settings->is_reachable = false;
|
||||||
|
$this->settings->save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->settings->is_reachable = true;
|
||||||
|
$this->settings->save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public function validateDockerEngine($throwError = false)
|
||||||
|
{
|
||||||
|
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
|
||||||
|
if (is_null($dockerBinary)) {
|
||||||
|
$this->settings->is_usable = false;
|
||||||
|
$this->settings->save();
|
||||||
|
if ($throwError) {
|
||||||
|
throw new \Exception('Server is not usable.');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->settings->is_usable = true;
|
||||||
|
$this->settings->save();
|
||||||
|
$this->validateCoolifyNetwork();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public function validateDockerEngineVersion()
|
||||||
|
{
|
||||||
|
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this, false);
|
||||||
|
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||||
|
if (is_null($dockerVersion)) {
|
||||||
|
$this->settings->is_usable = false;
|
||||||
|
$this->settings->save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->settings->is_usable = true;
|
||||||
|
$this->settings->save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public function validateCoolifyNetwork() {
|
||||||
|
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use Illuminate\Support\Collection;
|
|||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Spatie\Url\Url;
|
|
||||||
|
|
||||||
class Service extends BaseModel
|
class Service extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -17,7 +16,7 @@ class Service extends BaseModel
|
|||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::deleted(function ($service) {
|
static::deleting(function ($service) {
|
||||||
$storagesToDelete = collect([]);
|
$storagesToDelete = collect([]);
|
||||||
foreach ($service->applications()->get() as $application) {
|
foreach ($service->applications()->get() as $application) {
|
||||||
$storages = $application->persistentStorages()->get();
|
$storages = $application->persistentStorages()->get();
|
||||||
@@ -63,6 +62,10 @@ class Service extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->hasMany(ServiceDatabase::class);
|
return $this->hasMany(ServiceDatabase::class);
|
||||||
}
|
}
|
||||||
|
public function destination()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
public function environment()
|
public function environment()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Environment::class);
|
return $this->belongsTo(Environment::class);
|
||||||
@@ -112,7 +115,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);
|
||||||
@@ -124,9 +127,16 @@ class Service extends BaseModel
|
|||||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||||
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
||||||
$services = data_get($yaml, 'services');
|
$services = data_get($yaml, 'services');
|
||||||
$definedNetwork = $this->uuid;
|
|
||||||
|
|
||||||
$generatedServiceFQDNS = collect([]);
|
$generatedServiceFQDNS = collect([]);
|
||||||
|
if (is_null($this->destination)) {
|
||||||
|
$destination = $this->server->destinations()->first();
|
||||||
|
if ($destination) {
|
||||||
|
$this->destination()->associate($destination);
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$definedNetwork = collect([$this->uuid]);
|
||||||
|
|
||||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS) {
|
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS) {
|
||||||
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
||||||
@@ -237,18 +247,24 @@ class Service extends BaseModel
|
|||||||
return $value == $definedNetwork;
|
return $value == $definedNetwork;
|
||||||
});
|
});
|
||||||
if (!$definedNetworkExists) {
|
if (!$definedNetworkExists) {
|
||||||
$topLevelNetworks->put($definedNetwork, [
|
foreach ($definedNetwork as $network) {
|
||||||
'name' => $definedNetwork,
|
$topLevelNetworks->put($network, [
|
||||||
'external' => true
|
'name' => $network,
|
||||||
]);
|
'external' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$networks = $serviceNetworks->toArray();
|
$networks = $serviceNetworks->toArray();
|
||||||
$networks = array_merge($networks, [$definedNetwork]);
|
foreach ($definedNetwork as $key => $network) {
|
||||||
|
$networks = array_merge($networks, [
|
||||||
|
$network
|
||||||
|
]);
|
||||||
|
}
|
||||||
data_set($service, 'networks', $networks);
|
data_set($service, 'networks', $networks);
|
||||||
|
|
||||||
// Collect/create/update volumes
|
// Collect/create/update volumes
|
||||||
if ($serviceVolumes->count() > 0) {
|
if ($serviceVolumes->count() > 0) {
|
||||||
foreach ($serviceVolumes as $volume) {
|
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes) {
|
||||||
$type = null;
|
$type = null;
|
||||||
$source = null;
|
$source = null;
|
||||||
$target = null;
|
$target = null;
|
||||||
@@ -270,16 +286,19 @@ class Service extends BaseModel
|
|||||||
$isDirectory = (bool) data_get($volume, 'isDirectory', false);
|
$isDirectory = (bool) data_get($volume, 'isDirectory', false);
|
||||||
$foundConfig = $savedService->fileStorages()->whereMountPath($target)->first();
|
$foundConfig = $savedService->fileStorages()->whereMountPath($target)->first();
|
||||||
if ($foundConfig) {
|
if ($foundConfig) {
|
||||||
$content = data_get($foundConfig, 'content');
|
$contentNotNull = data_get($foundConfig, 'content');
|
||||||
|
if ($contentNotNull) {
|
||||||
|
$content = $contentNotNull;
|
||||||
|
}
|
||||||
$isDirectory = (bool) data_get($foundConfig, 'is_directory');
|
$isDirectory = (bool) data_get($foundConfig, 'is_directory');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($type->value() === 'bind') {
|
if ($type->value() === 'bind') {
|
||||||
if ($source->value() === "/var/run/docker.sock") {
|
if ($source->value() === "/var/run/docker.sock") {
|
||||||
continue;
|
return $volume;
|
||||||
}
|
}
|
||||||
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
|
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
|
||||||
continue;
|
return $volume;
|
||||||
}
|
}
|
||||||
LocalFileVolume::updateOrCreate(
|
LocalFileVolume::updateOrCreate(
|
||||||
[
|
[
|
||||||
@@ -297,7 +316,19 @@ class Service extends BaseModel
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if ($type->value() === 'volume') {
|
} else if ($type->value() === 'volume') {
|
||||||
$topLevelVolumes->put($source->value(), null);
|
$slugWithoutUuid = Str::slug($source, '-');
|
||||||
|
$name = "{$savedService->service->uuid}_{$slugWithoutUuid}";
|
||||||
|
if (is_string($volume)) {
|
||||||
|
$source = Str::of($volume)->before(':');
|
||||||
|
$target = Str::of($volume)->after(':')->beforeLast(':');
|
||||||
|
$source = $name;
|
||||||
|
$volume = "$source:$target";
|
||||||
|
} else if (is_array($volume)) {
|
||||||
|
data_set($volume, 'source', $name);
|
||||||
|
}
|
||||||
|
$topLevelVolumes->put($name, [
|
||||||
|
'name' => $name,
|
||||||
|
]);
|
||||||
LocalPersistentVolume::updateOrCreate(
|
LocalPersistentVolume::updateOrCreate(
|
||||||
[
|
[
|
||||||
'mount_path' => $target,
|
'mount_path' => $target,
|
||||||
@@ -305,15 +336,17 @@ class Service extends BaseModel
|
|||||||
'resource_type' => get_class($savedService)
|
'resource_type' => get_class($savedService)
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => Str::slug($source, '-'),
|
'name' => $name,
|
||||||
'mount_path' => $target,
|
'mount_path' => $target,
|
||||||
'resource_id' => $savedService->id,
|
'resource_id' => $savedService->id,
|
||||||
'resource_type' => get_class($savedService)
|
'resource_type' => get_class($savedService)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$savedService->getFilesFromServer();
|
$savedService->getFilesFromServer(isInit: true);
|
||||||
}
|
return $volume;
|
||||||
|
});
|
||||||
|
data_set($service, 'volumes', $serviceVolumes->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add env_file with at least .env to the service
|
// Add env_file with at least .env to the service
|
||||||
@@ -349,9 +382,23 @@ class Service extends BaseModel
|
|||||||
$value = Str::of($variable);
|
$value = Str::of($variable);
|
||||||
}
|
}
|
||||||
if ($key->startsWith('SERVICE_FQDN')) {
|
if ($key->startsWith('SERVICE_FQDN')) {
|
||||||
if (is_null(data_get($savedService, 'fqdn'))) {
|
if ($isNew || $savedService->fqdn === null) {
|
||||||
$fqdn = generateFqdn($this->server, $containerName);
|
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
|
||||||
if (substr_count($key->value(), '_') === 2 && $key->contains("=")) {
|
$fqdn = generateFqdn($this->server, "{$name->value()}-{$this->uuid}");
|
||||||
|
if (substr_count($key->value(), '_') === 3) {
|
||||||
|
// SERVICE_FQDN_UMAMI_1000
|
||||||
|
$port = $key->afterLast('_');
|
||||||
|
} else {
|
||||||
|
// SERVICE_FQDN_UMAMI
|
||||||
|
$port = null;
|
||||||
|
}
|
||||||
|
if ($port) {
|
||||||
|
$fqdn = "$fqdn:$port";
|
||||||
|
}
|
||||||
|
if (substr_count($key->value(), '_') >= 2) {
|
||||||
|
if (is_null($value)) {
|
||||||
|
$value = Str::of('/');
|
||||||
|
}
|
||||||
$path = $value->value();
|
$path = $value->value();
|
||||||
if ($generatedServiceFQDNS->count() > 0) {
|
if ($generatedServiceFQDNS->count() > 0) {
|
||||||
$alreadyGenerated = $generatedServiceFQDNS->has($key->value());
|
$alreadyGenerated = $generatedServiceFQDNS->has($key->value());
|
||||||
@@ -365,11 +412,22 @@ class Service extends BaseModel
|
|||||||
}
|
}
|
||||||
$fqdn = "$fqdn$path";
|
$fqdn = "$fqdn$path";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$isDatabase) {
|
if (!$isDatabase) {
|
||||||
|
if ($savedService->fqdn) {
|
||||||
|
$fqdn = $savedService->fqdn . ',' . $fqdn;
|
||||||
|
} else {
|
||||||
|
$fqdn = $fqdn;
|
||||||
|
}
|
||||||
$savedService->fqdn = $fqdn;
|
$savedService->fqdn = $fqdn;
|
||||||
$savedService->save();
|
$savedService->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// data_forget($service, "environment.$variableName");
|
||||||
|
// $yaml = data_forget($yaml, "services.$serviceName.environment.$variableName");
|
||||||
|
// if (count(data_get($yaml, 'services.' . $serviceName . '.environment')) === 0) {
|
||||||
|
// $yaml = data_forget($yaml, "services.$serviceName.environment");
|
||||||
|
// }
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($value?->startsWith('$')) {
|
if ($value?->startsWith('$')) {
|
||||||
@@ -384,10 +442,17 @@ class Service extends BaseModel
|
|||||||
$forService = $value->afterLast('_');
|
$forService = $value->afterLast('_');
|
||||||
$generatedValue = null;
|
$generatedValue = null;
|
||||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
||||||
$fqdn = generateFqdn($this->server, $containerName);
|
if (Str::lower($forService) === $serviceName) {
|
||||||
|
$fqdn = generateFqdn($this->server, $containerName);
|
||||||
|
} else {
|
||||||
|
$fqdn = generateFqdn($this->server, Str::lower($forService) . '-' . $this->uuid);
|
||||||
|
}
|
||||||
if ($foundEnv) {
|
if ($foundEnv) {
|
||||||
$fqdn = data_get($foundEnv, 'value');
|
$fqdn = data_get($foundEnv, 'value');
|
||||||
} else {
|
} else {
|
||||||
|
if ($command->value() === 'URL') {
|
||||||
|
$fqdn = Str::of($fqdn)->after('://')->value();
|
||||||
|
}
|
||||||
EnvironmentVariable::create([
|
EnvironmentVariable::create([
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
'value' => $fqdn,
|
'value' => $fqdn,
|
||||||
@@ -396,10 +461,11 @@ class Service extends BaseModel
|
|||||||
'is_preview' => false,
|
'is_preview' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$isDatabase) {
|
if (!$isDatabase) {
|
||||||
$savedService->fqdn = $fqdn;
|
if ($command->value() === 'FQDN') {
|
||||||
$savedService->save();
|
$savedService->fqdn = $fqdn;
|
||||||
|
$savedService->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch ($command) {
|
switch ($command) {
|
||||||
@@ -472,25 +538,27 @@ class Service extends BaseModel
|
|||||||
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
||||||
if (!$isDatabase && $fqdns->count() > 0) {
|
if (!$isDatabase && $fqdns->count() > 0) {
|
||||||
if ($fqdns) {
|
if ($fqdns) {
|
||||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, $containerName, true));
|
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($this->uuid, $fqdns, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data_set($service, 'labels', $serviceLabels->toArray());
|
data_set($service, 'labels', $serviceLabels->toArray());
|
||||||
data_forget($service, 'is_database');
|
data_forget($service, 'is_database');
|
||||||
data_set($service, 'restart', RESTART_MODE);
|
data_set($service, 'restart', RESTART_MODE);
|
||||||
data_set($service, 'container_name', $containerName);
|
data_set($service, 'container_name', $containerName);
|
||||||
data_forget($service, 'volumes.*.content');
|
data_forget($service, 'volumes.*.content');
|
||||||
data_forget($service, 'volumes.*.isDirectory');
|
data_forget($service, 'volumes.*.isDirectory');
|
||||||
|
|
||||||
// Remove unnecessary variables from service.environment
|
// Remove unnecessary variables from service.environment
|
||||||
$withoutServiceEnvs = collect([]);
|
// $withoutServiceEnvs = collect([]);
|
||||||
collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
|
// collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
|
||||||
if (!Str::of($key)->startsWith('$SERVICE_')) {
|
// ray($key, $value);
|
||||||
$withoutServiceEnvs->put($key, $value);
|
// if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) {
|
||||||
}
|
// $k = Str::of($value)->before("=");
|
||||||
});
|
// $v = Str::of($value)->after("=");
|
||||||
data_set($service, 'environment', $withoutServiceEnvs->toArray());
|
// $withoutServiceEnvs->put($k->value(), $v->value());
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// ray($withoutServiceEnvs);
|
||||||
|
// data_set($service, 'environment', $withoutServiceEnvs->toArray());
|
||||||
return $service;
|
return $service;
|
||||||
});
|
});
|
||||||
$finalServices = [
|
$finalServices = [
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ class ServiceApplication extends BaseModel
|
|||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public function getFilesFromServer()
|
public function getFilesFromServer(bool $isInit = false)
|
||||||
{
|
{
|
||||||
getFilesystemVolumesFromServer($this);
|
getFilesystemVolumesFromServer($this, $isInit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
|
|
||||||
class ServiceDatabase extends BaseModel
|
class ServiceDatabase extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -26,8 +25,8 @@ class ServiceDatabase extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
}
|
}
|
||||||
public function getFilesFromServer()
|
public function getFilesFromServer(bool $isInit = false)
|
||||||
{
|
{
|
||||||
getFilesystemVolumesFromServer($this);
|
getFilesystemVolumesFromServer($this, $isInit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ class StandaloneDocker extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
||||||
}
|
}
|
||||||
|
public function redis()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneRedis::class, 'destination');
|
||||||
|
}
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,11 +28,14 @@ class StandalonePostgresql extends BaseModel
|
|||||||
'is_readonly' => true
|
'is_readonly' => true
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleted(function ($database) {
|
static::deleting(function ($database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
|
||||||
|
}
|
||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +68,11 @@ class StandalonePostgresql extends BaseModel
|
|||||||
return $this->belongsTo(Environment::class);
|
return $this->belongsTo(Environment::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function fileStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
public function destination()
|
public function destination()
|
||||||
{
|
{
|
||||||
return $this->morphTo();
|
return $this->morphTo();
|
||||||
|
|||||||
95
app/Models/StandaloneRedis.php
Normal file
95
app/Models/StandaloneRedis.php
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class StandaloneRedis extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::created(function ($database) {
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => 'redis-data-' . $database->uuid,
|
||||||
|
'mount_path' => '/data',
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
'is_readonly' => true
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
static::deleting(function ($database) {
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
|
||||||
|
}
|
||||||
|
$database->persistentStorages()->delete();
|
||||||
|
$database->environment_variables()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappings(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: fn ($value) => $value === "" ? null : $value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal Deployments
|
||||||
|
|
||||||
|
public function portsMappingsArray(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => is_null($this->ports_mappings)
|
||||||
|
? []
|
||||||
|
: explode(',', $this->ports_mappings),
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return 'standalone-redis';
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user