Compare commits

...

46 Commits

Author SHA1 Message Date
Andras Bacsai
a95bd906bc Merge pull request #1363 from coollabsio/next
v4.0.0-beta.103
2023-10-25 20:21:27 +02:00
Andras Bacsai
21795cf788 fix: space in build args 2023-10-25 20:19:38 +02:00
Andras Bacsai
6e98fd9403 grafana + openblocks 2023-10-25 20:13:45 +02:00
Andras Bacsai
ead1edc2b9 Services 2023-10-25 15:44:34 +02:00
Andras Bacsai
db822cb876 Merge pull request #1357 from theh2so4/main
[+] Templates: Dashboard, Emby, EmbyStat and Grocy
2023-10-25 15:41:54 +02:00
Andras Bacsai
65bfce43c0 fix: server settings guarded 2023-10-25 11:50:22 +02:00
Andras Bacsai
50fc05ab52 update init script 2023-10-25 11:43:18 +02:00
Andras Bacsai
c9cf5c486f Merge pull request #1362 from coollabsio/next
v4.0.0-beta.102
2023-10-25 11:07:17 +02:00
Andras Bacsai
379f4b9dff feat: show webhook on ui
feat: n8n service
2023-10-25 10:43:07 +02:00
Andras Bacsai
aa02b8d433 fix: rate limit for api + add mariadb + mysql 2023-10-25 09:56:58 +02:00
Andras Bacsai
70ecb92e82 cleanup ssh dir on start 2023-10-25 09:41:41 +02:00
Andras Bacsai
d5cc2a2eed feat: download local backups 2023-10-25 09:28:26 +02:00
Andras Bacsai
2b91bd24c5 Merge pull request #1361 from coollabsio/next
v4.0.0-beta.101
2023-10-24 15:51:54 +02:00
Andras Bacsai
5e8ac1b48e fix: mongodb healtcheck command 2023-10-24 15:47:29 +02:00
Andras Bacsai
dc86170ef5 version++ 2023-10-24 15:41:44 +02:00
Andras Bacsai
0232cf5b4c feat: lock environment variables 2023-10-24 15:41:21 +02:00
Andras Bacsai
6e73f7f2e4 fix: encrypt mongodb password 2023-10-24 15:40:29 +02:00
Andras Bacsai
61c43804e3 Merge pull request #1360 from coollabsio/next
v4.0.0-beta.100
2023-10-24 14:44:31 +02:00
Andras Bacsai
72421d692b add slogans 2023-10-24 14:36:43 +02:00
Andras Bacsai
f801bb98cd feat: mysql, mariadb 2023-10-24 14:31:28 +02:00
Andras Bacsai
b2d111e49a feat: simple search functionality 2023-10-24 12:33:49 +02:00
Andras Bacsai
c82e02218f version++ 2023-10-24 11:08:59 +02:00
Andras Bacsai
29f64076de fix: syncbunny command 2023-10-24 11:08:15 +02:00
Andras Bacsai
393c334b12 version++ 2023-10-24 11:08:11 +02:00
Andras Bacsai
678b264688 fix: make sure coolfiy network exists on install 2023-10-24 11:08:05 +02:00
Andras Bacsai
2620bfbf08 Merge pull request #1359 from coollabsio/next
v4.0.0-beta.99
2023-10-24 10:59:36 +02:00
Andras Bacsai
18c32decad guarded 2023-10-24 10:43:34 +02:00
Andras Bacsai
a6f9e5f0af fixes 2023-10-24 10:42:33 +02:00
Andras Bacsai
f187040b7e fix: mongodb backup 2023-10-24 10:42:28 +02:00
Andras Bacsai
5510321776 syncbunny update 2023-10-24 10:22:36 +02:00
Andras Bacsai
69691b2ca7 fix: service template generator + appwrite 2023-10-24 10:19:12 +02:00
Andras Bacsai
8bfc1a7c06 fix: do not allow to delete env if a resource is defined 2023-10-24 10:11:21 +02:00
Andras Bacsai
554222abc7 fix: cleanup stucked resources on start 2023-10-24 10:10:55 +02:00
Andras Bacsai
b1a1aeeb75 fix: clone to with the same environment name 2023-10-24 10:10:45 +02:00
Andras Bacsai
91acd4cb6a fix: backups should be done with internal db url
fix: create default database on mongodb start with a collection
2023-10-24 09:34:35 +02:00
TheH2SO4
6c5a1c317a [!] Mistake 2023-10-23 13:14:43 +02:00
TheH2SO4
b09a9f871e [+] Template: Fenrus
🆕 **New Template**:

-> ℹ️ **Fenrus**: A personal home page for quick access to all your personal apps/sites.
2023-10-23 13:12:50 +02:00
TheH2SO4
b5506f006b [+] Template: Dashboard
🆕 **New Template**:

-> ℹ️ **Dashboard**: A dashboard. Inspired by SUI, it offers simple customization through JSON-files and a handy search bar to help you browse the internet more efficiently.
2023-10-23 13:05:22 +02:00
TheH2SO4
a6c3594448 [+] Template: Grocy
🆕 **New Template**:

-> ℹ️ **Grocy**: Grocy is a self-hosted, web-based household management and grocery list application, designed to simplify your household chores and grocery shopping.
2023-10-23 12:48:13 +02:00
TheH2SO4
5dd3952230 [+] Template: EmbyStat
🆕 **New Template**:

-> ℹ️ **EmbyStat**: EmyStat is an open-source, self-hosted web analytics tool, designed to provide insight into website traffic and user behavior, of your local Emby deployement, all within your control.
2023-10-23 12:41:48 +02:00
TheH2SO4
22ec0f8826 [+] Template: Emby
🆕 **New Template**:

-> ℹ️ **Emby**: A media server software that allows you to organize, stream, and access your multimedia content effortlessly, making it easy to enjoy your favorite movies, TV shows, music, and more.
2023-10-23 11:30:01 +02:00
TheH2SO4
da6e04bb1a Merge pull request #3 from coollabsio/main
Update
2023-10-21 22:57:27 +02:00
Andras Bacsai
aaeacad781 Merge pull request #1350 from coollabsio/next
v4.0.0-beta.98
2023-10-20 18:17:50 +02:00
Andras Bacsai
b539f40fa5 fix 2023-10-20 18:16:47 +02:00
Andras Bacsai
fae340afcb fix: boarding 2023-10-20 18:15:25 +02:00
TheH2SO4
1bfce6716c Merge pull request #2 from coollabsio/next
Next
2023-10-19 11:18:19 +02:00
103 changed files with 2341 additions and 251 deletions

View File

@@ -2,7 +2,9 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb; 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;
@@ -12,7 +14,7 @@ class StartDatabaseProxy
{ {
use AsAction; use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $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') {
@@ -21,6 +23,10 @@ class StartDatabaseProxy
$internalPort = 5432; $internalPort = 5432;
} else if ($database->getMorphClass() === 'App\Models\StandaloneMongodb') { } else if ($database->getMorphClass() === 'App\Models\StandaloneMongodb') {
$internalPort = 27017; $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);

View 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}";
}
}

View File

@@ -52,7 +52,7 @@ class StartMongodb
'healthcheck' => [ 'healthcheck' => [
'test' => [ 'test' => [
'CMD-SHELL', 'CMD-SHELL',
'mongo --eval "printjson(db.serverStatus())" | grep uptime | grep -v grep' 'mongosh --eval "printjson(db.runCommand(\"ping\"))"'
], ],
'interval' => '5s', 'interval' => '5s',
'timeout' => '5s', 'timeout' => '5s',
@@ -94,6 +94,14 @@ class StartMongodb
]; ];
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf'; $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 = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
@@ -160,4 +168,11 @@ class StartMongodb
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
} }
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";
}
} }

View 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}";
}
}

View File

@@ -2,7 +2,9 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb; 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,7 +13,7 @@ class StopDatabase
{ {
use AsAction; use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database) public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
{ {
$server = $database->destination->server; $server = $database->destination->server;
instant_remote_process( instant_remote_process(

View File

@@ -2,7 +2,9 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb; 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,7 +13,7 @@ class StopDatabaseProxy
{ {
use AsAction; use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $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;

View File

@@ -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";
}
}
} }

View File

@@ -5,7 +5,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
class GenerateServiceTemplates extends Command class ServicesGenerate extends Command
{ {
/** /**
* The name and signature of the console command. * The name and signature of the console command.
@@ -80,6 +80,14 @@ class GenerateServiceTemplates extends Command
$env_file = null; $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); $json = Yaml::parse($content);
$yaml = base64_encode(Yaml::dump($json, 10, 2)); $yaml = base64_encode(Yaml::dump($json, 10, 2));
$payload = [ $payload = [
@@ -87,9 +95,12 @@ class GenerateServiceTemplates extends Command
'documentation' => $documentation, 'documentation' => $documentation,
'slogan' => $slogan, 'slogan' => $slogan,
'compose' => $yaml, 'compose' => $yaml,
'tags' => $tags,
]; ];
if ($env_file) { if ($env_file) {
$payload['envs'] = $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; return $payload;
} }

View File

@@ -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";

View File

@@ -63,8 +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') { } else if ($type->value() === 'mongodb') {
$database = create_standalone_mongodb($environment->id, $destination_uuid); $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,
@@ -72,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);

View File

@@ -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);
} }
} }

View File

@@ -55,18 +55,21 @@ class CloneProject extends Component
'selectedServer' => 'required', 'selectedServer' => 'required',
'newProjectName' => '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([ $newProject = Project::create([
'name' => $this->newProjectName, 'name' => $this->newProjectName,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
'description' => $this->project->description . ' (clone)', 'description' => $this->project->description . ' (clone)',
]); ]);
if ($this->environment->id !== 1) { if ($this->environment->name !== 'production') {
$newProject->environments()->create([ $newProject->environments()->create([
'name' => $this->environment->name, 'name' => $this->environment->name,
]); ]);
$newProject->environments()->find(1)->delete();
} }
$newEnvironment = $newProject->environments->first(); $newEnvironment = $newProject->environments->where('name', $this->environment->name)->first();
// Clone Applications // Clone Applications
$applications = $this->environment->applications; $applications = $this->environment->applications;
$databases = $this->environment->databases(); $databases = $this->environment->databases();
@@ -80,7 +83,6 @@ class CloneProject extends Component
'environment_id' => $newEnvironment->id, 'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer, 'destination_id' => $this->selectedServer,
]); ]);
$newApplication->environment_id = $newProject->environments->first()->id;
$newApplication->save(); $newApplication->save();
$environmentVaribles = $application->environment_variables()->get(); $environmentVaribles = $application->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) { foreach ($environmentVaribles as $environmentVarible) {
@@ -105,7 +107,6 @@ class CloneProject extends Component
'environment_id' => $newEnvironment->id, 'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer, 'destination_id' => $this->selectedServer,
]); ]);
$newDatabase->environment_id = $newProject->environments->first()->id;
$newDatabase->save(); $newDatabase->save();
$environmentVaribles = $database->environment_variables()->get(); $environmentVaribles = $database->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) { foreach ($environmentVaribles as $environmentVarible) {
@@ -116,6 +117,10 @@ class CloneProject extends Component
$payload['standalone_redis_id'] = $newDatabase->id; $payload['standalone_redis_id'] = $newDatabase->id;
} else if ($database->type() === 'standalone_mongodb') { } else if ($database->type() === 'standalone_mongodb') {
$payload['standalone_mongodb_id'] = $newDatabase->id; $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 = $environmentVarible->replicate()->fill($payload);
$newEnvironmentVariable->save(); $newEnvironmentVariable->save();
@@ -128,7 +133,6 @@ class CloneProject extends Component
'environment_id' => $newEnvironment->id, 'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer, 'destination_id' => $this->selectedServer,
]); ]);
$newService->environment_id = $newProject->environments->first()->id;
$newService->save(); $newService->save();
$newService->parse(); $newService->parse();
} }

View File

@@ -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');
}
}

View File

@@ -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;

View File

@@ -48,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');

View File

@@ -2,7 +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\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;
@@ -49,14 +51,18 @@ class Heading extends Component
if ($this->database->type() === 'standalone-postgresql') { if ($this->database->type() === 'standalone-postgresql') {
$activity = StartPostgresql::run($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);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} } else if ($this->database->type() === 'standalone-mongodb') {
if ($this->database->type() === 'standalone-mongodb') {
$activity = StartMongodb::run($this->database); $activity = StartMongodb::run($this->database);
$this->emit('newMonitorActivity', $activity->id); $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);
} }
} }
} }

View 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');
}
}

View File

@@ -39,7 +39,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->mongo_conf === "") { if ($this->database->mongo_conf === "") {
@@ -60,7 +61,11 @@ 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 {
@@ -69,7 +74,7 @@ class General extends Component
} }
$this->db_url = $this->database->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);
} }

View 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');
}
}

View File

@@ -60,7 +60,11 @@ 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 {
@@ -69,11 +73,10 @@ class General extends Component
} }
$this->db_url = $this->database->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)
{ {

View File

@@ -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,7 +57,11 @@ 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 {
@@ -65,7 +70,7 @@ class General extends Component
} }
$this->db_url = $this->database->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);
} }

View File

@@ -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']]);
} }
} }

View File

@@ -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 {

View File

@@ -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');

View File

@@ -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;
@@ -81,6 +95,12 @@ class All extends Component
case 'standalone-mongodb': case 'standalone-mongodb':
$environment->standalone_mongodb_id = $this->resource->id; $environment->standalone_mongodb_id = $this->resource->id;
break; 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;

View File

@@ -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();

View File

@@ -5,7 +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\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;
@@ -13,7 +15,7 @@ use Livewire\Component;
class Logs extends Component class Logs extends Component
{ {
public ?string $type = null; public ?string $type = null;
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb $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;
@@ -41,11 +43,16 @@ class Logs extends Component
if (is_null($resource)) { if (is_null($resource)) {
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first(); $resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) { if (is_null($resource)) {
abort(404); $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;
$this->status = $this->resource->status; $this->status = $this->resource->status;
$this->server = $this->resource->destination->server; $this->server = $this->resource->destination->server;

View 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');
}
}

View File

@@ -885,14 +885,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function generate_build_env_variables() private function generate_build_env_variables()
{ {
$this->build_args = collect(["--build-arg SOURCE_COMMIT={$this->commit}"]); $this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]);
if ($this->pull_request_id === 0) { if ($this->pull_request_id === 0) {
foreach ($this->application->build_environment_variables as $env) { foreach ($this->application->build_environment_variables as $env) {
$this->build_args->push("--build-arg {$env->key}={$env->value}"); $this->build_args->push("--build-arg {$env->key}=\"{$env->value}\"");
} }
} else { } else {
foreach ($this->application->build_environment_variables_preview as $env) { foreach ($this->application->build_environment_variables_preview as $env) {
$this->build_args->push("--build-arg {$env->key}={$env->value}"); $this->build_args->push("--build-arg {$env->key}=\"{$env->value}\"");
} }
} }

View File

@@ -6,7 +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\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;
@@ -28,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|StandaloneMongodb $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;
@@ -75,6 +77,10 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$databasesToBackup = [$this->database->postgres_db]; $databasesToBackup = [$this->database->postgres_db];
} else if ($databaseType === 'standalone-mongodb') { } else if ($databaseType === 'standalone-mongodb') {
$databasesToBackup = ['*']; $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;
} }
@@ -88,6 +94,14 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$databasesToBackup = explode('|', $databasesToBackup); $databasesToBackup = explode('|', $databasesToBackup);
$databasesToBackup = array_map('trim', $databasesToBackup); $databasesToBackup = array_map('trim', $databasesToBackup);
ray($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 { } else {
return; return;
} }
@@ -124,7 +138,6 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$databaseName = $database; $databaseName = $database;
} }
ray($databaseName);
} }
$this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz"; $this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz";
$this->backup_location = $this->backup_dir . $this->backup_file; $this->backup_location = $this->backup_dir . $this->backup_file;
@@ -134,6 +147,24 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
'scheduled_database_backup_id' => $this->backup->id, 'scheduled_database_backup_id' => $this->backup->id,
]); ]);
$this->backup_standalone_mongodb($database); $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 { } else {
throw new \Exception('Unsupported database type'); throw new \Exception('Unsupported database type');
} }
@@ -170,18 +201,25 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
private function backup_standalone_mongodb(string $databaseWithCollections): void private function backup_standalone_mongodb(string $databaseWithCollections): void
{ {
try { try {
$url = $this->database->getDbUrl(); $url = $this->database->getDbUrl(useInternal: true);
if ($databaseWithCollections === 'all') { if ($databaseWithCollections === 'all') {
$commands[] = "mkdir -p " . $this->backup_dir; $commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
} else { } else {
$collectionsToExclude = str($databaseWithCollections)->after(':')->explode(','); if (str($databaseWithCollections)->contains(':')) {
$databaseName = str($databaseWithCollections)->before(':'); $databaseName = str($databaseWithCollections)->before(':');
$collectionsToExclude = str($databaseWithCollections)->after(':')->explode(',');
} else {
$databaseName = $databaseWithCollections;
$collectionsToExclude = collect();
}
$commands[] = "mkdir -p " . $this->backup_dir; $commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location"; 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";
}
} }
ray($commands);
$this->backup_output = instant_remote_process($commands, $this->server); $this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output); $this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') { if ($this->backup_output === '') {
@@ -211,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) {

View File

@@ -7,7 +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\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;
@@ -21,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|StandaloneMongodb $resource) public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource)
{ {
} }
@@ -45,6 +47,12 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
case 'standalone-mongodb': case 'standalone-mongodb':
StopDatabase::run($this->resource); StopDatabase::run($this->resource);
break; 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;

View File

@@ -7,12 +7,8 @@ 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 && return $this->applications()->count() == 0 &&
$this->redis()->count() == 0 && $this->redis()->count() == 0 &&
@@ -38,13 +34,23 @@ class Environment extends Model
{ {
return $this->hasMany(StandaloneMongodb::class); 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;
$mongodbs = $this->mongodbs; $mongodbs = $this->mongodbs;
return $postgresqls->concat($redis)->concat($mongodbs); $mysqls = $this->mysqls;
$mariadbs = $this->mariadbs;
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
} }
public function project() public function project()

View File

@@ -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(),
); );
} }
} }

View File

@@ -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);
}
} }

View File

@@ -122,10 +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([]));
$mongodbs = $standaloneDocker->mongodbs; $mongodbs = data_get($standaloneDocker, 'mongodbs', collect([]));
return $postgresqls->concat($redis)->concat($mongodbs); $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()
@@ -258,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);
} }
} }

View File

@@ -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()
{ {

View File

@@ -24,6 +24,14 @@ class StandaloneDocker extends BaseModel
{ {
return $this->morphMany(StandaloneMongodb::class, 'destination'); 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()
{ {
@@ -35,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;

View 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');
}
}

View File

@@ -15,8 +15,16 @@ class StandaloneMongodb extends BaseModel
{ {
static::created(function ($database) { static::created(function ($database) {
LocalPersistentVolume::create([ LocalPersistentVolume::create([
'name' => 'mongodb-data-' . $database->uuid, 'name' => 'mongodb-configdb-' . $database->uuid,
'mount_path' => '/data', '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, 'host_path' => null,
'resource_id' => $database->id, 'resource_id' => $database->id,
'resource_type' => $database->getMorphClass(), 'resource_type' => $database->getMorphClass(),
@@ -34,6 +42,20 @@ class StandaloneMongodb extends BaseModel
}); });
} }
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 public function portsMappings(): Attribute
{ {
return Attribute::make( return Attribute::make(
@@ -55,8 +77,9 @@ class StandaloneMongodb extends BaseModel
{ {
return 'standalone-mongodb'; return 'standalone-mongodb';
} }
public function getDbUrl() { public function getDbUrl(bool $useInternal = false)
if ($this->is_public) { {
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"; return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
} else { } else {
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true"; return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true";

View 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');
}
}

View File

@@ -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,9 +60,9 @@ class StandalonePostgresql extends BaseModel
{ {
return 'standalone-postgresql'; return 'standalone-postgresql';
} }
public function getDbUrl(): string public function getDbUrl(bool $useInternal = false): string
{ {
if ($this->is_public) { if ($this->is_public && !$useInternal) {
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}"; return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
} else { } else {
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}"; return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}";

View File

@@ -48,7 +48,7 @@ class RouteServiceProvider extends ServiceProvider
if ($request->path() === 'api/health') { if ($request->path() === 'api/health') {
return Limit::perMinute(1000)->by($request->user()?->id ?: $request->ip()); return Limit::perMinute(1000)->by($request->user()?->id ?: $request->ip());
} }
return Limit::perMinute(30)->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());

View File

@@ -1,6 +1,6 @@
<?php <?php
const DATABASE_TYPES = ['postgresql','redis', 'mongodb']; const DATABASE_TYPES = ['postgresql', 'redis', 'mongodb', 'mysql', 'mariadb'];
const VALID_CRON_STRINGS = [ const VALID_CRON_STRINGS = [
'every_minute' => '* * * * *', 'every_minute' => '* * * * *',
'hourly' => '0 * * * *', 'hourly' => '0 * * * *',

View File

@@ -2,7 +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\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;
@@ -58,6 +60,36 @@ function create_standalone_mongodb($environment_id, $destination_uuid): Standalo
'destination_type' => $destination->getMorphClass(), '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.

View File

@@ -4,7 +4,9 @@ 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\Service;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb; 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\Models\Team; use App\Models\Team;
@@ -484,5 +486,18 @@ function queryResourcesByUuid(string $uuid)
if ($redis) return $redis; if ($redis) return $redis;
$mongodb = StandaloneMongodb::whereUuid($uuid)->first(); $mongodb = StandaloneMongodb::whereUuid($uuid)->first();
if ($mongodb) return $mongodb; if ($mongodb) return $mongodb;
$mysql = StandaloneMysql::whereUuid($uuid)->first();
if ($mysql) return $mysql;
$mariadb = StandaloneMariadb::whereUuid($uuid)->first();
if ($mariadb) return $mariadb;
return $resource; 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;
}

View File

@@ -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
View File

@@ -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",

View File

@@ -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.97', 'release' => '4.0.0-beta.103',
// 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'),

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.97'; return '4.0.0-beta.103';

View File

@@ -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');
}
};

View File

@@ -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');
}
};

View File

@@ -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');
});
}
};

View File

@@ -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');
});
}
};

View File

@@ -28,3 +28,4 @@ networks:
coolify: coolify:
name: coolify name: coolify
driver: bridge driver: bridge
external: true

View File

@@ -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' || $database->getMorphClass() === 'App\Models\StandaloneMongodb') @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>

View File

@@ -96,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) => {

View File

@@ -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>

View File

@@ -35,6 +35,14 @@
<x-forms.input label="Databases To Include" <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." 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" /> 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 @endif
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">

View File

@@ -1,8 +0,0 @@
<div class="flex gap-2">
<div class="flex-1"></div>
{{-- @if (data_get($execution, 'status') !== 'failed') --}}
{{-- <x-forms.button class="bg-coollabs-100 hover:bg-coollabs" wire:click="download">Download</x-forms.button> --}}
{{-- @endif --}}
<x-forms.button isError wire:click="delete">Delete</x-forms.button>
</div>

View File

@@ -1,9 +1,10 @@
<div class="flex flex-col-reverse gap-2"> <div class="flex flex-col-reverse gap-2">
@forelse($executions as $execution) @forelse($executions as $execution)
<form class="flex flex-col p-2 border-dotted border-1 bg-coolgray-300" @class([ <form wire:key="{{ data_get($execution, 'id') }}" class="flex flex-col p-2 border-dotted border-1 bg-coolgray-300"
'border-green-500' => data_get($execution, 'status') === 'success', @class([
'border-red-500' => data_get($execution, 'status') === 'failed', 'border-green-500' => data_get($execution, 'status') === 'success',
])> 'border-red-500' => data_get($execution, 'status') === 'failed',
])>
<div>Database: {{ data_get($execution, 'database_name', 'N/A') }}</div> <div>Database: {{ data_get($execution, 'database_name', 'N/A') }}</div>
<div>Status: {{ data_get($execution, 'status') }}</div> <div>Status: {{ data_get($execution, 'status') }}</div>
<div>Started At: {{ data_get($execution, 'created_at') }}</div> <div>Started At: {{ data_get($execution, 'created_at') }}</div>
@@ -14,9 +15,24 @@
kB / {{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB kB / {{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
</div> </div>
<div>Location: {{ data_get($execution, 'filename', 'N/A') }}</div> <div>Location: {{ data_get($execution, 'filename', 'N/A') }}</div>
<livewire:project.database.backup-execution :execution="$execution" :wire:key="$execution->id" /> <div class="flex gap-2">
<div class="flex-1"></div>
@if (data_get($execution, 'status') === 'success')
<x-forms.button class=" hover:bg-coolgray-400"
wire:click="download({{ data_get($execution, 'id') }})">Download</x-forms.button>
@endif
<x-forms.button isError onclick="sure({{ data_get($execution, 'id') }})">Delete</x-forms.button>
</div>
</form> </form>
@empty @empty
<div>No executions found.</div> <div>No executions found.</div>
@endforelse @endforelse
<script>
function sure($id) {
const sure = confirm('Are you sure you want to delete this backup?');
if (sure) {
Livewire.emit('deleteBackup', $id);
}
}
</script>
</div> </div>

View File

@@ -0,0 +1,58 @@
<div>
<form wire:submit.prevent="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit">
Save
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mariadb'>https://hub.docker.com/_/mariadb</a>" />
</div>
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" readonly
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mariadb_user" required readonly
helper="You can only change this in the database." />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Database" id="database.mariadb_database"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="pt-8 text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2 pb-8">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password"
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mariadb_user" required
helper="You can only change this in the database." />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
helper="You can only change this in the database." />
<x-forms.input label="Initial Database" id="database.mariadb_database"
placeholder="If empty, it will be the same as Username."
helper="You can only change this in the database." />
</div>
@endif
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold text-warning'>Example</span>3000:5432,3002:5433" />
<x-forms.input placeholder="5432" disabled="{{ $database->is_public }}" id="database.public_port"
label="Public Port" />
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
</div>
<x-forms.input label="MariaDB URL"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" />
</div>
<x-forms.textarea label="Custom MariaDB Configuration" rows="10" id="database.mariadb_conf" />
</form>
</div>

View File

@@ -0,0 +1,58 @@
<div>
<form wire:submit.prevent="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit">
Save
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mysql'>https://hub.docker.com/_/mysql</a>" />
</div>
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password" readonly
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mysql_user" required readonly
helper="You can only change this in the database." />
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Database" id="database.mysql_database"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="pt-8 text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2 pb-8">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password"
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mysql_user" required
helper="You can only change this in the database." />
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
helper="You can only change this in the database." />
<x-forms.input label="Initial Database" id="database.mysql_database"
placeholder="If empty, it will be the same as Username."
helper="You can only change this in the database." />
</div>
@endif
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold text-warning'>Example</span>3000:5432,3002:5433" />
<x-forms.input placeholder="5432" disabled="{{ $database->is_public }}" id="database.public_port"
label="Public Port" />
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
</div>
<x-forms.input label="MySQL URL"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" />
</div>
<x-forms.textarea label="Custom Mysql Configuration" rows="10" id="database.mysql_conf" />
</form>
</div>

View File

@@ -1,4 +1,4 @@
<div x-data x-init="$wire.loadThings"> <div x-data x-init="$wire.loadServers">
<div class="flex gap-2 "> <div class="flex gap-2 ">
<h1>New Resource</h1> <h1>New Resource</h1>
<div class="w-96"> <div class="w-96">
@@ -90,7 +90,7 @@
New PostgreSQL New PostgreSQL
</div> </div>
<div class="description"> <div class="description">
The most loved relational database in the world. PostgreSQL is an open-source, object-relational database management system known for its robustness, advanced features, and strong standards compliance.
</div> </div>
</div> </div>
</div> </div>
@@ -100,7 +100,7 @@
New Redis New Redis
</div> </div>
<div class="description"> <div class="description">
The open source, in-memory data store for cache, streaming engine, and message broker. Redis is an open-source, in-memory data structure store used as a database, cache, and message broker, known for its high performance, flexibility, and rich data structures.
</div> </div>
</div> </div>
</div> </div>
@@ -110,7 +110,27 @@
New MongoDB New MongoDB
</div> </div>
<div class="description"> <div class="description">
MongoDB is a source-available cross-platform document-oriented database program. MongoDB is a source-available, NoSQL database program that uses JSON-like documents with optional schemas, known for its flexibility, scalability, and wide range of application use cases.
</div>
</div>
</div>
<div class="box group" wire:click="setType('mysql')">
<div class="flex flex-col mx-6">
<div class="font-bold text-white group-hover:text-white">
New MySQL
</div>
<div class="description">
MySQL is an open-source relational database management system known for its speed, reliability, and flexibility in managing and accessing data.
</div>
</div>
</div>
<div class="box group" wire:click="setType('mariadb')">
<div class="flex flex-col mx-6">
<div class="font-bold text-white group-hover:text-white">
New Mariadb
</div>
<div class="description">
MariaDB is an open-source relational database management system that serves as a drop-in replacement for MySQL, offering more robust, scalable, and reliable SQL server capabilities.
</div> </div>
</div> </div>
</div> </div>
@@ -128,12 +148,15 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h2 class="py-4">Services</h2> <h2 class="py-4">Services</h2>
<x-forms.button wire:click='loadServices(true)'>Reload Services List</x-forms.button> <x-forms.button wire:click='loadServices(true)'>Reload Services List</x-forms.button>
<input
class="w-full text-white rounded input input-sm bg-coolgray-200 disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
wire:model.debounce.200ms="search" placeholder="Search..."></input>
</div> </div>
<div class="grid justify-start grid-cols-1 gap-2 text-left xl:grid-cols-3"> <div class="grid justify-start grid-cols-1 gap-2 text-left xl:grid-cols-3">
@if ($loadingServices) @if ($loadingServices)
<span class="loading loading-xs loading-spinner"></span> <span class="loading loading-xs loading-spinner"></span>
@else @else
@foreach ($services as $serviceName => $service) @forelse ($services as $serviceName => $service)
@if (data_get($service, 'disabled')) @if (data_get($service, 'disabled'))
<button class="text-left cursor-not-allowed bg-coolgray-200/20 box-without-bg" disabled> <button class="text-left cursor-not-allowed bg-coolgray-200/20 box-without-bg" disabled>
<div class="flex flex-col mx-6"> <div class="flex flex-col mx-6">
@@ -158,7 +181,9 @@
</div> </div>
</button> </button>
@endif @endif
@endforeach @empty
<div>No service found.</div>
@endforelse
@endif @endif
</div> </div>
<div class="py-4 pb-10">Trademarks Policy: The respective trademarks mentioned here are owned by the <div class="py-4 pb-10">Trademarks Policy: The respective trademarks mentioned here are owned by the

View File

@@ -4,16 +4,25 @@
<div class="flex h-full pt-6"> <div class="flex h-full pt-6">
<div class="flex flex-col items-start gap-4 min-w-fit"> <div class="flex flex-col items-start gap-4 min-w-fit">
<a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a> <a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a>
<a :class="activeTab === 'service-stack' && 'text-white'" @click.prevent="activeTab = 'service-stack'; <a :class="activeTab === 'service-stack' && 'text-white'"
window.location.hash = 'service-stack'" href="#">Service Stack</a> @click.prevent="activeTab = 'service-stack';
<a :class="activeTab === 'storages' && 'text-white'" @click.prevent="activeTab = 'storages'; window.location.hash = 'service-stack'"
window.location.hash = 'storages'" href="#">Storages</a> href="#">Service Stack</a>
<a :class="activeTab === 'storages' && 'text-white'"
@click.prevent="activeTab = 'storages';
window.location.hash = 'storages'"
href="#">Storages</a>
<a :class="activeTab === 'webhooks' && 'text-white'"
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
</a>
<a :class="activeTab === 'environment-variables' && 'text-white'" <a :class="activeTab === 'environment-variables' && 'text-white'"
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'" @click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
href="#">Environment href="#">Environment
Variables</a> Variables</a>
<a :class="activeTab === 'danger' && 'text-white'" @click.prevent="activeTab = 'danger'; <a :class="activeTab === 'danger' && 'text-white'"
window.location.hash = 'danger'" href="#">Danger Zone @click.prevent="activeTab = 'danger';
window.location.hash = 'danger'"
href="#">Danger Zone
</a> </a>
</div> </div>
<div class="w-full pl-8"> <div class="w-full pl-8">
@@ -100,7 +109,9 @@
@foreach ($databases as $database) @foreach ($databases as $database)
<livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" /> <livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" />
@endforeach @endforeach
</div>
<div x-cloak x-show="activeTab === 'webhooks'">
<livewire:project.shared.webhooks :resource="$service" />
</div> </div>
<div x-cloak x-show="activeTab === 'environment-variables'"> <div x-cloak x-show="activeTab === 'environment-variables'">
<div x-cloak x-show="activeTab === 'environment-variables'"> <div x-cloak x-show="activeTab === 'environment-variables'">

View File

@@ -28,8 +28,7 @@
@endif @endif
@else @else
<form wire:submit.prevent='saveVariables(false)' class="flex flex-col gap-2"> <form wire:submit.prevent='saveVariables(false)' class="flex flex-col gap-2">
<x-forms.textarea rows=25 class="whitespace-pre-wrap" <x-forms.textarea rows=25 class="whitespace-pre-wrap" id="variables"></x-forms.textarea>
id="variables"></x-forms.textarea>
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button> <x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
</form> </form>
@if ($showPreview) @if ($showPreview)

View File

@@ -6,36 +6,54 @@
</x-slot:modalBody> </x-slot:modalBody>
</x-modal> </x-modal>
<form wire:submit.prevent='submit' class="flex flex-col items-center gap-2 xl:flex-row"> <form wire:submit.prevent='submit' class="flex flex-col items-center gap-2 xl:flex-row">
@if ($isDisabled) @if ($isLocked)
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M5 13a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-6z" />
<path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0-2 0m-3-5V7a4 4 0 1 1 8 0v4" />
</g>
</svg>
<x-forms.input disabled id="env.key" /> <x-forms.input disabled id="env.key" />
<x-forms.input disabled type="password" id="env.value" />
@if ($type !== 'service')
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
@endif
@else @else
<x-forms.input id="env.key" /> @if ($isDisabled)
<x-forms.input type="password" id="env.value" /> <x-forms.input disabled id="env.key" />
@if ($type !== 'service') <x-forms.input disabled type="password" id="env.value" />
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" /> @if ($type !== 'service')
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
@endif
@else
<x-forms.input id="env.key" />
<x-forms.input type="password" id="env.value" />
@if ($type !== 'service')
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
@endif
@endif @endif
@endif @endif
<div class="flex gap-2"> <div class="flex gap-2">
@if ($isDisabled) @if ($isLocked)
<x-forms.button disabled type="submit">
Update
</x-forms.button>
<x-forms.button disabled isError isModal modalId="{{ $modalId }}">
Delete
</x-forms.button>
@else
<x-forms.button type="submit">
Update
</x-forms.button>
<x-forms.button isError isModal modalId="{{ $modalId }}"> <x-forms.button isError isModal modalId="{{ $modalId }}">
Delete Delete
</x-forms.button> </x-forms.button>
@else
@if ($isDisabled)
<x-forms.button disabled type="submit">
Update
</x-forms.button>
<x-forms.button disabled isError isModal modalId="{{ $modalId }}">
Delete
</x-forms.button>
@else
<x-forms.button type="submit">
Update
</x-forms.button>
<x-forms.button wire:click='lock'>
Lock
</x-forms.button>
<x-forms.button isError isModal modalId="{{ $modalId }}">
Delete
</x-forms.button>
@endif
@endif @endif
</div> </div>
</form> </form>
</div> </div>

View File

@@ -0,0 +1,10 @@
<div>
<div class="flex items-center gap-2">
<h2>Webhooks</h2>
<x-helper
helper="For more details goto our <a class='text-white underline' href='https://coolify.io/docs/api-endpoints' target='_blank'>docs</a>." />
</div>
<div>
<x-forms.input readonly label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
</div>
</div>

View File

@@ -19,6 +19,9 @@
<a :class="activeTab === 'storages' && 'text-white'" <a :class="activeTab === 'storages' && 'text-white'"
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages @click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
</a> </a>
<a :class="activeTab === 'webhooks' && 'text-white'"
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
</a>
@if ($application->git_based()) @if ($application->git_based())
<a :class="activeTab === 'previews' && 'text-white'" <a :class="activeTab === 'previews' && 'text-white'"
@click.prevent="activeTab = 'previews'; window.location.hash = 'previews'" href="#">Preview @click.prevent="activeTab = 'previews'; window.location.hash = 'previews'" href="#">Preview
@@ -57,6 +60,9 @@
<div x-cloak x-show="activeTab === 'storages'"> <div x-cloak x-show="activeTab === 'storages'">
<livewire:project.service.storage :resource="$application" /> <livewire:project.service.storage :resource="$application" />
</div> </div>
<div x-cloak x-show="activeTab === 'webhooks'">
<livewire:project.shared.webhooks :resource="$application" />
</div>
<div x-cloak x-show="activeTab === 'previews'"> <div x-cloak x-show="activeTab === 'previews'">
<livewire:project.application.previews :application="$application" /> <livewire:project.application.previews :application="$application" />
</div> </div>

View File

@@ -12,7 +12,7 @@
</x-slot:modalSubmit> </x-slot:modalSubmit>
</x-modal> </x-modal>
<div class="pt-6"> <div class="pt-6">
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database,'status')" /> <livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
<h3 class="py-4">Executions</h3> <h3 class="py-4">Executions</h3>
<livewire:project.database.backup-executions :backup="$backup" :executions="$executions" /> <livewire:project.database.backup-executions :backup="$backup" :executions="$executions" />
</div> </div>

View File

@@ -13,36 +13,51 @@
</x-modal> </x-modal>
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex h-full pt-6"> <div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex h-full pt-6">
<div class="flex flex-col gap-4 min-w-fit"> <div class="flex flex-col gap-4 min-w-fit">
<a :class="activeTab === 'general' && 'text-white'" @click.prevent="activeTab = 'general'; <a :class="activeTab === 'general' && 'text-white'"
window.location.hash = 'general'" href="#">General</a> @click.prevent="activeTab = 'general';
window.location.hash = 'general'"
href="#">General</a>
<a :class="activeTab === 'environment-variables' && 'text-white'" <a :class="activeTab === 'environment-variables' && 'text-white'"
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'" @click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
href="#">Environment href="#">Environment
Variables</a> Variables</a>
<a :class="activeTab === 'server' && 'text-white'" @click.prevent="activeTab = 'server'; <a :class="activeTab === 'server' && 'text-white'"
window.location.hash = 'server'" href="#">Server @click.prevent="activeTab = 'server';
window.location.hash = 'server'"
href="#">Server
</a> </a>
<a :class="activeTab === 'storages' && 'text-white'" @click.prevent="activeTab = 'storages'; <a :class="activeTab === 'storages' && 'text-white'"
window.location.hash = 'storages'" href="#">Storages @click.prevent="activeTab = 'storages';
window.location.hash = 'storages'"
href="#">Storages
</a> </a>
<a :class="activeTab === 'resource-limits' && 'text-white'" @click.prevent="activeTab = 'resource-limits'; <a :class="activeTab === 'webhooks' && 'text-white'"
window.location.hash = 'resource-limits'" href="#">Resource Limits @click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
</a> </a>
<a :class="activeTab === 'danger' && 'text-white'" @click.prevent="activeTab = 'danger'; <a :class="activeTab === 'resource-limits' && 'text-white'"
window.location.hash = 'danger'" href="#">Danger Zone @click.prevent="activeTab = 'resource-limits';
window.location.hash = 'resource-limits'"
href="#">Resource Limits
</a>
<a :class="activeTab === 'danger' && 'text-white'"
@click.prevent="activeTab = 'danger';
window.location.hash = 'danger'"
href="#">Danger Zone
</a> </a>
</div> </div>
<div class="w-full pl-8"> <div class="w-full pl-8">
<div x-cloak x-show="activeTab === 'general'" class="h-full"> <div x-cloak x-show="activeTab === 'general'" class="h-full">
@if ($database->type() === 'standalone-postgresql') @if ($database->type() === 'standalone-postgresql')
<livewire:project.database.postgresql.general :database="$database" /> <livewire:project.database.postgresql.general :database="$database" />
@endif @elseif ($database->type() === 'standalone-redis')
@if ($database->type() === 'standalone-redis')
<livewire:project.database.redis.general :database="$database" /> <livewire:project.database.redis.general :database="$database" />
@elseif ($database->type() === 'standalone-mongodb')
<livewire:project.database.mongodb.general :database="$database" />
@elseif ($database->type() === 'standalone-mysql')
<livewire:project.database.mysql.general :database="$database" />
@elseif ($database->type() === 'standalone-mariadb')
<livewire:project.database.mariadb.general :database="$database" />
@endif @endif
@if ($database->type() === 'standalone-mongodb')
<livewire:project.database.mongodb.general :database="$database" />
@endif
</div> </div>
<div x-cloak x-show="activeTab === 'environment-variables'"> <div x-cloak x-show="activeTab === 'environment-variables'">
<livewire:project.shared.environment-variable.all :resource="$database" /> <livewire:project.shared.environment-variable.all :resource="$database" />
@@ -53,6 +68,9 @@
<div x-cloak x-show="activeTab === 'storages'"> <div x-cloak x-show="activeTab === 'storages'">
<livewire:project.service.storage :resource="$database" /> <livewire:project.service.storage :resource="$database" />
</div> </div>
<div x-cloak x-show="activeTab === 'webhooks'">
<livewire:project.shared.webhooks :resource="$database" />
</div>
<div x-cloak x-show="activeTab === 'resource-limits'"> <div x-cloak x-show="activeTab === 'resource-limits'">
<livewire:project.shared.resource-limits :resource="$database" /> <livewire:project.shared.resource-limits :resource="$database" />
</div> </div>

View File

@@ -2,17 +2,21 @@
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h1>Resources</h1> <h1>Resources</h1>
@if ($environment->can_delete_environment()) @if ($environment->isEmpty())
<a class="font-normal text-white normal-case border-none rounded hover:no-underline btn btn-primary btn-sm no-animation"
href="{{ route('project.clone', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => request()->route('environment_name')]) }}">
Clone
</a>
<livewire:project.delete-environment :environment_id="$environment->id" /> <livewire:project.delete-environment :environment_id="$environment->id" />
@else @else
<a href="{{ route('project.resources.new', ['project_uuid' => request()->route('project_uuid'), 'environment_name' => request()->route('environment_name')]) }} " <a href="{{ route('project.resources.new', ['project_uuid' => request()->route('project_uuid'), 'environment_name' => request()->route('environment_name')]) }} "
class="font-normal text-white normal-case border-none rounded hover:no-underline btn btn-primary btn-sm no-animation">+ class="font-normal text-white normal-case border-none rounded hover:no-underline btn btn-primary btn-sm no-animation">+
New</a> New</a>
<a class="font-normal text-white normal-case border-none rounded hover:no-underline btn btn-primary btn-sm no-animation"
href="{{ route('project.clone', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => request()->route('environment_name')]) }}">
Clone
</a>
@endif @endif
<a class="font-normal text-white normal-case border-none rounded hover:no-underline btn btn-primary btn-sm no-animation"
href="{{ route('project.clone', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => request()->route('environment_name')]) }}">
Clone
</a>
</div> </div>
<nav class="flex pt-2 pb-10"> <nav class="flex pt-2 pb-10">
<ol class="flex items-center"> <ol class="flex items-center">
@@ -36,7 +40,7 @@
</ol> </ol>
</nav> </nav>
</div> </div>
@if ($environment->can_delete_environment()) @if ($environment->isEmpty())
<a href="{{ route('project.resources.new', ['project_uuid' => request()->route('project_uuid'), 'environment_name' => request()->route('environment_name')]) }} " <a href="{{ route('project.resources.new', ['project_uuid' => request()->route('project_uuid'), 'environment_name' => request()->route('environment_name')]) }} "
class="items-center justify-center box">+ Add New Resource</a> class="items-center justify-center box">+ Add New Resource</a>
@endif @endif

View File

@@ -1,6 +1,8 @@
<?php <?php
use App\Actions\Database\StartMariadb;
use App\Actions\Database\StartMongodb; 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\Service\StartService; use App\Actions\Service\StartService;
@@ -32,7 +34,6 @@ Route::group([
$teamId = data_get($token, 'team_id'); $teamId = data_get($token, 'team_id');
$uuid = $request->query->get('uuid'); $uuid = $request->query->get('uuid');
$force = $request->query->get('force') ?? false; $force = $request->query->get('force') ?? false;
if (is_null($teamId)) { if (is_null($teamId)) {
return response()->json(['error' => 'Invalid token.'], 400); return response()->json(['error' => 'Invalid token.'], 400);
} }
@@ -50,29 +51,56 @@ Route::group([
); );
return response()->json(['message' => 'Deployment queued.'], 200); return response()->json(['message' => 'Deployment queued.'], 200);
} else if ($type === 'App\Models\StandalonePostgresql') { } else if ($type === 'App\Models\StandalonePostgresql') {
if (str($resource->status)->startsWith('running')) {
return response()->json(['message' => 'Database already running.'], 200);
}
StartPostgresql::run($resource); StartPostgresql::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
return response()->json(['message' => 'Database started.'], 200); return response()->json(['message' => 'Database started.'], 200);
} else if ($type === 'App\Models\StandaloneRedis') { } else if ($type === 'App\Models\StandaloneRedis') {
if (str($resource->status)->startsWith('running')) {
return response()->json(['message' => 'Database already running.'], 200);
}
StartRedis::run($resource); StartRedis::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
return response()->json(['message' => 'Database started.'], 200); return response()->json(['message' => 'Database started.'], 200);
} else if ($type === 'App\Models\StandaloneMongodb') { } else if ($type === 'App\Models\StandaloneMongodb') {
if (str($resource->status)->startsWith('running')) {
return response()->json(['message' => 'Database already running.'], 200);
}
StartMongodb::run($resource); StartMongodb::run($resource);
$resource->update([ $resource->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
return response()->json(['message' => 'Database started.'], 200); return response()->json(['message' => 'Database started.'], 200);
}else if ($type === 'App\Models\Service') { } else if ($type === 'App\Models\StandaloneMysql') {
if (str($resource->status)->startsWith('running')) {
return response()->json(['message' => 'Database already running.'], 200);
}
StartMysql::run($resource);
$resource->update([
'started_at' => now(),
]);
return response()->json(['message' => 'Database started.'], 200);
} else if ($type === 'App\Models\StandaloneMariadb') {
if (str($resource->status)->startsWith('running')) {
return response()->json(['message' => 'Database already running.'], 200);
}
StartMariadb::run($resource);
$resource->update([
'started_at' => now(),
]);
return response()->json(['message' => 'Database started.'], 200);
} else if ($type === 'App\Models\Service') {
StartService::run($resource); StartService::run($resource);
return response()->json(['message' => 'Service started.'], 200); return response()->json(['message' => 'Service started. It could take a while, be patient.'], 200);
} }
} }
return response()->json(['error' => 'No resource found.'], 404); return response()->json(['error' => "No resource found with {$uuid}."], 404);
}); });
}); });

View File

@@ -5,7 +5,7 @@
## Always run "php artisan app:sync-to-bunny-cdn --env=secrets" or "scripts/run sync-bunny" if you update this file. ## Always run "php artisan app:sync-to-bunny-cdn --env=secrets" or "scripts/run sync-bunny" if you update this file.
########### ###########
VERSION="1.0.0" VERSION="1.0.1"
DOCKER_VERSION="24.0" DOCKER_VERSION="24.0"
CDN="https://cdn.coollabs.io/coolify" CDN="https://cdn.coollabs.io/coolify"

View File

@@ -5,7 +5,7 @@
## Always run "php artisan app:sync-to-bunny-cdn --env=secrets" if you update this file. ## Always run "php artisan app:sync-to-bunny-cdn --env=secrets" if you update this file.
########### ###########
VERSION="1.0.0" VERSION="1.0.1"
CDN="https://cdn.coollabs.io/coolify" CDN="https://cdn.coollabs.io/coolify"
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
@@ -15,4 +15,7 @@ curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
# Merge .env and .env.production. New values will be added to .env # Merge .env and .env.production. New values will be added to .env
sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' > /data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' > /data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env
# Make sure coolify network exists
docker network create coolify 2>/dev/null
docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate" docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate"

View File

@@ -1,5 +1,6 @@
# documentation: https://docs.appsmith.com # documentation: https://docs.appsmith.com
# slogan: Appsmith is an open-source, self-hosted application development platform that enables you to build powerful web applications with ease. # slogan: Appsmith is an open-source, self-hosted application development platform that enables you to build powerful web applications with ease.
# tags: lowcode,nocode,no,low,platform
services: services:
appsmith: appsmith:
@@ -13,3 +14,5 @@ services:
- APPSMITH_SMART_LOOK_ID= - APPSMITH_SMART_LOOK_ID=
volumes: volumes:
- stacks-data:/appsmith-stacks - stacks-data:/appsmith-stacks
healthcheck:
test: ["NONE"]

View File

@@ -1,6 +1,7 @@
# documentation: https://appwrite.io/docs # documentation: https://appwrite.io/docs
# slogan: Appwrite is a self-hosted backend-as-a-service platform that simplifies the development of web and mobile applications by providing a range of features and APIs. # slogan: Appwrite is a self-hosted backend-as-a-service platform that simplifies the development of web and mobile applications by providing a range of features and APIs.
# env_file: appwrite.env # env_file: appwrite.env
# tags: backend-as-a-service, platform
x-logging: &x-logging x-logging: &x-logging

View File

@@ -1,5 +1,6 @@
# documentation: https://docs.baby-buddy.net # documentation: https://docs.baby-buddy.net
# slogan: Baby Buddy is an open-source web application that helps parents track their baby's daily activities, growth, and health with ease. # slogan: Baby Buddy is an open-source web application that helps parents track their baby's daily activities, growth, and health with ease.
# tags: baby, parents, health, growth, activities
services: services:
babybuddy: babybuddy:

View File

@@ -1,5 +1,6 @@
# documentation: https://coder.com/docs/code-server/latest/guide # documentation: https://coder.com/docs/code-server/latest/guide
# slogan: Code-Server is a self-hosted, web-based code editor that enables remote coding and collaboration from any device, anywhere. # slogan: Code-Server is a self-hosted, web-based code editor that enables remote coding and collaboration from any device, anywhere.
# tags: code, editor, remote, collaboration
services: services:
code-server: code-server:

View File

@@ -0,0 +1,16 @@
# documentation: https://github.com/phntxx/dashboard/wiki/Installation#installation-using-docker
# slogan: A dashboard. Inspired by SUI, it offers simple customization through JSON-files and a handy search bar to help you browse the internet more efficiently.
# tags: dashboard, web, search, bookmarks
services:
dashboard:
image: phntxx/dashboard:latest
environment:
- SERVICE_FQDN_DASHBOARD
volumes:
- dashboard-data:/app/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -1,5 +1,6 @@
# documentation: https://www.dokuwiki.org/faq # documentation: https://www.dokuwiki.org/faq
# slogan: A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility. # slogan: A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility.
# tags: wiki, documentation, knowledge, base
services: services:
dokuwiki: dokuwiki:

View File

@@ -0,0 +1,21 @@
# documentation: https://emby.media/support/articles/Home.html
# slogan: A media server software that allows you to organize, stream, and access your multimedia content effortlessly, making it easy to enjoy your favorite movies, TV shows, music, and more.
# tags: media, server, movies, tv, music
services:
emby:
image: lscr.io/linuxserver/emby:latest
environment:
- SERVICE_FQDN_EMBY
- PUID=1000
- PGID=1000
- TZ=Europe/Madrid
volumes:
- emby-config:/config
- emby-tvshows:/tvshows
- emby-movies:/movies
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8096"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -0,0 +1,19 @@
# documentation: https://github.com/mregni/EmbyStat/wiki/docker
# slogan: EmyStat is an open-source, self-hosted web analytics tool, designed to provide insight into website traffic and user behavior, of your local Emby deployement, all within your control.
# tags: media, server, movies, tv, music
services:
embystat:
image: lscr.io/linuxserver/embystat:latest
environment:
- SERVICE_FQDN_EMBYSTAT
- PUID=1000
- PGID=1000
- TZ=Europe/Madrid
volumes:
- embystat-config:/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6555"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -1,5 +1,6 @@
# documentation: https://fider.io/doc # documentation: https://fider.io/doc
# slogan: Fider is an open-source feedback platform for collecting and managing user feedback, helping you prioritize improvements to your products and services. # slogan: Fider is an open-source feedback platform for collecting and managing user feedback, helping you prioritize improvements to your products and services.
# tags: feedback, user-feedback
services: services:
fider: fider:

View File

@@ -1,5 +1,6 @@
# documentation: https://ghost.org/docs # documentation: https://ghost.org/docs
# slogan: Ghost is a popular open-source content management system (CMS) and blogging platform, known for its simplicity and focus on content creation. # slogan: Ghost is a popular open-source content management system (CMS) and blogging platform, known for its simplicity and focus on content creation.
# tags: cms, blog, content, management, system
services: services:
ghost: ghost:

View File

@@ -0,0 +1,40 @@
# documentation: https://grafana.com/docs/grafana/latest/installation/docker/
# slogan: Grafana is the open source analytics & monitoring solution for every database.
# tags: grafana,analytics,monitoring,dashboard
services:
grafana:
image: grafana/grafana-oss
environment:
- SERVICE_FQDN_GRAFANA
- GF_SERVER_ROOT_URL=${SERVICE_FQDN_GRAFANA}
- GF_SERVER_DOMAIN=${SERVICE_FQDN_GRAFANA}
- GF_SECURITY_ADMIN_PASSWORD=${SERVICE_PASSWORD_GRAFANA}
- GF_DATABASE_TYPE=postgres
- GF_DATABASE_HOST=postgresql
- GF_DATABASE_USER=$SERVICE_USER_POSTGRES
- GF_DATABASE_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- GF_DATABASE_NAME=${POSTGRES_DB:-grafana}
volumes:
- grafana-data:/var/lib/grafana
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 5s
timeout: 5s
retries: 10
depends_on:
- postgresql
postgresql:
image: postgres:15-alpine
volumes:
- postgresql-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- POSTGRES_DB=${POSTGRES_DB:-grafana}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10

View File

@@ -0,0 +1,19 @@
# documentation: https://grafana.com/docs/grafana/latest/installation/docker/
# slogan: Grafana is the open source analytics & monitoring solution for every database.
# tags: grafana,analytics,monitoring,dashboard
services:
grafana:
image: grafana/grafana-oss
environment:
- SERVICE_FQDN_GRAFANA
- GF_SERVER_ROOT_URL=${SERVICE_FQDN_GRAFANA}
- GF_SERVER_DOMAIN=${SERVICE_FQDN_GRAFANA}
- GF_SECURITY_ADMIN_PASSWORD=${SERVICE_PASSWORD_GRAFANA}
volumes:
- grafana-data:/var/lib/grafana
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 5s
timeout: 5s
retries: 10

View File

@@ -0,0 +1,19 @@
# documentation: https://github.com/grocy/grocy
# slogan: Grocy is a self-hosted, web-based household management and grocery list application, designed to simplify your household chores and grocery shopping.
# tags: groceries, household, management, grocery, shopping
services:
grocy:
image: lscr.io/linuxserver/grocy:latest
environment:
- SERVICE_FQDN_GROCY
- PUID=1000
- PGID=1000
- TZ=Europe/Madrid
volumes:
- grocy-config:/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -1,5 +1,6 @@
# documentation: https://github.com/linuxserver/Heimdall # documentation: https://github.com/linuxserver/Heimdall
# slogan: Heimdall is a self-hosted dashboard for managing and organizing your server applications, providing a centralized and efficient interface. # slogan: Heimdall is a self-hosted dashboard for managing and organizing your server applications, providing a centralized and efficient interface.
# tags: dashboard, server, applications, interface
services: services:
heimdall: heimdall:

View File

@@ -1,5 +1,6 @@
# documentation: https://github.com/alexta69/metube # documentation: https://github.com/alexta69/metube
# slogan: A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites. # slogan: A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.
# tags: youtube, download, videos, playlist
services: services:
metube: metube:

View File

@@ -1,5 +1,6 @@
# documentation: https://docs.min.io/docs/minio-docker-quickstart-guide.html # documentation: https://docs.min.io/docs/minio-docker-quickstart-guide.html
# slogan: MinIO is a high performance object storage server compatible with Amazon S3 APIs. # slogan: MinIO is a high performance object storage server compatible with Amazon S3 APIs.
# tags: object, storage, server, s3, api
services: services:
minio: minio:

View File

@@ -0,0 +1,38 @@
# documentation: https://docs.n8n.io/hosting/
# slogan: n8n is an extendable workflow automation tool which enables you to connect anything to everything via its open, fair-code model.
# tags: n8n,workflow,automation,open,source,low,code
services:
n8n:
image: docker.n8n.io/n8nio/n8n
environment:
- SERVICE_FQDN_N8N
- N8N_EDITOR_BASE_URL=${SERVICE_FQDN_N8N}
- N8N_HOST=${SERVICE_FQDN_N8N}
- GENERIC_TIMEZONE="Europe/Berlin"
- TZ="Europe/Berlin"
- DB_TYPE=postgresdb
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB:-umami}
- DB_POSTGRESDB_HOST=postgresql
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_USER=$SERVICE_USER_POSTGRES
- DB_POSTGRESDB_SCHEMA=public
- DB_POSTGRESDB_PASSWORD=$SERVICE_PASSWORD_POSTGRES
volumes:
- n8n-data:/home/node/.n8n
depends_on:
- postgresql
postgresql:
image: postgres:15-alpine
volumes:
- postgresql-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- POSTGRES_DB=${POSTGRES_DB:-umami}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10

View File

@@ -0,0 +1,15 @@
# documentation: https://docs.n8n.io/hosting/
# slogan: n8n is an extendable workflow automation tool which enables you to connect anything to everything via its open, fair-code model.
# tags: n8n,workflow,automation,open,source,low,code
services:
n8n:
image: docker.n8n.io/n8nio/n8n
environment:
- SERVICE_FQDN_N8N
- N8N_EDITOR_BASE_URL=${SERVICE_FQDN_N8N}
- N8N_HOST=${SERVICE_FQDN_N8N}
- GENERIC_TIMEZONE="Europe/Berlin"
- TZ="Europe/Berlin"
volumes:
- n8n-data:/home/node/.n8n

View File

@@ -0,0 +1,27 @@
# documentation: https://docs.openblocks.dev/self-hosting
# slogan: OpenBlocks is a self-hosted, open-source, low-code platform for building internal tools.
# tags: openblocks,low,code,platform,open,source,low,code
services:
openblocks:
image: openblocksdev/openblocks-ce
environment:
- SERVICE_FQDN_OPENBLOCKS
- REDIS_ENABLED=true
- MONGODB_ENABLED=true
- API_SERVICE_ENABLED=true
- NODE_SERVICE_ENABLED=true
- PUID=1000
- PGID=1000
- MONGODB_URI=mongodb://localhost:27017/openblocks?authSource=admin
- REDIS_URL=redis://localhost:6379
- JS_EXECUTOR_URI=http://localhost:6060
- ENABLE_USER_SIGN_UP=${ENABLE_USER_SIGN_UP:-true}
- ENCRYPTION_PASSWORD=$SERVICE_
volumes:
- openblocks-data:/openblocks-stacks
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 5s
timeout: 5s
retries: 10

View File

@@ -1,5 +1,6 @@
# documentation: https://github.com/schlagmichdoch/PairDrop/blob/master/docs/faq.md # documentation: https://github.com/schlagmichdoch/PairDrop
# slogan: Pairdrop is a self-hosted file sharing and collaboration platform, offering secure file sharing and collaboration capabilities for efficient teamwork. # slogan: Pairdrop is a self-hosted file sharing and collaboration platform, offering secure file sharing and collaboration capabilities for efficient teamwork.
# tags: file, sharing, collaboration, teamwork
services: services:
pairdrop: pairdrop:

View File

@@ -1,6 +1,7 @@
# ignore: true # ignore: true
# documentation: https://plausible.io/docs/self-hosting # documentation: https://plausible.io/docs/self-hosting
# slogan: "Plausible Analytics is a simple, open-source, lightweight (< 1 KB) and privacy-friendly web analytics alternative to Google Analytics." # slogan: "Plausible Analytics is a simple, open-source, lightweight (< 1 KB) and privacy-friendly web analytics alternative to Google Analytics."
# tags: analytics, privacy, google, alternative
version: "3.3" version: "3.3"
services: services:

View File

@@ -1,5 +1,6 @@
# documentation: https://github.com/RobinLinus/snapdrop/blob/master/docs/faq.md # documentation: https://github.com/RobinLinus/snapdrop
# slogan: A self-hosted file-sharing service for secure and convenient file transfers, whether on a local network or the internet. # slogan: A self-hosted file-sharing service for secure and convenient file transfers, whether on a local network or the internet.
# tags: file, sharing, transfer, local, network, internet
services: services:
snapdrop: snapdrop:

View File

@@ -1,5 +1,6 @@
# documentation: https://umami.is/docs/getting-started # documentation: https://umami.is/docs/getting-started
# slogan: Umami is a lightweight, self-hosted web analytics platform designed to provide website owners with insights into visitor behavior without compromising user privacy. # slogan: Umami is a lightweight, self-hosted web analytics platform designed to provide website owners with insights into visitor behavior without compromising user privacy.
# tags: analytics, insights, privacy
services: services:
umami: umami:

View File

@@ -1,5 +1,6 @@
# documentation: https://github.com/louislam/uptime-kuma/wiki # documentation: https://github.com/louislam/uptime-kuma/wiki
# slogan: Uptime Kuma is a free, self-hosted monitoring tool for tracking the status and performance of your web services and applications in real-time. # slogan: Uptime Kuma is a free, self-hosted monitoring tool for tracking the status and performance of your web services and applications in real-time.
# tags: monitoring, status, performance, web, services, applications, real-time
services: services:
uptime-kuma: uptime-kuma:

View File

@@ -1,5 +1,6 @@
# documentation: https://wordpress.org/documentation/ # documentation: https://wordpress.org/documentation/
# slogan: "WordPress is open source software you can use to create a beautiful website, blog, or app." # slogan: WordPress with MariaDB. Wordpress is open source software you can use to create a beautiful website, blog, or app.
# tags: cms, blog, content, management, mariadb
services: services:
wordpress: wordpress:

View File

@@ -1,5 +1,6 @@
# documentation: https://wordpress.org/documentation/ # documentation: https://wordpress.org/documentation/
# slogan: "WordPress is open source software you can use to create a beautiful website, blog, or app." # slogan: WordPress with MySQL. Wordpress is open source software you can use to create a beautiful website, blog, or app.
# tags: cms, blog, content, management, mysql
services: services:
wordpress: wordpress:

Some files were not shown because too many files have changed in this diff Show More