@@ -19,11 +19,7 @@ DB_PORT=5432
|
|||||||
# Set to true to enable Ray
|
# Set to true to enable Ray
|
||||||
RAY_ENABLED=false
|
RAY_ENABLED=false
|
||||||
# Set custom ray port
|
# Set custom ray port
|
||||||
RAY_PORT=
|
# RAY_PORT=
|
||||||
|
|
||||||
# Clockwork Configuration (remove?)
|
|
||||||
CLOCKWORK_ENABLED=false
|
|
||||||
CLOCKWORK_QUEUE_COLLECT=true
|
|
||||||
|
|
||||||
# Enable Laravel Telescope for debugging
|
# Enable Laravel Telescope for debugging
|
||||||
TELESCOPE_ENABLED=false
|
TELESCOPE_ENABLED=false
|
||||||
|
|||||||
14
.github/pull_request_template.md
vendored
14
.github/pull_request_template.md
vendored
@@ -1 +1,13 @@
|
|||||||
> Always use `next` branch as destination branch for PRs, not `main`
|
## Submit Checklist (REMOVE THIS SECTION BEFORE SUBMITTING)
|
||||||
|
- [ ] I have selected the `next` branch as the destination for my PR, not `main`.
|
||||||
|
- [ ] I have listed all changes in the `Changes` section.
|
||||||
|
- [ ] I have filled out the `Issues` section with the issue/discussion link(s) (if applicable).
|
||||||
|
- [ ] I have tested my changes.
|
||||||
|
- [ ] I have considered backwards compatibility.
|
||||||
|
- [ ] I have removed this checklist and any unused sections.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
-
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
- fix #
|
||||||
|
|||||||
@@ -6,15 +6,19 @@ You can ask for guidance anytime on our [Discord server](https://coollabs.io/dis
|
|||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
1. [Setup Development Environment](#1-setup-development-environment)
|
- [Contributing to Coolify](#contributing-to-coolify)
|
||||||
2. [Verify Installation](#2-verify-installation-optional)
|
- [Table of Contents](#table-of-contents)
|
||||||
3. [Fork and Setup Local Repository](#3-fork-and-setup-local-repository)
|
- [1. Setup Development Environment](#1-setup-development-environment)
|
||||||
4. [Set up Environment Variables](#4-set-up-environment-variables)
|
- [2. Verify Installation (Optional)](#2-verify-installation-optional)
|
||||||
5. [Start Coolify](#5-start-coolify)
|
- [3. Fork and Setup Local Repository](#3-fork-and-setup-local-repository)
|
||||||
6. [Start Development](#6-start-development)
|
- [4. Set up Environment Variables](#4-set-up-environment-variables)
|
||||||
7. [Development Notes](#7-development-notes)
|
- [5. Start Coolify](#5-start-coolify)
|
||||||
8. [Create a Pull Request](#8-create-a-pull-request)
|
- [6. Start Development](#6-start-development)
|
||||||
9. [Additional Contribution Guidelines](#additional-contribution-guidelines)
|
- [7. Development Notes](#7-development-notes)
|
||||||
|
- [8. Create a Pull Request](#8-create-a-pull-request)
|
||||||
|
- [Additional Contribution Guidelines](#additional-contribution-guidelines)
|
||||||
|
- [Contributing a New Service](#contributing-a-new-service)
|
||||||
|
- [Contributing to Documentation](#contributing-to-documentation)
|
||||||
|
|
||||||
## 1. Setup Development Environment
|
## 1. Setup Development Environment
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Actions\Application;
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
|
||||||
use App\Actions\Server\CleanupDocker;
|
use App\Actions\Server\CleanupDocker;
|
||||||
|
use App\Models\Application;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopApplication
|
class StopApplication
|
||||||
@@ -14,13 +14,14 @@ class StopApplication
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = $application->destination->server;
|
$server = $application->destination->server;
|
||||||
if (!$server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
ray('Stopping application: ' . $application->name);
|
ray('Stopping application: '.$application->name);
|
||||||
|
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
instant_remote_process(["docker stack rm {$application->uuid}"], $server);
|
instant_remote_process(["docker stack rm {$application->uuid}"], $server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,10 +33,11 @@ class StopApplication
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($dockerCleanup) {
|
if ($dockerCleanup) {
|
||||||
CleanupDocker::run($server, true);
|
CleanupDocker::dispatch($server, true);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ namespace App\Actions\CoolifyTask;
|
|||||||
|
|
||||||
use App\Enums\ActivityTypes;
|
use App\Enums\ActivityTypes;
|
||||||
use App\Enums\ProcessStatus;
|
use App\Enums\ProcessStatus;
|
||||||
|
use App\Helpers\SshMultiplexingHelper;
|
||||||
use App\Jobs\ApplicationDeploymentJob;
|
use App\Jobs\ApplicationDeploymentJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Process\ProcessResult;
|
use Illuminate\Process\ProcessResult;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
use App\Helpers\SshMultiplexingHelper;
|
|
||||||
|
|
||||||
class RunRemoteProcess
|
class RunRemoteProcess
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class StartDragonfly
|
|||||||
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -75,7 +75,7 @@ class StartDragonfly
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -118,10 +118,10 @@ class StartDragonfly
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ class StartDragonfly
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class StartKeydb
|
|||||||
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
|
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -74,7 +74,7 @@ class StartKeydb
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -94,10 +94,10 @@ class StartKeydb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) {
|
if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/keydb.conf',
|
'source' => $this->configuration_dir.'/keydb.conf',
|
||||||
'target' => '/etc/keydb/keydb.conf',
|
'target' => '/etc/keydb/keydb.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -125,10 +125,10 @@ class StartKeydb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ class StartKeydb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class StartMariadb
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -69,7 +69,7 @@ class StartMariadb
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -89,10 +89,10 @@ class StartMariadb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) {
|
if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
'source' => $this->configuration_dir.'/custom-config.cnf',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -120,10 +120,10 @@ class StartMariadb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,18 +154,18 @@ class StartMariadb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class StartMongodb
|
|||||||
$startCommand = 'mongod';
|
$startCommand = 'mongod';
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -77,7 +77,7 @@ class StartMongodb
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -97,19 +97,19 @@ class StartMongodb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) {
|
if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/mongod.conf',
|
'source' => $this->configuration_dir.'/mongod.conf',
|
||||||
'target' => '/etc/mongo/mongod.conf',
|
'target' => '/etc/mongo/mongod.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$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();
|
$this->add_default_database();
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
|
'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d',
|
||||||
'target' => '/docker-entrypoint-initdb.d',
|
'target' => '/docker-entrypoint-initdb.d',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -136,10 +136,10 @@ class StartMongodb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,15 +170,15 @@ class StartMongodb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class StartMysql
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -69,7 +69,7 @@ class StartMysql
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -89,10 +89,10 @@ class StartMysql
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) {
|
if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
'source' => $this->configuration_dir.'/custom-config.cnf',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -120,10 +120,10 @@ class StartMysql
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,18 +154,18 @@ class StartMysql
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class StartPostgresql
|
|||||||
$this->generate_init_scripts();
|
$this->generate_init_scripts();
|
||||||
$this->add_custom_conf();
|
$this->add_custom_conf();
|
||||||
|
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class StartRedis
|
|||||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -78,7 +78,7 @@ class StartRedis
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -98,10 +98,10 @@ class StartRedis
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) {
|
if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/redis.conf',
|
'source' => $this->configuration_dir.'/redis.conf',
|
||||||
'target' => '/usr/local/etc/redis/redis.conf',
|
'target' => '/usr/local/etc/redis/redis.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -130,10 +130,10 @@ class StartRedis
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ class StartRedis
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class StopDatabase
|
|||||||
$this->stopContainer($database, $database->uuid, 300);
|
$this->stopContainer($database, $database->uuid, 300);
|
||||||
if (! $isDeleteOperation) {
|
if (! $isDeleteOperation) {
|
||||||
if ($dockerCleanup) {
|
if ($dockerCleanup) {
|
||||||
CleanupDocker::run($server, true);
|
CleanupDocker::dispatch($server, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -543,7 +543,7 @@ class GetContainersStatus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$exitedServices = $exitedServices->unique('id');
|
$exitedServices = $exitedServices->unique('uuid');
|
||||||
foreach ($exitedServices as $exitedService) {
|
foreach ($exitedServices as $exitedService) {
|
||||||
if (str($exitedService->status)->startsWith('exited')) {
|
if (str($exitedService->status)->startsWith('exited')) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ class StartProxy
|
|||||||
"echo 'Pulling docker image.'",
|
"echo 'Pulling docker image.'",
|
||||||
'docker compose pull',
|
'docker compose pull',
|
||||||
"echo 'Stopping existing coolify-proxy.'",
|
"echo 'Stopping existing coolify-proxy.'",
|
||||||
'docker compose down -v --remove-orphans > /dev/null 2>&1',
|
'docker stop -t 10 coolify-proxy || true',
|
||||||
|
'docker rm coolify-proxy || true',
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
'docker compose up -d --remove-orphans',
|
'docker compose up -d --remove-orphans',
|
||||||
"echo 'Proxy started successfully.'",
|
"echo 'Proxy started successfully.'",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Events\CloudflareTunnelConfigured;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@@ -40,12 +41,17 @@ class ConfigureCloudflared
|
|||||||
instant_remote_process($commands, $server);
|
instant_remote_process($commands, $server);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
|
$server->settings->is_cloudflare_tunnel = false;
|
||||||
|
$server->settings->save();
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
|
CloudflareTunnelConfigured::dispatch($server->team_id);
|
||||||
|
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
'rm -fr /tmp/cloudflared',
|
'rm -fr /tmp/cloudflared',
|
||||||
]);
|
]);
|
||||||
instant_remote_process($commands, $server);
|
instant_remote_process($commands, $server);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use App\Models\Service;
|
|
||||||
use App\Actions\Server\CleanupDocker;
|
use App\Actions\Server\CleanupDocker;
|
||||||
|
use App\Models\Service;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class DeleteService
|
class DeleteService
|
||||||
@@ -36,7 +36,7 @@ class DeleteService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute volume deletion first, this must be done first otherwise volumes will not be deleted.
|
// Execute volume deletion first, this must be done first otherwise volumes will not be deleted.
|
||||||
if (!empty($commands)) {
|
if (! empty($commands)) {
|
||||||
foreach ($commands as $command) {
|
foreach ($commands as $command) {
|
||||||
$result = instant_remote_process([$command], $server, false);
|
$result = instant_remote_process([$command], $server, false);
|
||||||
if ($result !== 0) {
|
if ($result !== 0) {
|
||||||
@@ -70,7 +70,7 @@ class DeleteService
|
|||||||
$service->forceDelete();
|
$service->forceDelete();
|
||||||
|
|
||||||
if ($dockerCleanup) {
|
if ($dockerCleanup) {
|
||||||
CleanupDocker::run($server, true);
|
CleanupDocker::dispatch($server, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
use App\Models\Service;
|
|
||||||
use App\Actions\Server\CleanupDocker;
|
use App\Actions\Server\CleanupDocker;
|
||||||
|
use App\Models\Service;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopService
|
class StopService
|
||||||
@@ -14,21 +14,22 @@ class StopService
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = $service->destination->server;
|
$server = $service->destination->server;
|
||||||
if (!$server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
|
|
||||||
$containersToStop = $service->getContainersToStop();
|
$containersToStop = $service->getContainersToStop();
|
||||||
$service->stopContainers($containersToStop, $server);
|
$service->stopContainers($containersToStop, $server);
|
||||||
|
|
||||||
if (!$isDeleteOperation) {
|
if (! $isDeleteOperation) {
|
||||||
$service->delete_connected_networks($service->uuid);
|
$service->delete_connected_networks($service->uuid);
|
||||||
if ($dockerCleanup) {
|
if ($dockerCleanup) {
|
||||||
CleanupDocker::run($server, true);
|
CleanupDocker::dispatch($server, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
|
|
||||||
class CleanupQueue extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'cleanup:queue';
|
|
||||||
|
|
||||||
protected $description = 'Cleanup Queue';
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
echo "Running queue cleanup...\n";
|
|
||||||
$prefix = config('database.redis.options.prefix');
|
|
||||||
$keys = Redis::connection()->keys('*:laravel*');
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
|
||||||
Redis::connection()->del($keyWithoutPrefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
app/Console/Commands/CleanupRedis.php
Normal file
31
app/Console/Commands/CleanupRedis.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
class CleanupRedis extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:redis';
|
||||||
|
|
||||||
|
protected $description = 'Cleanup Redis';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Cleanup Redis keys.\n";
|
||||||
|
$prefix = config('database.redis.options.prefix');
|
||||||
|
|
||||||
|
$keys = Redis::connection()->keys('*:laravel*');
|
||||||
|
collect($keys)->each(function ($key) use ($prefix) {
|
||||||
|
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
||||||
|
Redis::connection()->del($keyWithoutPrefix);
|
||||||
|
});
|
||||||
|
|
||||||
|
$queueOverlaps = Redis::connection()->keys('*laravel-queue-overlap*');
|
||||||
|
collect($queueOverlaps)->each(function ($key) {
|
||||||
|
Redis::connection()->del($key);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
@@ -35,6 +37,16 @@ class CleanupStuckedResources extends Command
|
|||||||
private function cleanup_stucked_resources()
|
private function cleanup_stucked_resources()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$servers = Server::all()->filter(function ($server) {
|
||||||
|
return $server->isFunctional();
|
||||||
|
});
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
CleanupHelperContainersJob::dispatch($server);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace App\Console\Commands;
|
|||||||
use App\Actions\Server\StopSentinel;
|
use App\Actions\Server\StopSentinel;
|
||||||
use App\Enums\ActivityTypes;
|
use App\Enums\ActivityTypes;
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Environment;
|
use App\Models\Environment;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
@@ -18,7 +17,7 @@ use Illuminate\Support\Facades\Http;
|
|||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments} {--cleanup-proxy-networks}';
|
protected $signature = 'app:init {--force-cloud}';
|
||||||
|
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
@@ -26,9 +25,63 @@ class Init extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
if (isCloud() && ! $this->option('force-cloud')) {
|
||||||
|
echo "Skipping init as we are on cloud and --force-cloud option is not set\n";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->servers = Server::all();
|
$this->servers = Server::all();
|
||||||
$this->alive();
|
if (isCloud()) {
|
||||||
get_public_ips();
|
|
||||||
|
} else {
|
||||||
|
$this->send_alive_signal();
|
||||||
|
get_public_ips();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward compatibility
|
||||||
|
$this->disable_metrics();
|
||||||
|
$this->replace_slash_in_environment_name();
|
||||||
|
$this->restore_coolify_db_backup();
|
||||||
|
//
|
||||||
|
$this->update_traefik_labels();
|
||||||
|
if (! isCloud() || $this->option('force-cloud')) {
|
||||||
|
$this->cleanup_unused_network_from_coolify_proxy();
|
||||||
|
}
|
||||||
|
if (isCloud()) {
|
||||||
|
$this->cleanup_unnecessary_dynamic_proxy_configuration();
|
||||||
|
} else {
|
||||||
|
$this->cleanup_in_progress_application_deployments();
|
||||||
|
}
|
||||||
|
$this->call('cleanup:redis');
|
||||||
|
$this->call('cleanup:stucked-resources');
|
||||||
|
|
||||||
|
if (isCloud()) {
|
||||||
|
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||||
|
if ($response->successful()) {
|
||||||
|
$services = $response->json();
|
||||||
|
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$localhost = $this->servers->where('id', 0)->first();
|
||||||
|
$localhost->setupDynamicProxyConfiguration();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if (! is_null(env('AUTOUPDATE', null))) {
|
||||||
|
if (env('AUTOUPDATE') == true) {
|
||||||
|
$settings->update(['is_auto_update_enabled' => true]);
|
||||||
|
} else {
|
||||||
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function disable_metrics()
|
||||||
|
{
|
||||||
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
if ($server->settings->is_metrics_enabled === true) {
|
if ($server->settings->is_metrics_enabled === true) {
|
||||||
@@ -39,62 +92,6 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$full_cleanup = $this->option('full-cleanup');
|
|
||||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
|
||||||
$cleanup_proxy_networks = $this->option('cleanup-proxy-networks');
|
|
||||||
$this->replace_slash_in_environment_name();
|
|
||||||
if ($cleanup_deployments) {
|
|
||||||
echo "Running cleanup deployments.\n";
|
|
||||||
$this->cleanup_in_progress_application_deployments();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($cleanup_proxy_networks) {
|
|
||||||
echo "Running cleanup proxy networks.\n";
|
|
||||||
$this->cleanup_unused_network_from_coolify_proxy();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($full_cleanup) {
|
|
||||||
// Required for falsely deleted coolify db
|
|
||||||
$this->restore_coolify_db_backup();
|
|
||||||
$this->update_traefik_labels();
|
|
||||||
$this->cleanup_unused_network_from_coolify_proxy();
|
|
||||||
$this->cleanup_unnecessary_dynamic_proxy_configuration();
|
|
||||||
$this->cleanup_in_progress_application_deployments();
|
|
||||||
$this->cleanup_stucked_helper_containers();
|
|
||||||
$this->call('cleanup:queue');
|
|
||||||
$this->call('cleanup:stucked-resources');
|
|
||||||
if (! isCloud()) {
|
|
||||||
try {
|
|
||||||
$localhost = $this->servers->where('id', 0)->first();
|
|
||||||
$localhost->setupDynamicProxyConfiguration();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
if (! is_null(env('AUTOUPDATE', null))) {
|
|
||||||
if (env('AUTOUPDATE') == true) {
|
|
||||||
$settings->update(['is_auto_update_enabled' => true]);
|
|
||||||
} else {
|
|
||||||
$settings->update(['is_auto_update_enabled' => false]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isCloud()) {
|
|
||||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
|
||||||
if ($response->successful()) {
|
|
||||||
$services = $response->json();
|
|
||||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->cleanup_stucked_helper_containers();
|
|
||||||
$this->call('cleanup:stucked-resources');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function update_traefik_labels()
|
private function update_traefik_labels()
|
||||||
@@ -108,33 +105,28 @@ class Init extends Command
|
|||||||
|
|
||||||
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
foreach ($this->servers as $server) {
|
||||||
foreach ($this->servers as $server) {
|
try {
|
||||||
try {
|
if (! $server->isFunctional()) {
|
||||||
if (! $server->isFunctional()) {
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($server->id === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$file = $server->proxyPath().'/dynamic/coolify.yaml';
|
|
||||||
|
|
||||||
return instant_remote_process([
|
|
||||||
"rm -f $file",
|
|
||||||
], $server, false);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
|
|
||||||
}
|
}
|
||||||
|
if ($server->id === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$file = $server->proxyPath().'/dynamic/coolify.yaml';
|
||||||
|
|
||||||
|
return instant_remote_process([
|
||||||
|
"rm -f $file",
|
||||||
|
], $server, false);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanup_unused_network_from_coolify_proxy()
|
private function cleanup_unused_network_from_coolify_proxy()
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -175,39 +167,32 @@ class Init extends Command
|
|||||||
|
|
||||||
private function restore_coolify_db_backup()
|
private function restore_coolify_db_backup()
|
||||||
{
|
{
|
||||||
try {
|
if (version_compare('4.0.0-beta.179', config('version'), '<=')) {
|
||||||
$database = StandalonePostgresql::withTrashed()->find(0);
|
try {
|
||||||
if ($database && $database->trashed()) {
|
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||||
echo "Restoring coolify db backup\n";
|
if ($database && $database->trashed()) {
|
||||||
$database->restore();
|
echo "Restoring coolify db backup\n";
|
||||||
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
$database->restore();
|
||||||
if (! $scheduledBackup) {
|
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
||||||
ScheduledDatabaseBackup::create([
|
if (! $scheduledBackup) {
|
||||||
'id' => 0,
|
ScheduledDatabaseBackup::create([
|
||||||
'enabled' => true,
|
'id' => 0,
|
||||||
'save_s3' => false,
|
'enabled' => true,
|
||||||
'frequency' => '0 0 * * *',
|
'save_s3' => false,
|
||||||
'database_id' => $database->id,
|
'frequency' => '0 0 * * *',
|
||||||
'database_type' => 'App\Models\StandalonePostgresql',
|
'database_id' => $database->id,
|
||||||
'team_id' => 0,
|
'database_type' => 'App\Models\StandalonePostgresql',
|
||||||
]);
|
'team_id' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (\Throwable $e) {
|
||||||
} catch (\Throwable $e) {
|
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
|
||||||
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function cleanup_stucked_helper_containers()
|
|
||||||
{
|
|
||||||
foreach ($this->servers as $server) {
|
|
||||||
if ($server->isFunctional()) {
|
|
||||||
CleanupHelperContainersJob::dispatch($server);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function alive()
|
private function send_alive_signal()
|
||||||
{
|
{
|
||||||
$id = config('app.id');
|
$id = config('app.id');
|
||||||
$version = config('version');
|
$version = config('version');
|
||||||
@@ -225,23 +210,7 @@ class Init extends Command
|
|||||||
echo "Error in alive: {$e->getMessage()}\n";
|
echo "Error in alive: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// private function cleanup_ssh()
|
|
||||||
// {
|
|
||||||
|
|
||||||
// TODO: it will cleanup id.root@host.docker.internal
|
|
||||||
// try {
|
|
||||||
// $files = Storage::allFiles('ssh/keys');
|
|
||||||
// foreach ($files as $file) {
|
|
||||||
// Storage::delete($file);
|
|
||||||
// }
|
|
||||||
// $files = Storage::allFiles('ssh/mux');
|
|
||||||
// foreach ($files as $file) {
|
|
||||||
// Storage::delete($file);
|
|
||||||
// }
|
|
||||||
// } catch (\Throwable $e) {
|
|
||||||
// echo "Error in cleaning ssh: {$e->getMessage()}\n";
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanup_in_progress_application_deployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
@@ -263,11 +232,13 @@ class Init extends Command
|
|||||||
|
|
||||||
private function replace_slash_in_environment_name()
|
private function replace_slash_in_environment_name()
|
||||||
{
|
{
|
||||||
$environments = Environment::all();
|
if (version_compare('4.0.0-beta.298', config('version'), '<=')) {
|
||||||
foreach ($environments as $environment) {
|
$environments = Environment::all();
|
||||||
if (str_contains($environment->name, '/')) {
|
foreach ($environments as $environment) {
|
||||||
$environment->name = str_replace('/', '-', $environment->name);
|
if (str_contains($environment->name, '/')) {
|
||||||
$environment->save();
|
$environment->name = str_replace('/', '-', $environment->name);
|
||||||
|
$environment->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
app/Events/CloudflareTunnelConfigured.php
Normal file
34
app/Events/CloudflareTunnelConfigured.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CloudflareTunnelConfigured implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public $teamId;
|
||||||
|
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
$teamId = auth()->user()->currentTeam()->id ?? null;
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception('Team id is null');
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,28 +24,24 @@ class SshMultiplexingHelper
|
|||||||
public static function ensureMultiplexedConnection(Server $server)
|
public static function ensureMultiplexedConnection(Server $server)
|
||||||
{
|
{
|
||||||
if (! self::isMultiplexingEnabled()) {
|
if (! self::isMultiplexingEnabled()) {
|
||||||
// ray('SSH Multiplexing: DISABLED')->red();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ray('SSH Multiplexing: ENABLED')->green();
|
|
||||||
// ray('Ensuring multiplexed connection for server:', $server);
|
|
||||||
|
|
||||||
$sshConfig = self::serverSshConfiguration($server);
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
$muxSocket = $sshConfig['muxFilename'];
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||||
|
|
||||||
self::validateSshKey($sshKeyLocation);
|
self::validateSshKey($sshKeyLocation);
|
||||||
|
|
||||||
$checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip}";
|
$checkCommand = "ssh -O check -o ControlPath=$muxSocket ";
|
||||||
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$checkCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
|
$checkCommand .= "{$server->user}@{$server->ip}";
|
||||||
$process = Process::run($checkCommand);
|
$process = Process::run($checkCommand);
|
||||||
|
|
||||||
if ($process->exitCode() !== 0) {
|
if ($process->exitCode() !== 0) {
|
||||||
// ray('SSH Multiplexing: Existing connection check failed or not found')->orange();
|
|
||||||
// ray('Establishing new connection');
|
|
||||||
self::establishNewMultiplexedConnection($server);
|
self::establishNewMultiplexedConnection($server);
|
||||||
} else {
|
|
||||||
// ray('SSH Multiplexing: Existing connection is valid')->green();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,37 +51,24 @@ class SshMultiplexingHelper
|
|||||||
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||||
$muxSocket = $sshConfig['muxFilename'];
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
// ray('Establishing new multiplexed connection')->blue();
|
|
||||||
// ray('SSH Key Location:', $sshKeyLocation);
|
|
||||||
// ray('Mux Socket:', $muxSocket);
|
|
||||||
|
|
||||||
$connectionTimeout = config('constants.ssh.connection_timeout');
|
$connectionTimeout = config('constants.ssh.connection_timeout');
|
||||||
$serverInterval = config('constants.ssh.server_interval');
|
$serverInterval = config('constants.ssh.server_interval');
|
||||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
|
||||||
$establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "
|
$establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||||
.self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval)
|
|
||||||
."{$server->user}@{$server->ip}";
|
|
||||||
|
|
||||||
// ray('Establish Command:', $establishCommand);
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$establishCommand .= ' -o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$establishCommand .= self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval);
|
||||||
|
$establishCommand .= "{$server->user}@{$server->ip}";
|
||||||
|
|
||||||
$establishProcess = Process::run($establishCommand);
|
$establishProcess = Process::run($establishCommand);
|
||||||
|
|
||||||
// ray('Establish Process Exit Code:', $establishProcess->exitCode());
|
|
||||||
// ray('Establish Process Output:', $establishProcess->output());
|
|
||||||
// ray('Establish Process Error Output:', $establishProcess->errorOutput());
|
|
||||||
|
|
||||||
if ($establishProcess->exitCode() !== 0) {
|
if ($establishProcess->exitCode() !== 0) {
|
||||||
// ray('Failed to establish multiplexed connection')->red();
|
|
||||||
throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
|
throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ray('Successfully established multiplexed connection')->green();
|
|
||||||
|
|
||||||
// Check if the mux socket file was created
|
|
||||||
if (! file_exists($muxSocket)) {
|
|
||||||
// ray('Mux socket file not found after connection establishment')->orange();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function removeMuxFile(Server $server)
|
public static function removeMuxFile(Server $server)
|
||||||
@@ -93,20 +76,12 @@ class SshMultiplexingHelper
|
|||||||
$sshConfig = self::serverSshConfiguration($server);
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
$muxSocket = $sshConfig['muxFilename'];
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip}";
|
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket ";
|
||||||
$process = Process::run($closeCommand);
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$closeCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||||
// ray('Closing multiplexed connection')->blue();
|
|
||||||
// ray('Close command:', $closeCommand);
|
|
||||||
// ray('Close process exit code:', $process->exitCode());
|
|
||||||
// ray('Close process output:', $process->output());
|
|
||||||
// ray('Close process error output:', $process->errorOutput());
|
|
||||||
|
|
||||||
if ($process->exitCode() !== 0) {
|
|
||||||
// ray('Failed to close multiplexed connection')->orange();
|
|
||||||
} else {
|
|
||||||
// ray('Successfully closed multiplexed connection')->green();
|
|
||||||
}
|
}
|
||||||
|
$closeCommand .= "{$server->user}@{$server->ip}";
|
||||||
|
Process::run($closeCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function generateScpCommand(Server $server, string $source, string $dest)
|
public static function generateScpCommand(Server $server, string $source, string $dest)
|
||||||
@@ -116,16 +91,18 @@ class SshMultiplexingHelper
|
|||||||
$muxSocket = $sshConfig['muxFilename'];
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
$timeout = config('constants.ssh.command_timeout');
|
$timeout = config('constants.ssh.command_timeout');
|
||||||
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
|
||||||
$scp_command = "timeout $timeout scp ";
|
$scp_command = "timeout $timeout scp ";
|
||||||
|
|
||||||
if (self::isMultiplexingEnabled()) {
|
if (self::isMultiplexingEnabled()) {
|
||||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
|
||||||
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||||
self::ensureMultiplexedConnection($server);
|
self::ensureMultiplexedConnection($server);
|
||||||
}
|
}
|
||||||
|
|
||||||
self::addCloudflareProxyCommand($scp_command, $server);
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$scp_command .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
|
|
||||||
$scp_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'), isScp: true);
|
$scp_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'), isScp: true);
|
||||||
$scp_command .= "{$source} {$server->user}@{$server->ip}:{$dest}";
|
$scp_command .= "{$source} {$server->user}@{$server->ip}:{$dest}";
|
||||||
@@ -144,16 +121,18 @@ class SshMultiplexingHelper
|
|||||||
$muxSocket = $sshConfig['muxFilename'];
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
$timeout = config('constants.ssh.command_timeout');
|
$timeout = config('constants.ssh.command_timeout');
|
||||||
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
|
||||||
$ssh_command = "timeout $timeout ssh ";
|
$ssh_command = "timeout $timeout ssh ";
|
||||||
|
|
||||||
if (self::isMultiplexingEnabled()) {
|
if (self::isMultiplexingEnabled()) {
|
||||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
|
||||||
$ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
$ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||||
self::ensureMultiplexedConnection($server);
|
self::ensureMultiplexedConnection($server);
|
||||||
}
|
}
|
||||||
|
|
||||||
self::addCloudflareProxyCommand($ssh_command, $server);
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$ssh_command .= "-o ProxyCommand='cloudflared access ssh --hostname %h' ";
|
||||||
|
}
|
||||||
|
|
||||||
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
|
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
|
||||||
|
|
||||||
@@ -183,13 +162,6 @@ class SshMultiplexingHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function addCloudflareProxyCommand(string &$command, Server $server): void
|
|
||||||
{
|
|
||||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
|
||||||
$command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function getCommonSshOptions(Server $server, string $sshKeyLocation, int $connectionTimeout, int $serverInterval, bool $isScp = false): string
|
private static function getCommonSshOptions(Server $server, string $sshKeyLocation, int $connectionTimeout, int $serverInterval, bool $isScp = false): string
|
||||||
{
|
{
|
||||||
$options = "-i {$sshKeyLocation} "
|
$options = "-i {$sshKeyLocation} "
|
||||||
|
|||||||
@@ -2529,6 +2529,131 @@ class ApplicationsController extends Controller
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Execute Command',
|
||||||
|
description: "Execute a command on the application's current container.",
|
||||||
|
path: '/applications/{uuid}/execute',
|
||||||
|
operationId: 'execute-command-application',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Applications'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the application.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Command to execute.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'command' => ['type' => 'string', 'description' => 'Command to execute.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: "Execute a command on the application's current container.",
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Command executed.'],
|
||||||
|
'response' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function execute_command_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
// TODO: Need to review this from security perspective, to not allow arbitrary command execution
|
||||||
|
$allowedFields = ['command'];
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
if (! $application) {
|
||||||
|
return response()->json(['message' => 'Application not found.'], 404);
|
||||||
|
}
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'command' => 'string|required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail();
|
||||||
|
$status = getContainerStatus($application->destination->server, $container['Names']);
|
||||||
|
|
||||||
|
if ($status !== 'running') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Application is not running.',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$commands = collect([
|
||||||
|
executeInDocker($container['Names'], $request->command),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$res = instant_remote_process(command: $commands, server: $application->destination->server);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Command executed.',
|
||||||
|
'response' => $res,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
private function validateDataApplications(Request $request, Server $server)
|
private function validateDataApplications(Request $request, Server $server)
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
use Illuminate\Support\Sleep;
|
use Illuminate\Support\Sleep;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
@@ -2227,7 +2227,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (\Exception $error) {
|
} catch (\Exception $error) {
|
||||||
$this->application_deployment_queue->addLogEntry("Error stopping container $containerName: " . $error->getMessage(), 'stderr');
|
$this->application_deployment_queue->addLogEntry("Error stopping container $containerName: ".$error->getMessage(), 'stderr');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->remove_container($containerName);
|
$this->remove_container($containerName);
|
||||||
@@ -2250,7 +2250,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$containers = $containers->filter(function ($container) {
|
$containers = $containers->filter(function ($container) {
|
||||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id;
|
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$containers->each(function ($container) {
|
$containers->each(function ($container) {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
@@ -10,7 +11,6 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Carbon\Carbon;
|
|
||||||
|
|
||||||
class CleanupStaleMultiplexedConnections implements ShouldQueue
|
class CleanupStaleMultiplexedConnections implements ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -30,8 +30,9 @@ class CleanupStaleMultiplexedConnections implements ShouldQueue
|
|||||||
$serverUuid = $this->extractServerUuidFromMuxFile($muxFile);
|
$serverUuid = $this->extractServerUuidFromMuxFile($muxFile);
|
||||||
$server = Server::where('uuid', $serverUuid)->first();
|
$server = Server::where('uuid', $serverUuid)->first();
|
||||||
|
|
||||||
if (!$server) {
|
if (! $server) {
|
||||||
$this->removeMultiplexFile($muxFile);
|
$this->removeMultiplexFile($muxFile);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ class CleanupStaleMultiplexedConnections implements ShouldQueue
|
|||||||
|
|
||||||
foreach ($muxFiles as $muxFile) {
|
foreach ($muxFiles as $muxFile) {
|
||||||
$serverUuid = $this->extractServerUuidFromMuxFile($muxFile);
|
$serverUuid = $this->extractServerUuidFromMuxFile($muxFile);
|
||||||
if (!in_array($serverUuid, $existingServerUuids)) {
|
if (! in_array($serverUuid, $existingServerUuids)) {
|
||||||
$this->removeMultiplexFile($muxFile);
|
$this->removeMultiplexFile($muxFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
@@ -25,16 +24,6 @@ class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
GetContainersStatus::run($this->server);
|
GetContainersStatus::run($this->server);
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
@@ -80,16 +79,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [new WithoutOverlapping($this->backup->id)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->backup->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
|
||||||
use App\Models\Team;
|
|
||||||
use App\Notifications\Database\DailyBackup;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public $tries = 1;
|
|
||||||
|
|
||||||
public function __construct() {}
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
// $teams = Team::all();
|
|
||||||
// foreach ($teams as $team) {
|
|
||||||
// $scheduled_backups = $team->scheduledDatabaseBackups()->get();
|
|
||||||
// if ($scheduled_backups->isEmpty()) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// foreach ($scheduled_backups as $scheduled_backup) {
|
|
||||||
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
|
|
||||||
// if ($last_days_backups->isEmpty()) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// $failed = $last_days_backups->where('status', 'failed');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $scheduled_backups = ScheduledDatabaseBackup::all();
|
|
||||||
// $databases = collect();
|
|
||||||
// $teams = collect();
|
|
||||||
// foreach ($scheduled_backups as $scheduled_backup) {
|
|
||||||
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
|
|
||||||
// if ($last_days_backups->isEmpty()) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// $failed = $last_days_backups->where('status', 'failed');
|
|
||||||
// $database = $scheduled_backup->database;
|
|
||||||
// $team = $database->team();
|
|
||||||
// $teams->put($team->id, $team);
|
|
||||||
// $databases->put("{$team->id}:{$database->name}", [
|
|
||||||
// 'failed_count' => $failed->count(),
|
|
||||||
// ]);
|
|
||||||
// }
|
|
||||||
// foreach ($databases as $name => $database) {
|
|
||||||
// [$team_id, $name] = explode(':', $name);
|
|
||||||
// $team = $teams->get($team_id);
|
|
||||||
// $team?->notify(new DailyBackup($databases));
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -80,7 +80,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|| $this->resource instanceof StandaloneClickhouse;
|
|| $this->resource instanceof StandaloneClickhouse;
|
||||||
$server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server');
|
$server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server');
|
||||||
if (($this->dockerCleanup || $isDatabase) && $server) {
|
if (($this->dockerCleanup || $isDatabase) && $server) {
|
||||||
CleanupDocker::run($server, true);
|
CleanupDocker::dispatch($server, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->deleteConnectedNetworks && ! $isDatabase) {
|
if ($this->deleteConnectedNetworks && ! $isDatabase) {
|
||||||
@@ -92,7 +92,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
} finally {
|
} finally {
|
||||||
$this->resource->forceDelete();
|
$this->resource->forceDelete();
|
||||||
if ($this->dockerCleanup) {
|
if ($this->dockerCleanup) {
|
||||||
CleanupDocker::run($server, true);
|
CleanupDocker::dispatch($server, true);
|
||||||
}
|
}
|
||||||
Artisan::queue('cleanup:stucked-resources');
|
Artisan::queue('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
@@ -26,16 +25,6 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [new WithoutOverlapping($this->server->id)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->server->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
@@ -25,16 +24,6 @@ class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public GithubApp $github_app) {}
|
public function __construct(public GithubApp $github_app) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->github_app->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->github_app->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
|
class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
@@ -18,16 +17,6 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $timeout = 1000;
|
public $timeout = 1000;
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): string
|
|
||||||
{
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ use Illuminate\Bus\Queueable;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class ScheduledTaskJob implements ShouldQueue
|
class ScheduledTaskJob implements ShouldQueue
|
||||||
@@ -56,24 +55,17 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
{
|
{
|
||||||
if ($this->resource instanceof Application) {
|
if ($this->resource instanceof Application) {
|
||||||
$timezone = $this->resource->destination->server->settings->server_timezone;
|
$timezone = $this->resource->destination->server->settings->server_timezone;
|
||||||
|
|
||||||
return $timezone;
|
return $timezone;
|
||||||
} elseif ($this->resource instanceof Service) {
|
} elseif ($this->resource instanceof Service) {
|
||||||
$timezone = $this->resource->server->settings->server_timezone;
|
$timezone = $this->resource->server->settings->server_timezone;
|
||||||
|
|
||||||
return $timezone;
|
return $timezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'UTC';
|
return 'UTC';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [new WithoutOverlapping($this->task->id)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->task->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -94,12 +86,12 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
} elseif ($this->resource->type() == 'service') {
|
} elseif ($this->resource->type() == 'service') {
|
||||||
$this->resource->applications()->get()->each(function ($application) {
|
$this->resource->applications()->get()->each(function ($application) {
|
||||||
if (str(data_get($application, 'status'))->contains('running')) {
|
if (str(data_get($application, 'status'))->contains('running')) {
|
||||||
$this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
|
$this->containers[] = data_get($application, 'name').'-'.data_get($this->resource, 'uuid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$this->resource->databases()->get()->each(function ($database) {
|
$this->resource->databases()->get()->each(function ($database) {
|
||||||
if (str(data_get($database, 'status'))->contains('running')) {
|
if (str(data_get($database, 'status'))->contains('running')) {
|
||||||
$this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid');
|
$this->containers[] = data_get($database, 'name').'-'.data_get($this->resource, 'uuid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -112,8 +104,8 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->containers as $containerName) {
|
foreach ($this->containers as $containerName) {
|
||||||
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) {
|
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container.'-'.$this->resource->uuid)) {
|
||||||
$cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'";
|
$cmd = "sh -c '".str_replace("'", "'\''", $this->task->command)."'";
|
||||||
$exec = "docker exec {$containerName} {$cmd}";
|
$exec = "docker exec {$containerName} {$cmd}";
|
||||||
$this->task_output = instant_remote_process([$exec], $this->server, true);
|
$this->task_output = instant_remote_process([$exec], $this->server, true);
|
||||||
$this->task_log->update([
|
$this->task_log->update([
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $tries = 3;
|
public $tries = 1;
|
||||||
|
|
||||||
public $timeout = 60;
|
public $timeout = 60;
|
||||||
|
|
||||||
@@ -45,16 +44,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->id))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->server->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
|
class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
@@ -26,16 +26,6 @@ class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Team $team) {}
|
public function __construct(public Team $team) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->team->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->team->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
@@ -26,16 +25,6 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (! $this->server->isServerReady($this->tries)) {
|
if (! $this->server->isServerReady($this->tries)) {
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->createdPrivateKey = $privateKey;
|
$this->createdPrivateKey = $privateKey;
|
||||||
$this->currentState = 'create-server';
|
$this->currentState = 'create-server';
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->addError('privateKey', 'Failed to save private key: ' . $e->getMessage());
|
$this->addError('privateKey', 'Failed to save private key: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Livewire\Destination;
|
namespace App\Livewire\Destination;
|
||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Form extends Component
|
class Form extends Component
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
class NavbarDeleteTeam extends Component
|
class NavbarDeleteTeam extends Component
|
||||||
{
|
{
|
||||||
@@ -18,8 +18,9 @@ class NavbarDeleteTeam extends Component
|
|||||||
|
|
||||||
public function delete($password)
|
public function delete($password)
|
||||||
{
|
{
|
||||||
if (!Hash::check($password, Auth::user()->password)) {
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Livewire\Project\Application\Deployment;
|
|||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ namespace App\Livewire\Project\Application;
|
|||||||
use App\Actions\Docker\GetContainersStatus;
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
|
use Illuminate\Process\InvokedProcess;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
use Illuminate\Process\InvokedProcess;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
|
|
||||||
class Previews extends Component
|
class Previews extends Component
|
||||||
{
|
{
|
||||||
@@ -242,7 +242,7 @@ class Previews extends Component
|
|||||||
$startTime = time();
|
$startTime = time();
|
||||||
while (count($processes) > 0) {
|
while (count($processes) > 0) {
|
||||||
$finishedProcesses = array_filter($processes, function ($process) {
|
$finishedProcesses = array_filter($processes, function ($process) {
|
||||||
return !$process->running();
|
return ! $process->running();
|
||||||
});
|
});
|
||||||
foreach (array_keys($finishedProcesses) as $containerName) {
|
foreach (array_keys($finishedProcesses) as $containerName) {
|
||||||
unset($processes[$containerName]);
|
unset($processes[$containerName]);
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
namespace App\Livewire\Project\Database;
|
namespace App\Livewire\Project\Database;
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
|
|
||||||
class BackupEdit extends Component
|
class BackupEdit extends Component
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,9 @@ class BackupEdit extends Component
|
|||||||
public $s3s;
|
public $s3s;
|
||||||
|
|
||||||
public bool $delete_associated_backups_locally = false;
|
public bool $delete_associated_backups_locally = false;
|
||||||
|
|
||||||
public bool $delete_associated_backups_s3 = false;
|
public bool $delete_associated_backups_s3 = false;
|
||||||
|
|
||||||
public bool $delete_associated_backups_sftp = false;
|
public bool $delete_associated_backups_sftp = false;
|
||||||
|
|
||||||
public ?string $status = null;
|
public ?string $status = null;
|
||||||
@@ -54,8 +56,9 @@ class BackupEdit extends Component
|
|||||||
|
|
||||||
public function delete($password)
|
public function delete($password)
|
||||||
{
|
{
|
||||||
if (!Hash::check($password, Auth::user()->password)) {
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +77,7 @@ class BackupEdit extends Component
|
|||||||
$url = Url::fromString($previousUrl);
|
$url = Url::fromString($previousUrl);
|
||||||
$url = $url->withoutQueryParameter('selectedBackupId');
|
$url = $url->withoutQueryParameter('selectedBackupId');
|
||||||
$url = $url->withFragment('backups');
|
$url = $url->withFragment('backups');
|
||||||
$url = $url->getPath() . "#{$url->getFragment()}";
|
$url = $url->getPath()."#{$url->getFragment()}";
|
||||||
|
|
||||||
return redirect($url);
|
return redirect($url);
|
||||||
} else {
|
} else {
|
||||||
@@ -136,7 +139,7 @@ class BackupEdit extends Component
|
|||||||
$server = $this->backup->database->destination->server;
|
$server = $this->backup->database->destination->server;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$backupFolder) {
|
if (! $backupFolder) {
|
||||||
$backupFolder = dirname($execution->filename);
|
$backupFolder = dirname($execution->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +185,7 @@ class BackupEdit extends Component
|
|||||||
['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from local storage.'],
|
['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from local storage.'],
|
||||||
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected S3 Storage.']
|
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected S3 Storage.']
|
||||||
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
|
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,19 +3,23 @@
|
|||||||
namespace App\Livewire\Project\Database;
|
namespace App\Livewire\Project\Database;
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use Livewire\Component;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Livewire\Attributes\On;
|
use Livewire\Attributes\On;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
class BackupExecutions extends Component
|
class BackupExecutions extends Component
|
||||||
{
|
{
|
||||||
public ?ScheduledDatabaseBackup $backup = null;
|
public ?ScheduledDatabaseBackup $backup = null;
|
||||||
|
|
||||||
public $database;
|
public $database;
|
||||||
|
|
||||||
public $executions = [];
|
public $executions = [];
|
||||||
|
|
||||||
public $setDeletableBackup;
|
public $setDeletableBackup;
|
||||||
|
|
||||||
public $delete_backup_s3 = true;
|
public $delete_backup_s3 = true;
|
||||||
|
|
||||||
public $delete_backup_sftp = true;
|
public $delete_backup_sftp = true;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
@@ -40,14 +44,16 @@ class BackupExecutions extends Component
|
|||||||
#[On('deleteBackup')]
|
#[On('deleteBackup')]
|
||||||
public function deleteBackup($executionId, $password)
|
public function deleteBackup($executionId, $password)
|
||||||
{
|
{
|
||||||
if (!Hash::check($password, Auth::user()->password)) {
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$execution = $this->backup->executions()->where('id', $executionId)->first();
|
$execution = $this->backup->executions()->where('id', $executionId)->first();
|
||||||
if (is_null($execution)) {
|
if (is_null($execution)) {
|
||||||
$this->dispatch('error', 'Backup execution not found.');
|
$this->dispatch('error', 'Backup execution not found.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,16 +109,18 @@ class BackupExecutions extends Component
|
|||||||
return $server;
|
return $server;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getServerTimezone()
|
public function getServerTimezone()
|
||||||
{
|
{
|
||||||
$server = $this->server();
|
$server = $this->server();
|
||||||
if (!$server) {
|
if (! $server) {
|
||||||
return 'UTC';
|
return 'UTC';
|
||||||
}
|
}
|
||||||
$serverTimezone = $server->settings->server_timezone;
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
|
|
||||||
return $serverTimezone;
|
return $serverTimezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +133,7 @@ class BackupExecutions extends Component
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$dateObj->setTimezone(new \DateTimeZone('UTC'));
|
$dateObj->setTimezone(new \DateTimeZone('UTC'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $dateObj->format('Y-m-d H:i:s T');
|
return $dateObj->format('Y-m-d H:i:s T');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +143,7 @@ class BackupExecutions extends Component
|
|||||||
'checkboxes' => [
|
'checkboxes' => [
|
||||||
['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'],
|
['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'],
|
||||||
['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'],
|
['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'],
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -73,14 +73,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && !$this->database->public_port) {
|
if ($this->database->is_public && ! $this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (!str($this->database->status)->startsWith('running')) {
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -88,14 +88,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && !$this->database->public_port) {
|
if ($this->database->is_public && ! $this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (!str($this->database->status)->startsWith('running')) {
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -94,14 +94,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && !$this->database->public_port) {
|
if ($this->database->is_public && ! $this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (!str($this->database->status)->startsWith('running')) {
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -100,14 +100,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && !$this->database->public_port) {
|
if ($this->database->is_public && ! $this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (!str($this->database->status)->startsWith('running')) {
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -101,14 +101,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && !$this->database->public_port) {
|
if ($this->database->is_public && ! $this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (!str($this->database->status)->startsWith('running')) {
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -99,14 +99,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && !$this->database->public_port) {
|
if ($this->database->is_public && ! $this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (!str($this->database->status)->startsWith('running')) {
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -88,14 +88,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && !$this->database->public_port) {
|
if ($this->database->is_public && ! $this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (!str($this->database->status)->startsWith('running')) {
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ use App\Models\StandaloneMongodb;
|
|||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Livewire\Component;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
class FileStorage extends Component
|
class FileStorage extends Component
|
||||||
{
|
{
|
||||||
@@ -87,8 +87,9 @@ class FileStorage extends Component
|
|||||||
|
|
||||||
public function delete($password)
|
public function delete($password)
|
||||||
{
|
{
|
||||||
if (!Hash::check($password, Auth::user()->password)) {
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +135,7 @@ class FileStorage extends Component
|
|||||||
$this->submit();
|
$this->submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.file-storage', [
|
return view('livewire.project.service.file-storage', [
|
||||||
'directoryDeletionCheckboxes' => [
|
'directoryDeletionCheckboxes' => [
|
||||||
@@ -142,7 +143,7 @@ class FileStorage extends Component
|
|||||||
],
|
],
|
||||||
'fileDeletionCheckboxes' => [
|
'fileDeletionCheckboxes' => [
|
||||||
['id' => 'permanently_delete', 'label' => 'The selected file will be permanently deleted form the server.'],
|
['id' => 'permanently_delete', 'label' => 'The selected file will be permanently deleted form the server.'],
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class StackForm extends Component
|
|||||||
$key = data_get($field, 'key');
|
$key = data_get($field, 'key');
|
||||||
$value = data_get($field, 'value');
|
$value = data_get($field, 'value');
|
||||||
$rules = data_get($field, 'rules', 'nullable');
|
$rules = data_get($field, 'rules', 'nullable');
|
||||||
$isPassword = data_get($field, 'isPassword');
|
$isPassword = data_get($field, 'isPassword', false);
|
||||||
$this->fields->put($key, [
|
$this->fields->put($key, [
|
||||||
'serviceName' => $serviceName,
|
'serviceName' => $serviceName,
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
@@ -47,7 +47,15 @@ class StackForm extends Component
|
|||||||
$this->validationAttributes["fields.$key.value"] = $fieldKey;
|
$this->validationAttributes["fields.$key.value"] = $fieldKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->fields = $this->fields->sortBy('name');
|
$this->fields = $this->fields->groupBy('serviceName')->map(function ($group) {
|
||||||
|
return $group->sortBy(function ($field) {
|
||||||
|
return data_get($field, 'isPassword') ? 1 : 0;
|
||||||
|
})->mapWithKeys(function ($field) {
|
||||||
|
return [$field['key'] => $field];
|
||||||
|
});
|
||||||
|
})->flatMap(function ($group) {
|
||||||
|
return $group;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveCompose($raw)
|
public function saveCompose($raw)
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ use App\Events\ApplicationStatusChanged;
|
|||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
|
|
||||||
class Destination extends Component
|
class Destination extends Component
|
||||||
{
|
{
|
||||||
@@ -119,8 +119,9 @@ class Destination extends Component
|
|||||||
|
|
||||||
public function removeServer(int $network_id, int $server_id, $password)
|
public function removeServer(int $network_id, int $server_id, $password)
|
||||||
{
|
{
|
||||||
if (!Hash::check($password, Auth::user()->password)) {
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project\Shared;
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Helpers\SshMultiplexingHelper;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
@@ -17,7 +18,6 @@ use App\Models\StandalonePostgresql;
|
|||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Helpers\SshMultiplexingHelper;
|
|
||||||
|
|
||||||
class GetLogs extends Component
|
class GetLogs extends Component
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ use Livewire\Component;
|
|||||||
class Executions extends Component
|
class Executions extends Component
|
||||||
{
|
{
|
||||||
public $executions = [];
|
public $executions = [];
|
||||||
|
|
||||||
public $selectedKey;
|
public $selectedKey;
|
||||||
|
|
||||||
public $task;
|
public $task;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
@@ -29,7 +31,7 @@ class Executions extends Component
|
|||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
if (!$this->task) {
|
if (! $this->task) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +44,18 @@ class Executions extends Component
|
|||||||
return $this->task->service->destination->server;
|
return $this->task->service->destination->server;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getServerTimezone()
|
public function getServerTimezone()
|
||||||
{
|
{
|
||||||
$server = $this->server();
|
$server = $this->server();
|
||||||
if (!$server) {
|
if (! $server) {
|
||||||
return 'UTC';
|
return 'UTC';
|
||||||
}
|
}
|
||||||
$serverTimezone = $server->settings->server_timezone;
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
|
|
||||||
return $serverTimezone;
|
return $serverTimezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +68,7 @@ class Executions extends Component
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$dateObj->setTimezone(new \DateTimeZone('UTC'));
|
$dateObj->setTimezone(new \DateTimeZone('UTC'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $dateObj->format('Y-m-d H:i:s T');
|
return $dateObj->format('Y-m-d H:i:s T');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
namespace App\Livewire\Project\Shared\Storages;
|
namespace App\Livewire\Project\Shared\Storages;
|
||||||
|
|
||||||
use App\Models\LocalPersistentVolume;
|
use App\Models\LocalPersistentVolume;
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
@@ -40,8 +40,9 @@ class Show extends Component
|
|||||||
|
|
||||||
public function delete($password)
|
public function delete($password)
|
||||||
{
|
{
|
||||||
if (!Hash::check($password, Auth::user()->password)) {
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ use Livewire\Component;
|
|||||||
class Create extends Component
|
class Create extends Component
|
||||||
{
|
{
|
||||||
public string $name = '';
|
public string $name = '';
|
||||||
|
|
||||||
public string $value = '';
|
public string $value = '';
|
||||||
|
|
||||||
public ?string $from = null;
|
public ?string $from = null;
|
||||||
|
|
||||||
public ?string $description = null;
|
public ?string $description = null;
|
||||||
|
|
||||||
public ?string $publicKey = null;
|
public ?string $publicKey = null;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
@@ -49,7 +53,7 @@ class Create extends Component
|
|||||||
$privateKey = PrivateKey::createAndStore([
|
$privateKey = PrivateKey::createAndStore([
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'description' => $this->description,
|
'description' => $this->description,
|
||||||
'private_key' => trim($this->value) . "\n",
|
'private_key' => trim($this->value)."\n",
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@ class Create extends Component
|
|||||||
$validationResult = PrivateKey::validateAndExtractPublicKey($this->value);
|
$validationResult = PrivateKey::validateAndExtractPublicKey($this->value);
|
||||||
$this->publicKey = $validationResult['publicKey'];
|
$this->publicKey = $validationResult['publicKey'];
|
||||||
|
|
||||||
if (!$validationResult['isValid']) {
|
if (! $validationResult['isValid']) {
|
||||||
$this->addError('value', 'Invalid private key');
|
$this->addError('value', 'Invalid private key');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Security\PrivateKey;
|
namespace App\Livewire\Security\PrivateKey;
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
class Index extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,13 +31,12 @@ class ConfigureCloudflareTunnels extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
|
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
|
||||||
ConfigureCloudflared::run($server, $this->cloudflare_token);
|
ConfigureCloudflared::dispatch($server, $this->cloudflare_token);
|
||||||
$server->settings->is_cloudflare_tunnel = true;
|
$server->settings->is_cloudflare_tunnel = true;
|
||||||
$server->ip = $this->ssh_domain;
|
$server->ip = $this->ssh_domain;
|
||||||
$server->save();
|
$server->save();
|
||||||
$server->settings->save();
|
$server->settings->save();
|
||||||
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
|
$this->dispatch('warning', 'Cloudflare Tunnels configuration started.');
|
||||||
$this->dispatch('refreshServerShow');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace App\Livewire\Server;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Livewire\Component;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
class Delete extends Component
|
class Delete extends Component
|
||||||
{
|
{
|
||||||
@@ -15,8 +15,9 @@ class Delete extends Component
|
|||||||
|
|
||||||
public function delete($password)
|
public function delete($password)
|
||||||
{
|
{
|
||||||
if (!Hash::check($password, Auth::user()->password)) {
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -24,11 +24,16 @@ class Form extends Component
|
|||||||
|
|
||||||
public $timezones;
|
public $timezones;
|
||||||
|
|
||||||
protected $listeners = [
|
public function getListeners()
|
||||||
'serverInstalled',
|
{
|
||||||
'refreshServerShow' => 'serverInstalled',
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
'revalidate' => '$refresh',
|
|
||||||
];
|
return [
|
||||||
|
"echo-private:team.{$teamId},CloudflareTunnelConfigured" => 'cloudflareTunnelConfigured',
|
||||||
|
'refreshServerShow' => 'serverInstalled',
|
||||||
|
'revalidate' => '$refresh',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'server.name' => 'required',
|
'server.name' => 'required',
|
||||||
@@ -96,6 +101,12 @@ class Form extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function cloudflareTunnelConfigured()
|
||||||
|
{
|
||||||
|
$this->serverInstalled();
|
||||||
|
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
public function serverInstalled()
|
public function serverInstalled()
|
||||||
{
|
{
|
||||||
$this->server->refresh();
|
$this->server->refresh();
|
||||||
@@ -238,4 +249,12 @@ class Form extends Component
|
|||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
$this->dispatch('success', 'Server timezone updated.');
|
$this->dispatch('success', 'Server timezone updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function manualCloudflareConfig()
|
||||||
|
{
|
||||||
|
$this->server->settings->is_cloudflare_tunnel = true;
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->server->refresh();
|
||||||
|
$this->dispatch('success', 'Cloudflare Tunnels enabled.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use App\Actions\Proxy\CheckProxy;
|
|||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Events\ProxyStatusChanged;
|
use App\Events\ProxyStatusChanged;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use Illuminate\Process\InvokedProcess;
|
use Illuminate\Process\InvokedProcess;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
class Deploy extends Component
|
class Deploy extends Component
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ namespace App\Livewire\Team;
|
|||||||
|
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Livewire\Component;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
class AdminView extends Component
|
class AdminView extends Component
|
||||||
{
|
{
|
||||||
@@ -77,8 +77,9 @@ class AdminView extends Component
|
|||||||
|
|
||||||
public function delete($id, $password)
|
public function delete($id, $password)
|
||||||
{
|
{
|
||||||
if (!Hash::check($password, Auth::user()->password)) {
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (! auth()->user()->isInstanceAdmin()) {
|
if (! auth()->user()->isInstanceAdmin()) {
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ use App\Enums\ApplicationDeploymentStatus;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use Illuminate\Process\InvokedProcess;
|
use Illuminate\Process\InvokedProcess;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
@@ -170,7 +170,7 @@ class Application extends BaseModel
|
|||||||
$startTime = time();
|
$startTime = time();
|
||||||
while (count($processes) > 0) {
|
while (count($processes) > 0) {
|
||||||
$finishedProcesses = array_filter($processes, function ($process) {
|
$finishedProcesses = array_filter($processes, function ($process) {
|
||||||
return !$process->running();
|
return ! $process->running();
|
||||||
});
|
});
|
||||||
foreach ($finishedProcesses as $containerName => $process) {
|
foreach ($finishedProcesses as $containerName => $process) {
|
||||||
unset($processes[$containerName]);
|
unset($processes[$containerName]);
|
||||||
@@ -209,7 +209,7 @@ class Application extends BaseModel
|
|||||||
$server = data_get($this, 'destination.server');
|
$server = data_get($this, 'destination.server');
|
||||||
$workdir = $this->workdir();
|
$workdir = $this->workdir();
|
||||||
if (str($workdir)->endsWith($this->uuid)) {
|
if (str($workdir)->endsWith($this->uuid)) {
|
||||||
instant_remote_process(['rm -rf ' . $this->workdir()], $server, false);
|
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +237,6 @@ class Application extends BaseModel
|
|||||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function additional_servers()
|
public function additional_servers()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Server::class, 'additional_destinations')
|
return $this->belongsToMany(Server::class, 'additional_destinations')
|
||||||
@@ -345,7 +344,7 @@ class Application extends BaseModel
|
|||||||
public function publishDirectory(): Attribute
|
public function publishDirectory(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
set: fn ($value) => $value ? '/' . ltrim($value, '/') : null,
|
set: fn ($value) => $value ? '/'.ltrim($value, '/') : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +352,7 @@ class Application extends BaseModel
|
|||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||||
if (str($this->git_repository)->contains('bitbucket')) {
|
if (str($this->git_repository)->contains('bitbucket')) {
|
||||||
return "{$this->source->html_url}/{$this->git_repository}/src/{$this->git_branch}";
|
return "{$this->source->html_url}/{$this->git_repository}/src/{$this->git_branch}";
|
||||||
}
|
}
|
||||||
@@ -380,7 +379,7 @@ class Application extends BaseModel
|
|||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||||
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
|
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
|
||||||
}
|
}
|
||||||
// Convert the SSH URL to HTTPS URL
|
// Convert the SSH URL to HTTPS URL
|
||||||
@@ -399,7 +398,7 @@ class Application extends BaseModel
|
|||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||||
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
|
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
|
||||||
}
|
}
|
||||||
// Convert the SSH URL to HTTPS URL
|
// Convert the SSH URL to HTTPS URL
|
||||||
@@ -416,7 +415,7 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
public function gitCommitLink($link): string
|
public function gitCommitLink($link): string
|
||||||
{
|
{
|
||||||
if (!is_null(data_get($this, 'source.html_url')) && !is_null(data_get($this, 'git_repository')) && !is_null(data_get($this, 'git_branch'))) {
|
if (! is_null(data_get($this, 'source.html_url')) && ! is_null(data_get($this, 'git_repository')) && ! is_null(data_get($this, 'git_branch'))) {
|
||||||
if (str($this->source->html_url)->contains('bitbucket')) {
|
if (str($this->source->html_url)->contains('bitbucket')) {
|
||||||
return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}";
|
return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}";
|
||||||
}
|
}
|
||||||
@@ -427,7 +426,7 @@ class Application extends BaseModel
|
|||||||
$git_repository = str_replace('.git', '', $this->git_repository);
|
$git_repository = str_replace('.git', '', $this->git_repository);
|
||||||
$url = Url::fromString($git_repository);
|
$url = Url::fromString($git_repository);
|
||||||
$url = $url->withUserInfo('');
|
$url = $url->withUserInfo('');
|
||||||
$url = $url->withPath($url->getPath() . '/commits/' . $link);
|
$url = $url->withPath($url->getPath().'/commits/'.$link);
|
||||||
|
|
||||||
return $url->__toString();
|
return $url->__toString();
|
||||||
}
|
}
|
||||||
@@ -480,7 +479,7 @@ class Application extends BaseModel
|
|||||||
public function baseDirectory(): Attribute
|
public function baseDirectory(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
set: fn ($value) => '/' . ltrim($value, '/'),
|
set: fn ($value) => '/'.ltrim($value, '/'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,7 +822,7 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
public function workdir()
|
public function workdir()
|
||||||
{
|
{
|
||||||
return application_configuration_dir() . "/{$this->uuid}";
|
return application_configuration_dir()."/{$this->uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isLogDrainEnabled()
|
public function isLogDrainEnabled()
|
||||||
@@ -833,7 +832,7 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
public function isConfigurationChanged(bool $save = false)
|
public function isConfigurationChanged(bool $save = false)
|
||||||
{
|
{
|
||||||
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build . $this->redirect;
|
$newConfigHash = $this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect;
|
||||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||||
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
||||||
} else {
|
} else {
|
||||||
@@ -887,7 +886,7 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
public function dirOnServer()
|
public function dirOnServer()
|
||||||
{
|
{
|
||||||
return application_configuration_dir() . "/{$this->uuid}";
|
return application_configuration_dir()."/{$this->uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
||||||
@@ -933,7 +932,7 @@ class Application extends BaseModel
|
|||||||
if ($this->source->is_public) {
|
if ($this->source->is_public) {
|
||||||
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
|
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
|
||||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}";
|
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}";
|
||||||
if (!$only_checkout) {
|
if (! $only_checkout) {
|
||||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
||||||
}
|
}
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
@@ -950,7 +949,7 @@ class Application extends BaseModel
|
|||||||
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}";
|
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}";
|
||||||
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
|
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
|
||||||
}
|
}
|
||||||
if (!$only_checkout) {
|
if (! $only_checkout) {
|
||||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
|
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
|
||||||
}
|
}
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
@@ -1011,7 +1010,7 @@ class Application extends BaseModel
|
|||||||
} else {
|
} else {
|
||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||||
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
@@ -1019,14 +1018,14 @@ class Application extends BaseModel
|
|||||||
} else {
|
} else {
|
||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||||
} elseif ($git_type === 'bitbucket') {
|
} elseif ($git_type === 'bitbucket') {
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
} else {
|
} else {
|
||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1055,7 +1054,7 @@ class Application extends BaseModel
|
|||||||
} else {
|
} else {
|
||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||||
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
@@ -1063,14 +1062,14 @@ class Application extends BaseModel
|
|||||||
} else {
|
} else {
|
||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||||
} elseif ($git_type === 'bitbucket') {
|
} elseif ($git_type === 'bitbucket') {
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
} else {
|
} else {
|
||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,6 +1095,7 @@ class Application extends BaseModel
|
|||||||
throw new \Exception($e->getMessage());
|
throw new \Exception($e->getMessage());
|
||||||
}
|
}
|
||||||
$services = data_get($yaml, 'services');
|
$services = data_get($yaml, 'services');
|
||||||
|
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
$services = collect($services)->map(function ($service) use ($commands) {
|
$services = collect($services)->map(function ($service) use ($commands) {
|
||||||
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
||||||
@@ -1122,20 +1122,20 @@ class Application extends BaseModel
|
|||||||
}
|
}
|
||||||
if ($source->startsWith('.')) {
|
if ($source->startsWith('.')) {
|
||||||
$source = $source->after('.');
|
$source = $source->after('.');
|
||||||
$source = $workdir . $source;
|
$source = $workdir.$source;
|
||||||
}
|
}
|
||||||
$commands->push("mkdir -p $source > /dev/null 2>&1 || true");
|
$commands->push("mkdir -p $source > /dev/null 2>&1 || true");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$labels = collect(data_get($service, 'labels', []));
|
$labels = collect(data_get($service, 'labels', []));
|
||||||
if (!$labels->contains('coolify.managed')) {
|
if (! $labels->contains('coolify.managed')) {
|
||||||
$labels->push('coolify.managed=true');
|
$labels->push('coolify.managed=true');
|
||||||
}
|
}
|
||||||
if (!$labels->contains('coolify.applicationId')) {
|
if (! $labels->contains('coolify.applicationId')) {
|
||||||
$labels->push('coolify.applicationId=' . $this->id);
|
$labels->push('coolify.applicationId='.$this->id);
|
||||||
}
|
}
|
||||||
if (!$labels->contains('coolify.type')) {
|
if (! $labels->contains('coolify.type')) {
|
||||||
$labels->push('coolify.type=application');
|
$labels->push('coolify.type=application');
|
||||||
}
|
}
|
||||||
data_set($service, 'labels', $labels->toArray());
|
data_set($service, 'labels', $labels->toArray());
|
||||||
@@ -1211,7 +1211,7 @@ class Application extends BaseModel
|
|||||||
$jsonNames = $json->keys()->toArray();
|
$jsonNames = $json->keys()->toArray();
|
||||||
$diff = array_diff($jsonNames, $names);
|
$diff = array_diff($jsonNames, $names);
|
||||||
$json = $json->filter(function ($value, $key) use ($diff) {
|
$json = $json->filter(function ($value, $key) use ($diff) {
|
||||||
return !in_array($key, $diff);
|
return ! in_array($key, $diff);
|
||||||
});
|
});
|
||||||
if ($json) {
|
if ($json) {
|
||||||
$this->docker_compose_domains = json_encode($json);
|
$this->docker_compose_domains = json_encode($json);
|
||||||
@@ -1233,7 +1233,7 @@ class Application extends BaseModel
|
|||||||
public function parseContainerLabels(?ApplicationPreview $preview = null)
|
public function parseContainerLabels(?ApplicationPreview $preview = null)
|
||||||
{
|
{
|
||||||
$customLabels = data_get($this, 'custom_labels');
|
$customLabels = data_get($this, 'custom_labels');
|
||||||
if (!$customLabels) {
|
if (! $customLabels) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) {
|
if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) {
|
||||||
@@ -1316,10 +1316,10 @@ class Application extends BaseModel
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isset($healthcheckCommand) && str_contains($trimmedLine, '\\')) {
|
if (isset($healthcheckCommand) && str_contains($trimmedLine, '\\')) {
|
||||||
$healthcheckCommand .= ' ' . trim($trimmedLine, '\\ ');
|
$healthcheckCommand .= ' '.trim($trimmedLine, '\\ ');
|
||||||
}
|
}
|
||||||
if (isset($healthcheckCommand) && !str_contains($trimmedLine, '\\') && !empty($healthcheckCommand)) {
|
if (isset($healthcheckCommand) && ! str_contains($trimmedLine, '\\') && ! empty($healthcheckCommand)) {
|
||||||
$healthcheckCommand .= ' ' . $trimmedLine;
|
$healthcheckCommand .= ' '.$trimmedLine;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,17 @@ class ScheduledDatabaseBackup extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
|
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
if ($this->database) {
|
if ($this->database) {
|
||||||
if ($this->database->destination && $this->database->destination->server) {
|
if ($this->database->destination && $this->database->destination->server) {
|
||||||
$server = $this->database->destination->server;
|
$server = $this->database->destination->server;
|
||||||
|
|
||||||
return $server;
|
return $server;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use App\Models\Service;
|
|
||||||
use App\Models\Application;
|
|
||||||
|
|
||||||
class ScheduledTask extends BaseModel
|
class ScheduledTask extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -37,19 +35,23 @@ class ScheduledTask extends BaseModel
|
|||||||
if ($this->application) {
|
if ($this->application) {
|
||||||
if ($this->application->destination && $this->application->destination->server) {
|
if ($this->application->destination && $this->application->destination->server) {
|
||||||
$server = $this->application->destination->server;
|
$server = $this->application->destination->server;
|
||||||
|
|
||||||
return $server;
|
return $server;
|
||||||
}
|
}
|
||||||
} elseif ($this->service) {
|
} elseif ($this->service) {
|
||||||
if ($this->service->destination && $this->service->destination->server) {
|
if ($this->service->destination && $this->service->destination->server) {
|
||||||
$server = $this->service->destination->server;
|
$server = $this->service->destination->server;
|
||||||
|
|
||||||
return $server;
|
return $server;
|
||||||
}
|
}
|
||||||
} elseif ($this->database) {
|
} elseif ($this->database) {
|
||||||
if ($this->database->destination && $this->database->destination->server) {
|
if ($this->database->destination && $this->database->destination->server) {
|
||||||
$server = $this->database->destination->server;
|
$server = $this->database->destination->server;
|
||||||
|
|
||||||
return $server;
|
return $server;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,14 +165,13 @@ class Server extends BaseModel
|
|||||||
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
||||||
$proxy_type = $this->proxyType();
|
$proxy_type = $this->proxyType();
|
||||||
$redirect_url = $this->proxy->redirect_url;
|
$redirect_url = $this->proxy->redirect_url;
|
||||||
ray($proxy_type);
|
|
||||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
|
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
|
||||||
} elseif ($proxy_type === 'CADDY') {
|
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
||||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
|
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
|
||||||
}
|
}
|
||||||
if (empty($redirect_url)) {
|
if (empty($redirect_url)) {
|
||||||
if ($proxy_type === 'CADDY') {
|
if ($proxy_type === ProxyTypes::CADDY->value) {
|
||||||
$conf = ':80, :443 {
|
$conf = ':80, :443 {
|
||||||
respond 404
|
respond 404
|
||||||
}';
|
}';
|
||||||
@@ -242,7 +241,7 @@ respond 404
|
|||||||
$conf;
|
$conf;
|
||||||
|
|
||||||
$base64 = base64_encode($conf);
|
$base64 = base64_encode($conf);
|
||||||
} elseif ($proxy_type === 'CADDY') {
|
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
||||||
$conf = ":80, :443 {
|
$conf = ":80, :443 {
|
||||||
redir $redirect_url
|
redir $redirect_url
|
||||||
}";
|
}";
|
||||||
@@ -258,9 +257,6 @@ respond 404
|
|||||||
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
|
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
|
||||||
], $this);
|
], $this);
|
||||||
|
|
||||||
if (config('app.env') == 'local') {
|
|
||||||
ray($conf);
|
|
||||||
}
|
|
||||||
if ($proxy_type === 'CADDY') {
|
if ($proxy_type === 'CADDY') {
|
||||||
$this->reloadCaddy();
|
$this->reloadCaddy();
|
||||||
}
|
}
|
||||||
@@ -884,6 +880,35 @@ $schema://$host {
|
|||||||
return $this->hasMany(Service::class);
|
return $this->hasMany(Service::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function port(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function ($value) {
|
||||||
|
return preg_replace('/[^0-9]/', '', $value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function ($value) {
|
||||||
|
$sanitizedValue = preg_replace('/[^A-Za-z0-9\-_]/', '', $value);
|
||||||
|
|
||||||
|
return $sanitizedValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ip(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function ($value) {
|
||||||
|
return preg_replace('/[^0-9a-zA-Z.:%-]/', '', $value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function getIp(): Attribute
|
public function getIp(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Process\InvokedProcess;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Illuminate\Process\InvokedProcess;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
@@ -70,7 +70,7 @@ class Service extends BaseModel
|
|||||||
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
|
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
|
||||||
$storages = $applicationStorages->merge($databaseStorages)->implode('updated_at');
|
$storages = $applicationStorages->merge($databaseStorages)->implode('updated_at');
|
||||||
|
|
||||||
$newConfigHash = $images . $domains . $images . $storages;
|
$newConfigHash = $images.$domains.$images.$storages;
|
||||||
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
||||||
$newConfigHash = md5($newConfigHash);
|
$newConfigHash = md5($newConfigHash);
|
||||||
$oldConfigHash = data_get($this, 'config_hash');
|
$oldConfigHash = data_get($this, 'config_hash');
|
||||||
@@ -144,6 +144,7 @@ class Service extends BaseModel
|
|||||||
foreach ($dbs as $db) {
|
foreach ($dbs as $db) {
|
||||||
$containersToStop[] = "{$db->name}-{$this->uuid}";
|
$containersToStop[] = "{$db->name}-{$this->uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $containersToStop;
|
return $containersToStop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ class Service extends BaseModel
|
|||||||
$startTime = time();
|
$startTime = time();
|
||||||
while (count($processes) > 0) {
|
while (count($processes) > 0) {
|
||||||
$finishedProcesses = array_filter($processes, function ($process) {
|
$finishedProcesses = array_filter($processes, function ($process) {
|
||||||
return !$process->running();
|
return ! $process->running();
|
||||||
});
|
});
|
||||||
foreach (array_keys($finishedProcesses) as $containerName) {
|
foreach (array_keys($finishedProcesses) as $containerName) {
|
||||||
unset($processes[$containerName]);
|
unset($processes[$containerName]);
|
||||||
@@ -196,7 +197,7 @@ class Service extends BaseModel
|
|||||||
$server = data_get($this, 'destination.server');
|
$server = data_get($this, 'destination.server');
|
||||||
$workdir = $this->workdir();
|
$workdir = $this->workdir();
|
||||||
if (str($workdir)->endsWith($this->uuid)) {
|
if (str($workdir)->endsWith($this->uuid)) {
|
||||||
instant_remote_process(['rm -rf ' . $this->workdir()], $server, false);
|
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1061,7 +1062,7 @@ class Service extends BaseModel
|
|||||||
|
|
||||||
public function workdir()
|
public function workdir()
|
||||||
{
|
{
|
||||||
return service_configuration_dir() . "/{$this->uuid}";
|
return service_configuration_dir()."/{$this->uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveComposeConfigs()
|
public function saveComposeConfigs()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class ServiceApplication extends BaseModel
|
|||||||
|
|
||||||
public function restart()
|
public function restart()
|
||||||
{
|
{
|
||||||
$container_id = $this->name . '-' . $this->service->uuid;
|
$container_id = $this->name.'-'.$this->service->uuid;
|
||||||
instant_remote_process(["docker restart {$container_id}"], $this->service->server);
|
instant_remote_process(["docker restart {$container_id}"], $this->service->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ class ServiceApplication extends BaseModel
|
|||||||
|
|
||||||
public function workdir()
|
public function workdir()
|
||||||
{
|
{
|
||||||
return service_configuration_dir() . "/{$this->service->uuid}";
|
return service_configuration_dir()."/{$this->service->uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function serviceType()
|
public function serviceType()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class ServiceDatabase extends BaseModel
|
|||||||
|
|
||||||
public function restart()
|
public function restart()
|
||||||
{
|
{
|
||||||
$container_id = $this->name . '-' . $this->service->uuid;
|
$container_id = $this->name.'-'.$this->service->uuid;
|
||||||
remote_process(["docker restart {$container_id}"], $this->service->server);
|
remote_process(["docker restart {$container_id}"], $this->service->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ class ServiceDatabase extends BaseModel
|
|||||||
|
|
||||||
public function workdir()
|
public function workdir()
|
||||||
{
|
{
|
||||||
return service_configuration_dir() . "/{$this->service->uuid}";
|
return service_configuration_dir()."/{$this->service->uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function service()
|
public function service()
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class ForceDisabled extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function toDiscord(): string
|
public function toDiscord(): string
|
||||||
{
|
{
|
||||||
$message = "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions).";
|
$message = "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).";
|
||||||
|
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ class ForceDisabled extends Notification implements ShouldQueue
|
|||||||
public function toTelegram(): array
|
public function toTelegram(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'message' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions).",
|
'message' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
namespace App\Traits;
|
namespace App\Traits;
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Helpers\SshMultiplexingHelper;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use App\Helpers\SshMultiplexingHelper;
|
|
||||||
|
|
||||||
trait ExecuteRemoteCommand
|
trait ExecuteRemoteCommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
|||||||
return 'exited';
|
return 'exited';
|
||||||
}
|
}
|
||||||
$container = format_docker_command_output_to_json($container);
|
$container = format_docker_command_output_to_json($container);
|
||||||
|
if ($container->isEmpty()) {
|
||||||
|
return 'exited';
|
||||||
|
}
|
||||||
if ($all_data) {
|
if ($all_data) {
|
||||||
return $container[0];
|
return $container[0];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1247,6 +1247,10 @@ function get_public_ips()
|
|||||||
}
|
}
|
||||||
$settings->update(['public_ipv4' => $ipv4]);
|
$settings->update(['public_ipv4' => $ipv4]);
|
||||||
}
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo "Error: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
$ipv6 = $second->output();
|
$ipv6 = $second->output();
|
||||||
if ($ipv6) {
|
if ($ipv6) {
|
||||||
$ipv6 = trim($ipv6);
|
$ipv6 = trim($ipv6);
|
||||||
@@ -2924,10 +2928,11 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
}
|
}
|
||||||
|
|
||||||
$parsedServices = collect([]);
|
$parsedServices = collect([]);
|
||||||
ray()->clearAll();
|
// ray()->clearAll();
|
||||||
|
|
||||||
$allMagicEnvironments = collect([]);
|
$allMagicEnvironments = collect([]);
|
||||||
foreach ($services as $serviceName => $service) {
|
foreach ($services as $serviceName => $service) {
|
||||||
|
$predefinedPort = null;
|
||||||
$magicEnvironments = collect([]);
|
$magicEnvironments = collect([]);
|
||||||
$image = data_get_str($service, 'image');
|
$image = data_get_str($service, 'image');
|
||||||
$environment = collect(data_get($service, 'environment', []));
|
$environment = collect(data_get($service, 'environment', []));
|
||||||
@@ -2936,6 +2941,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
|
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
|
||||||
|
|
||||||
if ($isService) {
|
if ($isService) {
|
||||||
|
$containerName = "$serviceName-{$resource->uuid}";
|
||||||
|
|
||||||
|
if ($serviceName === 'registry') {
|
||||||
|
$tempServiceName = 'docker-registry';
|
||||||
|
} else {
|
||||||
|
$tempServiceName = $serviceName;
|
||||||
|
}
|
||||||
|
if (str(data_get($service, 'image'))->contains('glitchtip')) {
|
||||||
|
$tempServiceName = 'glitchtip';
|
||||||
|
}
|
||||||
|
if ($serviceName === 'supabase-kong') {
|
||||||
|
$tempServiceName = 'supabase';
|
||||||
|
}
|
||||||
|
$serviceDefinition = data_get($allServices, $tempServiceName);
|
||||||
|
$predefinedPort = data_get($serviceDefinition, 'port');
|
||||||
|
if ($serviceName === 'plausible') {
|
||||||
|
$predefinedPort = '8000';
|
||||||
|
}
|
||||||
if ($isDatabase) {
|
if ($isDatabase) {
|
||||||
$savedService = ServiceDatabase::firstOrCreate([
|
$savedService = ServiceDatabase::firstOrCreate([
|
||||||
'name' => $serviceName,
|
'name' => $serviceName,
|
||||||
@@ -2987,8 +3010,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
|
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
|
||||||
if (substr_count(str($key)->value(), '_') === 3) {
|
if (substr_count(str($key)->value(), '_') === 3) {
|
||||||
$fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
|
$fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
|
||||||
|
$port = $key->afterLast('_')->value();
|
||||||
} else {
|
} else {
|
||||||
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
||||||
|
$port = null;
|
||||||
}
|
}
|
||||||
if ($isApplication) {
|
if ($isApplication) {
|
||||||
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
||||||
@@ -2999,19 +3024,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
|
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
|
if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
|
||||||
$path = $value->value();
|
$path = $value->value();
|
||||||
if ($path !== '/') {
|
if ($path !== '/') {
|
||||||
$fqdn = "$fqdn$path";
|
$fqdn = "$fqdn$path";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$fqdnWithPort = $fqdn;
|
||||||
|
if ($port) {
|
||||||
|
$fqdnWithPort = "$fqdn:$port";
|
||||||
|
}
|
||||||
if ($isApplication && is_null($resource->fqdn)) {
|
if ($isApplication && is_null($resource->fqdn)) {
|
||||||
data_forget($resource, 'environment_variables');
|
data_forget($resource, 'environment_variables');
|
||||||
data_forget($resource, 'environment_variables_preview');
|
data_forget($resource, 'environment_variables_preview');
|
||||||
$resource->fqdn = $fqdn;
|
$resource->fqdn = $fqdnWithPort;
|
||||||
$resource->save();
|
$resource->save();
|
||||||
} elseif ($isService && is_null($savedService->fqdn)) {
|
} elseif ($isService && is_null($savedService->fqdn)) {
|
||||||
$savedService->fqdn = $fqdn;
|
$savedService->fqdn = $fqdnWithPort;
|
||||||
$savedService->save();
|
$savedService->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3040,7 +3070,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
}
|
}
|
||||||
|
|
||||||
$allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
|
$allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
|
||||||
|
|
||||||
if ($magicEnvironments->count() > 0) {
|
if ($magicEnvironments->count() > 0) {
|
||||||
foreach ($magicEnvironments as $key => $value) {
|
foreach ($magicEnvironments as $key => $value) {
|
||||||
$key = str($key);
|
$key = str($key);
|
||||||
@@ -3455,6 +3484,18 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
$value = $value->after('?');
|
$value = $value->after('?');
|
||||||
}
|
}
|
||||||
if ($originalValue->value() === $value->value()) {
|
if ($originalValue->value() === $value->value()) {
|
||||||
|
// This means the variable does not have a default value, so it needs to be created in Coolify
|
||||||
|
$parsedKeyValue = replaceVariables($value);
|
||||||
|
$resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->firstOrCreate([
|
||||||
|
'key' => $parsedKeyValue,
|
||||||
|
$nameOfId => $resource->id,
|
||||||
|
], [
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
// Add the variable to the environment so it will be shown in the deployable compose file
|
||||||
|
$environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->first()->value;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([
|
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([
|
||||||
@@ -3547,6 +3588,17 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
if ($environment->count() > 0) {
|
if ($environment->count() > 0) {
|
||||||
$environment = $environment->filter(function ($value, $key) {
|
$environment = $environment->filter(function ($value, $key) {
|
||||||
return ! str($key)->startsWith('SERVICE_FQDN_');
|
return ! str($key)->startsWith('SERVICE_FQDN_');
|
||||||
|
})->map(function ($value, $key) use ($resource) {
|
||||||
|
// if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used
|
||||||
|
if (str($value)->isEmpty()) {
|
||||||
|
if ($resource->environment_variables()->where('key', $key)->exists()) {
|
||||||
|
$value = $resource->environment_variables()->where('key', $key)->first()->value;
|
||||||
|
} else {
|
||||||
|
$value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$serviceLabels = $labels->merge($defaultLabels);
|
$serviceLabels = $labels->merge($defaultLabels);
|
||||||
@@ -3631,6 +3683,14 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
data_forget($service, 'volumes.*.is_directory');
|
data_forget($service, 'volumes.*.is_directory');
|
||||||
data_forget($service, 'exclude_from_hc');
|
data_forget($service, 'exclude_from_hc');
|
||||||
|
|
||||||
|
$volumesParsed = $volumesParsed->map(function ($volume) {
|
||||||
|
data_forget($volume, 'content');
|
||||||
|
data_forget($volume, 'is_directory');
|
||||||
|
data_forget($volume, 'isDirectory');
|
||||||
|
|
||||||
|
return $volume;
|
||||||
|
});
|
||||||
|
|
||||||
$payload = collect($service)->merge([
|
$payload = collect($service)->merge([
|
||||||
'container_name' => $containerName,
|
'container_name' => $containerName,
|
||||||
'restart' => $restart->value(),
|
'restart' => $restart->value(),
|
||||||
@@ -3661,6 +3721,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
$parsedServices->put($serviceName, $payload);
|
$parsedServices->put($serviceName, $payload);
|
||||||
}
|
}
|
||||||
$topLevel->put('services', $parsedServices);
|
$topLevel->put('services', $parsedServices);
|
||||||
|
|
||||||
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
|
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
|
||||||
|
|
||||||
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
|
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
|
||||||
|
|||||||
@@ -84,7 +84,11 @@
|
|||||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
|
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
|
||||||
"Illuminate\\Foundation\\ComposerScripts::postUpdate"
|
"Illuminate\\Foundation\\ComposerScripts::postUpdate"
|
||||||
],
|
],
|
||||||
"post-install-cmd": [],
|
"post-install-cmd": [
|
||||||
|
"cp -r 'hooks/' '.git/hooks/'",
|
||||||
|
"php -r \"copy('hooks/pre-commit', '.git/hooks/pre-commit');\"",
|
||||||
|
"php -r \"chmod('.git/hooks/pre-commit', 0777);\""
|
||||||
|
],
|
||||||
"post-root-package-install": [
|
"post-root-package-install": [
|
||||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,424 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Enable Clockwork
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork is enabled by default only when your application is in debug mode. Here you can explicitly enable or
|
|
||||||
| disable Clockwork. When disabled, no data is collected and the api and web ui are inactive.
|
|
||||||
| Unless explicitly enabled, Clockwork only runs on localhost, *.local, *.test and *.wip domains.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'enable' => env('CLOCKWORK_ENABLE', null),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Features
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| You can enable or disable various Clockwork features here. Some features have additional settings (eg. slow query
|
|
||||||
| threshold for database queries).
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'features' => [
|
|
||||||
|
|
||||||
// Cache usage stats and cache queries including results
|
|
||||||
'cache' => [
|
|
||||||
'enabled' => env('CLOCKWORK_CACHE_ENABLED', true),
|
|
||||||
|
|
||||||
// Collect cache queries
|
|
||||||
'collect_queries' => env('CLOCKWORK_CACHE_QUERIES', true),
|
|
||||||
|
|
||||||
// Collect values from cache queries (high performance impact with a very high number of queries)
|
|
||||||
'collect_values' => env('CLOCKWORK_CACHE_COLLECT_VALUES', false)
|
|
||||||
],
|
|
||||||
|
|
||||||
// Database usage stats and queries
|
|
||||||
'database' => [
|
|
||||||
'enabled' => env('CLOCKWORK_DATABASE_ENABLED', true),
|
|
||||||
|
|
||||||
// Collect database queries (high performance impact with a very high number of queries)
|
|
||||||
'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', true),
|
|
||||||
|
|
||||||
// Collect details of models updates (high performance impact with a lot of model updates)
|
|
||||||
'collect_models_actions' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_ACTIONS', true),
|
|
||||||
|
|
||||||
// Collect details of retrieved models (very high performance impact with a lot of models retrieved)
|
|
||||||
'collect_models_retrieved' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_RETRIEVED', false),
|
|
||||||
|
|
||||||
// Query execution time threshold in milliseconds after which the query will be marked as slow
|
|
||||||
'slow_threshold' => env('CLOCKWORK_DATABASE_SLOW_THRESHOLD'),
|
|
||||||
|
|
||||||
// Collect only slow database queries
|
|
||||||
'slow_only' => env('CLOCKWORK_DATABASE_SLOW_ONLY', false),
|
|
||||||
|
|
||||||
// Detect and report duplicate queries
|
|
||||||
'detect_duplicate_queries' => env('CLOCKWORK_DATABASE_DETECT_DUPLICATE_QUERIES', false)
|
|
||||||
],
|
|
||||||
|
|
||||||
// Dispatched events
|
|
||||||
'events' => [
|
|
||||||
'enabled' => env('CLOCKWORK_EVENTS_ENABLED', true),
|
|
||||||
|
|
||||||
// Ignored events (framework events are ignored by default)
|
|
||||||
'ignored_events' => [
|
|
||||||
// App\Events\UserRegistered::class,
|
|
||||||
// 'user.registered'
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
// Laravel log (you can still log directly to Clockwork with laravel log disabled)
|
|
||||||
'log' => [
|
|
||||||
'enabled' => env('CLOCKWORK_LOG_ENABLED', true)
|
|
||||||
],
|
|
||||||
|
|
||||||
// Sent notifications
|
|
||||||
'notifications' => [
|
|
||||||
'enabled' => env('CLOCKWORK_NOTIFICATIONS_ENABLED', true),
|
|
||||||
],
|
|
||||||
|
|
||||||
// Performance metrics
|
|
||||||
'performance' => [
|
|
||||||
// Allow collecting of client metrics. Requires separate clockwork-browser npm package.
|
|
||||||
'client_metrics' => env('CLOCKWORK_PERFORMANCE_CLIENT_METRICS', true)
|
|
||||||
],
|
|
||||||
|
|
||||||
// Dispatched queue jobs
|
|
||||||
'queue' => [
|
|
||||||
'enabled' => env('CLOCKWORK_QUEUE_ENABLED', true)
|
|
||||||
],
|
|
||||||
|
|
||||||
// Redis commands
|
|
||||||
'redis' => [
|
|
||||||
'enabled' => env('CLOCKWORK_REDIS_ENABLED', true)
|
|
||||||
],
|
|
||||||
|
|
||||||
// Routes list
|
|
||||||
'routes' => [
|
|
||||||
'enabled' => env('CLOCKWORK_ROUTES_ENABLED', false),
|
|
||||||
|
|
||||||
// Collect only routes from particular namespaces (only application routes by default)
|
|
||||||
'only_namespaces' => [ 'App' ]
|
|
||||||
],
|
|
||||||
|
|
||||||
// Rendered views
|
|
||||||
'views' => [
|
|
||||||
'enabled' => env('CLOCKWORK_VIEWS_ENABLED', true),
|
|
||||||
|
|
||||||
// Collect views including view data (high performance impact with a high number of views)
|
|
||||||
'collect_data' => env('CLOCKWORK_VIEWS_COLLECT_DATA', false),
|
|
||||||
|
|
||||||
// Use Twig profiler instead of Laravel events for apps using laravel-twigbridge (more precise, but does
|
|
||||||
// not support collecting view data)
|
|
||||||
'use_twig_profiler' => env('CLOCKWORK_VIEWS_USE_TWIG_PROFILER', false)
|
|
||||||
]
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Enable web UI
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork comes with a web UI accessible via http://your.app/clockwork. Here you can enable or disable this
|
|
||||||
| feature. You can also set a custom path for the web UI.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'web' => env('CLOCKWORK_WEB', true),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Enable toolbar
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork can show a toolbar with basic metrics on all responses. Here you can enable or disable this feature.
|
|
||||||
| Requires a separate clockwork-browser npm library.
|
|
||||||
| For installation instructions see https://underground.works/clockwork/#docs-viewing-data
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'toolbar' => env('CLOCKWORK_TOOLBAR', true),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| HTTP requests collection
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork collects data about HTTP requests to your app. Here you can choose which requests should be collected.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'requests' => [
|
|
||||||
// With on-demand mode enabled, Clockwork will only profile requests when the browser extension is open or you
|
|
||||||
// manually pass a "clockwork-profile" cookie or get/post data key.
|
|
||||||
// Optionally you can specify a "secret" that has to be passed as the value to enable profiling.
|
|
||||||
'on_demand' => env('CLOCKWORK_REQUESTS_ON_DEMAND', false),
|
|
||||||
|
|
||||||
// Collect only errors (requests with HTTP 4xx and 5xx responses)
|
|
||||||
'errors_only' => env('CLOCKWORK_REQUESTS_ERRORS_ONLY', false),
|
|
||||||
|
|
||||||
// Response time threshold in milliseconds after which the request will be marked as slow
|
|
||||||
'slow_threshold' => env('CLOCKWORK_REQUESTS_SLOW_THRESHOLD'),
|
|
||||||
|
|
||||||
// Collect only slow requests
|
|
||||||
'slow_only' => env('CLOCKWORK_REQUESTS_SLOW_ONLY', false),
|
|
||||||
|
|
||||||
// Sample the collected requests (e.g. set to 100 to collect only 1 in 100 requests)
|
|
||||||
'sample' => env('CLOCKWORK_REQUESTS_SAMPLE', false),
|
|
||||||
|
|
||||||
// List of URIs that should not be collected
|
|
||||||
'except' => [
|
|
||||||
'/horizon/.*', // Laravel Horizon requests
|
|
||||||
'/telescope/.*', // Laravel Telescope requests
|
|
||||||
'/_tt/.*', // Laravel Telescope toolbar
|
|
||||||
'/_debugbar/.*', // Laravel DebugBar requests
|
|
||||||
],
|
|
||||||
|
|
||||||
// List of URIs that should be collected, any other URI will not be collected if not empty
|
|
||||||
'only' => [
|
|
||||||
// '/api/.*'
|
|
||||||
],
|
|
||||||
|
|
||||||
// Don't collect OPTIONS requests, mostly used in the CSRF pre-flight requests and are rarely of interest
|
|
||||||
'except_preflight' => env('CLOCKWORK_REQUESTS_EXCEPT_PREFLIGHT', true)
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Artisan commands collection
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork can collect data about executed artisan commands. Here you can enable and configure which commands
|
|
||||||
| should be collected.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'artisan' => [
|
|
||||||
// Enable or disable collection of executed Artisan commands
|
|
||||||
'collect' => env('CLOCKWORK_ARTISAN_COLLECT', false),
|
|
||||||
|
|
||||||
// List of commands that should not be collected (built-in commands are not collected by default)
|
|
||||||
'except' => [
|
|
||||||
// 'inspire'
|
|
||||||
],
|
|
||||||
|
|
||||||
// List of commands that should be collected, any other command will not be collected if not empty
|
|
||||||
'only' => [
|
|
||||||
// 'inspire'
|
|
||||||
],
|
|
||||||
|
|
||||||
// Enable or disable collection of command output
|
|
||||||
'collect_output' => env('CLOCKWORK_ARTISAN_COLLECT_OUTPUT', false),
|
|
||||||
|
|
||||||
// Enable or disable collection of built-in Laravel commands
|
|
||||||
'except_laravel_commands' => env('CLOCKWORK_ARTISAN_EXCEPT_LARAVEL_COMMANDS', true)
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Queue jobs collection
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork can collect data about executed queue jobs. Here you can enable and configure which queue jobs should
|
|
||||||
| be collected.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'queue' => [
|
|
||||||
// Enable or disable collection of executed queue jobs
|
|
||||||
'collect' => env('CLOCKWORK_QUEUE_COLLECT', false),
|
|
||||||
|
|
||||||
// List of queue jobs that should not be collected
|
|
||||||
'except' => [
|
|
||||||
// App\Jobs\ExpensiveJob::class
|
|
||||||
],
|
|
||||||
|
|
||||||
// List of queue jobs that should be collected, any other queue job will not be collected if not empty
|
|
||||||
'only' => [
|
|
||||||
// App\Jobs\BuggyJob::class
|
|
||||||
]
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Tests collection
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork can collect data about executed tests. Here you can enable and configure which tests should be
|
|
||||||
| collected.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'tests' => [
|
|
||||||
// Enable or disable collection of ran tests
|
|
||||||
'collect' => env('CLOCKWORK_TESTS_COLLECT', false),
|
|
||||||
|
|
||||||
// List of tests that should not be collected
|
|
||||||
'except' => [
|
|
||||||
// Tests\Unit\ExampleTest::class
|
|
||||||
]
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Enable data collection when Clockwork is disabled
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| You can enable this setting to collect data even when Clockwork is disabled, e.g. for future analysis.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'collect_data_always' => env('CLOCKWORK_COLLECT_DATA_ALWAYS', false),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Metadata storage
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Configure how is the metadata collected by Clockwork stored. Three options are available:
|
|
||||||
| - files - A simple fast storage implementation storing data in one-per-request files.
|
|
||||||
| - sql - Stores requests in a sql database. Supports MySQL, PostgreSQL and SQLite. Requires PDO.
|
|
||||||
| - redis - Stores requests in redis. Requires phpredis.
|
|
||||||
*/
|
|
||||||
|
|
||||||
'storage' => env('CLOCKWORK_STORAGE', 'files'),
|
|
||||||
|
|
||||||
// Path where the Clockwork metadata is stored
|
|
||||||
'storage_files_path' => env('CLOCKWORK_STORAGE_FILES_PATH', storage_path('clockwork')),
|
|
||||||
|
|
||||||
// Compress the metadata files using gzip, trading a little bit of performance for lower disk usage
|
|
||||||
'storage_files_compress' => env('CLOCKWORK_STORAGE_FILES_COMPRESS', false),
|
|
||||||
|
|
||||||
// SQL database to use, can be a name of database configured in database.php or a path to a SQLite file
|
|
||||||
'storage_sql_database' => env('CLOCKWORK_STORAGE_SQL_DATABASE', storage_path('clockwork.sqlite')),
|
|
||||||
|
|
||||||
// SQL table name to use, the table is automatically created and updated when needed
|
|
||||||
'storage_sql_table' => env('CLOCKWORK_STORAGE_SQL_TABLE', 'clockwork'),
|
|
||||||
|
|
||||||
// Redis connection, name of redis connection or cluster configured in database.php
|
|
||||||
'storage_redis' => env('CLOCKWORK_STORAGE_REDIS', 'default'),
|
|
||||||
|
|
||||||
// Redis prefix for Clockwork keys ("clockwork" if not set)
|
|
||||||
'storage_redis_prefix' => env('CLOCKWORK_STORAGE_REDIS_PREFIX', 'clockwork'),
|
|
||||||
|
|
||||||
// Maximum lifetime of collected metadata in minutes, older requests will automatically be deleted, false to disable
|
|
||||||
'storage_expiration' => env('CLOCKWORK_STORAGE_EXPIRATION', 60 * 24 * 7),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Authentication
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork can be configured to require authentication before allowing access to the collected data. This might be
|
|
||||||
| useful when the application is publicly accessible. Setting to true will enable a simple authentication with a
|
|
||||||
| pre-configured password. You can also pass a class name of a custom implementation.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'authentication' => env('CLOCKWORK_AUTHENTICATION', false),
|
|
||||||
|
|
||||||
// Password for the simple authentication
|
|
||||||
'authentication_password' => env('CLOCKWORK_AUTHENTICATION_PASSWORD', 'VerySecretPassword'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Stack traces collection
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork can collect stack traces for log messages and certain data like database queries. Here you can set
|
|
||||||
| whether to collect stack traces, limit the number of collected frames and set further configuration. Collecting
|
|
||||||
| long stack traces considerably increases metadata size.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'stack_traces' => [
|
|
||||||
// Enable or disable collecting of stack traces
|
|
||||||
'enabled' => env('CLOCKWORK_STACK_TRACES_ENABLED', true),
|
|
||||||
|
|
||||||
// Limit the number of frames to be collected
|
|
||||||
'limit' => env('CLOCKWORK_STACK_TRACES_LIMIT', 10),
|
|
||||||
|
|
||||||
// List of vendor names to skip when determining caller, common vendors are automatically added
|
|
||||||
'skip_vendors' => [
|
|
||||||
// 'phpunit'
|
|
||||||
],
|
|
||||||
|
|
||||||
// List of namespaces to skip when determining caller
|
|
||||||
'skip_namespaces' => [
|
|
||||||
// 'Laravel'
|
|
||||||
],
|
|
||||||
|
|
||||||
// List of class names to skip when determining caller
|
|
||||||
'skip_classes' => [
|
|
||||||
// App\CustomLog::class
|
|
||||||
]
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Serialization
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork serializes the collected data to json for storage and transfer. Here you can configure certain aspects
|
|
||||||
| of serialization. Serialization has a large effect on the cpu time and memory usage.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Maximum depth of serialized multi-level arrays and objects
|
|
||||||
'serialization_depth' => env('CLOCKWORK_SERIALIZATION_DEPTH', 10),
|
|
||||||
|
|
||||||
// A list of classes that will never be serialized (e.g. a common service container class)
|
|
||||||
'serialization_blackbox' => [
|
|
||||||
\Illuminate\Container\Container::class,
|
|
||||||
\Illuminate\Foundation\Application::class,
|
|
||||||
\Laravel\Lumen\Application::class
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Register helpers
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork comes with a "clock" global helper function. You can use this helper to quickly log something and to
|
|
||||||
| access the Clockwork instance.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'register_helpers' => env('CLOCKWORK_REGISTER_HELPERS', true),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Send headers for AJAX request
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| When trying to collect data, the AJAX method can sometimes fail if it is missing required headers. For example, an
|
|
||||||
| API might require a version number using Accept headers to route the HTTP request to the correct codebase.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'headers' => [
|
|
||||||
// 'Accept' => 'application/vnd.com.whatever.v1+json',
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
| Server timing
|
|
||||||
|------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Clockwork supports the W3C Server Timing specification, which allows for collecting a simple performance metrics
|
|
||||||
| in a cross-browser way. E.g. in Chrome, your app, database and timeline event timings will be shown in the Dev
|
|
||||||
| Tools network tab. This setting specifies the max number of timeline events that will be sent. Setting to false
|
|
||||||
| will disable the feature.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'server_timing' => env('CLOCKWORK_SERVER_TIMING', 10)
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// 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.342',
|
'release' => '4.0.0-beta.343',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.342';
|
return '4.0.0-beta.343';
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class UpdateServerSettingsDefaultTimezone extends Migration
|
class UpdateServerSettingsDefaultTimezone extends Migration
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class EncryptExistingPrivateKeys extends Migration
|
class EncryptExistingPrivateKeys extends Migration
|
||||||
{
|
{
|
||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
DB::table('private_keys')->chunkById(100, function ($keys) {
|
try {
|
||||||
foreach ($keys as $key) {
|
DB::table('private_keys')->chunkById(100, function ($keys) {
|
||||||
DB::table('private_keys')
|
foreach ($keys as $key) {
|
||||||
->where('id', $key->id)
|
DB::table('private_keys')
|
||||||
->update(['private_key' => Crypt::encryptString($key->private_key)]);
|
->where('id', $key->id)
|
||||||
}
|
->update(['private_key' => Crypt::encryptString($key->private_key)]);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo 'Encrypting private keys failed.';
|
||||||
|
echo $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use App\Models\PrivateKey;
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
|
|
||||||
class PopulateSshKeysAndClearMuxDirectory extends Migration
|
|
||||||
{
|
|
||||||
public function up()
|
|
||||||
{
|
|
||||||
// Storage::disk('ssh-keys')->deleteDirectory('');
|
|
||||||
// Storage::disk('ssh-keys')->makeDirectory('');
|
|
||||||
|
|
||||||
// Storage::disk('ssh-mux')->deleteDirectory('');
|
|
||||||
// Storage::disk('ssh-mux')->makeDirectory('');
|
|
||||||
// PrivateKey::chunk(100, function ($keys) {
|
|
||||||
// foreach ($keys as $key) {
|
|
||||||
// $key->storeInFileSystem();
|
|
||||||
// if ($key->id === 0) {
|
|
||||||
// Storage::disk('ssh-keys')->put('id.root@host.docker.internal', $key->private_key);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class AddSshKeyFingerprintToPrivateKeysTable extends Migration
|
class AddSshKeyFingerprintToPrivateKeysTable extends Migration
|
||||||
@@ -13,13 +14,20 @@ class AddSshKeyFingerprintToPrivateKeysTable extends Migration
|
|||||||
$table->string('fingerprint')->after('private_key')->nullable();
|
$table->string('fingerprint')->after('private_key')->nullable();
|
||||||
});
|
});
|
||||||
|
|
||||||
PrivateKey::whereNull('fingerprint')->each(function ($key) {
|
try {
|
||||||
$fingerprint = PrivateKey::generateFingerprint($key->private_key);
|
DB::table('private_keys')->chunkById(100, function ($keys) {
|
||||||
if ($fingerprint) {
|
foreach ($keys as $key) {
|
||||||
$key->fingerprint = $fingerprint;
|
$fingerprint = PrivateKey::generateFingerprint($key->private_key);
|
||||||
$key->save();
|
if ($fingerprint) {
|
||||||
}
|
$key->fingerprint = $fingerprint;
|
||||||
});
|
$key->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo 'Generating fingerprints failed.';
|
||||||
|
echo $e->getMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down()
|
public function down()
|
||||||
|
|||||||
@@ -11,19 +11,30 @@ class PopulateSshKeysDirectorySeeder extends Seeder
|
|||||||
{
|
{
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
Storage::disk('ssh-keys')->deleteDirectory('');
|
try {
|
||||||
Storage::disk('ssh-keys')->makeDirectory('');
|
Storage::disk('ssh-keys')->deleteDirectory('');
|
||||||
Storage::disk('ssh-mux')->deleteDirectory('');
|
Storage::disk('ssh-keys')->makeDirectory('');
|
||||||
Storage::disk('ssh-mux')->makeDirectory('');
|
Storage::disk('ssh-mux')->deleteDirectory('');
|
||||||
|
Storage::disk('ssh-mux')->makeDirectory('');
|
||||||
|
|
||||||
PrivateKey::chunk(100, function ($keys) {
|
PrivateKey::chunk(100, function ($keys) {
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
echo 'Storing key: '.$key->name."\n";
|
echo 'Storing key: '.$key->name."\n";
|
||||||
$key->storeInFileSystem();
|
$key->storeInFileSystem();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDev()) {
|
||||||
|
$user = env('PUID').':'.env('PGID');
|
||||||
|
Process::run("chown -R $user ".storage_path('app/ssh/keys'));
|
||||||
|
Process::run("chown -R $user ".storage_path('app/ssh/mux'));
|
||||||
|
} else {
|
||||||
|
Process::run('chown -R 9999:root '.storage_path('app/ssh/keys'));
|
||||||
|
Process::run('chown -R 9999:root '.storage_path('app/ssh/mux'));
|
||||||
}
|
}
|
||||||
});
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error: {$e->getMessage()}\n";
|
||||||
Process::run('chown -R 9999:9999 '.storage_path('app/ssh/keys'));
|
ray($e->getMessage());
|
||||||
Process::run('chown -R 9999:9999 '.storage_path('app/ssh/mux'));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
@@ -16,6 +18,10 @@ class ServerSeeder extends Seeder
|
|||||||
'ip' => 'coolify-testing-host',
|
'ip' => 'coolify-testing-host',
|
||||||
'team_id' => 0,
|
'team_id' => 0,
|
||||||
'private_key_id' => 1,
|
'private_key_id' => 1,
|
||||||
|
'proxy' => [
|
||||||
|
'type' => ProxyTypes::TRAEFIK->value,
|
||||||
|
'status' => ProxyStatus::EXITED->value,
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/command/execlineb -P
|
#!/command/execlineb -P
|
||||||
s6-setuidgid webuser
|
s6-setuidgid webuser
|
||||||
php /var/www/html/artisan app:init --full-cleanup
|
php /var/www/html/artisan app:init
|
||||||
|
|||||||
21
hooks/pre-commit
Normal file
21
hooks/pre-commit
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Detect whether /dev/tty is available & functional
|
||||||
|
if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then
|
||||||
|
exec < /dev/tty
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get list of stashed PHP files
|
||||||
|
stashed_files=$(git diff --cached --name-only --diff-filter=ACM -- '*.php')
|
||||||
|
|
||||||
|
# If there are no stashed PHP files, exit early
|
||||||
|
if [ -z "$stashed_files" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set files variable to only include stashed PHP files
|
||||||
|
files="$stashed_files"
|
||||||
|
|
||||||
|
$(pwd)/vendor/bin/pint $files -q
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
git add $files
|
||||||
|
fi
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"service.stop": "This service will be stopped.",
|
"service.stop": "This service will be stopped.",
|
||||||
"resource.docker_cleanup": "Run Docker Cleanup (remove unused images and builder cache).",
|
"resource.docker_cleanup": "Run Docker Cleanup (remove unused images and builder cache).",
|
||||||
"resource.non_persistent": "All non-persistent data will be deleted.",
|
"resource.non_persistent": "All non-persistent data will be deleted.",
|
||||||
"resource.delete_volumes": "All volumes associated with this resource will be permanently deleted.",
|
"resource.delete_volumes": "Permanently delete all volumes associated with this resource.",
|
||||||
"resource.delete_connected_networks": "All non-predefined networks associated with this resource will be permanently deleted.",
|
"resource.delete_connected_networks": "Permanently delete all non-predefined networks associated with this resource.",
|
||||||
"resource.delete_configurations": "All configuration files will be permanently deleted from the server."
|
"resource.delete_configurations": "Permanently delete all configuration files from the server."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,7 @@ DB_PORT=5432
|
|||||||
# Set to true to enable Ray
|
# Set to true to enable Ray
|
||||||
RAY_ENABLED=false
|
RAY_ENABLED=false
|
||||||
# Set custom ray port
|
# Set custom ray port
|
||||||
RAY_PORT=
|
# RAY_PORT=
|
||||||
|
|
||||||
# Clockwork Configuration
|
|
||||||
CLOCKWORK_ENABLED=false
|
|
||||||
CLOCKWORK_QUEUE_COLLECT=true
|
|
||||||
|
|
||||||
# Enable Laravel Telescope for debugging
|
# Enable Laravel Telescope for debugging
|
||||||
TELESCOPE_ENABLED=false
|
TELESCOPE_ENABLED=false
|
||||||
|
|||||||
@@ -398,90 +398,10 @@ if [ ! -f ~/.ssh/authorized_keys ]; then
|
|||||||
chmod 600 ~/.ssh/authorized_keys
|
chmod 600 ~/.ssh/authorized_keys
|
||||||
fi
|
fi
|
||||||
|
|
||||||
checkSshKeyInAuthorizedKeys() {
|
|
||||||
grep -qw "root@coolify" ~/.ssh/authorized_keys
|
|
||||||
return $?
|
|
||||||
}
|
|
||||||
|
|
||||||
checkSshKeyInCoolifyData() {
|
|
||||||
[ -s /data/coolify/ssh/keys/id.root@host.docker.internal ]
|
|
||||||
return $?
|
|
||||||
}
|
|
||||||
|
|
||||||
generateAuthorizedKeys() {
|
|
||||||
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
|
||||||
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
|
||||||
}
|
|
||||||
generateSshKey() {
|
|
||||||
echo " - Generating SSH key."
|
|
||||||
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify
|
|
||||||
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
generateAuthorizedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
syncSshKeys() {
|
|
||||||
DB_RUNNING=$(docker inspect coolify-db --format '{{ .State.Status }}' 2>/dev/null)
|
|
||||||
# Check if SSH key exists in Coolify data but not in authorized_keys
|
|
||||||
if checkSshKeyInCoolifyData && ! checkSshKeyInAuthorizedKeys; then
|
|
||||||
# Add the existing Coolify SSH key to authorized_keys
|
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
|
||||||
# Check if SSH key exists in authorized_keys but not in Coolify data
|
|
||||||
elif checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
|
|
||||||
# Ensure Coolify DB is running before proceeding
|
|
||||||
if [ "$DB_RUNNING" = "running" ]; then
|
|
||||||
# Retrieve DB user and SSH key from Coolify database
|
|
||||||
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
|
|
||||||
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
|
|
||||||
|
|
||||||
if [ -z "$DB_SSH_KEY" ]; then
|
|
||||||
# If no key found in DB, generate a new one
|
|
||||||
echo " - SSH key not found in database. Generating new key."
|
|
||||||
generateSshKey
|
|
||||||
else
|
|
||||||
# If key found in DB, save it and update authorized_keys
|
|
||||||
echo " - SSH key found in database. Saving to file."
|
|
||||||
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
|
|
||||||
# Generate public key from private key and update authorized_keys
|
|
||||||
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
|
||||||
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
|
||||||
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
|
||||||
chmod 600 ~/.ssh/authorized_keys
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# If SSH key doesn't exist in either location
|
|
||||||
elif ! checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
|
|
||||||
# Ensure Coolify DB is running before proceeding
|
|
||||||
if [ "$DB_RUNNING" = "running" ]; then
|
|
||||||
# Retrieve DB user and SSH key from Coolify database
|
|
||||||
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
|
|
||||||
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
|
|
||||||
if [ -z "$DB_SSH_KEY" ]; then
|
|
||||||
# If no key found in DB, generate a new one
|
|
||||||
echo " - SSH key not found in database. Generating new key."
|
|
||||||
generateSshKey
|
|
||||||
else
|
|
||||||
# If key found in DB, save it and update authorized_keys
|
|
||||||
echo " - SSH key found in database. Saving to file."
|
|
||||||
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
|
||||||
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
generateSshKey
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
IS_COOLIFY_VOLUME_EXISTS=$(docker volume ls | grep coolify-db | wc -l)
|
IS_COOLIFY_VOLUME_EXISTS=$(docker volume ls | grep coolify-db | wc -l)
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$IS_COOLIFY_VOLUME_EXISTS" -eq 0 ]; then
|
if [ "$IS_COOLIFY_VOLUME_EXISTS" -eq 0 ]; then
|
||||||
echo " - Generating SSH key."
|
echo " - Generating SSH key."
|
||||||
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.$CURRENT_USER@host.docker.internal -q -N "" -C coolify
|
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.$CURRENT_USER@host.docker.internal -q -N "" -C coolify
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.339"
|
"version": "4.0.0-beta.343"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.340"
|
"version": "4.0.0-beta.344"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1860,9 +1860,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "3.29.4",
|
"version": "3.29.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
|
||||||
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
|
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
|
|||||||
@@ -8,20 +8,20 @@
|
|||||||
'hideLabel' => false,
|
'hideLabel' => false,
|
||||||
])
|
])
|
||||||
|
|
||||||
<div class="flex flex-row items-center gap-4 px-2 py-1 form-control min-w-fit dark:hover:bg-coolgray-100">
|
<div class="flex flex-row items-center py-1 form-control min-w-fit dark:hover:bg-coolgray-100">
|
||||||
@if(!$hideLabel)
|
@if (!$hideLabel)
|
||||||
<label class="flex gap-4 px-0 min-w-fit label">
|
<label class="flex gap-4 px-0 min-w-fit label">
|
||||||
<span class="flex gap-2">
|
<span class="flex gap-2">
|
||||||
@if ($label)
|
@if ($label)
|
||||||
{!! $label !!}
|
{!! $label !!}
|
||||||
@else
|
@else
|
||||||
{{ $id }}
|
{{ $id }}
|
||||||
@endif
|
@endif
|
||||||
@if ($helper)
|
@if ($helper)
|
||||||
<x-helper :helper="$helper" />
|
<x-helper :helper="$helper" />
|
||||||
@endif
|
@endif
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@endif
|
@endif
|
||||||
<span class="flex-grow"></span>
|
<span class="flex-grow"></span>
|
||||||
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
|
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
'w-full' => !$isMultiline,
|
'w-full' => !$isMultiline,
|
||||||
])>
|
])>
|
||||||
@if ($label)
|
@if ($label)
|
||||||
<label class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
|
<label class="flex gap-1 items-center mb-1 text-sm font-medium">{{ $label }}
|
||||||
@if ($required)
|
@if ($required)
|
||||||
<x-highlighted text="*" />
|
<x-highlighted text="*" />
|
||||||
@endif
|
@endif
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="relative" x-data="{ type: 'password' }">
|
<div class="relative" x-data="{ type: 'password' }">
|
||||||
@if ($allowToPeak)
|
@if ($allowToPeak)
|
||||||
<div x-on:click="changePasswordFieldType"
|
<div x-on:click="changePasswordFieldType"
|
||||||
class="absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer hover:dark:text-white">
|
class="flex absolute inset-y-0 right-0 items-center pr-2 cursor-pointer hover:dark:text-white">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
|||||||
@@ -139,7 +139,7 @@
|
|||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
@if ($buttonFullWidth)
|
@if ($buttonFullWidth)
|
||||||
<x-forms.button @click="modalOpen=true" class="flex w-full gap-2" wire:target>
|
<x-forms.button @click="modalOpen=true" class="flex gap-2 w-full" wire:target>
|
||||||
{{ $buttonTitle }}
|
{{ $buttonTitle }}
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@else
|
@else
|
||||||
@@ -162,17 +162,17 @@
|
|||||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
class="relative w-full py-6 border rounded min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
|
class="relative w-full py-6 border rounded min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
|
||||||
<div class="flex items-center justify-between pb-3">
|
<div class="flex justify-between items-center pb-3">
|
||||||
<h3 class="text-2xl font-bold pr-8">{{ $title }}</h3>
|
<h3 class="pr-8 text-2xl font-bold">{{ $title }}</h3>
|
||||||
<button @click="modalOpen = false; resetModal()"
|
<button @click="modalOpen = false; resetModal()"
|
||||||
class="absolute top-2 right-2 flex items-center justify-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
class="flex absolute top-2 right-2 justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
stroke-width="1.5" stroke="currentColor">
|
stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative w-auto pb-8">
|
<div class="relative pb-8 w-auto">
|
||||||
@if (!empty($checkboxes))
|
@if (!empty($checkboxes))
|
||||||
<!-- Step 1: Select actions -->
|
<!-- Step 1: Select actions -->
|
||||||
<div x-show="step === 1">
|
<div x-show="step === 1">
|
||||||
@@ -180,9 +180,9 @@
|
|||||||
<h4>Actions</h4>
|
<h4>Actions</h4>
|
||||||
</div>
|
</div>
|
||||||
@foreach ($checkboxes as $index => $checkbox)
|
@foreach ($checkboxes as $index => $checkbox)
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex justify-between items-center mb-2">
|
||||||
<label for="{{ $checkbox['id'] }}"
|
<label for="{{ $checkbox['id'] }}"
|
||||||
class="text-sm leading-5 text-gray-700 dark:text-gray-300 flex-grow pr-4">
|
class="flex-grow pr-4 text-sm leading-5 text-gray-700 dark:text-gray-300">
|
||||||
{{ $checkbox['label'] }}
|
{{ $checkbox['label'] }}
|
||||||
</label>
|
</label>
|
||||||
<x-forms.checkbox :id="$checkbox['id']" :wire:model="$checkbox['id']"
|
<x-forms.checkbox :id="$checkbox['id']" :wire:model="$checkbox['id']"
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
|
|
||||||
<!-- Step 2: Confirm deletion -->
|
<!-- Step 2: Confirm deletion -->
|
||||||
<div x-show="step === 2">
|
<div x-show="step === 2">
|
||||||
<div class="bg-error border-l-4 border-red-500 text-white p-4 mb-4" role="alert">
|
<div class="p-4 mb-4 text-white border-l-4 border-red-500 bg-error" role="alert">
|
||||||
<p class="font-bold">Warning</p>
|
<p class="font-bold">Warning</p>
|
||||||
<p>This operation is permanent and cannot be undone. Please think again before proceeding!
|
<p>This operation is permanent and cannot be undone. Please think again before proceeding!
|
||||||
</p>
|
</p>
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
<ul class="mb-4 space-y-2">
|
<ul class="mb-4 space-y-2">
|
||||||
@foreach ($actions as $action)
|
@foreach ($actions as $action)
|
||||||
<li class="flex items-center text-red-500">
|
<li class="flex items-center text-red-500">
|
||||||
<svg class="w-5 h-5 mr-2 flex-shrink-0" fill="none" stroke="currentColor"
|
<svg class="flex-shrink-0 mr-2 w-5 h-5" fill="none" stroke="currentColor"
|
||||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
d="M6 18L18 6M6 6l12 12"></path>
|
d="M6 18L18 6M6 6l12 12"></path>
|
||||||
@@ -216,7 +216,7 @@
|
|||||||
@foreach ($checkboxes as $checkbox)
|
@foreach ($checkboxes as $checkbox)
|
||||||
<template x-if="selectedActions.includes('{{ $checkbox['id'] }}')">
|
<template x-if="selectedActions.includes('{{ $checkbox['id'] }}')">
|
||||||
<li class="flex items-center text-red-500">
|
<li class="flex items-center text-red-500">
|
||||||
<svg class="w-5 h-5 mr-2 flex-shrink-0" fill="none" stroke="currentColor"
|
<svg class="flex-shrink-0 mr-2 w-5 h-5" fill="none" stroke="currentColor"
|
||||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
d="M6 18L18 6M6 6l12 12"></path>
|
d="M6 18L18 6M6 6l12 12"></path>
|
||||||
@@ -228,16 +228,16 @@
|
|||||||
</ul>
|
</ul>
|
||||||
@if ($confirmWithText)
|
@if ($confirmWithText)
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h4 class="text-lg font-semibold mb-2">Confirm Actions</h4>
|
<h4 class="mb-2 text-lg font-semibold">Confirm Actions</h4>
|
||||||
<p class="text-sm mb-2">{{ $confirmationLabel }}</p>
|
<p class="mb-2 text-sm">{{ $confirmationLabel }}</p>
|
||||||
<div class="relative mb-2">
|
<div class="relative mb-2">
|
||||||
<input type="text" x-model="confirmationText"
|
<input type="text" x-model="confirmationText"
|
||||||
class="w-full p-2 pr-10 rounded text-black input cursor-text" readonly>
|
class="p-2 pr-10 w-full text-black rounded cursor-text input" readonly>
|
||||||
<button @click="copyConfirmationText()"
|
<button @click="copyConfirmationText()"
|
||||||
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
class="absolute right-2 top-1/2 text-gray-500 transform -translate-y-1/2 hover:text-gray-700"
|
||||||
title="Copy confirmation text" x-ref="copyButton">
|
title="Copy confirmation text" x-ref="copyButton">
|
||||||
<template x-if="!copied">
|
<template x-if="!copied">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 20 20"
|
||||||
fill="currentColor">
|
fill="currentColor">
|
||||||
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
||||||
<path
|
<path
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="copied">
|
<template x-if="copied">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-green-500"
|
||||||
viewBox="0 0 20 20" fill="currentColor">
|
viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd"
|
<path fill-rule="evenodd"
|
||||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||||
@@ -256,18 +256,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label for="userConfirmationText"
|
<label for="userConfirmationText"
|
||||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mt-4">
|
class="block mt-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
{{ $shortConfirmationLabel }}
|
{{ $shortConfirmationLabel }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" x-model="userConfirmationText"
|
<input type="text" x-model="userConfirmationText"
|
||||||
class="w-full p-2 rounded text-black input mt-1">
|
class="p-2 mt-1 w-full text-black rounded input">
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 3: Password confirmation -->
|
<!-- Step 3: Password confirmation -->
|
||||||
<div x-show="step === 3 && confirmWithPassword">
|
<div x-show="step === 3 && confirmWithPassword">
|
||||||
<div class="bg-error border-l-4 border-red-500 text-white p-4 mb-4" role="alert">
|
<div class="p-4 mb-4 text-white border-l-4 border-red-500 bg-error" role="alert">
|
||||||
<p class="font-bold">Final Confirmation</p>
|
<p class="font-bold">Final Confirmation</p>
|
||||||
<p>Please enter your password to confirm this destructive action.</p>
|
<p>Please enter your password to confirm this destructive action.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,11 +276,11 @@
|
|||||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Your Password
|
Your Password
|
||||||
</label>
|
</label>
|
||||||
<input type="password" id="password-confirm" x-model="password" class="input w-full"
|
<input type="password" id="password-confirm" x-model="password" class="w-full input"
|
||||||
placeholder="Enter your password">
|
placeholder="Enter your password">
|
||||||
<p x-show="passwordError" x-text="passwordError" class="text-red-500 text-sm mt-1"></p>
|
<p x-show="passwordError" x-text="passwordError" class="mt-1 text-sm text-red-500"></p>
|
||||||
@error('password')
|
@error('password')
|
||||||
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
<p class="mt-1 text-sm text-red-500">{{ $message }}</p>
|
||||||
@enderror
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<x-emails.layout>
|
<x-emails.layout>
|
||||||
Your server ({{ $name }}) disabled because it is not paid! All automations and integrations are stopped.
|
Your server ({{ $name }}) disabled because it is not paid! All automations and integrations are stopped.
|
||||||
|
|
||||||
Please update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions).
|
Please update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).
|
||||||
</x-emails.layout>
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -273,27 +273,23 @@
|
|||||||
Please let me know your server details.
|
Please let me know your server details.
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 lg:pr-10">
|
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 lg:w-96">
|
||||||
<div class="flex flex-col gap-2 lg:flex-row">
|
<x-forms.input required placeholder="Choose a name for your Server. Could be anything."
|
||||||
<x-forms.input required placeholder="Choose a name for your Server. Could be anything."
|
label="Name" id="remoteServerName" wire:model="remoteServerName" />
|
||||||
label="Name" id="remoteServerName" wire:model="remoteServerName" />
|
<x-forms.input placeholder="Description, so others will know more about this."
|
||||||
<x-forms.input placeholder="Description, so others will know more about this."
|
label="Description" id="remoteServerDescription"
|
||||||
label="Description" id="remoteServerDescription"
|
wire:model="remoteServerDescription" />
|
||||||
wire:model="remoteServerDescription" />
|
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost"
|
||||||
</div>
|
wire:model="remoteServerHost" />
|
||||||
<div class="flex flex-col gap-2 lg:flex-row ">
|
|
||||||
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost"
|
|
||||||
wire:model="remoteServerHost" />
|
|
||||||
</div>
|
|
||||||
<div x-data="{ showAdvanced: false }" class="flex flex-col gap-2">
|
<div x-data="{ showAdvanced: false }" class="flex flex-col gap-2">
|
||||||
<button @click="showAdvanced = !showAdvanced" type="button"
|
<button @click="showAdvanced = !showAdvanced" type="button"
|
||||||
class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline">
|
class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline">
|
||||||
Advanced Settings
|
Advanced Settings
|
||||||
</button>
|
</button>
|
||||||
<div x-show="showAdvanced" class="flex flex-col gap-2 lg:flex-row">
|
<div x-show="showAdvanced" class="flex flex-col gap-2">
|
||||||
<x-forms.input placeholder="Port number of your server. Default is 22." label="Port"
|
<x-forms.input placeholder="Port number of your server. Default is 22." label="Port"
|
||||||
id="remoteServerPort" wire:model="remoteServerPort" />
|
id="remoteServerPort" wire:model="remoteServerPort" />
|
||||||
<div class="w-full">
|
<div>
|
||||||
<x-forms.input placeholder="Default is root." label="User"
|
<x-forms.input placeholder="Default is root." label="User"
|
||||||
id="remoteServerUser" wire:model="remoteServerUser" />
|
id="remoteServerUser" wire:model="remoteServerUser" />
|
||||||
<div class="text-xs text-gray-600 dark:text-gray-300">Non-root user is
|
<div class="text-xs text-gray-600 dark:text-gray-300">Non-root user is
|
||||||
@@ -303,11 +299,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:w-64">
|
|
||||||
<x-forms.checkbox
|
|
||||||
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
|
|
||||||
id="isCloudflareTunnel" label="Cloudflare Tunnel" wire:model="isCloudflareTunnel" />
|
|
||||||
</div>
|
|
||||||
<x-forms.button type="submit">Continue</x-forms.button>
|
<x-forms.button type="submit">Continue</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
|
|||||||
@@ -6,18 +6,10 @@
|
|||||||
Save
|
Save
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@if ($destination->network !== 'coolify')
|
@if ($destination->network !== 'coolify')
|
||||||
<x-modal-confirmation
|
<x-modal-confirmation title="Confirm Destination Deletion?" buttonTitle="Delete Destination" isErrorButton
|
||||||
title="Confirm Destination Deletion?"
|
submitAction="delete" :actions="['This will delete the selected destination/network.']" confirmationText="{{ $destination->name }}"
|
||||||
buttonTitle="Delete Destination"
|
|
||||||
isErrorButton
|
|
||||||
submitAction="delete"
|
|
||||||
:actions="['This will delete the selected destination/network.']"
|
|
||||||
confirmationText="{{ $destination->name }}"
|
|
||||||
confirmationLabel="Please confirm the execution of the actions by entering the Destination Name below"
|
confirmationLabel="Please confirm the execution of the actions by entering the Destination Name below"
|
||||||
shortConfirmationLabel="Destination Name"
|
shortConfirmationLabel="Destination Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" />
|
||||||
:confirmWithPassword="false"
|
|
||||||
step2ButtonText="Permanently Delete Destination"
|
|
||||||
/>
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user