From 8e61ace98d9b11d70b9f403469e3f69f9e6791e0 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:11:15 +0200 Subject: [PATCH] feat(database): add sqlite start, stop, restart and delete support --- app/Actions/Database/RestartDatabase.php | 3 +- app/Actions/Database/StartDatabase.php | 3 +- app/Actions/Database/StartSqlite.php | 158 +++++++++++++++++++++++ app/Actions/Database/StopDatabase.php | 3 +- app/Jobs/DatabaseStartJob.php | 3 + app/Jobs/DeleteResourceJob.php | 7 +- app/Jobs/PushServerUpdateJob.php | 2 + 7 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 app/Actions/Database/StartSqlite.php diff --git a/app/Actions/Database/RestartDatabase.php b/app/Actions/Database/RestartDatabase.php index 940bc69fb7..a44a9bfd14 100644 --- a/app/Actions/Database/RestartDatabase.php +++ b/app/Actions/Database/RestartDatabase.php @@ -10,13 +10,14 @@ use App\Models\StandaloneMongodb; use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; +use App\Models\StandaloneSqlite; use Lorisleiva\Actions\Concerns\AsAction; class RestartDatabase { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database) + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|StandaloneSqlite $database) { $server = $database->destination->server; if (! $server->isFunctional()) { diff --git a/app/Actions/Database/StartDatabase.php b/app/Actions/Database/StartDatabase.php index cb1c517539..c416ce2565 100644 --- a/app/Actions/Database/StartDatabase.php +++ b/app/Actions/Database/StartDatabase.php @@ -13,6 +13,7 @@ use App\Models\StandaloneMongodb; use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; +use App\Models\StandaloneSqlite; use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Decorators\JobDecorator; use Spatie\Activitylog\Models\Activity; @@ -26,7 +27,7 @@ class StartDatabase $job->onQueue(deployment_queue()); } - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database): Activity|string + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|StandaloneSqlite $database): Activity|string { $server = $database->destination->server; if (! $server->isFunctional()) { diff --git a/app/Actions/Database/StartSqlite.php b/app/Actions/Database/StartSqlite.php new file mode 100644 index 0000000000..1e602c1d1a --- /dev/null +++ b/app/Actions/Database/StartSqlite.php @@ -0,0 +1,158 @@ +database = $database; + + $container_name = $this->database->uuid; + $this->configuration_dir = database_configuration_dir().'/'.$container_name; + + $this->commands = [ + "echo 'Starting database.'", + "mkdir -p $this->configuration_dir", + ]; + + $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->database->fileStorages()->get(); + $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); + $environment_variables = $this->generate_environment_variables(); + + $docker_compose = [ + 'services' => [ + $container_name => [ + 'image' => $this->database->image, + 'container_name' => $container_name, + 'environment' => $environment_variables, + 'restart' => RESTART_MODE, + 'networks' => [ + $this->database->destination->network, + ], + 'labels' => defaultDatabaseLabels($this->database)->toArray(), + 'healthcheck' => $this->database->healthCheckConfiguration(['CMD', 'healthcheck.sh']), + 'mem_limit' => $this->database->limits_memory, + 'memswap_limit' => $this->database->limits_memory_swap, + 'mem_swappiness' => $this->database->limits_memory_swappiness, + 'mem_reservation' => $this->database->limits_memory_reservation, + 'cpus' => (float) $this->database->limits_cpus, + 'cpu_shares' => $this->database->limits_cpu_shares, + ], + ], + 'networks' => [ + $this->database->destination->network => [ + 'external' => true, + 'name' => $this->database->destination->network, + 'attachable' => true, + ], + ], + ]; + if (! is_null($this->database->limits_cpuset)) { + data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); + } + if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { + $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration(); + } + if (count($persistent_storages) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray() + ); + } + if (count($volume_names) > 0) { + $docker_compose['volumes'] = $volume_names; + } + + // Add custom docker run options + $docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options); + $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + + if (! $this->database->isHealthcheckEnabled()) { + unset($docker_compose['services'][$container_name]['healthcheck']); + } + $docker_compose = Yaml::dump($docker_compose, 10); + $docker_compose_base64 = base64_encode($docker_compose); + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; + $readme = generate_readme_file($this->database->name, now()); + $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; + $this->commands[] = 'echo '.escapeshellarg("Pulling {$database->image} image."); + $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; + $this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true'; + $this->commands[] = "docker rm -f $container_name 2>/dev/null || true"; + $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + $this->commands[] = "echo 'Database started.'"; + + return $this->executeDatabaseStartCommands($this->commands, $database, $activity); + } + + private function generate_local_persistent_volumes() + { + $local_persistent_volumes = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { + $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; + } else { + $volume_name = $persistentStorage->name; + $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; + } + } + + return $local_persistent_volumes; + } + + private function generate_local_persistent_volumes_only_volume_names() + { + $local_persistent_volumes_names = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + if ($persistentStorage->host_path) { + continue; + } + $name = $persistentStorage->name; + $local_persistent_volumes_names[$name] = [ + 'name' => $name, + 'external' => false, + ]; + } + + return $local_persistent_volumes_names; + } + + private function generate_environment_variables() + { + $environment_variables = collect(); + foreach ($this->database->runtime_environment_variables as $env) { + $rawValue = (string) $this->database->resolveSecretManagerEnvironmentVariableValue($env); + $resolvedValue = (string) $this->database->formatEnvironmentVariableValue($env, $rawValue); + $environment_variables->push($env->key.'='.$resolvedValue); + } + + if ($environment_variables->filter(fn ($env) => str($env)->contains('SQLITE_DATABASES'))->isEmpty()) { + $environment_variables->push("SQLITE_DATABASES={$this->database->sqlite_databases}"); + } + + add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); + + return $environment_variables->all(); + } +} diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index d3c6fafc4d..e830678fb1 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -13,13 +13,14 @@ use App\Models\StandaloneMongodb; use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; +use App\Models\StandaloneSqlite; use Lorisleiva\Actions\Concerns\AsAction; class StopDatabase { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $dockerCleanup = true, bool $resetRestartCount = true, bool $removeContainer = true): string + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|StandaloneSqlite $database, bool $dockerCleanup = true, bool $resetRestartCount = true, bool $removeContainer = true): string { try { $server = $database->destination->server; diff --git a/app/Jobs/DatabaseStartJob.php b/app/Jobs/DatabaseStartJob.php index e21ee38c61..def6b5d470 100644 --- a/app/Jobs/DatabaseStartJob.php +++ b/app/Jobs/DatabaseStartJob.php @@ -10,6 +10,7 @@ use App\Actions\Database\StartMongodb; use App\Actions\Database\StartMysql; use App\Actions\Database\StartPostgresql; use App\Actions\Database\StartRedis; +use App\Actions\Database\StartSqlite; use App\Enums\ProcessStatus; use App\Events\DatabaseStatusChanged; use App\Models\StandaloneClickhouse; @@ -20,6 +21,7 @@ use App\Models\StandaloneMongodb; use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; +use App\Models\StandaloneSqlite; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueue; @@ -62,6 +64,7 @@ class DatabaseStartJob implements ShouldBeEncrypted, ShouldQueue StandaloneKeydb::class => StartKeydb::run($database, $activity), StandaloneDragonfly::class => StartDragonfly::run($database, $activity), StandaloneClickhouse::class => StartClickhouse::run($database, $activity), + StandaloneSqlite::class => StartSqlite::run($database, $activity), }; event(new DatabaseStatusChanged($this->userId)); diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 57b5aa6dfc..b957f0a388 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -20,6 +20,7 @@ use App\Models\StandaloneMongodb; use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; +use App\Models\StandaloneSqlite; use App\Notifications\Internal\GeneralNotification; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; @@ -35,7 +36,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function __construct( - public Application|ApplicationPreview|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, + public Application|ApplicationPreview|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|StandaloneSqlite $resource, public bool $deleteVolumes = true, public bool $deleteConnectedNetworks = true, public bool $deleteConfigurations = true, @@ -72,6 +73,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue case 'standalone-keydb': case 'standalone-dragonfly': case 'standalone-clickhouse': + case 'standalone-sqlite': StopDatabase::run($this->resource, dockerCleanup: $this->dockerCleanup); break; case 'service': @@ -164,7 +166,8 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue || $this->resource instanceof StandaloneMariadb || $this->resource instanceof StandaloneKeydb || $this->resource instanceof StandaloneDragonfly - || $this->resource instanceof StandaloneClickhouse; + || $this->resource instanceof StandaloneClickhouse + || $this->resource instanceof StandaloneSqlite; } private function deleteScheduledVolumeBackups(): void diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index ef83d19446..bf08095c7c 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -25,6 +25,7 @@ use App\Models\StandaloneMongodb; use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; +use App\Models\StandaloneSqlite; use App\Models\SwarmDocker; use App\Notifications\Application\RestartLimitReached as ApplicationRestartLimitReached; use App\Notifications\Container\ContainerRestarted; @@ -516,6 +517,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue, Silenced StandaloneKeydb::class, StandaloneDragonfly::class, StandaloneClickhouse::class, + StandaloneSqlite::class, ])->flatMap(function (string $databaseClass) use ($databaseColumns, $standaloneDockerIds, $swarmDockerIds) { return $databaseClass::query() ->select($databaseColumns)