mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 20:59:24 +00:00
Compare commits
137 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce165719d6 | ||
|
|
4f543ce20f | ||
|
|
55f957df21 | ||
|
|
38f59b9410 | ||
|
|
ebe6655349 | ||
|
|
038ea08ca7 | ||
|
|
ba424efd39 | ||
|
|
75aef0e60b | ||
|
|
eda8b34297 | ||
|
|
d8151ddb2e | ||
|
|
928345c8ea | ||
|
|
52d6fb51d5 | ||
|
|
06d7c69487 | ||
|
|
756c7f81ca | ||
|
|
d7af57a95e | ||
|
|
f9c469497e | ||
|
|
7ecbedb48a | ||
|
|
76878f66b9 | ||
|
|
b9afef50c4 | ||
|
|
83ebd1e649 | ||
|
|
76431c3fd5 | ||
|
|
96a4d0bbb0 | ||
|
|
4cfc739730 | ||
|
|
a95bd906bc | ||
|
|
21795cf788 | ||
|
|
6e98fd9403 | ||
|
|
ead1edc2b9 | ||
|
|
db822cb876 | ||
|
|
65bfce43c0 | ||
|
|
50fc05ab52 | ||
|
|
c9cf5c486f | ||
|
|
379f4b9dff | ||
|
|
aa02b8d433 | ||
|
|
70ecb92e82 | ||
|
|
d5cc2a2eed | ||
|
|
2b91bd24c5 | ||
|
|
5e8ac1b48e | ||
|
|
dc86170ef5 | ||
|
|
0232cf5b4c | ||
|
|
6e73f7f2e4 | ||
|
|
61c43804e3 | ||
|
|
72421d692b | ||
|
|
f801bb98cd | ||
|
|
b2d111e49a | ||
|
|
c82e02218f | ||
|
|
29f64076de | ||
|
|
393c334b12 | ||
|
|
678b264688 | ||
|
|
2620bfbf08 | ||
|
|
18c32decad | ||
|
|
a6f9e5f0af | ||
|
|
f187040b7e | ||
|
|
5510321776 | ||
|
|
69691b2ca7 | ||
|
|
8bfc1a7c06 | ||
|
|
554222abc7 | ||
|
|
b1a1aeeb75 | ||
|
|
91acd4cb6a | ||
|
|
6c5a1c317a | ||
|
|
b09a9f871e | ||
|
|
b5506f006b | ||
|
|
a6c3594448 | ||
|
|
5dd3952230 | ||
|
|
22ec0f8826 | ||
|
|
da6e04bb1a | ||
|
|
aaeacad781 | ||
|
|
b539f40fa5 | ||
|
|
fae340afcb | ||
|
|
69ebff1a7a | ||
|
|
5d9cfc393e | ||
|
|
e2a256b31c | ||
|
|
4855af7e57 | ||
|
|
a664174c02 | ||
|
|
c19c13b4e2 | ||
|
|
266b99bc25 | ||
|
|
51ef24e1fb | ||
|
|
33d38ccf40 | ||
|
|
f470ebbbe0 | ||
|
|
11bd46b200 | ||
|
|
53f5674771 | ||
|
|
c53d88902c | ||
|
|
e342c4fd65 | ||
|
|
aab7bd5e28 | ||
|
|
3adefb9e49 | ||
|
|
1bfce6716c | ||
|
|
c904441787 | ||
|
|
b7f79ae034 | ||
|
|
2d63fcdc7f | ||
|
|
c1d0cabcfb | ||
|
|
166419b13a | ||
|
|
cfc4d3acc7 | ||
|
|
13a0c2cf43 | ||
|
|
6ef6975432 | ||
|
|
2c40e93d3b | ||
|
|
a30ae4fb38 | ||
|
|
5b8785d1a9 | ||
|
|
f6f3364269 | ||
|
|
2f93f4450f | ||
|
|
2ad7c2b1ce | ||
|
|
6c848199ed | ||
|
|
76aab722b8 | ||
|
|
12290304c4 | ||
|
|
3a27d13c3e | ||
|
|
4f588ced96 | ||
|
|
e266c7cdec | ||
|
|
eedc3faba3 | ||
|
|
2e2c932f07 | ||
|
|
e4aed185a2 | ||
|
|
dddbe40bbe | ||
|
|
59d6818f70 | ||
|
|
7678cd47df | ||
|
|
b101fbacd4 | ||
|
|
a61a86dc3b | ||
|
|
0b3cde44c3 | ||
|
|
618d5d837c | ||
|
|
d234e8969d | ||
|
|
1be77b3fea | ||
|
|
6b302ab786 | ||
|
|
5831dd6196 | ||
|
|
3c623f13e2 | ||
|
|
da54c24e8d | ||
|
|
1e39c3d5ab | ||
|
|
6071412986 | ||
|
|
ba7148206a | ||
|
|
59c5b22e6c | ||
|
|
be7f2ad9c4 | ||
|
|
62295ef573 | ||
|
|
ceb9fcf3b6 | ||
|
|
60282f7b6c | ||
|
|
f14b0a3411 | ||
|
|
30af317bd9 | ||
|
|
95faa1c3ad | ||
|
|
423d31f227 | ||
|
|
fbb063030d | ||
|
|
1968726cfe | ||
|
|
fb280afe41 | ||
|
|
fd488a561a |
@@ -6,13 +6,14 @@
|
|||||||
You can ask for guidance anytime on our
|
You can ask for guidance anytime on our
|
||||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
||||||
|
|
||||||
|
## Code Contribution
|
||||||
|
|
||||||
## 1) Setup your development environment
|
### 1) Setup your development environment
|
||||||
|
|
||||||
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
||||||
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
||||||
|
|
||||||
## 2) Set your environment variables
|
### 2) Set your environment variables
|
||||||
|
|
||||||
- Copy [.env.development.example](./.env.development.example) to .env.
|
- Copy [.env.development.example](./.env.development.example) to .env.
|
||||||
|
|
||||||
@@ -23,7 +24,14 @@ You can ask for guidance anytime on our
|
|||||||
|
|
||||||
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
|
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
|
||||||
|
|
||||||
## 4) Start development
|
### 4) Start development
|
||||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
||||||
|
|
||||||
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
||||||
|
|
||||||
|
Mails are caught by Mailpit: `localhost:8025`
|
||||||
|
|
||||||
|
|
||||||
|
## New Service Contribution
|
||||||
|
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Verc
|
|||||||
|
|
||||||
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
|
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
|
||||||
|
|
||||||
Image if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
||||||
|
|
||||||
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ Contact us [here](https://coolify.io/docs/contact).
|
|||||||
|
|
||||||
## Recognitions
|
## Recognitions
|
||||||
|
|
||||||
|
<p>
|
||||||
<a href="https://news.ycombinator.com/item?id=26624341">
|
<a href="https://news.ycombinator.com/item?id=26624341">
|
||||||
<img
|
<img
|
||||||
style="width: 250px; height: 54px;" width="250" height="54"
|
style="width: 250px; height: 54px;" width="250" height="54"
|
||||||
@@ -47,9 +48,12 @@ Contact us [here](https://coolify.io/docs/contact).
|
|||||||
src="https://hackernews-badge.vercel.app/api?id=26624341"
|
src="https://hackernews-badge.vercel.app/api?id=26624341"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
|
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
## 💰 Financial Contributors
|
## 💰 Financial Contributors
|
||||||
|
|
||||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -11,13 +14,19 @@ class StartDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||||
{
|
{
|
||||||
$internalPort = null;
|
$internalPort = null;
|
||||||
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
|
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
|
||||||
$internalPort = 6379;
|
$internalPort = 6379;
|
||||||
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
|
} else if ($database->getMorphClass() === 'App\Models\StandalonePostgresql') {
|
||||||
$internalPort = 5432;
|
$internalPort = 5432;
|
||||||
|
} else if ($database->getMorphClass() === 'App\Models\StandaloneMongodb') {
|
||||||
|
$internalPort = 27017;
|
||||||
|
} else if ($database->getMorphClass() === 'App\Models\StandaloneMysql') {
|
||||||
|
$internalPort = 3306;
|
||||||
|
} else if ($database->getMorphClass() === 'App\Models\StandaloneMariadb') {
|
||||||
|
$internalPort = 3306;
|
||||||
}
|
}
|
||||||
$containerName = "{$database->uuid}-proxy";
|
$containerName = "{$database->uuid}-proxy";
|
||||||
$configuration_dir = database_proxy_dir($database->uuid);
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
@@ -87,7 +96,7 @@ class StartDatabaseProxy
|
|||||||
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
||||||
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||||
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||||
"docker compose --project-directory {$configuration_dir} up --build -d >/dev/null",
|
"docker compose --project-directory {$configuration_dir} up --build -d",
|
||||||
], $database->destination->server);
|
], $database->destination->server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
158
app/Actions/Database/StartMariadb.php
Normal file
158
app/Actions/Database/StartMariadb.php
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartMariadb
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneMariadb $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneMariadb $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$container_name = $this->database->uuid;
|
||||||
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
|
$this->commands = [
|
||||||
|
"echo '####### Starting {$database->name}.'",
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
];
|
||||||
|
|
||||||
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
$this->add_custom_mysql();
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '5s',
|
||||||
|
'retries' => 10,
|
||||||
|
'start_period' => '5s'
|
||||||
|
],
|
||||||
|
'mem_limit' => $this->database->limits_memory,
|
||||||
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
|
'cpus' => $this->database->limits_cpus,
|
||||||
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $this->database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
|
}
|
||||||
|
if (count($persistent_storages) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
|
}
|
||||||
|
if (count($volume_names) > 0) {
|
||||||
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
}
|
||||||
|
if (!is_null($this->database->mariadb_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $database->destination->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
||||||
|
}
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
|
}
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_mysql()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->mariadb_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'custom-config.cnf';
|
||||||
|
$content = $this->database->mariadb_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
|
}
|
||||||
178
app/Actions/Database/StartMongodb.php
Normal file
178
app/Actions/Database/StartMongodb.php
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartMongodb
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneMongodb $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneMongodb $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$startCommand = "mongod";
|
||||||
|
|
||||||
|
$container_name = $this->database->uuid;
|
||||||
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
|
$this->commands = [
|
||||||
|
"echo '####### Starting {$database->name}.'",
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
];
|
||||||
|
|
||||||
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
$this->add_custom_mongo_conf();
|
||||||
|
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'command' => $startCommand,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => [
|
||||||
|
'CMD-SHELL',
|
||||||
|
'mongosh --eval "printjson(db.runCommand(\"ping\"))"'
|
||||||
|
],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '5s',
|
||||||
|
'retries' => 10,
|
||||||
|
'start_period' => '5s'
|
||||||
|
],
|
||||||
|
'mem_limit' => $this->database->limits_memory,
|
||||||
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
|
'cpus' => $this->database->limits_cpus,
|
||||||
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $this->database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
|
}
|
||||||
|
if (count($persistent_storages) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
|
}
|
||||||
|
if (count($volume_names) > 0) {
|
||||||
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
}
|
||||||
|
if (!is_null($this->database->mongo_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/mongod.conf',
|
||||||
|
'target' => '/etc/mongo/mongod.conf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
|
||||||
|
}
|
||||||
|
$this->add_default_database();
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
|
||||||
|
'target' => '/docker-entrypoint-initdb.d',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $database->destination->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||||
|
}
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_mongo_conf()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->mongo_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'mongod.conf';
|
||||||
|
$content = $this->database->mongo_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
|
private function add_default_database()
|
||||||
|
{
|
||||||
|
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js";
|
||||||
|
}
|
||||||
|
}
|
||||||
158
app/Actions/Database/StartMysql.php
Normal file
158
app/Actions/Database/StartMysql.php
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartMysql
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public StandaloneMysql $database;
|
||||||
|
public array $commands = [];
|
||||||
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
public function handle(StandaloneMysql $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
|
||||||
|
$container_name = $this->database->uuid;
|
||||||
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
|
$this->commands = [
|
||||||
|
"echo '####### Starting {$database->name}.'",
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
];
|
||||||
|
|
||||||
|
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||||
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
$this->add_custom_mysql();
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$container_name => [
|
||||||
|
'image' => $this->database->image,
|
||||||
|
'container_name' => $container_name,
|
||||||
|
'environment' => $environment_variables,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network,
|
||||||
|
],
|
||||||
|
'labels' => [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '5s',
|
||||||
|
'retries' => 10,
|
||||||
|
'start_period' => '5s'
|
||||||
|
],
|
||||||
|
'mem_limit' => $this->database->limits_memory,
|
||||||
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
|
'cpus' => $this->database->limits_cpus,
|
||||||
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$this->database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $this->database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
|
}
|
||||||
|
if (count($persistent_storages) > 0) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||||
|
}
|
||||||
|
if (count($volume_names) > 0) {
|
||||||
|
$docker_compose['volumes'] = $volume_names;
|
||||||
|
}
|
||||||
|
if (!is_null($this->database->mysql_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||||
|
return remote_process($this->commands, $database->destination->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||||
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_local_persistent_volumes_only_volume_names()
|
||||||
|
{
|
||||||
|
$local_persistent_volumes_names = [];
|
||||||
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
|
if ($persistentStorage->host_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $persistentStorage->name;
|
||||||
|
$local_persistent_volumes_names[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'external' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $local_persistent_volumes_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_environment_variables()
|
||||||
|
{
|
||||||
|
$environment_variables = collect();
|
||||||
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
||||||
|
}
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||||
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
|
}
|
||||||
|
return $environment_variables->all();
|
||||||
|
}
|
||||||
|
private function add_custom_mysql()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->mysql_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'custom-config.cnf';
|
||||||
|
$content = $this->database->mysql_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@@ -17,7 +16,7 @@ class StartPostgresql
|
|||||||
public array $init_scripts = [];
|
public array $init_scripts = [];
|
||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
public function handle(Server $server, StandalonePostgresql $database)
|
public function handle(StandalonePostgresql $database)
|
||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
@@ -104,7 +103,7 @@ class StartPostgresql
|
|||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||||
return remote_process($this->commands, $server);
|
return remote_process($this->commands, $database->destination->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
private function generate_local_persistent_volumes()
|
||||||
@@ -145,6 +144,9 @@ class StartPostgresql
|
|||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||||
}
|
}
|
||||||
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
|
||||||
|
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
||||||
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@@ -17,7 +16,7 @@ class StartRedis
|
|||||||
public string $configuration_dir;
|
public string $configuration_dir;
|
||||||
|
|
||||||
|
|
||||||
public function handle(Server $server, StandaloneRedis $database)
|
public function handle(StandaloneRedis $database)
|
||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
@@ -104,7 +103,7 @@ class StartRedis
|
|||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||||
return remote_process($this->commands, $server);
|
return remote_process($this->commands, $database->destination->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
private function generate_local_persistent_volumes()
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopDatabase
|
class StopDatabase
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||||
{
|
{
|
||||||
$server = $database->destination->server;
|
$server = $database->destination->server;
|
||||||
instant_remote_process(
|
instant_remote_process(
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -10,7 +13,7 @@ class StopDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||||
{
|
{
|
||||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
||||||
$database->is_public = false;
|
$database->is_public = false;
|
||||||
|
|||||||
@@ -2,41 +2,50 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Enums\ProxyTypes;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
|
||||||
|
|
||||||
class CheckProxy
|
class CheckProxy
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Server $server)
|
public function handle(Server $server, $fromUI = false)
|
||||||
{
|
{
|
||||||
if (!$server->isProxyShouldRun()) {
|
if (!$server->isProxyShouldRun()) {
|
||||||
throw new \Exception("Proxy should not run");
|
if ($fromUI) {
|
||||||
|
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$status = getContainerStatus($server, 'coolify-proxy');
|
$status = getContainerStatus($server, 'coolify-proxy');
|
||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
$server->proxy->set('status', 'running');
|
$server->proxy->set('status', 'running');
|
||||||
$server->save();
|
$server->save();
|
||||||
return 'OK';
|
return false;
|
||||||
}
|
}
|
||||||
$ip = $server->ip;
|
$ip = $server->ip;
|
||||||
if ($server->id === 0) {
|
if ($server->id === 0) {
|
||||||
$ip = 'host.docker.internal';
|
$ip = 'host.docker.internal';
|
||||||
}
|
}
|
||||||
|
|
||||||
$connection = @fsockopen($ip, '80');
|
$connection80 = @fsockopen($ip, '80');
|
||||||
$connection = @fsockopen($ip, '443');
|
$connection443 = @fsockopen($ip, '443');
|
||||||
$port80 = is_resource($connection) && fclose($connection);
|
$port80 = is_resource($connection80) && fclose($connection80);
|
||||||
$port443 = is_resource($connection) && fclose($connection);
|
$port443 = is_resource($connection443) && fclose($connection443);
|
||||||
ray($ip);
|
|
||||||
if ($port80) {
|
if ($port80) {
|
||||||
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>");
|
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 ($port443) {
|
||||||
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>>");
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ class StartProxy
|
|||||||
public function handle(Server $server, bool $async = true): string|Activity
|
public function handle(Server $server, bool $async = true): string|Activity
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
CheckProxy::run($server);
|
|
||||||
|
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
|
|||||||
@@ -3,8 +3,15 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
@@ -13,9 +20,27 @@ class Init extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
ray()->clearAll();
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
|
$this->cleanup_stucked_resources();
|
||||||
|
// $this->cleanup_ssh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function cleanup_ssh()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$files = Storage::allFiles('ssh/keys');
|
||||||
|
foreach ($files as $file) {
|
||||||
|
Storage::delete($file);
|
||||||
|
}
|
||||||
|
$files = Storage::allFiles('ssh/mux');
|
||||||
|
foreach ($files as $file) {
|
||||||
|
Storage::delete($file);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanup_in_progress_application_deployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
@@ -30,4 +55,93 @@ class Init extends Command
|
|||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function cleanup_stucked_resources()
|
||||||
|
{
|
||||||
|
// Cleanup any resources that are not attached to any environment or destination or server
|
||||||
|
try {
|
||||||
|
$applications = Application::all();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
if (!$application->environment) {
|
||||||
|
ray('Application without environment', $application->name);
|
||||||
|
$application->delete();
|
||||||
|
}
|
||||||
|
if (!$application->destination()) {
|
||||||
|
ray('Application without destination', $application->name);
|
||||||
|
$application->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$postgresqls = StandalonePostgresql::all();
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
if (!$postgresql->environment) {
|
||||||
|
ray('Postgresql without environment', $postgresql->name);
|
||||||
|
$postgresql->delete();
|
||||||
|
}
|
||||||
|
if (!$postgresql->destination()) {
|
||||||
|
ray('Postgresql without destination', $postgresql->name);
|
||||||
|
$postgresql->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$redis = StandaloneRedis::all();
|
||||||
|
foreach ($redis as $redis) {
|
||||||
|
if (!$redis->environment) {
|
||||||
|
ray('Redis without environment', $redis->name);
|
||||||
|
$redis->delete();
|
||||||
|
}
|
||||||
|
if (!$redis->destination()) {
|
||||||
|
ray('Redis without destination', $redis->name);
|
||||||
|
$redis->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$mongodbs = StandaloneMongodb::all();
|
||||||
|
foreach ($mongodbs as $mongodb) {
|
||||||
|
if (!$mongodb->environment) {
|
||||||
|
ray('Mongodb without environment', $mongodb->name);
|
||||||
|
$mongodb->delete();
|
||||||
|
}
|
||||||
|
if (!$mongodb->destination()) {
|
||||||
|
ray('Mongodb without destination', $mongodb->name);
|
||||||
|
$mongodb->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$mysqls = StandaloneMysql::all();
|
||||||
|
foreach ($mysqls as $mysql) {
|
||||||
|
if (!$mysql->environment) {
|
||||||
|
ray('Mysql without environment', $mysql->name);
|
||||||
|
$mysql->delete();
|
||||||
|
}
|
||||||
|
if (!$mysql->destination()) {
|
||||||
|
ray('Mysql without destination', $mysql->name);
|
||||||
|
$mysql->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$mariadbs = StandaloneMysql::all();
|
||||||
|
foreach ($mariadbs as $mariadb) {
|
||||||
|
if (!$mariadb->environment) {
|
||||||
|
ray('Mariadb without environment', $mariadb->name);
|
||||||
|
$mariadb->delete();
|
||||||
|
}
|
||||||
|
if (!$mariadb->destination()) {
|
||||||
|
ray('Mariadb without destination', $mariadb->name);
|
||||||
|
$mariadb->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$services = Service::all();
|
||||||
|
foreach ($services as $service) {
|
||||||
|
if (!$service->environment) {
|
||||||
|
ray('Service without environment', $service->name);
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
if (!$service->server) {
|
||||||
|
ray('Service without server', $service->name);
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
if (!$service->destination()) {
|
||||||
|
ray('Service without destination', $service->name);
|
||||||
|
$service->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
107
app/Console/Commands/ServicesGenerate.php
Normal file
107
app/Console/Commands/ServicesGenerate.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class ServicesGenerate extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'services:generate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
ray()->clearAll();
|
||||||
|
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
|
||||||
|
$files = array_filter($files, function ($file) {
|
||||||
|
return strpos($file, '.yaml') !== false;
|
||||||
|
});
|
||||||
|
$serviceTemplatesJson = [];
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$parsed = $this->process_file($file);
|
||||||
|
if ($parsed) {
|
||||||
|
$name = data_get($parsed, 'name');
|
||||||
|
$parsed = data_forget($parsed, 'name');
|
||||||
|
$serviceTemplatesJson[$name] = $parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT);
|
||||||
|
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function process_file($file)
|
||||||
|
{
|
||||||
|
$serviceName = str($file)->before('.yaml')->value();
|
||||||
|
$content = file_get_contents(base_path("templates/compose/$file"));
|
||||||
|
// $this->info($content);
|
||||||
|
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
|
||||||
|
if ($ignore->count() > 0) {
|
||||||
|
$ignore = (bool)str($ignore[0])->after('# ignore:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$ignore = false;
|
||||||
|
}
|
||||||
|
if ($ignore) {
|
||||||
|
$this->info("Ignoring $file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->info("Processing $file");
|
||||||
|
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
|
||||||
|
if ($documentation->count() > 0) {
|
||||||
|
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$documentation = 'https://coolify.io/docs';
|
||||||
|
}
|
||||||
|
|
||||||
|
$slogan = collect(preg_grep('/^# slogan:/', explode("\n", $content)))->values();
|
||||||
|
if ($slogan->count() > 0) {
|
||||||
|
$slogan = str($slogan[0])->after('# slogan:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$slogan = str($file)->headline()->value();
|
||||||
|
}
|
||||||
|
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
|
||||||
|
if ($env_file->count() > 0) {
|
||||||
|
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
|
||||||
|
} else {
|
||||||
|
$env_file = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tags = collect(preg_grep('/^# tags:/', explode("\n", $content)))->values();
|
||||||
|
if ($tags->count() > 0) {
|
||||||
|
$tags = str($tags[0])->after('# tags:')->trim()->explode(',')->map(function ($tag) {
|
||||||
|
return str($tag)->trim()->lower()->value();
|
||||||
|
})->values();
|
||||||
|
} else {
|
||||||
|
$tags = null;
|
||||||
|
}
|
||||||
|
$json = Yaml::parse($content);
|
||||||
|
$yaml = base64_encode(Yaml::dump($json, 10, 2));
|
||||||
|
$payload = [
|
||||||
|
'name' => $serviceName,
|
||||||
|
'documentation' => $documentation,
|
||||||
|
'slogan' => $slogan,
|
||||||
|
'compose' => $yaml,
|
||||||
|
'tags' => $tags,
|
||||||
|
];
|
||||||
|
if ($env_file) {
|
||||||
|
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
|
||||||
|
$env_file_base64 = base64_encode($env_file_content);
|
||||||
|
$payload['envs'] = $env_file_base64;
|
||||||
|
}
|
||||||
|
return $payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ class SyncBunny extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'sync:bunny {--only-template} {--only-version}';
|
protected $signature = 'sync:bunny {--templates} {--release}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -31,8 +31,8 @@ class SyncBunny extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$that = $this;
|
$that = $this;
|
||||||
$only_template = $this->option('only-template');
|
$only_template = $this->option('templates');
|
||||||
$only_version = $this->option('only-version');
|
$only_version = $this->option('release');
|
||||||
$bunny_cdn = "https://cdn.coollabs.io";
|
$bunny_cdn = "https://cdn.coollabs.io";
|
||||||
$bunny_cdn_path = "coolify";
|
$bunny_cdn_path = "coolify";
|
||||||
$bunny_cdn_storage_name = "coolcdn";
|
$bunny_cdn_storage_name = "coolcdn";
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use App\Jobs\DatabaseBackupJob;
|
|||||||
use App\Jobs\DockerCleanupJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\InstanceAutoUpdateJob;
|
use App\Jobs\InstanceAutoUpdateJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -19,23 +20,35 @@ class Kernel extends ConsoleKernel
|
|||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
|
// Instance Jobs
|
||||||
// $schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
|
||||||
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
// Server Jobs
|
||||||
// $this->instance_auto_update($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
// $this->check_scheduled_backups($schedule);
|
|
||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->cleanup_servers($schedule);
|
$this->cleanup_servers($schedule);
|
||||||
|
$this->check_scheduled_backups($schedule);
|
||||||
|
$this->pull_helper_image($schedule);
|
||||||
} else {
|
} else {
|
||||||
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
|
|
||||||
|
// Server Jobs
|
||||||
$this->instance_auto_update($schedule);
|
$this->instance_auto_update($schedule);
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->cleanup_servers($schedule);
|
$this->cleanup_servers($schedule);
|
||||||
|
$this->pull_helper_image($schedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function pull_helper_image($schedule)
|
||||||
|
{
|
||||||
|
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function cleanup_servers($schedule)
|
private function cleanup_servers($schedule)
|
||||||
@@ -68,7 +81,6 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
private function check_scheduled_backups($schedule)
|
private function check_scheduled_backups($schedule)
|
||||||
{
|
{
|
||||||
ray('check_scheduled_backups');
|
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||||
if ($scheduled_backups->isEmpty()) {
|
if ($scheduled_backups->isEmpty()) {
|
||||||
ray('no scheduled backups');
|
ray('no scheduled backups');
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ class Controller extends BaseController
|
|||||||
} else {
|
} else {
|
||||||
$team = $user->teams()->first();
|
$team = $user->teams()->first();
|
||||||
}
|
}
|
||||||
|
if (is_null(data_get($user, 'email_verified_at'))){
|
||||||
|
$user->email_verified_at = now();
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
session(['currentTeam' => $team]);
|
session(['currentTeam' => $team]);
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ class ProjectController extends Controller
|
|||||||
$database = create_standalone_postgresql($environment->id, $destination_uuid);
|
$database = create_standalone_postgresql($environment->id, $destination_uuid);
|
||||||
} else if ($type->value() === 'redis') {
|
} else if ($type->value() === 'redis') {
|
||||||
$database = create_standalone_redis($environment->id, $destination_uuid);
|
$database = create_standalone_redis($environment->id, $destination_uuid);
|
||||||
|
} else if ($type->value() === 'mongodb') {
|
||||||
|
$database = create_standalone_mongodb($environment->id, $destination_uuid);
|
||||||
|
} else if ($type->value() === 'mysql') {
|
||||||
|
$database = create_standalone_mysql($environment->id, $destination_uuid);
|
||||||
|
}else if ($type->value() === 'mariadb') {
|
||||||
|
$database = create_standalone_mariadb($environment->id, $destination_uuid);
|
||||||
}
|
}
|
||||||
return redirect()->route('project.database.configuration', [
|
return redirect()->route('project.database.configuration', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
@@ -70,7 +76,7 @@ class ProjectController extends Controller
|
|||||||
'database_uuid' => $database->uuid,
|
'database_uuid' => $database->uuid,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
|
if ($type->startsWith('one-click-service-') && !is_null((int)$server_id)) {
|
||||||
$oneClickServiceName = $type->after('one-click-service-')->value();
|
$oneClickServiceName = $type->after('one-click-service-')->value();
|
||||||
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
||||||
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
]);
|
]);
|
||||||
$this->getProxyType();
|
$this->getProxyType();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->dockerInstallationStarted = false;
|
// $this->dockerInstallationStarted = false;
|
||||||
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
|
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
@@ -22,6 +19,11 @@ class General extends Component
|
|||||||
public string $git_branch;
|
public string $git_branch;
|
||||||
public ?string $git_commit_sha = null;
|
public ?string $git_commit_sha = null;
|
||||||
public string $build_pack;
|
public string $build_pack;
|
||||||
|
public ?string $ports_exposes = null;
|
||||||
|
|
||||||
|
public $customLabels;
|
||||||
|
public 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;
|
||||||
@@ -52,6 +54,7 @@ class General extends Component
|
|||||||
'application.docker_registry_image_name' => 'nullable',
|
'application.docker_registry_image_name' => 'nullable',
|
||||||
'application.docker_registry_image_tag' => 'nullable',
|
'application.docker_registry_image_tag' => 'nullable',
|
||||||
'application.dockerfile_location' => 'nullable',
|
'application.dockerfile_location' => 'nullable',
|
||||||
|
'application.custom_labels' => 'nullable',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'application.name' => 'name',
|
'application.name' => 'name',
|
||||||
@@ -73,19 +76,52 @@ class General extends Component
|
|||||||
'application.docker_registry_image_name' => 'Docker registry image name',
|
'application.docker_registry_image_name' => 'Docker registry image name',
|
||||||
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
||||||
'application.dockerfile_location' => 'Dockerfile location',
|
'application.dockerfile_location' => 'Dockerfile location',
|
||||||
|
'application.custom_labels' => 'Custom labels',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function updatedApplicationBuildPack(){
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
|
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') {
|
if ($this->application->build_pack !== 'nixpacks') {
|
||||||
$this->application->settings->is_static = $this->is_static = false;
|
$this->application->settings->is_static = $this->is_static = false;
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
}
|
}
|
||||||
$this->submit();
|
$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
|
||||||
|
$force_https = $this->application->settings->is_force_https_enabled;
|
||||||
$this->application->settings->is_static = $this->is_static;
|
$this->application->settings->is_static = $this->is_static;
|
||||||
if ($this->is_static) {
|
if ($this->is_static) {
|
||||||
$this->application->ports_exposes = 80;
|
$this->application->ports_exposes = 80;
|
||||||
@@ -102,37 +138,43 @@ 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();
|
||||||
|
if ($force_https !== $this->is_force_https_enabled) {
|
||||||
|
$this->resetDefaultLabels(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
if (data_get($this->application,'settings')) {
|
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
||||||
$this->is_static = $this->application->settings->is_static;
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
$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') {
|
if ($this->ports_exposes !== $this->application->ports_exposes) {
|
||||||
|
$this->resetDefaultLabels(false);
|
||||||
|
}
|
||||||
|
if (data_get($this->application, 'build_pack') === 'dockerimage') {
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'application.docker_registry_image_name' => 'required',
|
'application.docker_registry_image_name' => 'required',
|
||||||
'application.docker_registry_image_tag' => 'required',
|
'application.docker_registry_image_tag' => 'required',
|
||||||
@@ -156,10 +198,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
147
app/Http/Livewire/Project/CloneProject.php
Normal file
147
app/Http/Livewire/Project/CloneProject.php
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project;
|
||||||
|
|
||||||
|
use App\Models\Environment;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class CloneProject extends Component
|
||||||
|
{
|
||||||
|
public string $project_uuid;
|
||||||
|
public string $environment_name;
|
||||||
|
public int $project_id;
|
||||||
|
|
||||||
|
public Project $project;
|
||||||
|
public $environments;
|
||||||
|
public $servers;
|
||||||
|
public ?Environment $environment = null;
|
||||||
|
public ?int $selectedServer = null;
|
||||||
|
public ?Server $server = null;
|
||||||
|
public $resources = [];
|
||||||
|
public string $newProjectName = '';
|
||||||
|
|
||||||
|
protected $messages = [
|
||||||
|
'selectedServer' => 'Please select a server.',
|
||||||
|
'newProjectName' => 'Please enter a name for the new project.',
|
||||||
|
];
|
||||||
|
public function mount($project_uuid)
|
||||||
|
{
|
||||||
|
$this->project_uuid = $project_uuid;
|
||||||
|
$this->project = Project::where('uuid', $project_uuid)->firstOrFail();
|
||||||
|
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
||||||
|
$this->project_id = $this->project->id;
|
||||||
|
$this->servers = currentTeam()->servers;
|
||||||
|
$this->newProjectName = $this->project->name . ' (clone)';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.clone-project');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selectServer($server_id)
|
||||||
|
{
|
||||||
|
$this->selectedServer = $server_id;
|
||||||
|
$this->server = $this->servers->where('id', $server_id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clone()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'selectedServer' => 'required',
|
||||||
|
'newProjectName' => 'required',
|
||||||
|
]);
|
||||||
|
$foundProject = Project::where('name', $this->newProjectName)->first();
|
||||||
|
if ($foundProject) {
|
||||||
|
throw new \Exception('Project with the same name already exists.');
|
||||||
|
}
|
||||||
|
$newProject = Project::create([
|
||||||
|
'name' => $this->newProjectName,
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
'description' => $this->project->description . ' (clone)',
|
||||||
|
]);
|
||||||
|
if ($this->environment->name !== 'production') {
|
||||||
|
$newProject->environments()->create([
|
||||||
|
'name' => $this->environment->name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$newEnvironment = $newProject->environments->where('name', $this->environment->name)->first();
|
||||||
|
// Clone Applications
|
||||||
|
$applications = $this->environment->applications;
|
||||||
|
$databases = $this->environment->databases();
|
||||||
|
$services = $this->environment->services;
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$uuid = (string)new Cuid2(7);
|
||||||
|
$newApplication = $application->replicate()->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'fqdn' => generateFqdn($this->server, $uuid),
|
||||||
|
'status' => 'exited',
|
||||||
|
'environment_id' => $newEnvironment->id,
|
||||||
|
'destination_id' => $this->selectedServer,
|
||||||
|
]);
|
||||||
|
$newApplication->save();
|
||||||
|
$environmentVaribles = $application->environment_variables()->get();
|
||||||
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
|
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
||||||
|
'application_id' => $newApplication->id,
|
||||||
|
]);
|
||||||
|
$newEnvironmentVariable->save();
|
||||||
|
}
|
||||||
|
$persistentVolumes = $application->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newPersistentVolume = $volume->replicate()->fill([
|
||||||
|
'name' => $newApplication->uuid . '-' . str($volume->name)->afterLast('-'),
|
||||||
|
'resource_id' => $newApplication->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($databases as $database) {
|
||||||
|
$uuid = (string)new Cuid2(7);
|
||||||
|
$newDatabase = $database->replicate()->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'environment_id' => $newEnvironment->id,
|
||||||
|
'destination_id' => $this->selectedServer,
|
||||||
|
]);
|
||||||
|
$newDatabase->save();
|
||||||
|
$environmentVaribles = $database->environment_variables()->get();
|
||||||
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
|
$payload = [];
|
||||||
|
if ($database->type() === 'standalone-postgres') {
|
||||||
|
$payload['standalone_postgresql_id'] = $newDatabase->id;
|
||||||
|
} else if ($database->type() === 'standalone_redis') {
|
||||||
|
$payload['standalone_redis_id'] = $newDatabase->id;
|
||||||
|
} else if ($database->type() === 'standalone_mongodb') {
|
||||||
|
$payload['standalone_mongodb_id'] = $newDatabase->id;
|
||||||
|
} else if ($database->type() === 'standalone_mysql') {
|
||||||
|
$payload['standalone_mysql_id'] = $newDatabase->id;
|
||||||
|
}else if ($database->type() === 'standalone_mariadb') {
|
||||||
|
$payload['standalone_mariadb_id'] = $newDatabase->id;
|
||||||
|
}
|
||||||
|
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
|
||||||
|
$newEnvironmentVariable->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$uuid = (string)new Cuid2(7);
|
||||||
|
$newService = $service->replicate()->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'environment_id' => $newEnvironment->id,
|
||||||
|
'destination_id' => $this->selectedServer,
|
||||||
|
]);
|
||||||
|
$newService->save();
|
||||||
|
$newService->parse();
|
||||||
|
}
|
||||||
|
return redirect()->route('project.resources', [
|
||||||
|
'project_uuid' => $newProject->uuid,
|
||||||
|
'environment_name' => $newEnvironment->name,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database;
|
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class BackupExecution extends Component
|
|
||||||
{
|
|
||||||
public ScheduledDatabaseBackupExecution $execution;
|
|
||||||
|
|
||||||
public function download()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(): void
|
|
||||||
{
|
|
||||||
delete_backup_locally($this->execution->filename, $this->execution->scheduledDatabaseBackup->database->destination->server);
|
|
||||||
$this->execution->delete();
|
|
||||||
$this->emit('success', 'Backup deleted successfully.');
|
|
||||||
$this->emit('refreshBackupExecutions');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,14 +2,51 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database;
|
namespace App\Http\Livewire\Project\Database;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class BackupExecutions extends Component
|
class BackupExecutions extends Component
|
||||||
{
|
{
|
||||||
public $backup;
|
public $backup;
|
||||||
public $executions;
|
public $executions;
|
||||||
protected $listeners = ['refreshBackupExecutions'];
|
public $setDeletableBackup;
|
||||||
|
protected $listeners = ['refreshBackupExecutions', 'deleteBackup'];
|
||||||
|
|
||||||
|
public function deleteBackup($exeuctionId)
|
||||||
|
{
|
||||||
|
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
|
||||||
|
if (is_null($execution)) {
|
||||||
|
$this->emit('error', 'Backup execution not found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
|
||||||
|
$execution->delete();
|
||||||
|
$this->emit('success', 'Backup deleted successfully.');
|
||||||
|
$this->emit('refreshBackupExecutions');
|
||||||
|
}
|
||||||
|
public function download($exeuctionId)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
|
||||||
|
if (is_null($execution)) {
|
||||||
|
$this->emit('error', 'Backup execution not found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = data_get($execution, 'filename');
|
||||||
|
$server = $execution->scheduledDatabaseBackup->database->destination->server;
|
||||||
|
$privateKeyLocation = savePrivateKeyToFs($server);
|
||||||
|
$disk = Storage::build([
|
||||||
|
'driver' => 'sftp',
|
||||||
|
'host' => $server->ip,
|
||||||
|
'port' => $server->port,
|
||||||
|
'username' => $server->user,
|
||||||
|
'privateKey' => $privateKeyLocation,
|
||||||
|
]);
|
||||||
|
return $disk->download($filename);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function refreshBackupExecutions(): void
|
public function refreshBackupExecutions(): void
|
||||||
{
|
{
|
||||||
$this->executions = $this->backup->executions;
|
$this->executions = $this->backup->executions;
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ class CreateScheduledBackup extends Component
|
|||||||
'frequency' => 'Backup Frequency',
|
'frequency' => 'Backup Frequency',
|
||||||
'save_s3' => 'Save to S3',
|
'save_s3' => 'Save to S3',
|
||||||
];
|
];
|
||||||
|
public function mount() {
|
||||||
|
if ($this->s3s->count() > 0) {
|
||||||
|
$this->s3_storage_id = $this->s3s->first()->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function submit(): void
|
public function submit(): void
|
||||||
{
|
{
|
||||||
@@ -43,6 +48,10 @@ class CreateScheduledBackup extends Component
|
|||||||
];
|
];
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
$payload['databases_to_backup'] = $this->database->postgres_db;
|
$payload['databases_to_backup'] = $this->database->postgres_db;
|
||||||
|
} else if ($this->database->type() === 'standalone-mysql') {
|
||||||
|
$payload['databases_to_backup'] = $this->database->mysql_database;
|
||||||
|
}else if ($this->database->type() === 'standalone-mariadb') {
|
||||||
|
$payload['databases_to_backup'] = $this->database->mariadb_database;
|
||||||
}
|
}
|
||||||
ScheduledDatabaseBackup::create($payload);
|
ScheduledDatabaseBackup::create($payload);
|
||||||
$this->emit('refreshScheduledBackups');
|
$this->emit('refreshScheduledBackups');
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Database;
|
namespace App\Http\Livewire\Project\Database;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartMariadb;
|
||||||
|
use App\Actions\Database\StartMongodb;
|
||||||
|
use App\Actions\Database\StartMysql;
|
||||||
use App\Actions\Database\StartPostgresql;
|
use App\Actions\Database\StartPostgresql;
|
||||||
use App\Actions\Database\StartRedis;
|
use App\Actions\Database\StartRedis;
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
@@ -46,11 +49,19 @@ class Heading extends Component
|
|||||||
public function start()
|
public function start()
|
||||||
{
|
{
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
$activity = StartPostgresql::run($this->database->destination->server, $this->database);
|
$activity = StartPostgresql::run($this->database);
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
}
|
} else if ($this->database->type() === 'standalone-redis') {
|
||||||
if ($this->database->type() === 'standalone-redis') {
|
$activity = StartRedis::run($this->database);
|
||||||
$activity = StartRedis::run($this->database->destination->server, $this->database);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
|
} else if ($this->database->type() === 'standalone-mongodb') {
|
||||||
|
$activity = StartMongodb::run($this->database);
|
||||||
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
|
} else if ($this->database->type() === 'standalone-mysql') {
|
||||||
|
$activity = StartMysql::run($this->database);
|
||||||
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
|
} else if ($this->database->type() === 'standalone-mariadb') {
|
||||||
|
$activity = StartMariadb::run($this->database);
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
95
app/Http/Livewire/Project/Database/Mariadb/General.php
Normal file
95
app/Http/Livewire/Project/Database/Mariadb/General.php
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Database\Mariadb;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use Exception;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class General extends Component
|
||||||
|
{
|
||||||
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
|
public StandaloneMariadb $database;
|
||||||
|
public string $db_url;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'database.name' => 'required',
|
||||||
|
'database.description' => 'nullable',
|
||||||
|
'database.mariadb_root_password' => 'required',
|
||||||
|
'database.mariadb_user' => 'required',
|
||||||
|
'database.mariadb_password' => 'required',
|
||||||
|
'database.mariadb_database' => 'required',
|
||||||
|
'database.mariadb_conf' => 'nullable',
|
||||||
|
'database.image' => 'required',
|
||||||
|
'database.ports_mappings' => 'nullable',
|
||||||
|
'database.is_public' => 'nullable|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'database.name' => 'Name',
|
||||||
|
'database.description' => 'Description',
|
||||||
|
'database.mariadb_root_password' => 'Root Password',
|
||||||
|
'database.mariadb_user' => 'User',
|
||||||
|
'database.mariadb_password' => 'Password',
|
||||||
|
'database.mariadb_database' => 'Database',
|
||||||
|
'database.mariadb_conf' => 'MariaDB Configuration',
|
||||||
|
'database.image' => 'Image',
|
||||||
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
|
'database.is_public' => 'Is Public',
|
||||||
|
'database.public_port' => 'Public Port',
|
||||||
|
];
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
|
$this->emit('error', 'Public port is required.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->db_url = $this->database->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->db_url = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.database.mariadb.general');
|
||||||
|
}
|
||||||
|
}
|
||||||
96
app/Http/Livewire/Project/Database/Mongodb/General.php
Normal file
96
app/Http/Livewire/Project/Database/Mongodb/General.php
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Database\Mongodb;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use Exception;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class General extends Component
|
||||||
|
{
|
||||||
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
|
public StandaloneMongodb $database;
|
||||||
|
public string $db_url;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'database.name' => 'required',
|
||||||
|
'database.description' => 'nullable',
|
||||||
|
'database.mongo_conf' => 'nullable',
|
||||||
|
'database.mongo_initdb_root_username' => 'required',
|
||||||
|
'database.mongo_initdb_root_password' => 'required',
|
||||||
|
'database.mongo_initdb_database' => 'required',
|
||||||
|
'database.image' => 'required',
|
||||||
|
'database.ports_mappings' => 'nullable',
|
||||||
|
'database.is_public' => 'nullable|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'database.name' => 'Name',
|
||||||
|
'database.description' => 'Description',
|
||||||
|
'database.mongo_conf' => 'Mongo Configuration',
|
||||||
|
'database.mongo_initdb_root_username' => 'Root Username',
|
||||||
|
'database.mongo_initdb_root_password' => 'Root Password',
|
||||||
|
'database.mongo_initdb_database' => 'Database',
|
||||||
|
'database.image' => 'Image',
|
||||||
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
|
'database.is_public' => 'Is Public',
|
||||||
|
'database.public_port' => 'Public Port',
|
||||||
|
];
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
if ($this->database->mongo_conf === "") {
|
||||||
|
$this->database->mongo_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) {
|
||||||
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->db_url = $this->database->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->db_url = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.database.mongodb.general');
|
||||||
|
}
|
||||||
|
}
|
||||||
95
app/Http/Livewire/Project/Database/Mysql/General.php
Normal file
95
app/Http/Livewire/Project/Database/Mysql/General.php
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Database\Mysql;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use Exception;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class General extends Component
|
||||||
|
{
|
||||||
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
|
public StandaloneMysql $database;
|
||||||
|
public string $db_url;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'database.name' => 'required',
|
||||||
|
'database.description' => 'nullable',
|
||||||
|
'database.mysql_root_password' => 'required',
|
||||||
|
'database.mysql_user' => 'required',
|
||||||
|
'database.mysql_password' => 'required',
|
||||||
|
'database.mysql_database' => 'required',
|
||||||
|
'database.mysql_conf' => 'nullable',
|
||||||
|
'database.image' => 'required',
|
||||||
|
'database.ports_mappings' => 'nullable',
|
||||||
|
'database.is_public' => 'nullable|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'database.name' => 'Name',
|
||||||
|
'database.description' => 'Description',
|
||||||
|
'database.mysql_root_password' => 'Root Password',
|
||||||
|
'database.mysql_user' => 'User',
|
||||||
|
'database.mysql_password' => 'Password',
|
||||||
|
'database.mysql_database' => 'Database',
|
||||||
|
'database.mysql_conf' => 'MySQL Configuration',
|
||||||
|
'database.image' => 'Image',
|
||||||
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
|
'database.is_public' => 'Is Public',
|
||||||
|
'database.public_port' => 'Public Port',
|
||||||
|
];
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->database->save();
|
||||||
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
|
$this->emit('error', 'Public port is required.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->db_url = $this->database->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->db_url = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.database.mysql.general');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,15 +49,7 @@ class General extends Component
|
|||||||
];
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->getDbUrl();
|
$this->db_url = $this->database->getDbUrl();
|
||||||
}
|
|
||||||
public function getDbUrl() {
|
|
||||||
|
|
||||||
if ($this->database->is_public) {
|
|
||||||
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/{$this->database->postgres_db}";
|
|
||||||
} else {
|
|
||||||
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
@@ -68,20 +60,23 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
$this->emit('success', 'Starting TCP proxy...');
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->getDbUrl();
|
$this->db_url = $this->database->getDbUrl();
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch(\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = !$this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public function save_init_script($script)
|
public function save_init_script($script)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ class General extends Component
|
|||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
];
|
];
|
||||||
public function submit() {
|
public function submit()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
if ($this->database->redis_conf === "") {
|
if ($this->database->redis_conf === "") {
|
||||||
@@ -56,16 +57,20 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
$this->emit('success', 'Starting TCP proxy...');
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->getDbUrl();
|
$this->db_url = $this->database->getDbUrl();
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch(\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = !$this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -77,15 +82,7 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->getDbUrl();
|
$this->db_url = $this->database->getDbUrl();
|
||||||
}
|
|
||||||
public function getDbUrl() {
|
|
||||||
|
|
||||||
if ($this->database->is_public) {
|
|
||||||
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/0";
|
|
||||||
} else {
|
|
||||||
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->uuid}:6379/0";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ class DeleteEnvironment extends Component
|
|||||||
'environment_id' => 'required|int',
|
'environment_id' => 'required|int',
|
||||||
]);
|
]);
|
||||||
$environment = Environment::findOrFail($this->environment_id);
|
$environment = Environment::findOrFail($this->environment_id);
|
||||||
if ($environment->applications->count() > 0) {
|
if ($environment->isEmpty()) {
|
||||||
return $this->emit('error', 'Environment has resources defined, please delete them first.');
|
$environment->delete();
|
||||||
|
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
|
||||||
}
|
}
|
||||||
$environment->delete();
|
return $this->emit('error', 'Environment has defined resources, please delete them first.');
|
||||||
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,18 @@ class Select extends Component
|
|||||||
public Collection|array $swarmDockers = [];
|
public Collection|array $swarmDockers = [];
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public Collection|array $services = [];
|
public Collection|array $services = [];
|
||||||
|
public Collection|array $allServices = [];
|
||||||
|
|
||||||
public bool $loadingServices = true;
|
public bool $loadingServices = true;
|
||||||
public bool $loading = false;
|
public bool $loading = false;
|
||||||
public $environments = [];
|
public $environments = [];
|
||||||
public ?string $selectedEnvironment = null;
|
public ?string $selectedEnvironment = null;
|
||||||
public ?string $existingPostgresqlUrl = null;
|
public ?string $existingPostgresqlUrl = null;
|
||||||
|
|
||||||
|
public ?string $search = null;
|
||||||
protected $queryString = [
|
protected $queryString = [
|
||||||
'server',
|
'server',
|
||||||
|
'search'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -41,6 +45,11 @@ class Select extends Component
|
|||||||
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
|
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
|
||||||
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
|
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
|
||||||
}
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$this->loadServices();
|
||||||
|
return view('livewire.project.new.select');
|
||||||
|
}
|
||||||
|
|
||||||
public function updatedSelectedEnvironment()
|
public function updatedSelectedEnvironment()
|
||||||
{
|
{
|
||||||
@@ -49,6 +58,7 @@ class Select extends Component
|
|||||||
'environment_name' => $this->selectedEnvironment,
|
'environment_name' => $this->selectedEnvironment,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function addExistingPostgresql()
|
// public function addExistingPostgresql()
|
||||||
// {
|
// {
|
||||||
// try {
|
// try {
|
||||||
@@ -59,19 +69,28 @@ class Select extends Component
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
public function loadThings()
|
public function loadServices(bool $force = false)
|
||||||
{
|
|
||||||
$this->loadServices();
|
|
||||||
$this->loadServers();
|
|
||||||
}
|
|
||||||
public function loadServices(bool $forceReload = false)
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($forceReload) {
|
if (count($this->allServices) > 0 && !$force) {
|
||||||
Cache::forget('services');
|
if (!$this->search) {
|
||||||
|
$this->services = $this->allServices;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->services = $this->allServices->filter(function ($service, $key) {
|
||||||
|
$tags = collect(data_get($service, 'tags', []));
|
||||||
|
return str_contains(strtolower($key), strtolower($this->search)) || $tags->contains(function ($tag) {
|
||||||
|
return str_contains(strtolower($tag), strtolower($this->search));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$this->search = null;
|
||||||
|
$this->allServices = getServiceTemplates();
|
||||||
|
$this->services = $this->allServices->filter(function ($service, $key) {
|
||||||
|
return str_contains(strtolower($key), strtolower($this->search));
|
||||||
|
});;
|
||||||
|
$this->emit('success', 'Successfully loaded services.');
|
||||||
}
|
}
|
||||||
$this->services = getServiceTemplates();
|
|
||||||
$this->emit('success', 'Successfully loaded services.');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class ComposeModal extends Component
|
class ComposeModal extends Component
|
||||||
{
|
{
|
||||||
public string $raw;
|
public ?string $raw = null;
|
||||||
public string $actual;
|
public ?string $actual = null;
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.compose-modal');
|
return view('livewire.project.service.compose-modal');
|
||||||
|
|||||||
@@ -31,11 +31,17 @@ class All extends Component
|
|||||||
public function getDevView()
|
public function getDevView()
|
||||||
{
|
{
|
||||||
$this->variables = $this->resource->environment_variables->map(function ($item) {
|
$this->variables = $this->resource->environment_variables->map(function ($item) {
|
||||||
|
if ($item->is_shown_once) {
|
||||||
|
return "$item->key=(locked secret)";
|
||||||
|
}
|
||||||
return "$item->key=$item->value";
|
return "$item->key=$item->value";
|
||||||
})->sort()->join('
|
})->sort()->join('
|
||||||
');
|
');
|
||||||
if ($this->showPreview) {
|
if ($this->showPreview) {
|
||||||
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
|
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
|
||||||
|
if ($item->is_shown_once) {
|
||||||
|
return "$item->key=(locked secret)";
|
||||||
|
}
|
||||||
return "$item->key=$item->value";
|
return "$item->key=$item->value";
|
||||||
})->sort()->join('
|
})->sort()->join('
|
||||||
');
|
');
|
||||||
@@ -49,19 +55,27 @@ class All extends Component
|
|||||||
{
|
{
|
||||||
if ($isPreview) {
|
if ($isPreview) {
|
||||||
$variables = parseEnvFormatToArray($this->variablesPreview);
|
$variables = parseEnvFormatToArray($this->variablesPreview);
|
||||||
$existingVariables = $this->resource->environment_variables_preview();
|
|
||||||
$this->resource->environment_variables_preview()->delete();
|
|
||||||
} else {
|
} else {
|
||||||
$variables = parseEnvFormatToArray($this->variables);
|
$variables = parseEnvFormatToArray($this->variables);
|
||||||
$existingVariables = $this->resource->environment_variables();
|
|
||||||
$this->resource->environment_variables()->delete();
|
|
||||||
}
|
}
|
||||||
foreach ($variables as $key => $variable) {
|
foreach ($variables as $key => $variable) {
|
||||||
$found = $existingVariables->where('key', $key)->first();
|
$found = $this->resource->environment_variables()->where('key', $key)->first();
|
||||||
|
$foundPreview = $this->resource->environment_variables_preview()->where('key', $key)->first();
|
||||||
if ($found) {
|
if ($found) {
|
||||||
|
if ($found->is_shown_once) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$found->value = $variable;
|
$found->value = $variable;
|
||||||
$found->save();
|
$found->save();
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
if ($foundPreview) {
|
||||||
|
if ($foundPreview->is_shown_once) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$foundPreview->value = $variable;
|
||||||
|
$foundPreview->save();
|
||||||
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$environment = new EnvironmentVariable();
|
$environment = new EnvironmentVariable();
|
||||||
$environment->key = $key;
|
$environment->key = $key;
|
||||||
@@ -78,6 +92,15 @@ class All extends Component
|
|||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
$environment->standalone_redis_id = $this->resource->id;
|
$environment->standalone_redis_id = $this->resource->id;
|
||||||
break;
|
break;
|
||||||
|
case 'standalone-mongodb':
|
||||||
|
$environment->standalone_mongodb_id = $this->resource->id;
|
||||||
|
break;
|
||||||
|
case 'standalone-mysql':
|
||||||
|
$environment->standalone_mysql_id = $this->resource->id;
|
||||||
|
break;
|
||||||
|
case 'standalone-mariadb':
|
||||||
|
$environment->standalone_mariadb_id = $this->resource->id;
|
||||||
|
break;
|
||||||
case 'service':
|
case 'service':
|
||||||
$environment->service_id = $this->resource->id;
|
$environment->service_id = $this->resource->id;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
|
|||||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
{
|
{
|
||||||
@@ -13,29 +12,45 @@ class Show extends Component
|
|||||||
public ModelsEnvironmentVariable $env;
|
public ModelsEnvironmentVariable $env;
|
||||||
public ?string $modalId = null;
|
public ?string $modalId = null;
|
||||||
public bool $isDisabled = false;
|
public bool $isDisabled = false;
|
||||||
|
public bool $isLocked = false;
|
||||||
public string $type;
|
public string $type;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'env.key' => 'required|string',
|
'env.key' => 'required|string',
|
||||||
'env.value' => 'nullable',
|
'env.value' => 'nullable',
|
||||||
'env.is_build_time' => 'required|boolean',
|
'env.is_build_time' => 'required|boolean',
|
||||||
|
'env.is_shown_once' => 'required|boolean',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'key' => 'key',
|
'key' => 'Key',
|
||||||
'value' => 'value',
|
'value' => 'Value',
|
||||||
'is_build_time' => 'build',
|
'is_build_time' => 'Build Time',
|
||||||
|
'is_shown_once' => 'Shown Once',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->isDisabled = false;
|
|
||||||
if (Str::of($this->env->key)->startsWith('SERVICE_FQDN') || Str::of($this->env->key)->startsWith('SERVICE_URL')) {
|
|
||||||
$this->isDisabled = true;
|
|
||||||
}
|
|
||||||
$this->modalId = new Cuid2(7);
|
$this->modalId = new Cuid2(7);
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
|
$this->checkEnvs();
|
||||||
|
}
|
||||||
|
public function checkEnvs()
|
||||||
|
{
|
||||||
|
$this->isDisabled = false;
|
||||||
|
if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) {
|
||||||
|
$this->isDisabled = true;
|
||||||
|
}
|
||||||
|
if ($this->env->is_shown_once) {
|
||||||
|
$this->isLocked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function lock()
|
||||||
|
{
|
||||||
|
$this->env->is_shown_once = true;
|
||||||
|
$this->env->save();
|
||||||
|
$this->checkEnvs();
|
||||||
|
$this->emit('refreshEnvs');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
$this->submit();
|
$this->submit();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class GetLogs extends Component
|
|||||||
public Server $server;
|
public Server $server;
|
||||||
public ?string $container = null;
|
public ?string $container = null;
|
||||||
public ?bool $streamLogs = false;
|
public ?bool $streamLogs = false;
|
||||||
|
public ?bool $showTimeStamps = true;
|
||||||
public int $numberOfLines = 100;
|
public int $numberOfLines = 100;
|
||||||
public function doSomethingWithThisChunkOfOutput($output)
|
public function doSomethingWithThisChunkOfOutput($output)
|
||||||
{
|
{
|
||||||
@@ -24,7 +25,11 @@ class GetLogs extends Component
|
|||||||
public function getLogs($refresh = false)
|
public function getLogs($refresh = false)
|
||||||
{
|
{
|
||||||
if ($this->container) {
|
if ($this->container) {
|
||||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
if ($this->showTimeStamps) {
|
||||||
|
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
||||||
|
} else {
|
||||||
|
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
|
||||||
|
}
|
||||||
if ($refresh) {
|
if ($refresh) {
|
||||||
$this->outputs = '';
|
$this->outputs = '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ namespace App\Http\Livewire\Project\Shared;
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -12,7 +15,7 @@ use Livewire\Component;
|
|||||||
class Logs extends Component
|
class Logs extends Component
|
||||||
{
|
{
|
||||||
public ?string $type = null;
|
public ?string $type = null;
|
||||||
public Application|StandalonePostgresql|Service|StandaloneRedis $resource;
|
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public ?string $container = null;
|
public ?string $container = null;
|
||||||
public $parameters;
|
public $parameters;
|
||||||
@@ -38,7 +41,16 @@ class Logs extends Component
|
|||||||
if (is_null($resource)) {
|
if (is_null($resource)) {
|
||||||
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
if (is_null($resource)) {
|
if (is_null($resource)) {
|
||||||
abort(404);
|
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
|
if (is_null($resource)) {
|
||||||
|
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
|
if (is_null($resource)) {
|
||||||
|
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||||
|
if (is_null($resource)) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->resource = $resource;
|
$this->resource = $resource;
|
||||||
|
|||||||
19
app/Http/Livewire/Project/Shared/Webhooks.php
Normal file
19
app/Http/Livewire/Project/Shared/Webhooks.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Webhooks extends Component
|
||||||
|
{
|
||||||
|
public $resource;
|
||||||
|
public ?string $deploywebhook = null;
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->deploywebhook = generateDeployWebhook($this->resource);
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.shared.webhooks');
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/Http/Livewire/Security/ApiTokens.php
Normal file
38
app/Http/Livewire/Security/ApiTokens.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Security;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class ApiTokens extends Component
|
||||||
|
{
|
||||||
|
public ?string $description = null;
|
||||||
|
public $tokens = [];
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.security.api-tokens');
|
||||||
|
}
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->tokens = auth()->user()->tokens;
|
||||||
|
}
|
||||||
|
public function addNewToken()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'description' => 'required|min:3|max:255',
|
||||||
|
]);
|
||||||
|
$token = auth()->user()->createToken($this->description);
|
||||||
|
$this->tokens = auth()->user()->tokens;
|
||||||
|
session()->flash('token', $token->plainTextToken);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function revoke(int $id)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->tokens()->where('id', $id)->first();
|
||||||
|
$token->delete();
|
||||||
|
$this->tokens = auth()->user()->tokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ class Deploy extends Component
|
|||||||
public function checkProxy()
|
public function checkProxy()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
CheckProxy::run($this->server);
|
CheckProxy::run($this->server, true);
|
||||||
$this->emit('startProxyPolling');
|
$this->emit('startProxyPolling');
|
||||||
$this->emit('proxyChecked');
|
$this->emit('proxyChecked');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class Status extends Component
|
|||||||
}
|
}
|
||||||
$this->numberOfPolls++;
|
$this->numberOfPolls++;
|
||||||
}
|
}
|
||||||
CheckProxy::run($this->server);
|
CheckProxy::run($this->server, true);
|
||||||
$this->emit('proxyStatusUpdated');
|
$this->emit('proxyStatusUpdated');
|
||||||
if ($this->server->proxy->status === 'running') {
|
if ($this->server->proxy->status === 'running') {
|
||||||
$this->polling = false;
|
$this->polling = false;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class DecideWhatToDoWithUser
|
|||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||||
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||||
return redirect('boarding');
|
return redirect('boarding');
|
||||||
}
|
}
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
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 $basedir;
|
||||||
private string $workdir;
|
private string $workdir;
|
||||||
private ?string $build_pack = null;
|
private ?string $build_pack = null;
|
||||||
@@ -71,10 +71,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
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';
|
||||||
|
|
||||||
|
private int $customPort = 22;
|
||||||
|
|
||||||
|
private ?string $fullRepoUrl = null;
|
||||||
|
private ?string $branch = null;
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -92,7 +101,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
$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->basedir = "/artifacts/{$this->deployment_uuid}";
|
$this->basedir = "/artifacts/{$this->deployment_uuid}";
|
||||||
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
|
$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}";
|
||||||
@@ -160,6 +169,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return "--add-host $name:$ip";
|
return "--add-host $name:$ip";
|
||||||
})->implode(' ');
|
})->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);
|
||||||
|
|
||||||
|
// Check custom port
|
||||||
|
preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches);
|
||||||
|
if (count($matches) === 1) {
|
||||||
|
$this->customPort = $matches[0];
|
||||||
|
$gitHost = str($this->application->git_repository)->before(':');
|
||||||
|
$gitRepo = str($this->application->git_repository)->after('/');
|
||||||
|
$this->application->git_repository = "$gitHost:$gitRepo";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if ($this->application->dockerfile) {
|
if ($this->application->dockerfile) {
|
||||||
$this->deploy_simple_dockerfile();
|
$this->deploy_simple_dockerfile();
|
||||||
@@ -178,6 +199,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
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);
|
||||||
@@ -331,7 +353,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->clone_repository();
|
$this->check_git_if_build_needed();
|
||||||
$this->set_base_dir();
|
$this->set_base_dir();
|
||||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
||||||
if (strlen($tag) > 128) {
|
if (strlen($tag) > 128) {
|
||||||
@@ -346,15 +368,21 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$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->clone_repository();
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
$this->generate_nixpacks_confs();
|
$this->generate_nixpacks_confs();
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
@@ -450,19 +478,22 @@ 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";
|
|
||||||
$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 -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 -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||||
|
}
|
||||||
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n 'Pulling helper image from $helperImage.'",
|
"echo -n 'Preparing container with helper image: $helperImage.'",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
$runCommand,
|
$runCommand,
|
||||||
@@ -482,27 +513,44 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
private function check_git_if_build_needed()
|
||||||
|
{
|
||||||
|
$this->generate_git_import_commands();
|
||||||
|
$private_key = base64_encode($this->application->private_key->private_key);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->fullRepoUrl} {$this->branch}"),
|
||||||
|
"hidden" => true,
|
||||||
|
"save" => "git_commit_sha"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
|
||||||
|
}
|
||||||
private function clone_repository()
|
private function clone_repository()
|
||||||
{
|
{
|
||||||
|
$importCommands = $this->generate_git_import_commands();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
|
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
$this->importing_git_repository(), "hidden" => true
|
$importCommands, "hidden" => true
|
||||||
],
|
]
|
||||||
[
|
|
||||||
executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git rev-parse HEAD"),
|
|
||||||
"hidden" => true,
|
|
||||||
"save" => "git_commit_sha"
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
$this->commit = $this->saved_outputs->get('git_commit_sha');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function importing_git_repository()
|
private function generate_git_import_commands()
|
||||||
{
|
{
|
||||||
|
$this->branch = $this->application->git_branch;
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
$git_clone_command = "git clone -q -b {$this->application->git_branch}";
|
$git_clone_command = "git clone -q -b {$this->application->git_branch}";
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
@@ -517,6 +565,7 @@ 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) {
|
||||||
|
$this->fullRepoUrl = "{$this->source->html_url}/{$this->application->git_repository}";
|
||||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->basedir}";
|
$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);
|
||||||
|
|
||||||
@@ -524,21 +573,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
} else {
|
} else {
|
||||||
$github_access_token = generate_github_installation_token($this->source);
|
$github_access_token = generate_github_installation_token($this->source);
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->basedir}"));
|
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->basedir}"));
|
||||||
|
$this->fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git";
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
|
$this->branch = "pull/{$this->pull_request_id}/head:$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"));
|
$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;
|
$this->fullRepoUrl = $this->application->git_repository;
|
||||||
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 -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 = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->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"),
|
||||||
@@ -549,10 +596,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
if ($this->application->deploymentType() === 'other') {
|
if ($this->application->deploymentType() === 'other') {
|
||||||
|
$this->fullRepoUrl = $this->application->git_repository;
|
||||||
$git_clone_command = "{$git_clone_command} {$this->application->git_repository} {$this->basedir}";
|
$git_clone_command = "{$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->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
||||||
ray($commands);
|
|
||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,6 +685,12 @@ 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);
|
||||||
|
|
||||||
|
if (data_get($this->application, 'custom_labels')) {
|
||||||
|
$labels = collect(str($this->application->custom_labels)->explode(',')->toArray());
|
||||||
|
} else {
|
||||||
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
|
}
|
||||||
|
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
'version' => '3.8',
|
||||||
'services' => [
|
'services' => [
|
||||||
@@ -646,7 +699,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, $ports),
|
'labels' => $labels,
|
||||||
'expose' => $ports,
|
'expose' => $ports,
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->destination->network,
|
$this->destination->network,
|
||||||
@@ -825,7 +878,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ray("docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}");
|
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]);
|
]);
|
||||||
@@ -853,20 +905,20 @@ 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 --build -d >/dev/null"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_build_env_variables()
|
private function generate_build_env_variables()
|
||||||
{
|
{
|
||||||
$this->build_args = collect(["--build-arg SOURCE_COMMIT={$this->commit}"]);
|
$this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]);
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
foreach ($this->application->build_environment_variables as $env) {
|
foreach ($this->application->build_environment_variables as $env) {
|
||||||
$this->build_args->push("--build-arg {$env->key}={$env->value}");
|
$this->build_args->push("--build-arg {$env->key}=\"{$env->value}\"");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||||
$this->build_args->push("--build-arg {$env->key}={$env->value}");
|
$this->build_args->push("--build-arg {$env->key}=\"{$env->value}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
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;
|
||||||
@@ -11,7 +12,6 @@ use App\Notifications\Server\Revived;
|
|||||||
use App\Notifications\Server\Unreachable;
|
use App\Notifications\Server\Unreachable;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
@@ -24,30 +24,23 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $tries = 1;
|
|
||||||
public $timeout = 120;
|
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): string
|
|
||||||
{
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server)
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
}
|
||||||
$this->handle();
|
public function middleware(): array
|
||||||
}
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle()
|
public function uniqueId(): int
|
||||||
{
|
{
|
||||||
|
return $this->server->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// ray("checking server status for {$this->server->id}");
|
||||||
try {
|
try {
|
||||||
// ray("checking server status for {$this->server->id}");
|
|
||||||
// ray()->clearAll();
|
// ray()->clearAll();
|
||||||
$serverUptimeCheckNumber = $this->server->unreachable_count;
|
$serverUptimeCheckNumber = $this->server->unreachable_count;
|
||||||
$serverUptimeCheckNumberMax = 3;
|
$serverUptimeCheckNumberMax = 3;
|
||||||
@@ -117,9 +110,16 @@ 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) {
|
||||||
if ($this->server->isProxyShouldRun()) {
|
try {
|
||||||
StartProxy::run($this->server, false);
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
} else {
|
||||||
|
ray('Proxy could not be started.');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
@@ -299,7 +299,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());
|
||||||
return handleError($e);
|
handleError($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ use App\Models\S3Storage;
|
|||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
use App\Models\ScheduledDatabaseBackupExecution;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Database\BackupFailed;
|
use App\Notifications\Database\BackupFailed;
|
||||||
@@ -27,7 +30,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public ?Team $team = null;
|
public ?Team $team = null;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public ScheduledDatabaseBackup $backup;
|
public ScheduledDatabaseBackup $backup;
|
||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database;
|
||||||
|
|
||||||
public ?string $container_name = null;
|
public ?string $container_name = null;
|
||||||
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
||||||
@@ -72,12 +75,36 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (is_null($databasesToBackup)) {
|
if (is_null($databasesToBackup)) {
|
||||||
if ($databaseType === 'standalone-postgresql') {
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
$databasesToBackup = [$this->database->postgres_db];
|
$databasesToBackup = [$this->database->postgres_db];
|
||||||
|
} else if ($databaseType === 'standalone-mongodb') {
|
||||||
|
$databasesToBackup = ['*'];
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
$databasesToBackup = [$this->database->mysql_database];
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
$databasesToBackup = [$this->database->mariadb_database];
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$databasesToBackup = explode(',', $databasesToBackup);
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
$databasesToBackup = array_map('trim', $databasesToBackup);
|
// Format: db1,db2,db3
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
} else if ($databaseType === 'standalone-mongodb') {
|
||||||
|
// Format: db1:collection1,collection2|db2:collection3,collection4
|
||||||
|
$databasesToBackup = explode('|', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
ray($databasesToBackup);
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
// Format: db1,db2,db3
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
// Format: db1,db2,db3
|
||||||
|
$databasesToBackup = explode(',', $databasesToBackup);
|
||||||
|
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->container_name = $this->database->uuid;
|
$this->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;
|
||||||
@@ -92,15 +119,54 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$size = 0;
|
$size = 0;
|
||||||
ray('Backing up ' . $database);
|
ray('Backing up ' . $database);
|
||||||
try {
|
try {
|
||||||
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
|
||||||
$this->backup_location = $this->backup_dir . $this->backup_file;
|
|
||||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
|
||||||
'database_name' => $database,
|
|
||||||
'filename' => $this->backup_location,
|
|
||||||
'scheduled_database_backup_id' => $this->backup->id,
|
|
||||||
]);
|
|
||||||
if ($databaseType === 'standalone-postgresql') {
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
|
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
||||||
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
|
'database_name' => $database,
|
||||||
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
$this->backup_standalone_postgresql($database);
|
$this->backup_standalone_postgresql($database);
|
||||||
|
} else if ($databaseType === 'standalone-mongodb') {
|
||||||
|
if ($database === '*') {
|
||||||
|
$database = 'all';
|
||||||
|
$databaseName = 'all';
|
||||||
|
} else {
|
||||||
|
if (str($database)->contains(':')) {
|
||||||
|
$databaseName = str($database)->before(':');
|
||||||
|
} else {
|
||||||
|
$databaseName = $database;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz";
|
||||||
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
|
'database_name' => $databaseName,
|
||||||
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
|
$this->backup_standalone_mongodb($database);
|
||||||
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
|
$this->backup_file = "/mysql-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
||||||
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
|
'database_name' => $database,
|
||||||
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
|
$this->backup_standalone_mysql($database);
|
||||||
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
|
$this->backup_file = "/mariadb-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
||||||
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
|
'database_name' => $database,
|
||||||
|
'filename' => $this->backup_location,
|
||||||
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
|
]);
|
||||||
|
$this->backup_standalone_mariadb($database);
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Unsupported database type');
|
||||||
}
|
}
|
||||||
$size = $this->calculate_size();
|
$size = $this->calculate_size();
|
||||||
$this->remove_old_backups();
|
$this->remove_old_backups();
|
||||||
@@ -114,12 +180,14 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
'size' => $size,
|
'size' => $size,
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->backup_log->update([
|
if ($this->backup_log) {
|
||||||
'status' => 'failed',
|
$this->backup_log->update([
|
||||||
'message' => $this->backup_output,
|
'status' => 'failed',
|
||||||
'size' => $size,
|
'message' => $this->backup_output,
|
||||||
'filename' => null
|
'size' => $size,
|
||||||
]);
|
'filename' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||||
throw $e;
|
throw $e;
|
||||||
@@ -130,11 +198,43 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$url = $this->database->getDbUrl(useInternal: true);
|
||||||
|
if ($databaseWithCollections === 'all') {
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
||||||
|
} else {
|
||||||
|
if (str($databaseWithCollections)->contains(':')) {
|
||||||
|
$databaseName = str($databaseWithCollections)->before(':');
|
||||||
|
$collectionsToExclude = str($databaseWithCollections)->after(':')->explode(',');
|
||||||
|
} else {
|
||||||
|
$databaseName = $databaseWithCollections;
|
||||||
|
$collectionsToExclude = collect();
|
||||||
|
}
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
if ($collectionsToExclude->count() === 0) {
|
||||||
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
|
||||||
|
} else {
|
||||||
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
|
$this->backup_output = trim($this->backup_output);
|
||||||
|
if ($this->backup_output === '') {
|
||||||
|
$this->backup_output = null;
|
||||||
|
}
|
||||||
|
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->add_to_backup_output($e->getMessage());
|
||||||
|
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
private function backup_standalone_postgresql(string $database): void
|
private function backup_standalone_postgresql(string $database): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ray($this->backup_dir);
|
|
||||||
$commands[] = "mkdir -p " . $this->backup_dir;
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
@@ -149,7 +249,42 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function backup_standalone_mysql(string $database): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
|
||||||
|
ray($commands);
|
||||||
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
|
$this->backup_output = trim($this->backup_output);
|
||||||
|
if ($this->backup_output === '') {
|
||||||
|
$this->backup_output = null;
|
||||||
|
}
|
||||||
|
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->add_to_backup_output($e->getMessage());
|
||||||
|
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function backup_standalone_mariadb(string $database): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
|
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
|
||||||
|
ray($commands);
|
||||||
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
|
$this->backup_output = trim($this->backup_output);
|
||||||
|
if ($this->backup_output === '') {
|
||||||
|
$this->backup_output = null;
|
||||||
|
}
|
||||||
|
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->add_to_backup_output($e->getMessage());
|
||||||
|
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
private function add_to_backup_output($output): void
|
private function add_to_backup_output($output): void
|
||||||
{
|
{
|
||||||
if ($this->backup_output) {
|
if ($this->backup_output) {
|
||||||
@@ -189,11 +324,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$bucket = $this->s3->bucket;
|
$bucket = $this->s3->bucket;
|
||||||
$endpoint = $this->s3->endpoint;
|
$endpoint = $this->s3->endpoint;
|
||||||
$this->s3->testConnection();
|
$this->s3->testConnection();
|
||||||
if (isDev()) {
|
$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 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 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}/";
|
||||||
|
|||||||
45
app/Jobs/PullHelperImageJob.php
Normal file
45
app/Jobs/PullHelperImageJob.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 1000;
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uniqueId(): string
|
||||||
|
{
|
||||||
|
return $this->server->uuid;
|
||||||
|
}
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$helperImage = config('coolify.helper_image');
|
||||||
|
ray("Pulling {$helperImage}");
|
||||||
|
instant_remote_process(["docker pull -q {$helperImage}"], $this->server, false);
|
||||||
|
ray('PullHelperImageJob done');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification('PullHelperImageJob failed with: ' . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,9 @@ use App\Actions\Database\StopDatabase;
|
|||||||
use App\Actions\Service\StopService;
|
use App\Actions\Service\StopService;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
@@ -20,7 +23,7 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis $resource)
|
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +44,15 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
StopDatabase::run($this->resource);
|
StopDatabase::run($this->resource);
|
||||||
break;
|
break;
|
||||||
|
case 'standalone-mongodb':
|
||||||
|
StopDatabase::run($this->resource);
|
||||||
|
break;
|
||||||
|
case 'standalone-mysql':
|
||||||
|
StopDatabase::run($this->resource);
|
||||||
|
break;
|
||||||
|
case 'standalone-mariadb':
|
||||||
|
StopDatabase::run($this->resource);
|
||||||
|
break;
|
||||||
case 'service':
|
case 'service':
|
||||||
StopService::run($this->resource);
|
StopService::run($this->resource);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -277,4 +277,31 @@ class Application extends BaseModel
|
|||||||
}
|
}
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class Environment extends Model
|
class Environment extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $guarded = [];
|
||||||
'name',
|
public function isEmpty()
|
||||||
'project_id',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function can_delete_environment()
|
|
||||||
{
|
{
|
||||||
return $this->applications()->count() == 0 && $this->redis()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
|
return $this->applications()->count() == 0 &&
|
||||||
|
$this->redis()->count() == 0 &&
|
||||||
|
$this->postgresqls()->count() == 0 &&
|
||||||
|
$this->mongodbs()->count() == 0 &&
|
||||||
|
$this->services()->count() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function applications()
|
public function applications()
|
||||||
@@ -30,12 +30,27 @@ class Environment extends Model
|
|||||||
{
|
{
|
||||||
return $this->hasMany(StandaloneRedis::class);
|
return $this->hasMany(StandaloneRedis::class);
|
||||||
}
|
}
|
||||||
|
public function mongodbs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMongodb::class);
|
||||||
|
}
|
||||||
|
public function mysqls()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMysql::class);
|
||||||
|
}
|
||||||
|
public function mariadbs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMariadb::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function databases()
|
public function databases()
|
||||||
{
|
{
|
||||||
$postgresqls = $this->postgresqls;
|
$postgresqls = $this->postgresqls;
|
||||||
$redis = $this->redis;
|
$redis = $this->redis;
|
||||||
return $postgresqls->concat($redis);
|
$mongodbs = $this->mongodbs;
|
||||||
|
$mysqls = $this->mysqls;
|
||||||
|
$mariadbs = $this->mariadbs;
|
||||||
|
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function project()
|
public function project()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class EnvironmentVariable extends Model
|
|||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
"key" => 'string',
|
'key' => 'string',
|
||||||
'value' => 'encrypted',
|
'value' => 'encrypted',
|
||||||
'is_build_time' => 'boolean',
|
'is_build_time' => 'boolean',
|
||||||
];
|
];
|
||||||
@@ -21,6 +21,10 @@ class EnvironmentVariable extends Model
|
|||||||
static::created(function ($environment_variable) {
|
static::created(function ($environment_variable) {
|
||||||
if ($environment_variable->application_id && !$environment_variable->is_preview) {
|
if ($environment_variable->application_id && !$environment_variable->is_preview) {
|
||||||
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first();
|
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first();
|
||||||
|
$application = Application::find($environment_variable->application_id);
|
||||||
|
if ($application->build_pack === 'dockerfile') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!$found) {
|
if (!$found) {
|
||||||
ModelsEnvironmentVariable::create([
|
ModelsEnvironmentVariable::create([
|
||||||
'key' => $environment_variable->key,
|
'key' => $environment_variable->key,
|
||||||
@@ -33,7 +37,8 @@ class EnvironmentVariable extends Model
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public function service() {
|
public function service()
|
||||||
|
{
|
||||||
return $this->belongsTo(Service::class);
|
return $this->belongsTo(Service::class);
|
||||||
}
|
}
|
||||||
protected function value(): Attribute
|
protected function value(): Attribute
|
||||||
@@ -55,9 +60,9 @@ class EnvironmentVariable extends Model
|
|||||||
$variable = Str::after($environment_variable, 'global.');
|
$variable = Str::after($environment_variable, 'global.');
|
||||||
$variable = Str::before($variable, '}}');
|
$variable = Str::before($variable, '}}');
|
||||||
$variable = Str::of($variable)->trim()->value;
|
$variable = Str::of($variable)->trim()->value;
|
||||||
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
|
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
|
||||||
ray('global env variable');
|
ray('global env variable');
|
||||||
return $environment_variable;
|
return $environment_variable;
|
||||||
}
|
}
|
||||||
return $environment_variable;
|
return $environment_variable;
|
||||||
}
|
}
|
||||||
@@ -77,5 +82,4 @@ class EnvironmentVariable extends Model
|
|||||||
set: fn (string $value) => Str::of($value)->trim(),
|
set: fn (string $value) => Str::of($value)->trim(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
15
app/Models/PersonalAccessToken.php
Normal file
15
app/Models/PersonalAccessToken.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
|
||||||
|
|
||||||
|
class PersonalAccessToken extends SanctumPersonalAccessToken
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'token',
|
||||||
|
'abilities',
|
||||||
|
'expires_at',
|
||||||
|
'team_id',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ class Project extends BaseModel
|
|||||||
'project_id' => $project->id,
|
'project_id' => $project->id,
|
||||||
]);
|
]);
|
||||||
Environment::create([
|
Environment::create([
|
||||||
'name' => 'Production',
|
'name' => 'production',
|
||||||
'project_id' => $project->id,
|
'project_id' => $project->id,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -56,4 +56,16 @@ class Project extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->hasManyThrough(StandaloneRedis::class, Environment::class);
|
return $this->hasManyThrough(StandaloneRedis::class, Environment::class);
|
||||||
}
|
}
|
||||||
|
public function mongodbs()
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(StandaloneMongodb::class, Environment::class);
|
||||||
|
}
|
||||||
|
public function mysqls()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMysql::class, Environment::class);
|
||||||
|
}
|
||||||
|
public function mariadbs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StandaloneMariadb::class, Environment::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,9 +122,12 @@ class Server extends BaseModel
|
|||||||
public function databases()
|
public function databases()
|
||||||
{
|
{
|
||||||
return $this->destinations()->map(function ($standaloneDocker) {
|
return $this->destinations()->map(function ($standaloneDocker) {
|
||||||
$postgresqls = $standaloneDocker->postgresqls;
|
$postgresqls = data_get($standaloneDocker, 'postgresqls', collect([]));
|
||||||
$redis = $standaloneDocker->redis;
|
$redis = data_get($standaloneDocker, 'redis', collect([]));
|
||||||
return $postgresqls->concat($redis);
|
$mongodbs = data_get($standaloneDocker, 'mongodbs', collect([]));
|
||||||
|
$mysqls = data_get($standaloneDocker, 'mysqls', collect([]));
|
||||||
|
$mariadbs = data_get($standaloneDocker, 'mariadbs', collect([]));
|
||||||
|
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||||
})->flatten();
|
})->flatten();
|
||||||
}
|
}
|
||||||
public function applications()
|
public function applications()
|
||||||
@@ -193,23 +196,24 @@ 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()
|
||||||
{
|
{
|
||||||
@@ -256,7 +260,8 @@ class Server extends BaseModel
|
|||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public function validateCoolifyNetwork() {
|
public function validateCoolifyNetwork()
|
||||||
|
{
|
||||||
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class ServerSetting extends Model
|
class ServerSetting extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $guarded = [];
|
||||||
'server_id',
|
|
||||||
'is_usable',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class Service extends BaseModel
|
|||||||
|
|
||||||
public function documentation()
|
public function documentation()
|
||||||
{
|
{
|
||||||
$services = Cache::get('services', []);
|
$services = getServiceTemplates();
|
||||||
$service = data_get($services, Str::of($this->name)->beforeLast('-')->value, []);
|
$service = data_get($services, Str::of($this->name)->beforeLast('-')->value, []);
|
||||||
return data_get($service, 'documentation', config('constants.docs.base_url'));
|
return data_get($service, 'documentation', config('constants.docs.base_url'));
|
||||||
}
|
}
|
||||||
@@ -538,7 +538,7 @@ 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, true));
|
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($this->uuid, $fqdns, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data_set($service, 'labels', $serviceLabels->toArray());
|
data_set($service, 'labels', $serviceLabels->toArray());
|
||||||
@@ -568,7 +568,7 @@ class Service extends BaseModel
|
|||||||
'networks' => $topLevelNetworks->toArray(),
|
'networks' => $topLevelNetworks->toArray(),
|
||||||
];
|
];
|
||||||
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||||
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
|
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
|
||||||
$this->save();
|
$this->save();
|
||||||
$this->saveComposeConfigs();
|
$this->saveComposeConfigs();
|
||||||
return collect([]);
|
return collect([]);
|
||||||
|
|||||||
@@ -15,10 +15,23 @@ class StandaloneDocker extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function redis()
|
public function redis()
|
||||||
{
|
{
|
||||||
return $this->morphMany(StandaloneRedis::class, 'destination');
|
return $this->morphMany(StandaloneRedis::class, 'destination');
|
||||||
}
|
}
|
||||||
|
public function mongodbs()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneMongodb::class, 'destination');
|
||||||
|
}
|
||||||
|
public function mysqls()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneMysql::class, 'destination');
|
||||||
|
}
|
||||||
|
public function mariadbs()
|
||||||
|
{
|
||||||
|
return $this->morphMany(StandaloneMariadb::class, 'destination');
|
||||||
|
}
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
@@ -30,6 +43,16 @@ class StandaloneDocker extends BaseModel
|
|||||||
return $this->morphMany(Service::class, 'destination');
|
return $this->morphMany(Service::class, 'destination');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databases()
|
||||||
|
{
|
||||||
|
$postgresqls = $this->postgresqls;
|
||||||
|
$redis = $this->redis;
|
||||||
|
$mongodbs = $this->mongodbs;
|
||||||
|
$mysqls = $this->mysqls;
|
||||||
|
$mariadbs = $this->mariadbs;
|
||||||
|
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||||
|
}
|
||||||
|
|
||||||
public function attachedTo()
|
public function attachedTo()
|
||||||
{
|
{
|
||||||
return $this->applications?->count() > 0 || $this->databases?->count() > 0;
|
return $this->applications?->count() > 0 || $this->databases?->count() > 0;
|
||||||
|
|||||||
106
app/Models/StandaloneMariadb.php
Normal file
106
app/Models/StandaloneMariadb.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class StandaloneMariadb extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
protected $casts = [
|
||||||
|
'mariadb_password' => 'encrypted',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::created(function ($database) {
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => 'mariadb-data-' . $database->uuid,
|
||||||
|
'mount_path' => '/var/lib/mysql',
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
'is_readonly' => true
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
static::deleting(function ($database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
|
||||||
|
}
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
|
$database->persistentStorages()->delete();
|
||||||
|
$database->environment_variables()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return 'standalone-mariadb';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappings(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: fn ($value) => $value === "" ? null : $value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappingsArray(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => is_null($this->ports_mappings)
|
||||||
|
? []
|
||||||
|
: explode(',', $this->ports_mappings),
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDbUrl(bool $useInternal = false): string
|
||||||
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
|
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
|
||||||
|
} else {
|
||||||
|
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Environment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destination()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runtime_environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function persistentStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scheduledBackups()
|
||||||
|
{
|
||||||
|
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||||
|
}
|
||||||
|
}
|
||||||
122
app/Models/StandaloneMongodb.php
Normal file
122
app/Models/StandaloneMongodb.php
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class StandaloneMongodb extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::created(function ($database) {
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => 'mongodb-configdb-' . $database->uuid,
|
||||||
|
'mount_path' => '/data/configdb',
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
'is_readonly' => true
|
||||||
|
]);
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => 'mongodb-db-' . $database->uuid,
|
||||||
|
'mount_path' => '/data/db',
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
'is_readonly' => true
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
static::deleting(function ($database) {
|
||||||
|
$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 mongoInitdbRootPassword(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function ($value) {
|
||||||
|
try {
|
||||||
|
return decrypt($value);
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
$this->mongo_initdb_root_password = encrypt($value);
|
||||||
|
$this->save();
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function portsMappings(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: fn ($value) => $value === "" ? null : $value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappingsArray(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => is_null($this->ports_mappings)
|
||||||
|
? []
|
||||||
|
: explode(',', $this->ports_mappings),
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return 'standalone-mongodb';
|
||||||
|
}
|
||||||
|
public function getDbUrl(bool $useInternal = false)
|
||||||
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
|
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
|
||||||
|
} else {
|
||||||
|
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function environment()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Environment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destination()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runtime_environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function persistentStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scheduledBackups()
|
||||||
|
{
|
||||||
|
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||||
|
}
|
||||||
|
}
|
||||||
106
app/Models/StandaloneMysql.php
Normal file
106
app/Models/StandaloneMysql.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class StandaloneMysql extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
protected $casts = [
|
||||||
|
'mysql_password' => 'encrypted',
|
||||||
|
'mysql_root_password' => 'encrypted',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::created(function ($database) {
|
||||||
|
LocalPersistentVolume::create([
|
||||||
|
'name' => 'mysql-data-' . $database->uuid,
|
||||||
|
'mount_path' => '/var/lib/mysql',
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
'is_readonly' => true
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
static::deleting(function ($database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
|
||||||
|
}
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
|
$database->persistentStorages()->delete();
|
||||||
|
$database->environment_variables()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return 'standalone-mysql';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappings(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: fn ($value) => $value === "" ? null : $value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function portsMappingsArray(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => is_null($this->ports_mappings)
|
||||||
|
? []
|
||||||
|
: explode(',', $this->ports_mappings),
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDbUrl(bool $useInternal = false): string
|
||||||
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
|
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
|
||||||
|
} else {
|
||||||
|
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Environment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destination()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runtime_environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function persistentStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scheduledBackups()
|
||||||
|
{
|
||||||
|
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,8 +46,6 @@ class StandalonePostgresql extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal Deployments
|
|
||||||
|
|
||||||
public function portsMappingsArray(): Attribute
|
public function portsMappingsArray(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -62,6 +60,14 @@ class StandalonePostgresql extends BaseModel
|
|||||||
{
|
{
|
||||||
return 'standalone-postgresql';
|
return 'standalone-postgresql';
|
||||||
}
|
}
|
||||||
|
public function getDbUrl(bool $useInternal = false): string
|
||||||
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
|
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
|
||||||
|
} else {
|
||||||
|
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ class StandaloneRedis extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal Deployments
|
|
||||||
|
|
||||||
public function portsMappingsArray(): Attribute
|
public function portsMappingsArray(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -57,6 +55,13 @@ class StandaloneRedis extends BaseModel
|
|||||||
{
|
{
|
||||||
return 'standalone-redis';
|
return 'standalone-redis';
|
||||||
}
|
}
|
||||||
|
public function getDbUrl(): string {
|
||||||
|
if ($this->is_public) {
|
||||||
|
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||||
|
} else {
|
||||||
|
return "redis://:{$this->redis_password}@{$this->uuid}:6379/0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,14 +39,18 @@ class Subscription extends Model
|
|||||||
if (!$subscription) {
|
if (!$subscription) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$subscriptionPlanId = data_get($subscription,'stripe_plan_id');
|
$subscriptionPlanId = data_get($subscription, 'stripe_plan_id');
|
||||||
if (!$subscriptionPlanId) {
|
if (!$subscriptionPlanId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
$subscriptionInvoicePaid = data_get($subscription, 'stripe_invoice_paid');
|
||||||
|
if (!$subscriptionInvoicePaid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
$subscriptionConfigs = collect(config('subscription'));
|
$subscriptionConfigs = collect(config('subscription'));
|
||||||
$stripePlanId = null;
|
$stripePlanId = null;
|
||||||
$subscriptionConfigs->map(function ($value, $key) use ($subscriptionPlanId, &$stripePlanId) {
|
$subscriptionConfigs->map(function ($value, $key) use ($subscriptionPlanId, &$stripePlanId) {
|
||||||
if ($value === $subscriptionPlanId){
|
if ($value === $subscriptionPlanId) {
|
||||||
$stripePlanId = $key;
|
$stripePlanId = $key;
|
||||||
};
|
};
|
||||||
})->first();
|
})->first();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
use App\Notifications\Channels\SendsEmail;
|
use App\Notifications\Channels\SendsEmail;
|
||||||
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
||||||
|
use DateTimeInterface;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
@@ -14,6 +15,8 @@ use Illuminate\Support\Facades\Config;
|
|||||||
use Illuminate\Support\Facades\URL;
|
use Illuminate\Support\Facades\URL;
|
||||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
use Laravel\Sanctum\NewAccessToken;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class User extends Authenticatable implements SendsEmail
|
class User extends Authenticatable implements SendsEmail
|
||||||
{
|
{
|
||||||
@@ -47,7 +50,26 @@ class User extends Authenticatable implements SendsEmail
|
|||||||
$user->teams()->attach($new_team, ['role' => 'owner']);
|
$user->teams()->attach($new_team, ['role' => 'owner']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public function createToken(string $name, array $abilities = ['*'], DateTimeInterface $expiresAt = null)
|
||||||
|
{
|
||||||
|
ray('asd');
|
||||||
|
$plainTextToken = sprintf(
|
||||||
|
'%s%s%s',
|
||||||
|
config('sanctum.token_prefix', ''),
|
||||||
|
$tokenEntropy = Str::random(40),
|
||||||
|
hash('crc32b', $tokenEntropy)
|
||||||
|
);
|
||||||
|
|
||||||
|
$token = $this->tokens()->create([
|
||||||
|
'name' => $name,
|
||||||
|
'token' => hash('sha256', $plainTextToken),
|
||||||
|
'abilities' => $abilities,
|
||||||
|
'expires_at' => $expiresAt,
|
||||||
|
'team_id' => session('currentTeam')->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken);
|
||||||
|
}
|
||||||
public function teams()
|
public function teams()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Team::class)->withPivot('role');
|
return $this->belongsToMany(Team::class)->withPivot('role');
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ class EmailChannel
|
|||||||
);
|
);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
send_internal_notification("EmailChannel error: {$e->getMessage()}. Failed to send email to: " . implode(', ', $recepients) . " with subject: {$mailMessage->subject}");
|
$message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:";
|
||||||
|
if (isset($recepients)) {
|
||||||
|
$message .= implode(', ', $recepients);
|
||||||
|
}
|
||||||
|
$message .= " with subject: {$mailMessage->subject}";
|
||||||
|
send_internal_notification($message);
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ namespace App\Providers;
|
|||||||
|
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
use App\Models\PersonalAccessToken;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -13,6 +15,8 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
|
||||||
|
|
||||||
Http::macro('github', function (string $api_url, string|null $github_access_token = null) {
|
Http::macro('github', function (string $api_url, string|null $github_access_token = null) {
|
||||||
if ($github_access_token) {
|
if ($github_access_token) {
|
||||||
return Http::withHeaders([
|
return Http::withHeaders([
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
RateLimiter::for('api', function (Request $request) {
|
RateLimiter::for('api', function (Request $request) {
|
||||||
if ($request->path() === 'api/health') {
|
if ($request->path() === 'api/health') {
|
||||||
return Limit::perMinute(5000)->by($request->user()?->id ?: $request->ip());
|
return Limit::perMinute(1000)->by($request->user()?->id ?: $request->ip());
|
||||||
}
|
}
|
||||||
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
return Limit::perMinute(200)->by($request->user()?->id ?: $request->ip());
|
||||||
});
|
});
|
||||||
RateLimiter::for('5', function (Request $request) {
|
RateLimiter::for('5', function (Request $request) {
|
||||||
return Limit::perMinute(5)->by($request->user()?->id ?: $request->ip());
|
return Limit::perMinute(5)->by($request->user()?->id ?: $request->ip());
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
const DATABASE_TYPES = ['postgresql','redis'];
|
const DATABASE_TYPES = ['postgresql', 'redis', 'mongodb', 'mysql', 'mariadb'];
|
||||||
const VALID_CRON_STRINGS = [
|
const VALID_CRON_STRINGS = [
|
||||||
'every_minute' => '* * * * *',
|
'every_minute' => '* * * * *',
|
||||||
'hourly' => '0 * * * *',
|
'hourly' => '0 * * * *',
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
@@ -43,6 +46,51 @@ function create_standalone_redis($environment_id, $destination_uuid): Standalone
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function create_standalone_mongodb($environment_id, $destination_uuid): StandaloneMongodb
|
||||||
|
{
|
||||||
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
|
if (!$destination) {
|
||||||
|
throw new Exception('Destination not found');
|
||||||
|
}
|
||||||
|
return StandaloneMongodb::create([
|
||||||
|
'name' => generate_database_name('mongodb'),
|
||||||
|
'mongo_initdb_root_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||||
|
'environment_id' => $environment_id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination->getMorphClass(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
function create_standalone_mysql($environment_id, $destination_uuid): StandaloneMysql
|
||||||
|
{
|
||||||
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
|
if (!$destination) {
|
||||||
|
throw new Exception('Destination not found');
|
||||||
|
}
|
||||||
|
return StandaloneMysql::create([
|
||||||
|
'name' => generate_database_name('mysql'),
|
||||||
|
'mysql_root_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||||
|
'mysql_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||||
|
'environment_id' => $environment_id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination->getMorphClass(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
function create_standalone_mariadb($environment_id, $destination_uuid): StandaloneMariadb
|
||||||
|
{
|
||||||
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
|
if (!$destination) {
|
||||||
|
throw new Exception('Destination not found');
|
||||||
|
}
|
||||||
|
return StandaloneMariadb::create([
|
||||||
|
'name' => generate_database_name('mariadb'),
|
||||||
|
'mariadb_root_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||||
|
'mariadb_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||||
|
'environment_id' => $environment_id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination->getMorphClass(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete file locally on the filesystem.
|
* Delete file locally on the filesystem.
|
||||||
* @param string $filename
|
* @param string $filename
|
||||||
|
|||||||
@@ -147,12 +147,11 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
|
|||||||
}
|
}
|
||||||
return $labels;
|
return $labels;
|
||||||
}
|
}
|
||||||
function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled, $onlyPort = null)
|
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled, $onlyPort = null)
|
||||||
{
|
{
|
||||||
$labels = collect([]);
|
$labels = collect([]);
|
||||||
$labels->push('traefik.enable=true');
|
$labels->push('traefik.enable=true');
|
||||||
foreach ($domains as $domain) {
|
foreach ($domains as $loop => $domain) {
|
||||||
$uuid = (string)new Cuid2(7);
|
|
||||||
$url = Url::fromString($domain);
|
$url = Url::fromString($domain);
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
$path = $url->getPath();
|
$path = $url->getPath();
|
||||||
@@ -161,8 +160,8 @@ function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled,
|
|||||||
if (is_null($port) && !is_null($onlyPort)) {
|
if (is_null($port) && !is_null($onlyPort)) {
|
||||||
$port = $onlyPort;
|
$port = $onlyPort;
|
||||||
}
|
}
|
||||||
$http_label = "{$uuid}-http";
|
$http_label = "{$uuid}-{$loop}-http";
|
||||||
$https_label = "{$uuid}-https";
|
$https_label = "{$uuid}-{$loop}-https";
|
||||||
|
|
||||||
if ($schema === 'https') {
|
if ($schema === 'https') {
|
||||||
// Set labels for https
|
// Set labels for https
|
||||||
@@ -205,20 +204,19 @@ function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled,
|
|||||||
|
|
||||||
return $labels;
|
return $labels;
|
||||||
}
|
}
|
||||||
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null, $ports): array
|
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array
|
||||||
{
|
{
|
||||||
|
$ports = $application->settings->is_static ? [80] : $application->ports_exposes_array;
|
||||||
$onlyPort = null;
|
$onlyPort = null;
|
||||||
if (count($ports) === 1) {
|
if (count($ports) === 1) {
|
||||||
$onlyPort = $ports[0];
|
$onlyPort = $ports[0];
|
||||||
}
|
}
|
||||||
$pull_request_id = data_get($preview, 'pull_request_id', 0);
|
$pull_request_id = data_get($preview, 'pull_request_id', 0);
|
||||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
|
||||||
$appId = $application->id;
|
$appId = $application->id;
|
||||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||||
$appId = $appId . '-pr-' . $pull_request_id;
|
$appId = $appId . '-pr-' . $pull_request_id;
|
||||||
}
|
}
|
||||||
$labels = collect([]);
|
$labels = collect([]);
|
||||||
$labels = $labels->merge(defaultLabels($appId, $container_name, $pull_request_id));
|
|
||||||
if ($application->fqdn) {
|
if ($application->fqdn) {
|
||||||
if ($pull_request_id !== 0) {
|
if ($pull_request_id !== 0) {
|
||||||
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
|
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
|
||||||
@@ -226,7 +224,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
||||||
}
|
}
|
||||||
// Add Traefik labels no matter which proxy is selected
|
// Add Traefik labels no matter which proxy is selected
|
||||||
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $application->settings->is_force_https_enabled,$onlyPort));
|
$labels = $labels->merge(fqdnLabelsForTraefik($application->uuid, $domains, $application->settings->is_force_https_enabled, $onlyPort));
|
||||||
}
|
}
|
||||||
return $labels->all();
|
return $labels->all();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
@@ -437,9 +444,6 @@ function getServiceTemplates()
|
|||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$services = File::get(base_path('templates/service-templates.json'));
|
$services = File::get(base_path('templates/service-templates.json'));
|
||||||
$services = collect(json_decode($services))->sortKeys();
|
$services = collect(json_decode($services))->sortKeys();
|
||||||
$deprecated = File::get(base_path('templates/deprecated.json'));
|
|
||||||
$deprecated = collect(json_decode($deprecated))->sortKeys();
|
|
||||||
$services = $services->merge($deprecated);
|
|
||||||
$version = config('version');
|
$version = config('version');
|
||||||
$services = $services->map(function ($service) use ($version) {
|
$services = $services->map(function ($service) use ($version) {
|
||||||
if (version_compare($version, data_get($service, 'minVersion', '0.0.0'), '<')) {
|
if (version_compare($version, data_get($service, 'minVersion', '0.0.0'), '<')) {
|
||||||
@@ -456,3 +460,44 @@ function getServiceTemplates()
|
|||||||
}
|
}
|
||||||
return $services;
|
return $services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getResourceByUuid(string $uuid, ?int $teamId = null)
|
||||||
|
{
|
||||||
|
$resource = queryResourcesByUuid($uuid);
|
||||||
|
if (!is_null($teamId)) {
|
||||||
|
if (!is_null($resource) && $resource->environment->project->team_id === $teamId) {
|
||||||
|
return $resource;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return $resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function queryResourcesByUuid(string $uuid)
|
||||||
|
{
|
||||||
|
$resource = null;
|
||||||
|
$application = Application::whereUuid($uuid)->first();
|
||||||
|
if ($application) return $application;
|
||||||
|
$service = Service::whereUuid($uuid)->first();
|
||||||
|
if ($service) return $service;
|
||||||
|
$postgresql = StandalonePostgresql::whereUuid($uuid)->first();
|
||||||
|
if ($postgresql) return $postgresql;
|
||||||
|
$redis = StandaloneRedis::whereUuid($uuid)->first();
|
||||||
|
if ($redis) return $redis;
|
||||||
|
$mongodb = StandaloneMongodb::whereUuid($uuid)->first();
|
||||||
|
if ($mongodb) return $mongodb;
|
||||||
|
$mysql = StandaloneMysql::whereUuid($uuid)->first();
|
||||||
|
if ($mysql) return $mysql;
|
||||||
|
$mariadb = StandaloneMariadb::whereUuid($uuid)->first();
|
||||||
|
if ($mariadb) return $mariadb;
|
||||||
|
return $resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDeployWebhook($resource) {
|
||||||
|
$baseUrl = base_url();
|
||||||
|
$api = Url::fromString($baseUrl) . '/api/v1';
|
||||||
|
$endpoint = '/deploy';
|
||||||
|
$uuid = data_get($resource, 'uuid');
|
||||||
|
$url = $api . $endpoint . "?uuid=$uuid&force=false";
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ function allowedPathsForInvalidAccounts() {
|
|||||||
return [
|
return [
|
||||||
'logout',
|
'logout',
|
||||||
'verify',
|
'verify',
|
||||||
|
'force-password-reset',
|
||||||
|
'livewire/message/force-password-reset',
|
||||||
'livewire/message/verify-email',
|
'livewire/message/verify-email',
|
||||||
'livewire/message/help'
|
'livewire/message/help'
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"laravel/ui": "^4.2",
|
"laravel/ui": "^4.2",
|
||||||
"lcobucci/jwt": "^5.0.0",
|
"lcobucci/jwt": "^5.0.0",
|
||||||
"league/flysystem-aws-s3-v3": "^3.0",
|
"league/flysystem-aws-s3-v3": "^3.0",
|
||||||
|
"league/flysystem-sftp-v3": "^3.0",
|
||||||
"livewire/livewire": "^v2.12.3",
|
"livewire/livewire": "^v2.12.3",
|
||||||
"lorisleiva/laravel-actions": "^2.7",
|
"lorisleiva/laravel-actions": "^2.7",
|
||||||
"masmerise/livewire-toaster": "^1.2",
|
"masmerise/livewire-toaster": "^1.2",
|
||||||
|
|||||||
62
composer.lock
generated
62
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "de2c45be3f03d43430549d963778dc4a",
|
"content-hash": "21ed976753483557403be75318585442",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
@@ -2938,6 +2938,66 @@
|
|||||||
],
|
],
|
||||||
"time": "2023-08-30T10:23:59+00:00"
|
"time": "2023-08-30T10:23:59+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "league/flysystem-sftp-v3",
|
||||||
|
"version": "3.16.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thephpleague/flysystem-sftp-v3.git",
|
||||||
|
"reference": "1ba682def8e87fd7fa00883629553c0200d2e974"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/1ba682def8e87fd7fa00883629553c0200d2e974",
|
||||||
|
"reference": "1ba682def8e87fd7fa00883629553c0200d2e974",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"league/flysystem": "^3.0.14",
|
||||||
|
"league/mime-type-detection": "^1.0.0",
|
||||||
|
"php": "^8.0.2",
|
||||||
|
"phpseclib/phpseclib": "^3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"League\\Flysystem\\PhpseclibV3\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Frank de Jonge",
|
||||||
|
"email": "info@frankdejonge.nl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "SFTP filesystem adapter for Flysystem.",
|
||||||
|
"keywords": [
|
||||||
|
"Flysystem",
|
||||||
|
"file",
|
||||||
|
"files",
|
||||||
|
"filesystem",
|
||||||
|
"sftp"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thephpleague/flysystem-sftp-v3/issues",
|
||||||
|
"source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.16.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://ecologi.com/frankdejonge",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/frankdejonge",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-08-30T10:25:05+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "league/mime-type-detection",
|
"name": "league/mime-type-detection",
|
||||||
"version": "1.13.0",
|
"version": "1.13.0",
|
||||||
|
|||||||
@@ -210,13 +210,13 @@ return [
|
|||||||
'production' => [
|
'production' => [
|
||||||
's6' => [
|
's6' => [
|
||||||
'autoScalingStrategy' => 'size',
|
'autoScalingStrategy' => 'size',
|
||||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 10),
|
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||||
],
|
],
|
||||||
'long-running' => [
|
'long-running' => [
|
||||||
'autoScalingStrategy' => 'size',
|
'autoScalingStrategy' => 'size',
|
||||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 10),
|
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||||
],
|
],
|
||||||
@@ -225,13 +225,13 @@ return [
|
|||||||
'local' => [
|
'local' => [
|
||||||
's6' => [
|
's6' => [
|
||||||
'autoScalingStrategy' => 'size',
|
'autoScalingStrategy' => 'size',
|
||||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 10),
|
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||||
],
|
],
|
||||||
'long-running' => [
|
'long-running' => [
|
||||||
'autoScalingStrategy' => 'size',
|
'autoScalingStrategy' => 'size',
|
||||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 10),
|
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
||||||
'dsn' => 'https://72f02655749d5d687297b6b9f078b8b9@o1082494.ingest.sentry.io/4505347448045568',
|
'dsn' => 'https://c35fe90ee56e18b220bb55e8217d4839@o1082494.ingest.sentry.io/4505347448045568',
|
||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.87',
|
'release' => '4.0.0-beta.106',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.87';
|
return '4.0.0-beta.106';
|
||||||
|
|||||||
23
database/factories/StandaloneMongoDBFactory.php
Normal file
23
database/factories/StandaloneMongoDBFactory.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\StandaloneMongodb>
|
||||||
|
*/
|
||||||
|
class StandaloneMongodbFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->text('custom_labels')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('custom_labels');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('standalone_mongodbs', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('uuid')->unique();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('description')->nullable();
|
||||||
|
|
||||||
|
$table->text('mongo_conf')->nullable();
|
||||||
|
$table->text('mongo_initdb_root_username')->default('root');
|
||||||
|
$table->text('mongo_initdb_root_password');
|
||||||
|
$table->text('mongo_initdb_database')->default('default');
|
||||||
|
|
||||||
|
$table->string('status')->default('exited');
|
||||||
|
|
||||||
|
$table->string('image')->default('mongo:7');
|
||||||
|
|
||||||
|
$table->boolean('is_public')->default(false);
|
||||||
|
$table->integer('public_port')->nullable();
|
||||||
|
$table->text('ports_mappings')->nullable();
|
||||||
|
|
||||||
|
$table->string('limits_memory')->default("0");
|
||||||
|
$table->string('limits_memory_swap')->default("0");
|
||||||
|
$table->integer('limits_memory_swappiness')->default(60);
|
||||||
|
$table->string('limits_memory_reservation')->default("0");
|
||||||
|
|
||||||
|
$table->string('limits_cpus')->default("0");
|
||||||
|
$table->string('limits_cpuset')->nullable()->default("0");
|
||||||
|
$table->integer('limits_cpu_shares')->default(1024);
|
||||||
|
|
||||||
|
$table->timestamp('started_at')->nullable();
|
||||||
|
$table->morphs('destination');
|
||||||
|
$table->foreignId('environment_id')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('standalone_mongodbs');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->foreignId('standalone_mongodb_id')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('standalone_mongodb_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('standalone_mysqls', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('uuid')->unique();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('description')->nullable();
|
||||||
|
|
||||||
|
$table->text('mysql_root_password');
|
||||||
|
$table->string('mysql_user')->default('mysql');
|
||||||
|
$table->text('mysql_password');
|
||||||
|
$table->string('mysql_database')->default('default');
|
||||||
|
$table->longText('mysql_conf')->nullable();
|
||||||
|
|
||||||
|
$table->string('status')->default('exited');
|
||||||
|
|
||||||
|
$table->string('image')->default('mysql:8');
|
||||||
|
$table->boolean('is_public')->default(false);
|
||||||
|
$table->integer('public_port')->nullable();
|
||||||
|
$table->text('ports_mappings')->nullable();
|
||||||
|
|
||||||
|
$table->string('limits_memory')->default("0");
|
||||||
|
$table->string('limits_memory_swap')->default("0");
|
||||||
|
$table->integer('limits_memory_swappiness')->default(60);
|
||||||
|
$table->string('limits_memory_reservation')->default("0");
|
||||||
|
|
||||||
|
$table->string('limits_cpus')->default("0");
|
||||||
|
$table->string('limits_cpuset')->nullable()->default("0");
|
||||||
|
$table->integer('limits_cpu_shares')->default(1024);
|
||||||
|
|
||||||
|
$table->timestamp('started_at')->nullable();
|
||||||
|
$table->morphs('destination');
|
||||||
|
|
||||||
|
$table->foreignId('environment_id')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('standalone_mysqls');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('standalone_mariadbs', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('uuid')->unique();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('description')->nullable();
|
||||||
|
|
||||||
|
$table->text('mariadb_root_password');
|
||||||
|
$table->string('mariadb_user')->default('mariadb');
|
||||||
|
$table->text('mariadb_password');
|
||||||
|
$table->string('mariadb_database')->default('default');
|
||||||
|
$table->longText('mariadb_conf')->nullable();
|
||||||
|
|
||||||
|
$table->string('status')->default('exited');
|
||||||
|
|
||||||
|
$table->string('image')->default('mariadb:11');
|
||||||
|
$table->boolean('is_public')->default(false);
|
||||||
|
$table->integer('public_port')->nullable();
|
||||||
|
$table->text('ports_mappings')->nullable();
|
||||||
|
|
||||||
|
$table->string('limits_memory')->default("0");
|
||||||
|
$table->string('limits_memory_swap')->default("0");
|
||||||
|
$table->integer('limits_memory_swappiness')->default(60);
|
||||||
|
$table->string('limits_memory_reservation')->default("0");
|
||||||
|
|
||||||
|
$table->string('limits_cpus')->default("0");
|
||||||
|
$table->string('limits_cpuset')->nullable()->default("0");
|
||||||
|
$table->integer('limits_cpu_shares')->default(1024);
|
||||||
|
|
||||||
|
$table->timestamp('started_at')->nullable();
|
||||||
|
$table->morphs('destination');
|
||||||
|
|
||||||
|
$table->foreignId('environment_id')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('standalone_mariadbs');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->foreignId('standalone_mysql_id')->nullable();
|
||||||
|
$table->foreignId('standalone_mariadb_id')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('standalone_mysql_id');
|
||||||
|
$table->dropColumn('standalone_mariadb_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_shown_once')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_shown_once');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -37,7 +37,7 @@ class ApplicationSeeder extends Seeder
|
|||||||
'git_repository' => 'coollabsio/coolify-examples',
|
'git_repository' => 'coollabsio/coolify-examples',
|
||||||
'git_branch' => 'dockerfile',
|
'git_branch' => 'dockerfile',
|
||||||
'build_pack' => 'dockerfile',
|
'build_pack' => 'dockerfile',
|
||||||
'ports_exposes' => '3000',
|
'ports_exposes' => '80',
|
||||||
'environment_id' => 1,
|
'environment_id' => 1,
|
||||||
'destination_id' => 0,
|
'destination_id' => 0,
|
||||||
'destination_type' => StandaloneDocker::class,
|
'destination_type' => StandaloneDocker::class,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ services:
|
|||||||
POSTGRES_DB: "${DB_DATABASE:-coolify}"
|
POSTGRES_DB: "${DB_DATABASE:-coolify}"
|
||||||
POSTGRES_HOST_AUTH_METHOD: "trust"
|
POSTGRES_HOST_AUTH_METHOD: "trust"
|
||||||
volumes:
|
volumes:
|
||||||
- ./_data/coolify/_volumes/database/:/var/lib/postgresql/data
|
- /data/coolify/_volumes/database/:/var/lib/postgresql/data
|
||||||
# - coolify-pg-data-dev:/var/lib/postgresql/data
|
# - coolify-pg-data-dev:/var/lib/postgresql/data
|
||||||
redis:
|
redis:
|
||||||
ports:
|
ports:
|
||||||
@@ -42,7 +42,7 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
- ./_data/coolify/_volumes/redis/:/data
|
- /data/coolify/_volumes/redis/:/data
|
||||||
# - coolify-redis-data-dev:/data
|
# - coolify-redis-data-dev:/data
|
||||||
vite:
|
vite:
|
||||||
image: node:19
|
image: node:19
|
||||||
@@ -58,7 +58,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /:/host
|
- /:/host
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ./_data/coolify/:/data/coolify
|
- /data/coolify/:/data/coolify
|
||||||
# - coolify-data-dev:/data/coolify
|
# - coolify-data-dev:/data/coolify
|
||||||
mailpit:
|
mailpit:
|
||||||
image: "axllent/mailpit:latest"
|
image: "axllent/mailpit:latest"
|
||||||
@@ -79,7 +79,7 @@ services:
|
|||||||
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}"
|
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}"
|
||||||
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}"
|
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./_data/coolify/_volumes/minio/:/data
|
- /data/coolify/_volumes/minio/:/data
|
||||||
# - coolify-minio-data-dev:/data
|
# - coolify-minio-data-dev:/data
|
||||||
networks:
|
networks:
|
||||||
- coolify
|
- coolify
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ services:
|
|||||||
- QUEUE_CONNECTION
|
- QUEUE_CONNECTION
|
||||||
- REDIS_HOST
|
- REDIS_HOST
|
||||||
- REDIS_PASSWORD
|
- REDIS_PASSWORD
|
||||||
|
- HORIZON_MAX_PROCESSES
|
||||||
- SSL_MODE=off
|
- SSL_MODE=off
|
||||||
- PHP_PM_CONTROL=dynamic
|
- PHP_PM_CONTROL=dynamic
|
||||||
- PHP_PM_START_SERVERS=1
|
- PHP_PM_START_SERVERS=1
|
||||||
|
|||||||
@@ -28,3 +28,4 @@ networks:
|
|||||||
coolify:
|
coolify:
|
||||||
name: coolify
|
name: coolify
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
external: true
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres
|
|
||||||
command: 'postgres -c config_file=/etc/postgresql/postgresql.conf'
|
|
||||||
volumes:
|
|
||||||
- type: bind
|
|
||||||
source: ./postgresql.conf
|
|
||||||
target: /etc/postgresql/postgresql.conf
|
|
||||||
- type: bind
|
|
||||||
source: ./docker-entrypoint-initdb.d
|
|
||||||
target: /docker-entrypoint-initdb.d/
|
|
||||||
isDirectory: true
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: $SERVICE_USER_POSTGRES
|
|
||||||
POSTGRES_PASSWORD: $SERVICE_PASSWORD_POSTGRES
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
services:
|
|
||||||
uptime-kuma:
|
|
||||||
image: louislam/uptime-kuma:1
|
|
||||||
volumes:
|
|
||||||
- uptime-kuma:/app/data
|
|
||||||
@@ -53,12 +53,14 @@ a {
|
|||||||
@apply text-white;
|
@apply text-white;
|
||||||
}
|
}
|
||||||
.box {
|
.box {
|
||||||
@apply flex items-center p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
|
@apply flex p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
|
||||||
}
|
}
|
||||||
.box-without-bg {
|
.box-without-bg {
|
||||||
@apply flex items-center p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
|
@apply flex p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
@apply pt-2 text-xs font-bold text-neutral-500 group-hover:text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-heart {
|
.lds-heart {
|
||||||
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<div >
|
<div>
|
||||||
<div class="flex items-center p-1 px-2 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200"
|
<div class="flex items-center p-1 px-2 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200"
|
||||||
@click="showCommandPalette = true">
|
@click="showCommandPalette = true">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24" stroke-width="2"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24" stroke-width="2"
|
||||||
@@ -54,11 +54,12 @@
|
|||||||
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
|
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
|
||||||
will be:
|
will be:
|
||||||
<span class="inline-block text-warning">{{ search }}</span>
|
<span class="inline-block text-warning">{{ search }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-else><span class="capitalize ">{{
|
<span v-else><span class="capitalize ">{{
|
||||||
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
|
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
|
||||||
will be:
|
will be:
|
||||||
<span class="inline-block text-warning">randomly generated (type to change)</span>
|
<span class="inline-block text-warning">randomly generated (type to
|
||||||
|
change)</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -338,82 +339,96 @@ const magicActions = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 11,
|
id: 11,
|
||||||
name: 'Goto: Dashboard',
|
name: 'Goto: S3 Storage',
|
||||||
|
tags: 's3,storage',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 12,
|
id: 12,
|
||||||
name: 'Goto: Servers',
|
name: 'Goto: Dashboard',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 13,
|
id: 13,
|
||||||
|
name: 'Goto: Servers',
|
||||||
|
icon: 'goto',
|
||||||
|
sequence: ['main', 'redirect']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
name: 'Goto: Private Keys',
|
name: 'Goto: Private Keys',
|
||||||
tags: 'destination,docker,network,new,create,ssh,private,key',
|
tags: 'destination,docker,network,new,create,ssh,private,key',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 14,
|
id: 15,
|
||||||
name: 'Goto: Projects',
|
name: 'Goto: Projects',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 15,
|
id: 16,
|
||||||
name: 'Goto: Sources',
|
name: 'Goto: Sources',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 16,
|
id: 17,
|
||||||
name: 'Goto: Destinations',
|
name: 'Goto: Destinations',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 17,
|
id: 18,
|
||||||
name: 'Goto: Settings',
|
name: 'Goto: Settings',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 18,
|
id: 19,
|
||||||
name: 'Goto: Command Center',
|
name: 'Goto: Command Center',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 19,
|
id: 20,
|
||||||
name: 'Goto: Notifications',
|
name: 'Goto: Notifications',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 20,
|
id: 21,
|
||||||
name: 'Goto: Profile',
|
name: 'Goto: Profile',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 21,
|
id: 22,
|
||||||
name: 'Goto: Teams',
|
name: 'Goto: Teams',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 22,
|
id: 23,
|
||||||
name: 'Goto: Switch Teams',
|
name: 'Goto: Switch Teams',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 23,
|
id: 24,
|
||||||
name: 'Goto: Boarding process',
|
name: 'Goto: Boarding process',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 25,
|
||||||
|
name: 'Goto: API Tokens',
|
||||||
|
tags: 'api,tokens,rest',
|
||||||
|
icon: 'goto',
|
||||||
|
sequence: ['main', 'redirect']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@@ -606,44 +621,50 @@ async function redirect() {
|
|||||||
targetUrl.pathname = `/team/storages/new`
|
targetUrl.pathname = `/team/storages/new`
|
||||||
break;
|
break;
|
||||||
case 11:
|
case 11:
|
||||||
targetUrl.pathname = `/`
|
targetUrl.pathname = `/team/storages/`
|
||||||
break;
|
break;
|
||||||
case 12:
|
case 12:
|
||||||
targetUrl.pathname = `/servers`
|
targetUrl.pathname = `/`
|
||||||
break;
|
break;
|
||||||
case 13:
|
case 13:
|
||||||
targetUrl.pathname = `/security/private-key`
|
targetUrl.pathname = `/servers`
|
||||||
break;
|
break;
|
||||||
case 14:
|
case 14:
|
||||||
targetUrl.pathname = `/projects`
|
targetUrl.pathname = `/security/private-key`
|
||||||
break;
|
break;
|
||||||
case 15:
|
case 15:
|
||||||
targetUrl.pathname = `/sources`
|
targetUrl.pathname = `/projects`
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
targetUrl.pathname = `/destinations`
|
targetUrl.pathname = `/sources`
|
||||||
break;
|
break;
|
||||||
case 17:
|
case 17:
|
||||||
targetUrl.pathname = `/settings`
|
targetUrl.pathname = `/destinations`
|
||||||
break;
|
break;
|
||||||
case 18:
|
case 18:
|
||||||
targetUrl.pathname = `/command-center`
|
targetUrl.pathname = `/settings`
|
||||||
break;
|
break;
|
||||||
case 19:
|
case 19:
|
||||||
targetUrl.pathname = `/team/notifications`
|
targetUrl.pathname = `/command-center`
|
||||||
break;
|
break;
|
||||||
case 20:
|
case 20:
|
||||||
targetUrl.pathname = `/profile`
|
targetUrl.pathname = `/team/notifications`
|
||||||
break;
|
break;
|
||||||
case 21:
|
case 21:
|
||||||
targetUrl.pathname = `/team`
|
targetUrl.pathname = `/profile`
|
||||||
break;
|
break;
|
||||||
case 22:
|
case 22:
|
||||||
targetUrl.pathname = `/team`
|
targetUrl.pathname = `/team`
|
||||||
break;
|
break;
|
||||||
case 23:
|
case 23:
|
||||||
|
targetUrl.pathname = `/team`
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
targetUrl.pathname = `/boarding`
|
targetUrl.pathname = `/boarding`
|
||||||
break;
|
break;
|
||||||
|
case 25:
|
||||||
|
targetUrl.pathname = `/security/api-tokens`
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
window.location.href = targetUrl;
|
window.location.href = targetUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,11 @@
|
|||||||
href="{{ route('project.database.logs', $parameters) }}">
|
href="{{ route('project.database.logs', $parameters) }}">
|
||||||
<button>Logs</button>
|
<button>Logs</button>
|
||||||
</a>
|
</a>
|
||||||
@if ($database->getMorphClass() === 'App\Models\StandalonePostgresql')
|
@if (
|
||||||
|
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
||||||
|
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||||
|
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||||
|
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||||
<a class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}"
|
||||||
href="{{ route('project.database.backups.all', $parameters) }}">
|
href="{{ route('project.database.backups.all', $parameters) }}">
|
||||||
<button>Backups</button>
|
<button>Backups</button>
|
||||||
|
|||||||
@@ -10,8 +10,15 @@
|
|||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<nav class="navbar-main">
|
<nav class="navbar-main">
|
||||||
<a class="{{ request()->routeIs('security.private-key.index') ? 'text-white' : '' }}" href="{{ route('security.private-key.index') }}">
|
<a href="{{ route('security.private-key.index') }}">
|
||||||
<button>Private Keys</button>
|
<button>Private Keys</button>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{ route('security.api-tokens') }}">
|
||||||
|
<button>API tokens</button>
|
||||||
|
</a>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
<div class="-mt-9">
|
||||||
|
<livewire:switch-team />
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
|
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
|
||||||
<link href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" rel="stylesheet">
|
<link href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" rel="stylesheet">
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
<title>Coolify</title>
|
<title>Coolify</title>
|
||||||
@env('local')
|
@env('local')
|
||||||
<link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" />
|
<link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" />
|
||||||
@@ -95,8 +96,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copyToClipboard(text) {
|
function copyToClipboard(text) {
|
||||||
navigator.clipboard.writeText(text);
|
navigator?.clipboard?.writeText(text) && Livewire.emit('success', 'Copied to clipboard.');
|
||||||
Livewire.emit('success', 'Copied to clipboard.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Livewire.on('reloadWindow', (timeout) => {
|
Livewire.on('reloadWindow', (timeout) => {
|
||||||
|
|||||||
@@ -225,12 +225,12 @@
|
|||||||
Could not find Docker Engine on your server. Do you want me to install it for you?
|
Could not find Docker Engine on your server. Do you want me to install it for you?
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
@if ($dockerInstallationStarted)
|
|
||||||
<x-forms.button class="justify-center box" wire:click="installDocker"
|
<x-forms.button class="justify-center box" wire:click="installDocker"
|
||||||
onclick="installDocker.showModal()">
|
onclick="installDocker.showModal()">
|
||||||
Let's do it!</x-forms.button>
|
Let's do it!</x-forms.button>
|
||||||
|
@if ($dockerInstallationStarted)
|
||||||
<x-forms.button class="justify-center box" wire:click="dockerInstalledOrSkipped">
|
<x-forms.button class="justify-center box" wire:click="dockerInstalledOrSkipped">
|
||||||
Next</x-forms.button>
|
Validate Server & Continue</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
@@ -314,7 +314,7 @@
|
|||||||
I will redirect you to the new resource page, where you can create your first resource.
|
I will redirect you to the new resource page, where you can create your first resource.
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<div class="justify-center box" wire:click="showNewResource">Let's do
|
<div class="items-center justify-center box" wire:click="showNewResource">Let's do
|
||||||
it!</div>
|
it!</div>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if ($projects->count() === 0 && $servers->count() === 0)
|
@if ($projects->count() === 0 && $servers->count() === 0)
|
||||||
No resources found. Add your first server / private key <a class="text-white underline" href="{{route('server.create')}}">here</a>.
|
No resources found. Add your first server / private key <a class="text-white underline"
|
||||||
|
href="{{ route('server.create') }}">here</a>.
|
||||||
@endif
|
@endif
|
||||||
@if ($projects->count() > 0)
|
@if ($projects->count() > 0)
|
||||||
<h3 class="pb-4">Projects</h3>
|
<h3 class="pb-4">Projects</h3>
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
@if ($projects->count() === 1)
|
@if ($projects->count() === 1)
|
||||||
<div class="grid grid-cols-1 gap-2">
|
<div class="grid grid-cols-1 gap-2">
|
||||||
@else
|
@else
|
||||||
<div class="grid grid-cols-3 gap-2">
|
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
|
||||||
@endif
|
@endif
|
||||||
@foreach ($projects as $project)
|
@foreach ($projects as $project)
|
||||||
<div class="gap-2 border border-transparent cursor-pointer box group" x-data
|
<div class="gap-2 border border-transparent cursor-pointer box group" x-data
|
||||||
@@ -32,37 +33,39 @@
|
|||||||
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
|
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
|
||||||
href="{{ route('project.resources', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
href="{{ route('project.resources', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
||||||
<div class="font-bold text-white">{{ $project->name }}</div>
|
<div class="font-bold text-white">{{ $project->name }}</div>
|
||||||
<div class="text-xs group-hover:text-white hover:no-underline">
|
<div class="description">
|
||||||
{{ $project->description }}</div>
|
{{ $project->description }}</div>
|
||||||
</a>
|
</a>
|
||||||
@else
|
@else
|
||||||
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
|
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
|
||||||
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||||
<div class="font-bold text-white">{{ $project->name }}</div>
|
<div class="font-bold text-white">{{ $project->name }}</div>
|
||||||
<div class="text-xs group-hover:text-white hover:no-underline">
|
<div class="description">
|
||||||
{{ $project->description }}</div>
|
{{ $project->description }}</div>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<a class="mx-4 rounded group-hover:text-white hover:no-underline "
|
<div class="flex items-center">
|
||||||
href="{{ route('project.resources.new', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
<a class="mx-4 rounded group-hover:text-white hover:no-underline"
|
||||||
<span class="font-bold hover:text-warning">+ New Resource</span>
|
href="{{ route('project.resources.new', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
||||||
</a>
|
<span class="font-bold hover:text-warning">+ New Resource</span>
|
||||||
<a class="mx-4 rounded group-hover:text-white"
|
</a>
|
||||||
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
<a class="mx-4 rounded group-hover:text-white"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24"
|
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24"
|
||||||
stroke-linejoin="round">
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
stroke-linejoin="round">
|
||||||
<path
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
|
<path
|
||||||
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
|
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
|
||||||
</svg>
|
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
|
||||||
</a>
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@if ($projects->count() > 0)
|
@if ($projects->count() > 0)
|
||||||
<h3 class="pb-4">Servers</h3>
|
<h3 class="py-4">Servers</h3>
|
||||||
@endif
|
@endif
|
||||||
@if ($servers->count() === 1)
|
@if ($servers->count() === 1)
|
||||||
<div class="grid grid-cols-1 gap-2">
|
<div class="grid grid-cols-1 gap-2">
|
||||||
@@ -79,7 +82,7 @@
|
|||||||
<div class="font-bold text-white">
|
<div class="font-bold text-white">
|
||||||
{{ $server->name }}
|
{{ $server->name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs group-hover:text-white">
|
<div class="description">
|
||||||
{{ $server->description }}</div>
|
{{ $server->description }}</div>
|
||||||
<div class="flex gap-1 text-xs text-error">
|
<div class="flex gap-1 text-xs text-error">
|
||||||
@if (!$server->settings->is_reachable)
|
@if (!$server->settings->is_reachable)
|
||||||
@@ -97,7 +100,6 @@
|
|||||||
</a>
|
</a>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function gotoProject(uuid, environment = 'production') {
|
function gotoProject(uuid, environment = 'production') {
|
||||||
window.location.href = '/project/' + uuid + '/' + environment;
|
window.location.href = '/project/' + uuid + '/' + environment;
|
||||||
|
|||||||
@@ -5,8 +5,12 @@
|
|||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
Save
|
Save
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
@if ($isConfigurationChanged && !is_null($application->config_hash))
|
||||||
|
<div class="font-bold text-warning">Configuration not applied to the running application. You need to
|
||||||
|
redeploy.</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="">General configuration for your application.</div>
|
<div>General configuration for your application.</div>
|
||||||
<div class="flex flex-col gap-2 py-4">
|
<div class="flex flex-col gap-2 py-4">
|
||||||
<div class="flex flex-col items-end gap-2 xl:flex-row">
|
<div class="flex flex-col items-end gap-2 xl:flex-row">
|
||||||
<x-forms.input id="application.name" label="Name" required />
|
<x-forms.input id="application.name" label="Name" required />
|
||||||
@@ -81,7 +85,6 @@
|
|||||||
@if ($application->dockerfile)
|
@if ($application->dockerfile)
|
||||||
<x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea>
|
<x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<h3>Network</h3>
|
<h3>Network</h3>
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
@if ($application->settings->is_static)
|
@if ($application->settings->is_static)
|
||||||
@@ -93,6 +96,12 @@
|
|||||||
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
|
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
|
||||||
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
|
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
|
||||||
</div>
|
</div>
|
||||||
|
@if ($labelsChanged)
|
||||||
|
<x-forms.textarea label="Custom Labels" rows="15" id="customLabels"></x-forms.textarea>
|
||||||
|
@else
|
||||||
|
<x-forms.textarea label="Coolify Generated Labels" rows="15" id="customLabels"></x-forms.textarea>
|
||||||
|
@endif
|
||||||
|
<x-forms.button wire:click="resetDefaultLabels">Reset to Coolify Generated Labels</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<h3>Advanced</h3>
|
<h3>Advanced</h3>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
|||||||
57
resources/views/livewire/project/clone-project.blade.php
Normal file
57
resources/views/livewire/project/clone-project.blade.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<form wire:submit.prevent='clone'>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<h1>Clone</h1>
|
||||||
|
<x-forms.button type="submit">Clone to a New Project</x-forms.button>
|
||||||
|
</div>
|
||||||
|
<div class="subtitle ">Quickly clone a project</div>
|
||||||
|
</div>
|
||||||
|
<x-forms.input required id="newProjectName" label="New Project Name" />
|
||||||
|
<h3 class="pt-4 pb-2">Servers</h3>
|
||||||
|
<div class="grid gap-2 lg:grid-cols-3">
|
||||||
|
@foreach ($servers as $srv)
|
||||||
|
<div wire:click="selectServer('{{ $srv->id }}')"
|
||||||
|
class="cursor-pointer box-without-bg bg-coolgray-200 group"
|
||||||
|
:class="'{{ $selectedServer === $srv->id }}' && 'bg-coollabs'">
|
||||||
|
<div class="flex flex-col mx-6">
|
||||||
|
<div :class="'{{ $selectedServer === $srv->id }}' && 'text-white'"> {{ $srv->name }}</div>
|
||||||
|
@isset($selectedServer)
|
||||||
|
<div :class="'{{ $selectedServer === $srv->id }}' && 'text-white pt-2 text-xs font-bold'">
|
||||||
|
{{ $srv->description }}</div>
|
||||||
|
@else
|
||||||
|
<div class="description">
|
||||||
|
{{ $srv->description }}</div>
|
||||||
|
@endisset
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
<h3 class="pt-4 pb-2">Resources</h3>
|
||||||
|
<div class="grid grid-cols-1 gap-2">
|
||||||
|
@foreach ($environment->applications->sortBy('name') as $application)
|
||||||
|
<div class="p-2 border border-coolgray-200">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="font-bold text-white">{{ $application->name }}</div>
|
||||||
|
<div class="description">{{ $application->description }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
@foreach ($environment->databases()->sortBy('name') as $database)
|
||||||
|
<div class="p-2 border border-coolgray-200">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="font-bold text-white">{{ $database->name }}</div>
|
||||||
|
<div class="description">{{ $database->description }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
@foreach ($environment->services->sortBy('name') as $service)
|
||||||
|
<div class="p-2 border border-coolgray-200">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="font-bold text-white">{{ $service->name }}</div>
|
||||||
|
<div class="description">{{ $service->description }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -25,9 +25,29 @@
|
|||||||
</x-forms.select>
|
</x-forms.select>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<x-forms.input label="Databases To Backup" helper="Comma separated list of databases to backup. Empty will include the default one." id="backup.databases_to_backup" />
|
<div class="flex gap-2">
|
||||||
<x-forms.input label="Frequency" id="backup.frequency" />
|
@if ($backup->database_type === 'App\Models\StandalonePostgresql')
|
||||||
<x-forms.input label="Number of backups to keep (locally)" id="backup.number_of_backups_locally" />
|
<x-forms.input label="Databases To Backup"
|
||||||
|
helper="Comma separated list of databases to backup. Empty will include the default one."
|
||||||
|
id="backup.databases_to_backup" />
|
||||||
|
@elseif($backup->database_type === 'App\Models\StandaloneMongodb')
|
||||||
|
<x-forms.input label="Databases To Include"
|
||||||
|
helper="A list of databases to backup. You can specify which collection(s) per database to exclude from the backup. Empty will include all databases and collections.<br><br>Example:<br><br>database1:collection1,collection2|database2:collection3,collection4<br><br> database1 will include all collections except collection1 and collection2. <br>database2 will include all collections except collection3 and collection4.<br><br>Another Example:<br><br>database1:collection1|database2<br><br> database1 will include all collections except collection1.<br>database2 will include ALL collections."
|
||||||
|
id="backup.databases_to_backup" />
|
||||||
|
@elseif($backup->database_type === 'App\Models\StandaloneMysql')
|
||||||
|
<x-forms.input label="Databases To Backup"
|
||||||
|
helper="Comma separated list of databases to backup. Empty will include the default one."
|
||||||
|
id="backup.databases_to_backup" />
|
||||||
|
@elseif($backup->database_type === 'App\Models\StandaloneMariadb')
|
||||||
|
<x-forms.input label="Databases To Backup"
|
||||||
|
helper="Comma separated list of databases to backup. Empty will include the default one."
|
||||||
|
id="backup.databases_to_backup" />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input label="Frequency" id="backup.frequency" />
|
||||||
|
<x-forms.input label="Number of backups to keep (locally)" id="backup.number_of_backups_locally" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user