coolify/bootstrap/helpers/databases.php

380 lines
13 KiB
PHP
Raw Normal View History

<?php
use App\Models\EnvironmentVariable;
use App\Models\S3Storage;
use App\Models\Server;
2024-04-10 13:00:46 +00:00
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDocker;
2024-04-10 13:00:46 +00:00
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
2023-10-24 12:31:28 +00:00
use App\Models\StandaloneMariadb;
2023-10-19 11:32:03 +00:00
use App\Models\StandaloneMongodb;
2023-10-24 12:31:28 +00:00
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
2023-10-12 15:18:33 +00:00
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Storage;
use Visus\Cuid2\Cuid2;
function generate_database_name(string $type): string
{
2025-01-13 15:43:23 +00:00
return $type.'-database-'.(new Cuid2);
}
function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null, string $databaseImage = 'postgres:16-alpine'): StandalonePostgresql
{
2025-01-13 15:43:23 +00:00
$destination = StandaloneDocker::where('uuid', $destinationUuid)->firstOrFail();
2024-07-24 19:11:12 +00:00
$database = new StandalonePostgresql;
2024-07-01 14:26:50 +00:00
$database->name = generate_database_name('postgresql');
$database->image = $databaseImage;
2024-07-01 14:26:50 +00:00
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environmentId;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
2024-06-10 20:43:34 +00:00
2024-07-01 14:26:50 +00:00
return $database;
}
2024-07-01 14:26:50 +00:00
function create_standalone_redis($environment_id, $destination_uuid, ?array $otherData = null): StandaloneRedis
2023-10-12 15:18:33 +00:00
{
2025-01-13 15:43:23 +00:00
$destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail();
2024-07-24 19:11:12 +00:00
$database = new StandaloneRedis;
2024-07-01 14:26:50 +00:00
$database->name = generate_database_name('redis');
$redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
2024-07-01 14:26:50 +00:00
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
2024-06-10 20:43:34 +00:00
EnvironmentVariable::create([
'key' => 'REDIS_PASSWORD',
'value' => $redis_password,
'standalone_redis_id' => $database->id,
'is_shared' => false,
]);
EnvironmentVariable::create([
'key' => 'REDIS_USERNAME',
2024-10-21 10:13:42 +00:00
'value' => 'default',
'standalone_redis_id' => $database->id,
'is_shared' => false,
]);
2024-07-01 14:26:50 +00:00
return $database;
2023-10-12 15:18:33 +00:00
}
2024-07-01 14:26:50 +00:00
function create_standalone_mongodb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMongodb
2023-10-19 11:32:03 +00:00
{
2025-01-13 15:43:23 +00:00
$destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail();
2024-07-24 19:11:12 +00:00
$database = new StandaloneMongodb;
2024-07-01 14:26:50 +00:00
$database->name = generate_database_name('mongodb');
$database->mongo_initdb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
2024-06-10 20:43:34 +00:00
2024-07-01 14:26:50 +00:00
return $database;
2023-10-19 11:32:03 +00:00
}
2025-01-13 15:43:23 +00:00
2024-07-01 14:26:50 +00:00
function create_standalone_mysql($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMysql
2023-10-24 12:31:28 +00:00
{
2025-01-13 15:43:23 +00:00
$destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail();
2024-07-24 19:11:12 +00:00
$database = new StandaloneMysql;
2024-07-01 14:26:50 +00:00
$database->name = generate_database_name('mysql');
$database->mysql_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->mysql_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
2024-06-10 20:43:34 +00:00
2024-07-01 14:26:50 +00:00
return $database;
2023-10-24 12:31:28 +00:00
}
2025-01-13 15:43:23 +00:00
2024-07-01 14:26:50 +00:00
function create_standalone_mariadb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMariadb
2023-10-24 12:31:28 +00:00
{
2025-01-13 15:43:23 +00:00
$destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail();
2024-07-24 19:11:12 +00:00
$database = new StandaloneMariadb;
2024-07-01 14:26:50 +00:00
$database->name = generate_database_name('mariadb');
$database->mariadb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->mariadb_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
2024-06-10 20:43:34 +00:00
2024-07-01 14:26:50 +00:00
return $database;
2023-10-24 12:31:28 +00:00
}
2025-01-13 15:43:23 +00:00
2024-07-01 14:26:50 +00:00
function create_standalone_keydb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneKeydb
2024-04-10 13:00:46 +00:00
{
2025-01-13 15:43:23 +00:00
$destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail();
2024-07-24 19:11:12 +00:00
$database = new StandaloneKeydb;
2024-07-01 14:26:50 +00:00
$database->name = generate_database_name('keydb');
$database->keydb_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
2024-06-10 20:43:34 +00:00
2024-07-01 14:26:50 +00:00
return $database;
2024-04-10 13:00:46 +00:00
}
2024-07-01 14:26:50 +00:00
function create_standalone_dragonfly($environment_id, $destination_uuid, ?array $otherData = null): StandaloneDragonfly
2024-04-10 13:00:46 +00:00
{
2025-01-13 15:43:23 +00:00
$destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail();
2024-07-24 19:11:12 +00:00
$database = new StandaloneDragonfly;
2024-07-01 14:26:50 +00:00
$database->name = generate_database_name('dragonfly');
$database->dragonfly_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
2024-06-10 20:43:34 +00:00
2024-07-01 14:26:50 +00:00
return $database;
2024-04-10 13:00:46 +00:00
}
2025-01-13 15:43:23 +00:00
2024-07-01 14:26:50 +00:00
function create_standalone_clickhouse($environment_id, $destination_uuid, ?array $otherData = null): StandaloneClickhouse
2024-04-10 13:00:46 +00:00
{
2025-01-13 15:43:23 +00:00
$destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail();
2024-07-24 19:11:12 +00:00
$database = new StandaloneClickhouse;
2024-07-01 14:26:50 +00:00
$database->name = generate_database_name('clickhouse');
$database->clickhouse_admin_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
2024-06-10 20:43:34 +00:00
2024-07-01 14:26:50 +00:00
return $database;
2024-04-10 13:00:46 +00:00
}
2023-10-19 11:32:03 +00:00
function deleteBackupsLocally(string|array|null $filenames, Server $server): void
{
if (empty($filenames)) {
return;
}
if (is_string($filenames)) {
$filenames = [$filenames];
}
2025-01-13 15:43:23 +00:00
$quotedFiles = array_map(fn ($file) => "\"$file\"", $filenames);
instant_remote_process(['rm -f '.implode(' ', $quotedFiles)], $server, throwError: false);
}
2025-01-13 15:43:23 +00:00
function deleteBackupsS3(string|array|null $filenames, S3Storage $s3): void
{
if (empty($filenames) || ! $s3) {
return;
}
if (is_string($filenames)) {
$filenames = [$filenames];
}
$disk = Storage::build([
'driver' => 's3',
'key' => $s3->key,
'secret' => $s3->secret,
'region' => $s3->region,
'bucket' => $s3->bucket,
'endpoint' => $s3->endpoint,
'use_path_style_endpoint' => true,
2025-01-13 15:43:23 +00:00
'bucket_endpoint' => $s3->isHetzner() || $s3->isDigitalOcean(),
'aws_url' => $s3->awsUrl(),
]);
$disk->delete($filenames);
}
function deleteEmptyBackupFolder($folderPath, Server $server): void
{
$escapedPath = escapeshellarg($folderPath);
$escapedParentPath = escapeshellarg(dirname($folderPath));
$checkEmpty = instant_remote_process(["[ -d $escapedPath ] && [ -z \"$(ls -A $escapedPath)\" ] && echo 'empty' || echo 'not empty'"], $server, throwError: false);
if (trim($checkEmpty) === 'empty') {
instant_remote_process(["rmdir $escapedPath"], $server, throwError: false);
2025-01-13 15:43:23 +00:00
$checkParentEmpty = instant_remote_process(["[ -d $escapedParentPath ] && [ -z \"$(ls -A $escapedParentPath)\" ] && echo 'empty' || echo 'not empty'"], $server, throwError: false);
if (trim($checkParentEmpty) === 'empty') {
instant_remote_process(["rmdir $escapedParentPath"], $server, throwError: false);
}
}
}
2025-01-13 15:43:23 +00:00
function deleteOldBackupsLocally($backup): void
{
if (! $backup || ! $backup->executions) {
return;
}
$successfulBackups = $backup->executions()
->where('status', 'success')
->orderBy('created_at', 'desc')
->get();
if ($successfulBackups->isEmpty()) {
return;
}
$retentionAmount = $backup->database_backup_retention_amount_locally;
$retentionDays = $backup->database_backup_retention_days_locally;
if ($retentionAmount === 0 && $retentionDays === 0) {
return;
}
$backupsToDelete = collect();
if ($retentionAmount > 0) {
2025-01-13 15:43:23 +00:00
$backupsToDelete = $backupsToDelete->merge($successfulBackups->skip($retentionAmount));
}
if ($retentionDays > 0) {
$oldestAllowedDate = $successfulBackups->first()->created_at->clone()->utc()->subDays($retentionDays);
2025-01-13 15:43:23 +00:00
$oldBackups = $successfulBackups->filter(fn ($execution) => $execution->created_at->utc() < $oldestAllowedDate);
$backupsToDelete = $backupsToDelete->merge($oldBackups);
}
$backupsToDelete = $backupsToDelete->unique('id');
$foldersToCheck = collect();
$backupsToDelete->chunk(10)->each(function ($chunk) use ($backup, &$foldersToCheck) {
$executionIds = [];
$filesToDelete = [];
foreach ($chunk as $execution) {
if ($execution->filename) {
$filesToDelete[] = $execution->filename;
$executionIds[] = $execution->id;
$foldersToCheck->push(dirname($execution->filename));
}
}
if (! empty($filesToDelete)) {
deleteBackupsLocally($filesToDelete, $backup->server);
if (! empty($executionIds)) {
$backup->executions()->whereIn('id', $executionIds)->delete();
}
}
});
2025-01-13 15:43:23 +00:00
$foldersToCheck->unique()->each(fn ($folder) => deleteEmptyBackupFolder($folder, $backup->server));
}
2025-01-13 15:43:23 +00:00
function deleteOldBackupsFromS3($backup): void
{
if (! $backup || ! $backup->executions || ! $backup->s3) {
return;
}
$successfulBackups = $backup->executions()
->where('status', 'success')
->orderBy('created_at', 'desc')
->get();
if ($successfulBackups->isEmpty()) {
return;
}
$retentionAmount = $backup->database_backup_retention_amount_s3;
$retentionDays = $backup->database_backup_retention_days_s3;
$maxStorageGB = $backup->database_backup_retention_max_storage_s3;
if ($retentionAmount === 0 && $retentionDays === 0 && $maxStorageGB === 0) {
return;
}
$backupsToDelete = collect();
if ($retentionAmount > 0) {
2025-01-13 15:43:23 +00:00
$backupsToDelete = $backupsToDelete->merge($successfulBackups->skip($retentionAmount));
}
if ($retentionDays > 0) {
$oldestAllowedDate = $successfulBackups->first()->created_at->clone()->utc()->subDays($retentionDays);
2025-01-13 15:43:23 +00:00
$oldBackups = $successfulBackups->filter(fn ($execution) => $execution->created_at->utc() < $oldestAllowedDate);
$backupsToDelete = $backupsToDelete->merge($oldBackups);
}
if ($maxStorageGB > 0) {
2025-01-13 15:43:23 +00:00
$maxStorageBytes = $maxStorageGB * 1024 * 1024 * 1024;
$totalSize = 0;
$backupsOverLimit = collect();
foreach ($successfulBackups as $backup) {
$totalSize += (int) $backup->size;
if ($totalSize > $maxStorageBytes) {
2025-01-13 15:43:23 +00:00
$backupsOverLimit = $successfulBackups->filter(fn ($b) => $b->created_at->utc() <= $backup->created_at->utc());
break;
}
}
$backupsToDelete = $backupsToDelete->merge($backupsOverLimit);
}
$backupsToDelete = $backupsToDelete->unique('id');
$foldersToCheck = collect();
$backupsToDelete->chunk(10)->each(function ($chunk) use ($backup, &$foldersToCheck) {
$executionIds = [];
$filesToDelete = [];
foreach ($chunk as $execution) {
if ($execution->filename) {
$filesToDelete[] = $execution->filename;
$executionIds[] = $execution->id;
$foldersToCheck->push(dirname($execution->filename));
}
}
if (! empty($filesToDelete)) {
2025-01-13 15:54:17 +00:00
deleteBackupsS3($filesToDelete, $backup->s3);
if (! empty($executionIds)) {
$backup->executions()
->whereIn('id', $executionIds)
->update(['s3_backup_deleted_at' => now()]);
}
}
});
2025-01-13 15:43:23 +00:00
$foldersToCheck->unique()->each(fn ($folder) => deleteEmptyBackupFolder($folder, $backup->server));
}
function isPublicPortAlreadyUsed(Server $server, int $port, ?string $id = null): bool
{
if ($id) {
$foundDatabase = $server->databases()->where('public_port', $port)->where('is_public', true)->where('id', '!=', $id)->first();
} else {
$foundDatabase = $server->databases()->where('public_port', $port)->where('is_public', true)->first();
}
if ($foundDatabase) {
return true;
}
return false;
}