From 4cc668253ea9c7a900fc5b0f61c215e1bb2e5eca Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:46:24 +0100 Subject: [PATCH] fix(database): prevent malformed URLs when server IP is empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add defensive null/empty checks in externalDbUrl() for all standalone database models to prevent "invalid proto:" errors when server IP is not available. **Problem:** When `$this->destination->server->getIp` returns null or empty string, database URLs become malformed (e.g., `mongodb://user:pass@:27017` with empty host), causing "invalid proto:" validation errors. **Solution:** Added early return with null check in externalDbUrl() method for all 8 database types: - Check if server IP is empty before building URL - Return null instead of generating malformed URL - Maintains graceful degradation - UI handles null URLs appropriately **Defense in Depth:** While mount() guard (from commit 74c70b431) prevents most cases, this adds an additional safety layer for edge cases: - Race conditions during server updates - State changes between mount and URL access - Direct model access bypassing Livewire lifecycle **Affected Models:** - StandaloneMongodb - StandalonePostgresql - StandaloneMysql - StandaloneMariadb - StandaloneClickhouse - StandaloneRedis - StandaloneKeydb - StandaloneDragonfly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Models/StandaloneClickhouse.php | 6 +++++- app/Models/StandaloneDragonfly.php | 6 +++++- app/Models/StandaloneKeydb.php | 6 +++++- app/Models/StandaloneMariadb.php | 6 +++++- app/Models/StandaloneMongodb.php | 6 +++++- app/Models/StandaloneMysql.php | 6 +++++- app/Models/StandalonePostgresql.php | 6 +++++- app/Models/StandaloneRedis.php | 6 +++++- templates/service-templates-latest.json | 2 +- templates/service-templates.json | 2 +- 10 files changed, 42 insertions(+), 10 deletions(-) diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index 146ee0a2d..6ac685618 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -243,10 +243,14 @@ protected function externalDbUrl(): Attribute return new Attribute( get: function () { if ($this->is_public && $this->public_port) { + $serverIp = $this->destination->server->getIp; + if (empty($serverIp)) { + return null; + } $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 "clickhouse://{$encodedUser}:{$encodedPass}@{$serverIp}:{$this->public_port}/{$this->clickhouse_db}"; } return null; diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index 90e7304f1..2d004246c 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -249,9 +249,13 @@ protected function externalDbUrl(): Attribute return new Attribute( get: function () { if ($this->is_public && $this->public_port) { + $serverIp = $this->destination->server->getIp; + if (empty($serverIp)) { + return null; + } $scheme = $this->enable_ssl ? 'rediss' : 'redis'; $encodedPass = rawurlencode($this->dragonfly_password); - $url = "{$scheme}://:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/0"; + $url = "{$scheme}://:{$encodedPass}@{$serverIp}:{$this->public_port}/0"; if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') { $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt'; diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index ad0cabf7e..131e5bb3f 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -249,9 +249,13 @@ protected function externalDbUrl(): Attribute return new Attribute( get: function () { if ($this->is_public && $this->public_port) { + $serverIp = $this->destination->server->getIp; + if (empty($serverIp)) { + return null; + } $scheme = $this->enable_ssl ? 'rediss' : 'redis'; $encodedPass = rawurlencode($this->keydb_password); - $url = "{$scheme}://:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/0"; + $url = "{$scheme}://:{$encodedPass}@{$serverIp}:{$this->public_port}/0"; if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') { $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt'; diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 3d9e38147..675c7987f 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -239,10 +239,14 @@ protected function externalDbUrl(): Attribute return new Attribute( get: function () { if ($this->is_public && $this->public_port) { + $serverIp = $this->destination->server->getIp; + if (empty($serverIp)) { + return null; + } $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 "mysql://{$encodedUser}:{$encodedPass}@{$serverIp}:{$this->public_port}/{$this->mariadb_database}"; } return null; diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index 7cccd332a..7b70988f6 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -269,9 +269,13 @@ protected function externalDbUrl(): Attribute return new Attribute( get: function () { if ($this->is_public && $this->public_port) { + $serverIp = $this->destination->server->getIp; + if (empty($serverIp)) { + return null; + } $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"; + $url = "mongodb://{$encodedUser}:{$encodedPass}@{$serverIp}:{$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'])) { diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index 80269972f..6f79241af 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -251,9 +251,13 @@ protected function externalDbUrl(): Attribute return new Attribute( get: function () { if ($this->is_public && $this->public_port) { + $serverIp = $this->destination->server->getIp; + if (empty($serverIp)) { + return null; + } $encodedUser = rawurlencode($this->mysql_user); $encodedPass = rawurlencode($this->mysql_password); - $url = "mysql://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}"; + $url = "mysql://{$encodedUser}:{$encodedPass}@{$serverIp}:{$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'])) { diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index acde7a20c..2dc5616a2 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -246,9 +246,13 @@ protected function externalDbUrl(): Attribute return new Attribute( get: function () { if ($this->is_public && $this->public_port) { + $serverIp = $this->destination->server->getIp; + if (empty($serverIp)) { + return null; + } $encodedUser = rawurlencode($this->postgres_user); $encodedPass = rawurlencode($this->postgres_password); - $url = "postgres://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}"; + $url = "postgres://{$encodedUser}:{$encodedPass}@{$serverIp}:{$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'])) { diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 001ebe36a..c0223304a 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -253,11 +253,15 @@ protected function externalDbUrl(): Attribute return new Attribute( get: function () { if ($this->is_public && $this->public_port) { + $serverIp = $this->destination->server->getIp; + if (empty($serverIp)) { + return null; + } $redis_version = $this->getRedisVersion(); $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"; + $url = "{$scheme}://{$username_part}{$encodedPass}@{$serverIp}:{$this->public_port}/0"; if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') { $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt'; diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json index 1629cc152..859981ac0 100644 --- a/templates/service-templates-latest.json +++ b/templates/service-templates-latest.json @@ -4527,7 +4527,7 @@ "web", "admin" ], - "category": "vps", + "category": "vpn", "logo": "svgs/wireguard.svg", "minversion": "0.0.0", "port": "8000" diff --git a/templates/service-templates.json b/templates/service-templates.json index 13a6d7382..468bf7543 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -4527,7 +4527,7 @@ "web", "admin" ], - "category": "vps", + "category": "vpn", "logo": "svgs/wireguard.svg", "minversion": "0.0.0", "port": "8000"