Merge remote-tracking branch 'origin/next' into jean/port-exposes-improvement

This commit is contained in:
Andras Bacsai
2026-06-03 10:32:57 +02:00
766 changed files with 43286 additions and 12686 deletions
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->integer('stop_grace_period')
->nullable()
->after('use_build_secrets')
->comment('Seconds to wait for graceful shutdown before forcing container stop (1-3600). Null uses default of 30 seconds.');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('stop_grace_period');
});
}
};
@@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::transaction(function () {
if (DB::getDriverName() !== 'sqlite') {
DB::statement('ALTER TABLE shared_environment_variables DROP CONSTRAINT IF EXISTS shared_environment_variables_type_check');
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment', 'server'))");
}
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->foreignId('server_id')->nullable()->constrained()->onDelete('cascade');
// NULL != NULL in PostgreSQL unique indexes, so this only enforces uniqueness
// for server-scoped rows (where server_id is non-null). Other scopes are covered
// by existing unique constraints on ['key', 'project_id', 'team_id'] and ['key', 'environment_id', 'team_id'].
$table->unique(['key', 'server_id', 'team_id']);
});
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
DB::transaction(function () {
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->dropUnique(['key', 'server_id', 'team_id']);
$table->dropForeign(['server_id']);
$table->dropColumn('server_id');
});
if (DB::getDriverName() !== 'sqlite') {
DB::statement('ALTER TABLE shared_environment_variables DROP CONSTRAINT IF EXISTS shared_environment_variables_type_check');
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment'))");
}
});
}
};
@@ -0,0 +1,56 @@
<?php
use App\Models\Server;
use App\Models\SharedEnvironmentVariable;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Server::query()->whereHas('team')->chunk(100, function ($servers) {
foreach ($servers as $server) {
$existingKeys = SharedEnvironmentVariable::where('type', 'server')
->where('server_id', $server->id)
->whereIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])
->pluck('key')
->toArray();
if (! in_array('COOLIFY_SERVER_UUID', $existingKeys)) {
SharedEnvironmentVariable::create([
'key' => 'COOLIFY_SERVER_UUID',
'value' => $server->uuid,
'type' => 'server',
'server_id' => $server->id,
'team_id' => $server->team_id,
'is_literal' => true,
]);
}
if (! in_array('COOLIFY_SERVER_NAME', $existingKeys)) {
SharedEnvironmentVariable::create([
'key' => 'COOLIFY_SERVER_NAME',
'value' => $server->name,
'type' => 'server',
'server_id' => $server->id,
'team_id' => $server->team_id,
'is_literal' => true,
]);
}
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
SharedEnvironmentVariable::where('type', 'server')
->whereIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])
->delete();
}
};
@@ -10,14 +10,15 @@ return new class extends Migration
{
public function up(): void
{
Schema::table('local_persistent_volumes', function (Blueprint $table) {
$table->string('uuid')->nullable()->after('id');
});
if (! Schema::hasColumn('local_persistent_volumes', 'uuid')) {
Schema::table('local_persistent_volumes', function (Blueprint $table) {
$table->string('uuid')->nullable()->after('id');
});
}
DB::table('local_persistent_volumes')
->whereNull('uuid')
->orderBy('id')
->chunk(1000, function ($volumes) {
->chunkById(1000, function ($volumes) {
foreach ($volumes as $volume) {
DB::table('local_persistent_volumes')
->where('id', $volume->id)
@@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
class EncryptExistingClickhouseAdminPasswords extends Migration
{
public function up(): void
{
try {
DB::table('standalone_clickhouses')->chunkById(100, function ($clickhouses) {
foreach ($clickhouses as $clickhouse) {
$password = $clickhouse->clickhouse_admin_password;
if (empty($password)) {
continue;
}
// Skip if already encrypted (idempotent)
try {
Crypt::decryptString($password);
continue;
} catch (Exception) {
// Not encrypted yet — encrypt it
}
DB::table('standalone_clickhouses')
->where('id', $clickhouse->id)
->update(['clickhouse_admin_password' => Crypt::encryptString($password)]);
}
});
} catch (Exception $e) {
echo 'Encrypting ClickHouse admin passwords failed.';
echo $e->getMessage();
}
}
}
@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('application_previews', function (Blueprint $table) {
$table->string('docker_registry_image_tag')->nullable()->after('docker_compose_domains');
});
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->string('docker_registry_image_tag')->nullable()->after('pull_request_id');
});
}
public function down(): void
{
Schema::table('application_previews', function (Blueprint $table) {
$table->dropColumn('docker_registry_image_tag');
});
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->dropColumn('docker_registry_image_tag');
});
}
};
@@ -0,0 +1,59 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
class BackfillAndEncryptWebhookSecrets extends Migration
{
public function up(): void
{
$columns = [
'manual_webhook_secret_github',
'manual_webhook_secret_gitlab',
'manual_webhook_secret_bitbucket',
'manual_webhook_secret_gitea',
];
Schema::table('applications', function ($table) use ($columns) {
foreach ($columns as $col) {
$table->text($col)->nullable()->change();
}
});
try {
DB::table('applications')->chunkById(100, function ($apps) use ($columns) {
foreach ($apps as $app) {
$updates = [];
foreach ($columns as $col) {
$current = $app->{$col};
if (empty($current)) {
$updates[$col] = Crypt::encryptString(Str::random(40));
continue;
}
try {
Crypt::decryptString($current);
continue;
} catch (Exception) {
// Not encrypted yet
}
$updates[$col] = Crypt::encryptString($current);
}
if ($updates !== []) {
DB::table('applications')->where('id', $app->id)->update($updates);
}
}
});
} catch (Exception $e) {
echo 'Backfilling and encrypting webhook secrets failed.';
echo $e->getMessage();
}
}
}
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('instance_settings', function (Blueprint $table) {
$table->boolean('is_mcp_server_enabled')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('instance_settings', function (Blueprint $table) {
$table->dropColumn('is_mcp_server_enabled');
});
}
};
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('server_settings', function (Blueprint $table) {
$table->integer('connection_timeout')->default(10)->after('deployment_queue_limit');
});
}
public function down(): void
{
Schema::table('server_settings', function (Blueprint $table) {
$table->dropColumn('connection_timeout');
});
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->string('configuration_hash')->nullable()->after('docker_registry_image_tag');
$table->json('configuration_snapshot')->nullable()->after('configuration_hash');
$table->json('configuration_diff')->nullable()->after('configuration_snapshot');
});
}
public function down(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->dropColumn([
'configuration_hash',
'configuration_snapshot',
'configuration_diff',
]);
});
}
};
@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('personal_access_tokens', function (Blueprint $table) {
$table->timestamp('api_token_expiration_warning_sent_at')->nullable()->after('expires_at');
$table->index(['expires_at', 'api_token_expiration_warning_sent_at'], 'personal_access_tokens_expiration_warning_index');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('personal_access_tokens', function (Blueprint $table) {
$table->dropIndex('personal_access_tokens_expiration_warning_index');
$table->dropColumn('api_token_expiration_warning_sent_at');
});
}
};
@@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
DB::statement('CREATE INDEX IF NOT EXISTS swarm_dockers_server_id_index ON swarm_dockers (server_id)');
DB::statement('CREATE INDEX IF NOT EXISTS services_server_id_index ON services (server_id)');
DB::statement('CREATE INDEX IF NOT EXISTS application_previews_application_id_index ON application_previews (application_id)');
DB::statement('CREATE INDEX IF NOT EXISTS service_applications_service_id_index ON service_applications (service_id)');
DB::statement('CREATE INDEX IF NOT EXISTS service_databases_service_id_index ON service_databases (service_id)');
DB::statement('CREATE INDEX IF NOT EXISTS servers_sentinel_updated_at_index ON servers (sentinel_updated_at)');
}
public function down(): void
{
DB::statement('DROP INDEX IF EXISTS swarm_dockers_server_id_index');
DB::statement('DROP INDEX IF EXISTS services_server_id_index');
DB::statement('DROP INDEX IF EXISTS application_previews_application_id_index');
DB::statement('DROP INDEX IF EXISTS service_applications_service_id_index');
DB::statement('DROP INDEX IF EXISTS service_databases_service_id_index');
DB::statement('DROP INDEX IF EXISTS servers_sentinel_updated_at_index');
}
};
@@ -0,0 +1,66 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
if (DB::connection()->getDriverName() !== 'pgsql') {
return;
}
// Fillfactor < 100 leaves free space per page so Postgres can do HOT
// (Heap-Only Tuple) in-place updates instead of allocating a new tuple
// elsewhere. Coolify's hot-update tables churn rows on every Sentinel
// push / status change; without page-local headroom, non-HOT updates
// accumulate dead tuples and bloat the heap (we've seen up to 50× on
// cloud). Lower fillfactor on hot-update tables, default on the rest.
DB::statement('ALTER TABLE applications SET (fillfactor = 70)');
DB::statement('ALTER TABLE servers SET (fillfactor = 85)');
DB::statement('ALTER TABLE services SET (fillfactor = 85)');
DB::statement('ALTER TABLE service_applications SET (fillfactor = 85)');
DB::statement('ALTER TABLE service_databases SET (fillfactor = 85)');
DB::statement('ALTER TABLE standalone_postgresqls SET (fillfactor = 85)');
DB::statement('ALTER TABLE standalone_redis SET (fillfactor = 85)');
DB::statement('ALTER TABLE standalone_mongodbs SET (fillfactor = 85)');
DB::statement('ALTER TABLE standalone_mysqls SET (fillfactor = 85)');
DB::statement('ALTER TABLE standalone_mariadbs SET (fillfactor = 85)');
DB::statement('ALTER TABLE standalone_keydbs SET (fillfactor = 85)');
DB::statement('ALTER TABLE standalone_dragonflies SET (fillfactor = 85)');
DB::statement('ALTER TABLE standalone_clickhouses SET (fillfactor = 85)');
DB::statement('ALTER TABLE application_deployment_queues SET (fillfactor = 90)');
// Autovacuum default kicks in at 20% dead tuples — too lazy for our
// churn rate. Trigger at 5% on the highest-write tables to keep heap
// pages tidy and prevent visibility-map gaps that hurt scan plans.
DB::statement('ALTER TABLE applications SET (autovacuum_vacuum_scale_factor = 0.05)');
DB::statement('ALTER TABLE servers SET (autovacuum_vacuum_scale_factor = 0.05)');
DB::statement('ALTER TABLE service_applications SET (autovacuum_vacuum_scale_factor = 0.05)');
DB::statement('ALTER TABLE service_databases SET (autovacuum_vacuum_scale_factor = 0.05)');
DB::statement('ALTER TABLE standalone_postgresqls SET (autovacuum_vacuum_scale_factor = 0.05)');
}
public function down(): void
{
if (DB::connection()->getDriverName() !== 'pgsql') {
return;
}
DB::statement('ALTER TABLE applications RESET (fillfactor, autovacuum_vacuum_scale_factor)');
DB::statement('ALTER TABLE servers RESET (fillfactor, autovacuum_vacuum_scale_factor)');
DB::statement('ALTER TABLE services RESET (fillfactor)');
DB::statement('ALTER TABLE service_applications RESET (fillfactor, autovacuum_vacuum_scale_factor)');
DB::statement('ALTER TABLE service_databases RESET (fillfactor, autovacuum_vacuum_scale_factor)');
DB::statement('ALTER TABLE standalone_postgresqls RESET (fillfactor, autovacuum_vacuum_scale_factor)');
DB::statement('ALTER TABLE standalone_redis RESET (fillfactor)');
DB::statement('ALTER TABLE standalone_mongodbs RESET (fillfactor)');
DB::statement('ALTER TABLE standalone_mysqls RESET (fillfactor)');
DB::statement('ALTER TABLE standalone_mariadbs RESET (fillfactor)');
DB::statement('ALTER TABLE standalone_keydbs RESET (fillfactor)');
DB::statement('ALTER TABLE standalone_dragonflies RESET (fillfactor)');
DB::statement('ALTER TABLE standalone_clickhouses RESET (fillfactor)');
DB::statement('ALTER TABLE application_deployment_queues RESET (fillfactor)');
}
};
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* The configuration snapshot/diff now store an encrypted blob (not valid
* JSON), so the columns must hold arbitrary text instead of json.
*/
public function up(): void
{
DB::statement('ALTER TABLE application_deployment_queues ALTER COLUMN configuration_snapshot TYPE text USING configuration_snapshot::text');
DB::statement('ALTER TABLE application_deployment_queues ALTER COLUMN configuration_diff TYPE text USING configuration_diff::text');
}
public function down(): void
{
DB::statement('ALTER TABLE application_deployment_queues ALTER COLUMN configuration_snapshot TYPE json USING configuration_snapshot::json');
DB::statement('ALTER TABLE application_deployment_queues ALTER COLUMN configuration_diff TYPE json USING configuration_diff::json');
}
};
@@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
private array $tables = [
'standalone_postgresqls',
'standalone_mysqls',
'standalone_mariadbs',
'standalone_redis',
'standalone_clickhouses',
'standalone_dragonflies',
'standalone_keydbs',
'standalone_mongodbs',
];
public function up(): void
{
foreach ($this->tables as $table) {
Schema::table($table, function (Blueprint $table) {
$table->boolean('health_check_enabled')->default(true);
$table->integer('health_check_interval')->default(15);
$table->integer('health_check_timeout')->default(5);
$table->integer('health_check_retries')->default(5);
$table->integer('health_check_start_period')->default(5);
});
}
}
public function down(): void
{
foreach ($this->tables as $table) {
Schema::table($table, function (Blueprint $table) {
$table->dropColumn([
'health_check_enabled',
'health_check_interval',
'health_check_timeout',
'health_check_retries',
'health_check_start_period',
]);
});
}
}
};