chore: merge main into release/v4.5 (#12206)

Co-authored-by: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Andras Bacsai <andrasbacsai@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
github-actions[bot]
2026-10-20 13:26:07 +02:00
committed by GitHub
co-authored by Andras Bacsai Cursor Agent Andras Bacsai Claude Opus 5.5
parent cf71fda5c1
commit e0f6b41dac
18 changed files with 287 additions and 645 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ Your configurations stay on your servers. If you stop using Coolify, your runnin
## What can Coolify do?
- **Deploy any application:** Build from GitHub, GitLab, Bitbucket, or Gitea with Nixpacks, Railpack, Dockerfile, Docker Compose, or a prebuilt Docker image.
- **Run databases and services:** Deploy managed databases and more than 300 one-click services with persistent storage and generated credentials.
- **Run databases and services:** Deploy managed databases and more than 360 one-click services with persistent storage and generated credentials.
- **Automate deployments:** Deploy on every Git push, create pull-request previews, call deployment webhooks, and roll back to retained application images.
- **Manage networking:** Configure custom domains, automatic HTTPS certificates, reverse proxies, health checks, and container networks.
- **Operate your infrastructure:** Manage multiple servers, inspect deployment and runtime logs, open container terminals, and monitor resource status.
-47
View File
@@ -2,17 +2,12 @@
namespace App\Exceptions;
use App\Models\InstanceSettings;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Session\TokenMismatchException;
use Psr\Log\LogLevel;
use RuntimeException;
use Sentry\Laravel\Integration;
use Sentry\State\Scope;
use Throwable;
class Handler extends ExceptionHandler
@@ -49,8 +44,6 @@ class Handler extends ExceptionHandler
'password_confirmation',
];
private InstanceSettings $settings;
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
@@ -108,44 +101,4 @@ class Handler extends ExceptionHandler
return parent::render($request, $e);
}
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
if (isDev()) {
return;
}
if ($e instanceof RuntimeException) {
return;
}
$this->settings = instanceSettings();
if ($this->settings->do_not_track) {
return;
}
app('sentry')->configureScope(
function (Scope $scope) {
$email = auth()?->user() ? auth()->user()->email : 'guest';
$instanceAdmin = User::find(0)->email ?? 'admin@localhost';
$scope->setUser(
[
'email' => $email,
'instanceAdmin' => $instanceAdmin,
]
);
}
);
// Check for errors that should not be reported to Sentry
if (str($e->getMessage())->contains('No space left on device')) {
// Log locally but don't send to Sentry
logger()->warning('Disk space error: '.$e->getMessage());
return;
}
Integration::captureUnhandledException($e);
});
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ namespace App\Exceptions;
use Exception;
/**
* Exception that should not be reported to Sentry or other error tracking services.
* Exception that should not be reported to error tracking services.
* Use this for known, expected errors that don't require external tracking.
*/
class NonReportableException extends Exception
+2
View File
@@ -50,6 +50,8 @@ class GithubApp extends BaseModel
'type' => 'string',
'webhook_events' => 'array',
'runner_group_id' => 'integer',
'client_secret' => 'encrypted',
'webhook_secret' => 'encrypted',
];
protected $hidden = [
+1
View File
@@ -51,6 +51,7 @@ class GitlabApp extends BaseModel
'access_token' => 'encrypted',
'refresh_token' => 'encrypted',
'client_secret' => 'encrypted',
'app_secret' => 'encrypted',
'is_system_wide' => 'boolean',
'is_public' => 'boolean',
];
+1
View File
@@ -200,6 +200,7 @@ class ServerSetting extends Model
'is_geoip_enabled' => 'boolean',
'geoip_refresh_days' => 'integer',
'geoip_maxmind_license_key' => 'encrypted',
'logdrain_custom_config' => 'encrypted',
'traffic_ip_mode' => TrafficIpMode::class,
'docker_version_checked_at' => 'datetime',
'compose_version_checked_at' => 'datetime',
+1
View File
@@ -80,6 +80,7 @@ class StandaloneMariadb extends BaseModel
'health_check_retries' => 'integer',
'health_check_start_period' => 'integer',
'mariadb_password' => 'encrypted',
'mariadb_root_password' => 'encrypted',
'public_port_timeout' => 'integer',
'restart_count' => 'integer',
'last_restart_at' => 'datetime',
+1 -16
View File
@@ -74,6 +74,7 @@ class StandaloneMongodb extends BaseModel
protected $casts = [
'include_coolify_default_variables' => 'boolean',
'health_check_enabled' => 'boolean',
'mongo_initdb_root_password' => 'encrypted',
'health_check_interval' => 'integer',
'health_check_timeout' => 'integer',
'health_check_retries' => 'integer',
@@ -283,22 +284,6 @@ class StandaloneMongodb extends BaseModel
return null;
}
public function mongoInitdbRootPassword(): Attribute
{
return Attribute::make(
get: function ($value) {
try {
return decrypt($value);
} catch (\Throwable $th) {
$this->mongo_initdb_root_password = encrypt($value);
$this->save();
return $value;
}
}
);
}
public function portsMappings(): Attribute
{
return Attribute::make(
+4
View File
@@ -15,6 +15,10 @@ class TeamInvitation extends Model
'via',
];
protected $casts = [
'link' => 'encrypted',
];
/**
* Set the email attribute to lowercase.
*/
+2 -2
View File
@@ -157,7 +157,7 @@ class EmailChannel
json_encode(app(SensitiveDataRedactor::class)->redactValue($emailSettings), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
));
// Don't report expected errors (invalid keys, validation) to Sentry
// Don't report expected errors (invalid keys, validation) to error tracking
if (in_array($statusCode, [403, 401, 400, 422], true)) {
throw NonReportableException::fromException(new Exception($userMessage, $e->getCode(), $e));
}
@@ -178,7 +178,7 @@ class EmailChannel
} catch (\Throwable $e) {
// Check if this is a Resend domain verification error on cloud instances
if (isCloud() && str_contains($e->getMessage(), 'domain is not verified')) {
// Throw as NonReportableException so it won't go to Sentry
// Throw as NonReportableException so it won't go to error tracking
throw NonReportableException::fromException($e);
}
throw $e;
+1 -2
View File
@@ -253,8 +253,7 @@ function instant_remote_process(Collection|array $command, Server $server, bool
}
/**
* Throws the remote command error. Handler::register() never sends RuntimeException to Sentry,
* so SSH key, DNS, and timeout failures on user servers are not reported.
* Throws the remote command error.
*/
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null): never
{
-1
View File
@@ -40,7 +40,6 @@
"purplepixie/phpdns": "^2.4.0",
"pusher/pusher-php-server": "^7.3.0",
"resend/resend-laravel": "^1.6.0",
"sentry/sentry-laravel": "^4.28.0",
"socialiteproviders/authentik": "^5.3.0",
"socialiteproviders/clerk": "^5.1.0",
"socialiteproviders/discord": "^4.2.0",
Generated
+61 -485
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "664589c32e86e8e6fc2a0fb17feab410",
"content-hash": "f97af22f1628cec49170f1d40c582a9d",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -1837,66 +1837,6 @@
],
"time": "2026-08-24T09:15:32+00:00"
},
{
"name": "jean85/pretty-package-versions",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/Jean85/pretty-package-versions.git",
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2.1.0",
"php": "^7.4|^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"jean85/composer-provided-replaced-stub-package": "^1.0",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^7.5|^8.5|^9.6",
"rector/rector": "^2.0",
"vimeo/psalm": "^4.3 || ^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Jean85\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alessandro Lai",
"email": "alessandro.lai85@gmail.com"
}
],
"description": "A library to get pretty versions strings of installed dependencies",
"keywords": [
"composer",
"package",
"release",
"versions"
],
"support": {
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1"
},
"time": "2025-03-19T14:43:43+00:00"
},
{
"name": "laravel/fortify",
"version": "v1.40.0",
@@ -4746,84 +4686,6 @@
],
"time": "2026-02-16T23:10:27+00:00"
},
{
"name": "nyholm/psr7",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/Nyholm/psr7.git",
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0"
},
"provide": {
"php-http/message-factory-implementation": "1.0",
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"http-interop/http-factory-tests": "^0.9",
"php-http/message-factory": "^1.0",
"php-http/psr7-integration-tests": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"symfony/error-handler": "^4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8-dev"
}
},
"autoload": {
"psr-4": {
"Nyholm\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
},
{
"name": "Martijn van der Ven",
"email": "martijn@vanderven.se"
}
],
"description": "A fast PHP7 implementation of PSR-7",
"homepage": "https://tnyholm.se",
"keywords": [
"psr-17",
"psr-7"
],
"support": {
"issues": "https://github.com/Nyholm/psr7/issues",
"source": "https://github.com/Nyholm/psr7/tree/1.8.2"
},
"funding": [
{
"url": "https://github.com/Zegnat",
"type": "github"
},
{
"url": "https://github.com/nyholm",
"type": "github"
}
],
"time": "2024-09-09T07:06:30+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v3.1.3",
@@ -7161,194 +7023,6 @@
},
"time": "2026-09-25T18:18:55+00:00"
},
{
"name": "sentry/sentry",
"version": "4.32.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-php.git",
"reference": "c345642bdc0b678965a35ab61fc6323808eb3d2c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/c345642bdc0b678965a35ab61fc6323808eb3d2c",
"reference": "c345642bdc0b678965a35ab61fc6323808eb3d2c",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"guzzlehttp/psr7": "^1.8.4|^2.1.1|^3.0.0",
"jean85/pretty-package-versions": "^1.5|^2.0.4",
"php": "^7.2|^8.0",
"psr/log": "^1.0|^2.0|^3.0",
"symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0|^8.0"
},
"conflict": {
"raven/raven": "*"
},
"require-dev": {
"carthage-software/mago": "1.30.0",
"friendsofphp/php-cs-fixer": "^3.4",
"guzzlehttp/guzzle": "^7.0|^8.0",
"guzzlehttp/promises": "^2.0.3|^3.0.0",
"monolog/monolog": "^1.6|^2.0|^3.0",
"nyholm/psr7": "^1.8",
"open-telemetry/api": "^1.1",
"open-telemetry/context": "^1.1",
"open-telemetry/exporter-otlp": "^1.1",
"open-telemetry/sdk": "^1.1",
"open-telemetry/sem-conv": "^1.27",
"phpstan/phpstan": "^1.3",
"phpunit/phpunit": "^8.5.52|^9.6.34",
"spiral/roadrunner-http": "^3.6",
"spiral/roadrunner-worker": "^3.6"
},
"suggest": {
"ext-excimer": "Enable Sentry profiling with the Excimer PHP extension.",
"monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler."
},
"type": "library",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Sentry\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sentry",
"email": "accounts@sentry.io"
}
],
"description": "PHP SDK for Sentry (http://sentry.io)",
"homepage": "http://sentry.io",
"keywords": [
"crash-reporting",
"crash-reports",
"error-handler",
"error-monitoring",
"log",
"logging",
"profiling",
"sentry",
"tracing"
],
"support": {
"issues": "https://github.com/getsentry/sentry-php/issues",
"source": "https://github.com/getsentry/sentry-php/tree/4.32.0"
},
"funding": [
{
"url": "https://sentry.io/",
"type": "custom"
},
{
"url": "https://sentry.io/pricing/",
"type": "custom"
}
],
"time": "2026-09-24T13:32:44+00:00"
},
{
"name": "sentry/sentry-laravel",
"version": "4.28.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-laravel.git",
"reference": "34bf18fbf99b884a9bfbb46957d0178d2d0b988e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/34bf18fbf99b884a9bfbb46957d0178d2d0b988e",
"reference": "34bf18fbf99b884a9bfbb46957d0178d2d0b988e",
"shasum": ""
},
"require": {
"illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0 | ^13.0",
"nyholm/psr7": "^1.0",
"php": "^7.2 | ^8.0",
"sentry/sentry": "^4.31.0",
"symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0 | ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.11",
"guzzlehttp/guzzle": "^7.2",
"laravel/ai": "^0.1.5",
"laravel/folio": "^1.1",
"laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0 | ^13.0",
"laravel/octane": "^2.15",
"laravel/pennant": "^1.0",
"livewire/livewire": "^2.0 | ^3.0 | ^4.0",
"mockery/mockery": "^1.3",
"orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8.5 | ^9.6 | ^10.4 | ^11.5"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Sentry": "Sentry\\Laravel\\Facade"
},
"providers": [
"Sentry\\Laravel\\ServiceProvider",
"Sentry\\Laravel\\Tracing\\ServiceProvider"
]
}
},
"autoload": {
"psr-0": {
"Sentry\\Laravel\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sentry",
"email": "accounts@sentry.io"
}
],
"description": "Laravel SDK for Sentry (https://sentry.io)",
"homepage": "https://sentry.io",
"keywords": [
"crash-reporting",
"crash-reports",
"error-handler",
"error-monitoring",
"laravel",
"log",
"logging",
"profiling",
"sentry",
"tracing"
],
"support": {
"issues": "https://github.com/getsentry/sentry-laravel/issues",
"source": "https://github.com/getsentry/sentry-laravel/tree/4.28.0"
},
"funding": [
{
"url": "https://sentry.io/",
"type": "custom"
},
{
"url": "https://sentry.io/pricing/",
"type": "custom"
}
],
"time": "2026-09-23T14:29:19+00:00"
},
{
"name": "socialiteproviders/authentik",
"version": "5.3.0",
@@ -9853,77 +9527,6 @@
],
"time": "2026-09-04T10:45:44+00:00"
},
{
"name": "symfony/options-resolver",
"version": "v8.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "88f9c561f678a02d54b897014049fa839e33ff82"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82",
"reference": "88f9c561f678a02d54b897014049fa839e33ff82",
"shasum": ""
},
"require": {
"php": ">=8.4.1",
"symfony/deprecation-contracts": "^2.5|^3"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\OptionsResolver\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides an improved replacement for the array_replace PHP function",
"homepage": "https://symfony.com",
"keywords": [
"config",
"configuration",
"options"
],
"support": {
"source": "https://github.com/symfony/options-resolver/tree/v8.1.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-05-29T05:06:50+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.37.0",
@@ -11065,93 +10668,6 @@
],
"time": "2026-09-28T19:13:15+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
"version": "v8.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/psr-http-message-bridge.git",
"reference": "67fd34de15ded1763aa1e330fe345f080a94022c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/67fd34de15ded1763aa1e330fe345f080a94022c",
"reference": "67fd34de15ded1763aa1e330fe345f080a94022c",
"shasum": ""
},
"require": {
"php": ">=8.4.1",
"psr/http-message": "^1.0|^2.0",
"symfony/http-foundation": "^7.4|^8.0"
},
"conflict": {
"php-http/discovery": "<1.15"
},
"require-dev": {
"nyholm/psr7": "^1.1",
"php-http/discovery": "^1.15",
"psr/log": "^1.1.4|^2|^3",
"symfony/browser-kit": "^7.4|^8.0",
"symfony/config": "^7.4|^8.0",
"symfony/event-dispatcher": "^7.4|^8.0",
"symfony/framework-bundle": "^7.4|^8.0",
"symfony/http-kernel": "^7.4|^8.0",
"symfony/runtime": "^7.4|^8.0"
},
"type": "symfony-bridge",
"autoload": {
"psr-4": {
"Symfony\\Bridge\\PsrHttpMessage\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "PSR HTTP message bridge",
"homepage": "https://symfony.com",
"keywords": [
"http",
"http-message",
"psr-17",
"psr-7"
],
"support": {
"source": "https://github.com/symfony/psr-http-message-bridge/tree/v8.1.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-05-29T05:06:50+00:00"
},
{
"name": "symfony/routing",
"version": "v7.4.20",
@@ -14616,6 +14132,66 @@
},
"time": "2026-03-17T11:56:53+00:00"
},
{
"name": "jean85/pretty-package-versions",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/Jean85/pretty-package-versions.git",
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2.1.0",
"php": "^7.4|^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"jean85/composer-provided-replaced-stub-package": "^1.0",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^7.5|^8.5|^9.6",
"rector/rector": "^2.0",
"vimeo/psalm": "^4.3 || ^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Jean85\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alessandro Lai",
"email": "alessandro.lai85@gmail.com"
}
],
"description": "A library to get pretty versions strings of installed dependencies",
"keywords": [
"composer",
"package",
"release",
"versions"
],
"support": {
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1"
},
"time": "2025-03-19T14:43:43+00:00"
},
{
"name": "kelunik/certificate",
"version": "v1.1.3",
-4
View File
@@ -105,10 +105,6 @@ return [
'verification_code_expiry_minutes' => 10,
],
'sentry' => [
'sentry_dsn' => env('SENTRY_DSN'),
],
'sentinel' => [
'dev_url' => env('DEV_SENTINEL_URL'),
// How often (seconds) PushServerUpdateJob is force-dispatched even when
-85
View File
@@ -1,85 +0,0 @@
<?php
return [
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
'dsn' => config('constants.sentry.sentry_dsn'),
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => config('constants.coolify.version'),
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),
'breadcrumbs' => [
// Capture Laravel logs in breadcrumbs
'logs' => true,
// Capture Laravel cache events in breadcrumbs
'cache' => true,
// Capture Livewire components in breadcrumbs
'livewire' => true,
// Capture SQL queries in breadcrumbs
'sql_queries' => true,
// Capture bindings on SQL queries logged in breadcrumbs
'sql_bindings' => true,
// Capture queue job information in breadcrumbs
'queue_info' => true,
// Capture command information in breadcrumbs
'command_info' => true,
// Capture HTTP client requests information in breadcrumbs
'http_client_requests' => true,
],
'tracing' => [
// Trace queue jobs as their own transactions
'queue_job_transactions' => env('SENTRY_TRACE_QUEUE_ENABLED', false),
// Capture queue jobs as spans when executed on the sync driver
'queue_jobs' => true,
// Capture SQL queries as spans
'sql_queries' => true,
// Try to find out where the SQL query originated from and add it to the query spans
'sql_origin' => true,
// Capture views as spans
'views' => true,
// Capture Livewire components as spans
'livewire' => true,
// Capture HTTP client requests as spans
'http_client_requests' => true,
// Capture Redis operations as spans (this enables Redis events in Laravel)
'redis_commands' => env('SENTRY_TRACE_REDIS_COMMANDS', false),
// Try to find out where the Redis command originated from and add it to the command spans
'redis_origin' => true,
// Indicates if the tracing integrations supplied by Sentry should be loaded
'default_integrations' => true,
// Indicates that requests without a matching route should be traced
'missing_routes' => false,
],
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#send-default-pii
'send_default_pii' => env('SENTRY_SEND_DEFAULT_PII', false),
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#traces-sample-rate
'enable_tracing' => env('SENTRY_ENABLE_TRACING', false),
'traces_sample_rate' => 0.2,
'profiles_sample_rate' => env('SENTRY_PROFILES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_PROFILES_SAMPLE_RATE'),
];
@@ -0,0 +1,88 @@
<?php
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Encrypt secret columns that were stored as plaintext, to match their new `encrypted` casts.
* Values that are already encrypted are skipped, so the migration is safe to run again.
*/
public function up(): void
{
// Ciphertext does not fit in the original varchar(255) column.
Schema::table('gitlab_apps', function (Blueprint $table) {
$table->text('app_secret')->nullable()->change();
});
$this->encryptColumns('github_apps', ['client_secret', 'webhook_secret']);
$this->encryptColumns('gitlab_apps', ['app_secret']);
$this->encryptColumns('standalone_mariadbs', ['mariadb_root_password']);
// The old accessor stored encrypt($value), which serializes the value before encryption.
$this->encryptColumns('standalone_mongodbs', ['mongo_initdb_root_password'], legacySerialized: true);
$this->encryptColumns('server_settings', ['logdrain_custom_config']);
$this->encryptColumns('team_invitations', ['link']);
}
/**
* @param array<int, string> $columns
*/
private function encryptColumns(string $table, array $columns, bool $legacySerialized = false): void
{
try {
DB::table($table)->select(['id', ...$columns])->chunkById(100, function ($rows) use ($table, $columns, $legacySerialized) {
foreach ($rows as $row) {
$updates = [];
foreach ($columns as $column) {
$value = $row->{$column};
if (blank($value)) {
continue;
}
$plaintext = $this->plaintextToEncrypt($value, $legacySerialized);
if ($plaintext !== null) {
$updates[$column] = Crypt::encryptString($plaintext);
}
}
if ($updates !== []) {
DB::table($table)->where('id', $row->id)->update($updates);
}
}
});
} catch (Exception $e) {
echo "Encrypting secrets in {$table} failed.";
echo $e->getMessage();
}
}
/**
* Return the plaintext that still needs encryption, or null when the value is already stored as encryptString() output.
*/
private function plaintextToEncrypt(string $value, bool $legacySerialized): ?string
{
try {
$decrypted = Crypt::decryptString($value);
} catch (DecryptException) {
return $value;
}
if ($legacySerialized) {
$unserialized = @unserialize($decrypted, ['allowed_classes' => false]);
if (is_string($unserialized) && serialize($unserialized) === $decrypted) {
return $unserialized;
}
}
return null;
}
};
-1
View File
@@ -191,7 +191,6 @@
</style>
@if (config('app.name') == 'Coolify Cloud')
<script defer data-domain="app.coolify.io" src="https://analytics.coollabs.io/js/plausible.js"></script>
<script src="https://js.sentry-cdn.com/0f8593910512b5cdd48c6da78d4093be.min.js" crossorigin="anonymous"></script>
@endif
@auth
<script type="text/javascript" src="{{ URL::asset('js/echo.js') }}"></script>
@@ -0,0 +1,123 @@
<?php
use App\Models\Environment;
use App\Models\GithubApp;
use App\Models\GitlabApp;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\Team;
use App\Models\TeamInvitation;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
uses(RefreshDatabase::class);
beforeEach(function () {
$team = Team::factory()->create();
$server = Server::factory()->create(['team_id' => $team->id]);
$destination = $server->standaloneDockers()->firstOrFail();
$environment = Environment::factory()->create([
'project_id' => Project::factory()->create(['team_id' => $team->id])->id,
]);
$databaseAttributes = [
'destination_type' => $destination->getMorphClass(),
'destination_id' => $destination->id,
'environment_id' => $environment->id,
];
$gitAppAttributes = [
'html_url' => 'https://example.com',
'custom_user' => 'git',
'custom_port' => 22,
'team_id' => $team->id,
'is_system_wide' => false,
];
$githubApp = GithubApp::create([
...$gitAppAttributes,
'name' => 'github',
'api_url' => 'https://api.github.com',
'client_secret' => 'github-client-secret',
'webhook_secret' => 'github-webhook-secret',
]);
$gitlabApp = GitlabApp::create([
...$gitAppAttributes,
'name' => 'gitlab',
'api_url' => 'https://gitlab.com/api/v4',
'app_secret' => str_repeat('g', 64),
]);
$mariadb = StandaloneMariadb::create([
...$databaseAttributes,
'name' => 'mariadb',
'mariadb_root_password' => 'mariadb-root-password',
'mariadb_password' => 'mariadb-password',
]);
$mongodb = StandaloneMongodb::create([
...$databaseAttributes,
'name' => 'mongodb',
'mongo_initdb_root_password' => 'mongo-root-password',
]);
$server->settings->update(['logdrain_custom_config' => "[OUTPUT]\n Name http\n http_Passwd secret"]);
$invitation = TeamInvitation::create([
'team_id' => $team->id,
'uuid' => 'invite-uuid',
'email' => 'new@example.com',
'role' => 'member',
'link' => 'https://coolify.test/auth/link?token=secret-token',
'via' => 'link',
]);
$this->secrets = [
['github_apps', $githubApp, 'client_secret', 'github-client-secret'],
['github_apps', $githubApp, 'webhook_secret', 'github-webhook-secret'],
['gitlab_apps', $gitlabApp, 'app_secret', str_repeat('g', 64)],
['standalone_mariadbs', $mariadb, 'mariadb_root_password', 'mariadb-root-password'],
['standalone_mongodbs', $mongodb, 'mongo_initdb_root_password', 'mongo-root-password'],
['server_settings', $server->settings, 'logdrain_custom_config', "[OUTPUT]\n Name http\n http_Passwd secret"],
['team_invitations', $invitation, 'link', 'https://coolify.test/auth/link?token=secret-token'],
];
});
function rawSecret(string $table, $model, string $column): ?string
{
return DB::table($table)->where('id', $model->id)->value($column);
}
it('encrypts secrets at rest when they are saved through the model', function () {
foreach ($this->secrets as [$table, $model, $column, $plaintext]) {
expect(Crypt::decryptString(rawSecret($table, $model, $column)))->toBe($plaintext, "{$table}.{$column}")
->and($model->fresh()->{$column})->toBe($plaintext, "{$table}.{$column}");
}
});
it('stores the GitLab app secret in a text column so the ciphertext fits', function () {
expect(Schema::getColumnType('gitlab_apps', 'app_secret'))->toBe('text');
});
it('migrates plaintext and legacy serialized secrets without encrypting them twice', function () {
foreach ($this->secrets as [$table, $model, $column, $plaintext]) {
$legacyValue = $column === 'mongo_initdb_root_password' ? encrypt($plaintext) : $plaintext;
DB::table($table)->where('id', $model->id)->update([$column => $legacyValue]);
}
$emptyGithubApp = GithubApp::create([
'name' => 'empty',
'api_url' => 'https://api.github.com',
'html_url' => 'https://github.com',
'team_id' => $this->secrets[0][1]->team_id,
'is_system_wide' => false,
]);
$migration = require database_path('migrations/2026_10_19_083521_encrypt_plaintext_secrets.php');
$migration->up();
$migration->up();
foreach ($this->secrets as [$table, $model, $column, $plaintext]) {
expect(Crypt::decryptString(rawSecret($table, $model, $column)))->toBe($plaintext, "{$table}.{$column}")
->and($model->fresh()->{$column})->toBe($plaintext, "{$table}.{$column}");
}
expect(rawSecret('github_apps', $emptyGithubApp, 'client_secret'))->toBeNull()
->and(rawSecret('github_apps', $emptyGithubApp, 'webhook_secret'))->toBeNull();
});