Merge branch 'next' into docker-network-aliases

This commit is contained in:
Andras Bacsai
2025-04-08 13:27:59 +02:00
committed by GitHub
193 changed files with 11890 additions and 3393 deletions

View File

@@ -1068,7 +1068,6 @@ class Application extends BaseModel
if ($this->deploymentType() === 'other') {
$fullRepoUrl = $customRepository;
$base_command = "{$base_command} {$customRepository}";
$base_command = $this->setGitImportSettings($deployment_uuid, $base_command, public: true);
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, $base_command));
@@ -1511,6 +1510,7 @@ class Application extends BaseModel
public function parseHealthcheckFromDockerfile($dockerfile, bool $isInit = false)
{
$dockerfile = str($dockerfile)->trim()->explode("\n");
if (str($dockerfile)->contains('HEALTHCHECK') && ($this->isHealthcheckDisabled() || $isInit)) {
$healthcheckCommand = null;
$lines = $dockerfile->toArray();
@@ -1530,27 +1530,24 @@ class Application extends BaseModel
}
}
if (str($healthcheckCommand)->isNotEmpty()) {
$interval = str($healthcheckCommand)->match('/--interval=(\d+)/');
$timeout = str($healthcheckCommand)->match('/--timeout=(\d+)/');
$start_period = str($healthcheckCommand)->match('/--start-period=(\d+)/');
$start_interval = str($healthcheckCommand)->match('/--start-interval=(\d+)/');
$interval = str($healthcheckCommand)->match('/--interval=([0-9]+[a-zµ]*)/');
$timeout = str($healthcheckCommand)->match('/--timeout=([0-9]+[a-zµ]*)/');
$start_period = str($healthcheckCommand)->match('/--start-period=([0-9]+[a-zµ]*)/');
$retries = str($healthcheckCommand)->match('/--retries=(\d+)/');
if ($interval->isNotEmpty()) {
$this->health_check_interval = $interval->toInteger();
$this->health_check_interval = parseDockerfileInterval($interval);
}
if ($timeout->isNotEmpty()) {
$this->health_check_timeout = $timeout->toInteger();
$this->health_check_timeout = parseDockerfileInterval($timeout);
}
if ($start_period->isNotEmpty()) {
$this->health_check_start_period = $start_period->toInteger();
$this->health_check_start_period = parseDockerfileInterval($start_period);
}
// if ($start_interval) {
// $this->health_check_start_interval = $start_interval->value();
// }
if ($retries->isNotEmpty()) {
$this->health_check_retries = $retries->toInteger();
}
if ($interval || $timeout || $start_period || $start_interval || $retries) {
if ($interval || $timeout || $start_period || $retries) {
$this->custom_healthcheck_found = true;
$this->save();
}

View File

@@ -28,6 +28,7 @@ class DiscordNotificationSettings extends Model
'server_disk_usage_discord_notifications',
'server_reachable_discord_notifications',
'server_unreachable_discord_notifications',
'discord_ping_enabled',
];
protected $casts = [
@@ -45,6 +46,7 @@ class DiscordNotificationSettings extends Model
'server_disk_usage_discord_notifications' => 'boolean',
'server_reachable_discord_notifications' => 'boolean',
'server_unreachable_discord_notifications' => 'boolean',
'discord_ping_enabled' => 'boolean',
];
public function team()
@@ -56,4 +58,9 @@ class DiscordNotificationSettings extends Model
{
return $this->discord_enabled;
}
public function isPingEnabled()
{
return $this->discord_ping_enabled;
}
}

View File

@@ -70,10 +70,6 @@ class EmailNotificationSettings extends Model
public function isEnabled()
{
if (isCloud()) {
return true;
}
return $this->smtp_enabled || $this->resend_enabled || $this->use_instance_email_settings;
}
}

View File

@@ -3,16 +3,12 @@
namespace App\Models;
use App\Jobs\PullHelperImageJob;
use App\Notifications\Channels\SendsEmail;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Spatie\Url\Url;
class InstanceSettings extends Model implements SendsEmail
class InstanceSettings extends Model
{
use Notifiable;
protected $guarded = [];
protected $casts = [
@@ -92,15 +88,15 @@ class InstanceSettings extends Model implements SendsEmail
return InstanceSettings::findOrFail(0);
}
public function getRecipients($notification)
{
$recipients = data_get($notification, 'emails', null);
if (is_null($recipients) || $recipients === '') {
return [];
}
// public function getRecipients($notification)
// {
// $recipients = data_get($notification, 'emails', null);
// if (is_null($recipients) || $recipients === '') {
// return [];
// }
return explode(',', $recipients);
}
// return explode(',', $recipients);
// }
public function getTitleDisplayName(): string
{

View File

@@ -8,6 +8,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
class LocalFileVolume extends BaseModel
{
protected $casts = [
// 'fs_path' => 'encrypted',
// 'mount_path' => 'encrypted',
'content' => 'encrypted',
'is_directory' => 'boolean',
];
use HasFactory;
protected $guarded = [];
@@ -169,4 +176,19 @@ class LocalFileVolume extends BaseModel
return instant_remote_process($commands, $server);
}
// Accessor for convenient access
protected function plainMountPath(): Attribute
{
return Attribute::make(
get: fn () => $this->mount_path,
set: fn ($value) => $this->mount_path = $value
);
}
// Scope for searching
public function scopeWherePlainMountPath($query, $path)
{
return $query->get()->where('plain_mount_path', $path);
}
}

View File

@@ -24,11 +24,6 @@ class LocalPersistentVolume extends Model
return $this->morphTo('resource');
}
public function standalone_postgresql()
{
return $this->morphTo('resource');
}
protected function name(): Attribute
{
return Attribute::make(

View File

@@ -7,9 +7,12 @@ use App\Actions\Server\InstallDocker;
use App\Actions\Server\StartSentinel;
use App\Enums\ProxyTypes;
use App\Events\ServerReachabilityChanged;
use App\Helpers\SslHelper;
use App\Jobs\CheckAndStartSentinelJob;
use App\Jobs\RegenerateSslCertJob;
use App\Notifications\Server\Reachable;
use App\Notifications\Server\Unreachable;
use App\Services\ConfigurationRepository;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -484,7 +487,7 @@ $schema://$host {
$base_path = config('constants.coolify.base_config_path');
$proxyType = $this->proxyType();
$proxy_path = "$base_path/proxy";
// TODO: should use /traefik for already exisiting configurations?
// TODO: should use /traefik for already existing configurations?
// Should move everything except /caddy and /nginx to /traefik
// The code needs to be modified as well, so maybe it does not worth it
if ($proxyType === ProxyTypes::TRAEFIK->value) {
@@ -543,7 +546,7 @@ $schema://$host {
$this->settings->save();
$sshKeyFileLocation = "id.root@{$this->uuid}";
Storage::disk('ssh-keys')->delete($sshKeyFileLocation);
Storage::disk('ssh-mux')->delete($this->muxFilename());
$this->disableSshMux();
}
public function sentinelHeartbeat(bool $isReset = false)
@@ -922,7 +925,7 @@ $schema://$host {
public function isFunctional()
{
$isFunctional = $this->settings->is_reachable && $this->settings->is_usable && $this->settings->force_disabled === false && $this->ip !== '1.2.3.4';
$isFunctional = data_get($this->settings, 'is_reachable') && data_get($this->settings, 'is_usable') && data_get($this->settings, 'force_disabled') === false && $this->ip !== '1.2.3.4';
if ($isFunctional === false) {
Storage::disk('ssh-mux')->delete($this->muxFilename());
@@ -1103,7 +1106,7 @@ $schema://$host {
public function validateConnection(bool $justCheckingNewKey = false)
{
config()->set('constants.ssh.mux_enabled', false);
$this->disableSshMux();
if ($this->skipServer()) {
return ['uptime' => false, 'error' => 'Server skipped.'];
@@ -1330,4 +1333,47 @@ $schema://$host {
$this->databases()->count() == 0 &&
$this->services()->count() == 0;
}
private function disableSshMux(): void
{
$configRepository = app(ConfigurationRepository::class);
$configRepository->disableSshMux();
}
public function generateCaCertificate()
{
try {
ray('Generating CA certificate for server', $this->id);
SslHelper::generateSslCertificate(
commonName: 'Coolify CA Certificate',
serverId: $this->id,
isCaCertificate: true,
validityDays: 10 * 365
);
$caCertificate = SslCertificate::where('server_id', $this->id)->where('is_ca_certificate', true)->first();
ray('CA certificate generated', $caCertificate);
if ($caCertificate) {
$certificateContent = $caCertificate->ssl_certificate;
$caCertPath = config('constants.coolify.base_config_path').'/ssl/';
$commands = collect([
"mkdir -p $caCertPath",
"chown -R 9999:root $caCertPath",
"chmod -R 700 $caCertPath",
"rm -rf $caCertPath/coolify-ca.crt",
"echo '{$certificateContent}' > $caCertPath/coolify-ca.crt",
"chmod 644 $caCertPath/coolify-ca.crt",
]);
instant_remote_process($commands, $this, false);
dispatch(new RegenerateSslCertJob(
server_id: $this->id,
force_regeneration: true
));
}
} catch (\Throwable $e) {
return handleError($e);
}
}
}

View File

@@ -50,6 +50,11 @@ class Service extends BaseModel
protected static function booted()
{
static::creating(function ($service) {
if (blank($service->name)) {
$service->name = 'service-'.(new Cuid2);
}
});
static::created(function ($service) {
$service->compose_parsing_version = self::$parserVersion;
$service->save();

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SslCertificate extends Model
{
protected $fillable = [
'ssl_certificate',
'ssl_private_key',
'configuration_dir',
'mount_path',
'resource_type',
'resource_id',
'server_id',
'common_name',
'subject_alternative_names',
'valid_until',
'is_ca_certificate',
];
protected $casts = [
'ssl_certificate' => 'encrypted',
'ssl_private_key' => 'encrypted',
'subject_alternative_names' => 'array',
'valid_until' => 'datetime',
];
public function application()
{
return $this->morphTo('resource');
}
public function service()
{
return $this->morphTo('resource');
}
public function database()
{
return $this->morphTo('resource');
}
public function server()
{
return $this->belongsTo(Server::class);
}
}

View File

@@ -163,6 +163,11 @@ class StandaloneClickhouse extends BaseModel
return data_get($this, 'environment.project');
}
public function sslCertificates()
{
return $this->morphMany(SslCertificate::class, 'resource');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {
@@ -218,7 +223,12 @@ class StandaloneClickhouse extends BaseModel
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "clickhouse://{$this->clickhouse_admin_user}:{$this->clickhouse_admin_password}@{$this->uuid}:9000/{$this->clickhouse_db}",
get: function () {
$encodedUser = rawurlencode($this->clickhouse_admin_user);
$encodedPass = rawurlencode($this->clickhouse_admin_password);
return "clickhouse://{$encodedUser}:{$encodedPass}@{$this->uuid}:9000/{$this->clickhouse_db}";
},
);
}
@@ -227,7 +237,10 @@ class StandaloneClickhouse extends BaseModel
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "clickhouse://{$this->clickhouse_admin_user}:{$this->clickhouse_admin_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
$encodedUser = rawurlencode($this->clickhouse_admin_user);
$encodedPass = rawurlencode($this->clickhouse_admin_password);
return "clickhouse://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
}
return null;

View File

@@ -168,6 +168,11 @@ class StandaloneDragonfly extends BaseModel
return data_get($this, 'environment.project.team');
}
public function sslCertificates()
{
return $this->morphMany(SslCertificate::class, 'resource');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {
@@ -218,7 +223,18 @@ class StandaloneDragonfly extends BaseModel
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0",
get: function () {
$scheme = $this->enable_ssl ? 'rediss' : 'redis';
$port = $this->enable_ssl ? 6380 : 6379;
$encodedPass = rawurlencode($this->dragonfly_password);
$url = "{$scheme}://:{$encodedPass}@{$this->uuid}:{$port}/0";
if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
$url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
}
return $url;
}
);
}
@@ -227,7 +243,15 @@ class StandaloneDragonfly extends BaseModel
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
$scheme = $this->enable_ssl ? 'rediss' : 'redis';
$encodedPass = rawurlencode($this->dragonfly_password);
$url = "{$scheme}://:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/0";
if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
$url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
}
return $url;
}
return null;

View File

@@ -168,6 +168,11 @@ class StandaloneKeydb extends BaseModel
return data_get($this, 'environment.project.team');
}
public function sslCertificates()
{
return $this->morphMany(SslCertificate::class, 'resource');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {
@@ -218,7 +223,18 @@ class StandaloneKeydb extends BaseModel
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "redis://:{$this->keydb_password}@{$this->uuid}:6379/0",
get: function () {
$scheme = $this->enable_ssl ? 'rediss' : 'redis';
$port = $this->enable_ssl ? 6380 : 6379;
$encodedPass = rawurlencode($this->keydb_password);
$url = "{$scheme}://:{$encodedPass}@{$this->uuid}:{$port}/0";
if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
$url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
}
return $url;
}
);
}
@@ -227,7 +243,15 @@ class StandaloneKeydb extends BaseModel
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "redis://:{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
$scheme = $this->enable_ssl ? 'rediss' : 'redis';
$encodedPass = rawurlencode($this->keydb_password);
$url = "{$scheme}://:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/0";
if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
$url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
}
return $url;
}
return null;

View File

@@ -218,7 +218,12 @@ class StandaloneMariadb extends BaseModel
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}",
get: function () {
$encodedUser = rawurlencode($this->mariadb_user);
$encodedPass = rawurlencode($this->mariadb_password);
return "mysql://{$encodedUser}:{$encodedPass}@{$this->uuid}:3306/{$this->mariadb_database}";
},
);
}
@@ -227,7 +232,10 @@ class StandaloneMariadb extends BaseModel
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
$encodedUser = rawurlencode($this->mariadb_user);
$encodedPass = rawurlencode($this->mariadb_password);
return "mysql://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
}
return null;
@@ -271,6 +279,11 @@ class StandaloneMariadb extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function sslCertificates()
{
return $this->morphMany(SslCertificate::class, 'resource');
}
public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;

View File

@@ -177,6 +177,11 @@ class StandaloneMongodb extends BaseModel
return data_get($this, 'is_log_drain_enabled', false);
}
public function sslCertificates()
{
return $this->morphMany(SslCertificate::class, 'resource');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {
@@ -238,7 +243,19 @@ class StandaloneMongodb extends BaseModel
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true",
get: function () {
$encodedUser = rawurlencode($this->mongo_initdb_root_username);
$encodedPass = rawurlencode($this->mongo_initdb_root_password);
$url = "mongodb://{$encodedUser}:{$encodedPass}@{$this->uuid}:27017/?directConnection=true";
if ($this->enable_ssl) {
$url .= '&tls=true&tlsCAFile=/etc/mongo/certs/ca.pem';
if (in_array($this->ssl_mode, ['verify-full'])) {
$url .= '&tlsCertificateKeyFile=/etc/mongo/certs/server.pem';
}
}
return $url;
},
);
}
@@ -247,7 +264,17 @@ class StandaloneMongodb extends BaseModel
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
$encodedUser = rawurlencode($this->mongo_initdb_root_username);
$encodedPass = rawurlencode($this->mongo_initdb_root_password);
$url = "mongodb://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
if ($this->enable_ssl) {
$url .= '&tls=true&tlsCAFile=/etc/mongo/certs/ca.pem';
if (in_array($this->ssl_mode, ['verify-full'])) {
$url .= '&tlsCertificateKeyFile=/etc/mongo/certs/server.pem';
}
}
return $url;
}
return null;

View File

@@ -169,6 +169,11 @@ class StandaloneMysql extends BaseModel
return data_get($this, 'environment.project.team');
}
public function sslCertificates()
{
return $this->morphMany(SslCertificate::class, 'resource');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {
@@ -219,7 +224,19 @@ class StandaloneMysql extends BaseModel
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}",
get: function () {
$encodedUser = rawurlencode($this->mysql_user);
$encodedPass = rawurlencode($this->mysql_password);
$url = "mysql://{$encodedUser}:{$encodedPass}@{$this->uuid}:3306/{$this->mysql_database}";
if ($this->enable_ssl) {
$url .= "?ssl-mode={$this->ssl_mode}";
if (in_array($this->ssl_mode, ['VERIFY_CA', 'VERIFY_IDENTITY'])) {
$url .= '&ssl-ca=/etc/ssl/certs/coolify-ca.crt';
}
}
return $url;
},
);
}
@@ -228,7 +245,17 @@ class StandaloneMysql extends BaseModel
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
$encodedUser = rawurlencode($this->mysql_user);
$encodedPass = rawurlencode($this->mysql_password);
$url = "mysql://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
if ($this->enable_ssl) {
$url .= "?ssl-mode={$this->ssl_mode}";
if (in_array($this->ssl_mode, ['VERIFY_CA', 'VERIFY_IDENTITY'])) {
$url .= '&ssl-ca=/etc/ssl/certs/coolify-ca.crt';
}
}
return $url;
}
return null;

View File

@@ -219,7 +219,19 @@ class StandalonePostgresql extends BaseModel
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}",
get: function () {
$encodedUser = rawurlencode($this->postgres_user);
$encodedPass = rawurlencode($this->postgres_password);
$url = "postgres://{$encodedUser}:{$encodedPass}@{$this->uuid}:5432/{$this->postgres_db}";
if ($this->enable_ssl) {
$url .= "?sslmode={$this->ssl_mode}";
if (in_array($this->ssl_mode, ['verify-ca', 'verify-full'])) {
$url .= '&sslrootcert=/etc/ssl/certs/coolify-ca.crt';
}
}
return $url;
},
);
}
@@ -228,7 +240,17 @@ class StandalonePostgresql extends BaseModel
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
$encodedUser = rawurlencode($this->postgres_user);
$encodedPass = rawurlencode($this->postgres_password);
$url = "postgres://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
if ($this->enable_ssl) {
$url .= "?sslmode={$this->ssl_mode}";
if (in_array($this->ssl_mode, ['verify-ca', 'verify-full'])) {
$url .= '&sslrootcert=/etc/ssl/certs/coolify-ca.crt';
}
}
return $url;
}
return null;
@@ -241,11 +263,21 @@ class StandalonePostgresql extends BaseModel
return $this->belongsTo(Environment::class);
}
public function persistentStorages()
{
return $this->morphMany(LocalPersistentVolume::class, 'resource');
}
public function fileStorages()
{
return $this->morphMany(LocalFileVolume::class, 'resource');
}
public function sslCertificates()
{
return $this->morphMany(SslCertificate::class, 'resource');
}
public function destination()
{
return $this->morphTo();
@@ -256,16 +288,17 @@ class StandalonePostgresql extends BaseModel
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
}
public function persistentStorages()
{
return $this->morphMany(LocalPersistentVolume::class, 'resource');
}
public function scheduledBackups()
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function environment_variables()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->orderBy('key', 'asc');
}
public function isBackupSolutionAvailable()
{
return true;
@@ -314,10 +347,4 @@ class StandalonePostgresql extends BaseModel
return $parsedCollection->toArray();
}
public function environment_variables()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->orderBy('key', 'asc');
}
}

View File

@@ -170,6 +170,11 @@ class StandaloneRedis extends BaseModel
return data_get($this, 'environment.project.team');
}
public function sslCertificates()
{
return $this->morphMany(SslCertificate::class, 'resource');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {
@@ -222,9 +227,17 @@ class StandaloneRedis extends BaseModel
return new Attribute(
get: function () {
$redis_version = $this->getRedisVersion();
$username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
$username_part = version_compare($redis_version, '6.0', '>=') ? rawurlencode($this->redis_username).':' : '';
$encodedPass = rawurlencode($this->redis_password);
$scheme = $this->enable_ssl ? 'rediss' : 'redis';
$port = $this->enable_ssl ? 6380 : 6379;
$url = "{$scheme}://{$username_part}{$encodedPass}@{$this->uuid}:{$port}/0";
return "redis://{$username_part}{$this->redis_password}@{$this->uuid}:6379/0";
if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
$url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
}
return $url;
}
);
}
@@ -235,9 +248,16 @@ class StandaloneRedis extends BaseModel
get: function () {
if ($this->is_public && $this->public_port) {
$redis_version = $this->getRedisVersion();
$username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
$username_part = version_compare($redis_version, '6.0', '>=') ? rawurlencode($this->redis_username).':' : '';
$encodedPass = rawurlencode($this->redis_password);
$scheme = $this->enable_ssl ? 'rediss' : 'redis';
$url = "{$scheme}://{$username_part}{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/0";
return "redis://{$username_part}{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
$url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
}
return $url;
}
return null;

View File

@@ -163,14 +163,17 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
];
}
public function getRecipients($notification)
public function getRecipients(): array
{
$recipients = data_get($notification, 'emails', null);
if (is_null($recipients)) {
return $this->members()->pluck('email')->toArray();
$recipients = $this->members()->pluck('email')->toArray();
$validatedEmails = array_filter($recipients, function ($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
});
if (is_null($validatedEmails)) {
return [];
}
return explode(',', $recipients);
return array_values($validatedEmails);
}
public function isAnyNotificationEnabled()

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use App\Notifications\Channels\SendsEmail;
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
use App\Traits\DeletesUserSessions;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
@@ -37,7 +38,7 @@ use OpenApi\Attributes as OA;
)]
class User extends Authenticatable implements SendsEmail
{
use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable;
use DeletesUserSessions, HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable;
protected $guarded = [];
@@ -57,6 +58,7 @@ class User extends Authenticatable implements SendsEmail
protected static function boot()
{
parent::boot();
static::created(function (User $user) {
$team = [
'name' => $user->name."'s Team",
@@ -114,9 +116,9 @@ class User extends Authenticatable implements SendsEmail
return $this->belongsToMany(Team::class)->withPivot('role');
}
public function getRecipients($notification)
public function getRecipients(): array
{
return $this->email;
return [$this->email];
}
public function sendVerificationEmail()