diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index eeddab924..3705ce938 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -21,8 +21,6 @@ class StartRedis { $this->database = $database; - $startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; - $container_name = $this->database->uuid; $this->configuration_dir = database_configuration_dir().'/'.$container_name; @@ -37,6 +35,8 @@ class StartRedis $environment_variables = $this->generate_environment_variables(); $this->add_custom_redis(); + $startCommand = $this->buildStartCommand(); + $docker_compose = [ 'services' => [ $container_name => [ @@ -105,7 +105,6 @@ class StartRedis 'target' => '/usr/local/etc/redis/redis.conf', 'read_only' => true, ]; - $docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes"; } // Add custom docker run options @@ -160,12 +159,26 @@ class StartRedis private function generate_environment_variables() { $environment_variables = collect(); - foreach ($this->database->runtime_environment_variables as $env) { - $environment_variables->push("$env->key=$env->real_value"); - } - if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { - $environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}"); + foreach ($this->database->runtime_environment_variables as $env) { + if ($env->is_shared) { + $environment_variables->push("$env->key=$env->real_value"); + + if ($env->key === 'REDIS_PASSWORD') { + $this->database->update(['redis_password' => $env->real_value]); + } + + if ($env->key === 'REDIS_USERNAME') { + $this->database->update(['redis_username' => $env->real_value]); + } + } else { + if ($env->key === 'REDIS_PASSWORD') { + $env->update(['value' => $this->database->redis_password]); + } elseif ($env->key === 'REDIS_USERNAME') { + $env->update(['value' => $this->database->redis_username]); + } + $environment_variables->push("$env->key=$env->real_value"); + } } add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); @@ -173,6 +186,27 @@ class StartRedis return $environment_variables->all(); } + private function buildStartCommand(): string + { + $hasRedisConf = ! is_null($this->database->redis_conf) && ! empty($this->database->redis_conf); + $redisConfPath = '/usr/local/etc/redis/redis.conf'; + + if ($hasRedisConf) { + $confContent = $this->database->redis_conf; + $hasRequirePass = str_contains($confContent, 'requirepass'); + + if ($hasRequirePass) { + $command = "redis-server $redisConfPath"; + } else { + $command = "redis-server $redisConfPath --requirepass {$this->database->redis_password}"; + } + } else { + $command = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; + } + + return $command; + } + private function add_custom_redis() { if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) { diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php index 72fd95de8..20a881523 100644 --- a/app/Livewire/Project/Database/Redis/General.php +++ b/app/Livewire/Project/Database/Redis/General.php @@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Redis; use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StopDatabaseProxy; +use App\Models\EnvironmentVariable; use App\Models\Server; use App\Models\StandaloneRedis; use Exception; @@ -25,6 +26,7 @@ class General extends Component 'database.name' => 'required', 'database.description' => 'nullable', 'database.redis_conf' => 'nullable', + 'database.redis_username' => 'required', 'database.redis_password' => 'required', 'database.image' => 'required', 'database.ports_mappings' => 'nullable', @@ -38,6 +40,7 @@ class General extends Component 'database.name' => 'Name', 'database.description' => 'Description', 'database.redis_conf' => 'Redis Configuration', + 'database.redis_username' => 'Redis Username', 'database.redis_password' => 'Redis Password', 'database.image' => 'Image', 'database.ports_mappings' => 'Port Mapping', @@ -75,16 +78,32 @@ class General extends Component { try { $this->validate(); - if ($this->database->redis_conf === '') { - $this->database->redis_conf = null; + + $redis_version = $this->get_redis_version(); + + if (version_compare($redis_version, '6.0', '>=') && $this->database->isDirty('redis_username')) { + $this->updateEnvironmentVariable('REDIS_USERNAME', $this->database->redis_username); } + + if ($this->database->isDirty('redis_password')) { + $this->updateEnvironmentVariable('REDIS_PASSWORD', $this->database->redis_password); + } + $this->database->save(); + $this->dispatch('success', 'Database updated.'); } catch (Exception $e) { return handleError($e, $this); } } + private function get_redis_version() + { + $image_parts = explode(':', $this->database->image); + + return $image_parts[1] ?? '0.0'; + } + public function instantSave() { try { @@ -125,4 +144,31 @@ class General extends Component { return view('livewire.project.database.redis.general'); } + + public function isSharedVariable($name) + { + return EnvironmentVariable::where('key', $name) + ->where('standalone_redis_id', $this->database->id) + ->where('is_shared', true) + ->exists(); + } + + private function updateEnvironmentVariable($key, $value) + { + $envVar = $this->database->runtime_environment_variables() + ->where('key', $key) + ->first(); + + if ($envVar) { + if (! $envVar->is_shared) { + $envVar->update(['value' => $value]); + } + } else { + $this->database->runtime_environment_variables()->create([ + 'key' => $key, + 'value' => $value, + 'is_shared' => false, + ]); + } + } } diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 531c8fa40..f77d73db8 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -74,6 +74,9 @@ class EnvironmentVariable extends Model 'version' => config('version'), ]); }); + static::saving(function (EnvironmentVariable $environmentVariable) { + $environmentVariable->updateIsShared(); + }); } public function service() @@ -217,4 +220,11 @@ class EnvironmentVariable extends Model set: fn (string $value) => str($value)->trim()->replace(' ', '_')->value, ); } + + protected function updateIsShared(): void + { + $type = str($this->value)->after('{{')->before('.')->value; + $isShared = str($this->value)->startsWith('{{'.$type) && str($this->value)->endsWith('}}'); + $this->is_shared = $isShared; + } } diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index fe9f6dfc7..188982f47 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -16,6 +16,14 @@ class StandaloneRedis extends BaseModel protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; + protected $casts = [ + 'redis_password' => 'encrypted', + ]; + + protected $attributes = [ + 'redis_username' => 'default', + ]; + protected static function booted() { static::created(function ($database) { @@ -210,7 +218,12 @@ class StandaloneRedis extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "redis://:{$this->redis_password}@{$this->uuid}:6379/0", + get: function () { + $redis_version = $this->get_redis_version(); + $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : ''; + + return "redis://{$username_part}{$this->redis_password}@{$this->uuid}:6379/0"; + } ); } @@ -219,7 +232,10 @@ class StandaloneRedis extends BaseModel return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; + $redis_version = $this->get_redis_version(); + $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : ''; + + return "redis://{$username_part}{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; } return null; @@ -227,6 +243,13 @@ class StandaloneRedis extends BaseModel ); } + private function get_redis_version() + { + $image_parts = explode(':', $this->image); + + return $image_parts[1] ?? '0.0'; + } + public function environment() { return $this->belongsTo(Environment::class); diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php index 950eb67b6..d77327c8f 100644 --- a/bootstrap/helpers/databases.php +++ b/bootstrap/helpers/databases.php @@ -1,5 +1,6 @@ name = generate_database_name('redis'); $database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false); + $database->redis_username = 'default'; $database->environment_id = $environment_id; $database->destination_id = $destination->id; $database->destination_type = $destination->getMorphClass(); @@ -57,6 +59,20 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth } $database->save(); + EnvironmentVariable::create([ + 'key' => 'REDIS_PASSWORD', + 'value' => $database->redis_password, + 'standalone_redis_id' => $database->id, + 'is_shared' => false, + ]); + + EnvironmentVariable::create([ + 'key' => 'REDIS_USERNAME', + 'value' => $database->redis_username, + 'standalone_redis_id' => $database->id, + 'is_shared' => false, + ]); + return $database; } diff --git a/database/migrations/2024_08_21_165435_add_redis_username_to_standalone_redis_table.php b/database/migrations/2024_08_21_165435_add_redis_username_to_standalone_redis_table.php new file mode 100644 index 000000000..397c6a68f --- /dev/null +++ b/database/migrations/2024_08_21_165435_add_redis_username_to_standalone_redis_table.php @@ -0,0 +1,22 @@ +string('redis_username')->default('redis')->after('description'); + }); + } + + public function down(): void + { + Schema::table('standalone_redis', function (Blueprint $table) { + $table->dropColumn('redis_username'); + }); + } +}; diff --git a/database/migrations/2024_10_15_172139_add_is_shared_to_environment_variables.php b/database/migrations/2024_10_15_172139_add_is_shared_to_environment_variables.php new file mode 100644 index 000000000..eb878e2f6 --- /dev/null +++ b/database/migrations/2024_10_15_172139_add_is_shared_to_environment_variables.php @@ -0,0 +1,22 @@ +boolean('is_shared')->default(false); + }); + } + + public function down() + { + Schema::table('environment_variables', function (Blueprint $table) { + $table->dropColumn('is_shared'); + }); + } +} diff --git a/database/migrations/2024_10_16_120026_encrypt_existing_redis_passwords.php b/database/migrations/2024_10_16_120026_encrypt_existing_redis_passwords.php new file mode 100644 index 000000000..3b4f11986 --- /dev/null +++ b/database/migrations/2024_10_16_120026_encrypt_existing_redis_passwords.php @@ -0,0 +1,27 @@ +chunkById(100, function ($redisInstances) { + foreach ($redisInstances as $redis) { + DB::table('standalone_redis') + ->where('id', $redis->id) + ->update(['redis_password' => Crypt::encryptString($redis->redis_password)]); + } + }); + } catch (\Exception $e) { + echo 'Encrypting Redis passwords failed.'; + echo $e->getMessage(); + } + } +} diff --git a/resources/views/livewire/project/database/redis/general.blade.php b/resources/views/livewire/project/database/redis/general.blade.php index 7d4de27cd..e61c210b0 100644 --- a/resources/views/livewire/project/database/redis/general.blade.php +++ b/resources/views/livewire/project/database/redis/general.blade.php @@ -9,8 +9,29 @@
- + +
+
+ @php + $redis_version = explode(':', $database->image)[1] ?? '0.0'; + @endphp + @if (version_compare($redis_version, '6.0', '>=')) + + @endif +

Network

- +
- + @if ($db_url_public) - + @endif

Proxy

- + Proxy Logs - + - Proxy Logs + Proxy Logs
- +

Advanced

- +
+