2023-08-07 16:46:40 +00:00
< ? php
namespace App\Actions\Database ;
2025-01-29 12:30:45 +00:00
use App\Helpers\SslHelper ;
use App\Models\SslCertificate ;
2023-08-07 20:14:21 +00:00
use App\Models\StandalonePostgresql ;
2023-10-14 12:22:07 +00:00
use Lorisleiva\Actions\Concerns\AsAction ;
2024-06-10 20:43:34 +00:00
use Symfony\Component\Yaml\Yaml ;
2023-08-07 16:46:40 +00:00
class StartPostgresql
{
2023-10-14 12:22:07 +00:00
use AsAction ;
2023-08-08 12:35:01 +00:00
public StandalonePostgresql $database ;
2024-06-10 20:43:34 +00:00
2023-08-08 12:35:01 +00:00
public array $commands = [];
2024-06-10 20:43:34 +00:00
2023-08-08 12:35:01 +00:00
public array $init_scripts = [];
2024-06-10 20:43:34 +00:00
2023-08-09 12:44:36 +00:00
public string $configuration_dir ;
2023-08-08 09:51:36 +00:00
2025-01-29 12:30:45 +00:00
private ? SslCertificate $ssl_certificate = null ;
2025-01-07 14:31:43 +00:00
public function handle ( StandalonePostgresql $database )
2023-08-07 16:46:40 +00:00
{
2025-01-07 14:31:43 +00:00
$this -> database = $database ;
2023-08-21 16:00:12 +00:00
$container_name = $this -> database -> uuid ;
2024-08-27 18:47:17 +00:00
$this -> configuration_dir = database_configuration_dir () . '/' . $container_name ;
2024-12-18 09:44:56 +00:00
if ( isDev ()) {
$this -> configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/' . $container_name ;
}
2023-08-08 12:35:01 +00:00
$this -> commands = [
2024-12-03 13:09:51 +00:00
" echo 'Starting database.' " ,
2025-01-31 12:56:20 +00:00
" echo 'Creating directories.' " ,
2023-08-09 12:44:36 +00:00
" mkdir -p $this->configuration_dir " ,
2024-06-10 20:43:34 +00:00
" mkdir -p $this->configuration_dir /docker-entrypoint-initdb.d/ " ,
2025-01-29 12:30:45 +00:00
" mkdir -p $this->configuration_dir /ssl " ,
2025-01-31 12:56:20 +00:00
" echo 'Directories created successfully.' " ,
2023-08-08 12:35:01 +00:00
];
2023-08-07 20:27:20 +00:00
2025-01-29 12:30:45 +00:00
if ( $this -> database -> enable_ssl ) {
2025-01-31 12:56:20 +00:00
$this -> commands [] = " echo 'Setting up SSL for this database.' " ;
$server = $this -> database -> destination -> server ;
$caCert = SslCertificate :: where ( 'server_id' , $server -> id ) -> firstOrFail ();
$this -> ssl_certificate = SslCertificate :: where ( 'resource_type' , $this -> database -> getMorphClass ()) -> where ( 'resource_id' , $this -> database -> id ) -> first ();
2025-01-29 12:30:45 +00:00
if ( ! $this -> ssl_certificate ) {
2025-01-31 12:56:20 +00:00
$this -> commands [] = " echo 'No SSL certificate found, generating new SSL certificate for this database.' " ;
2025-01-29 12:30:45 +00:00
$this -> ssl_certificate = SslHelper :: generateSslCertificate (
2025-01-31 12:56:20 +00:00
commonName : $this -> database -> uuid ,
// additionalSans: ["IP:{$server->ip_address}"], // Issue is the server IP can be also be a domain/ hostname and we need to be sure what it is before setting it.
2025-01-29 12:30:45 +00:00
resourceType : $this -> database -> getMorphClass (),
resourceId : $this -> database -> id ,
2025-01-31 12:56:20 +00:00
caCert : $caCert -> ssl_certificate ,
caKey : $caCert -> ssl_private_key ,
2025-01-29 12:30:45 +00:00
);
$this -> addSslFilesToFileStorage ();
}
}
2023-08-07 20:27:20 +00:00
$persistent_storages = $this -> generate_local_persistent_volumes ();
2024-05-27 12:14:44 +00:00
$persistent_file_volumes = $this -> database -> fileStorages () -> get ();
2023-08-07 20:27:20 +00:00
$volume_names = $this -> generate_local_persistent_volumes_only_volume_names ();
$environment_variables = $this -> generate_environment_variables ();
2023-08-08 12:35:01 +00:00
$this -> generate_init_scripts ();
2023-11-08 11:40:05 +00:00
$this -> add_custom_conf ();
2023-08-07 20:14:21 +00:00
$docker_compose = [
'services' => [
$container_name => [
2023-08-08 12:35:01 +00:00
'image' => $this -> database -> image ,
2023-08-07 20:14:21 +00:00
'container_name' => $container_name ,
2023-08-07 20:27:20 +00:00
'environment' => $environment_variables ,
2023-08-21 16:00:12 +00:00
'restart' => RESTART_MODE ,
2023-08-07 20:14:21 +00:00
'networks' => [
2023-08-08 12:35:01 +00:00
$this -> database -> destination -> network ,
2023-08-07 20:14:21 +00:00
],
2024-11-17 21:49:44 +00:00
'labels' => defaultDatabaseLabels ( $this -> database ) -> toArray (),
2023-08-07 20:14:21 +00:00
'healthcheck' => [
'test' => [
2024-06-10 20:43:34 +00:00
'CMD-SHELL' ,
" psql -U { $this -> database -> postgres_user } -d { $this -> database -> postgres_db } -c 'SELECT 1' || exit 1 " ,
2023-08-07 20:14:21 +00:00
],
'interval' => '5s' ,
'timeout' => '5s' ,
'retries' => 10 ,
2024-06-10 20:43:34 +00:00
'start_period' => '5s' ,
2023-08-07 20:14:21 +00:00
],
2023-08-07 20:27:20 +00:00
'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 ,
2023-12-27 12:01:57 +00:00
'cpus' => ( float ) $this -> database -> limits_cpus ,
2023-08-07 20:27:20 +00:00
'cpu_shares' => $this -> database -> limits_cpu_shares ,
2024-06-10 20:43:34 +00:00
],
2023-08-07 20:14:21 +00:00
],
'networks' => [
2023-08-08 12:35:01 +00:00
$this -> database -> destination -> network => [
2023-09-05 13:43:56 +00:00
'external' => true ,
2023-08-08 12:35:01 +00:00
'name' => $this -> database -> destination -> network ,
2023-08-07 20:14:21 +00:00
'attachable' => true ,
2024-06-10 20:43:34 +00:00
],
],
2023-08-07 20:14:21 +00:00
];
2024-12-18 09:44:56 +00:00
if ( filled ( $this -> database -> limits_cpuset )) {
2024-01-12 12:47:01 +00:00
data_set ( $docker_compose , " services. { $container_name } .cpuset " , $this -> database -> limits_cpuset );
}
2024-03-04 10:01:14 +00:00
if ( $this -> database -> destination -> server -> isLogDrainEnabled () && $this -> database -> isLogDrainEnabled ()) {
2024-08-28 09:11:14 +00:00
$docker_compose [ 'services' ][ $container_name ][ 'logging' ] = generate_fluentd_configuration ();
2023-11-17 10:32:52 +00:00
}
2023-08-08 12:35:01 +00:00
if ( count ( $this -> database -> ports_mappings_array ) > 0 ) {
$docker_compose [ 'services' ][ $container_name ][ 'ports' ] = $this -> database -> ports_mappings_array ;
}
2023-08-07 20:27:20 +00:00
if ( count ( $persistent_storages ) > 0 ) {
$docker_compose [ 'services' ][ $container_name ][ 'volumes' ] = $persistent_storages ;
}
2024-05-27 12:14:44 +00:00
if ( count ( $persistent_file_volumes ) > 0 ) {
$docker_compose [ 'services' ][ $container_name ][ 'volumes' ] = $persistent_file_volumes -> map ( function ( $item ) {
return " $item->fs_path : $item->mount_path " ;
}) -> toArray ();
}
2023-08-07 20:27:20 +00:00
if ( count ( $volume_names ) > 0 ) {
$docker_compose [ 'volumes' ] = $volume_names ;
}
2025-01-07 14:31:43 +00:00
if ( count ( $this -> init_scripts ) > 0 ) {
foreach ( $this -> init_scripts as $init_script ) {
$docker_compose [ 'services' ][ $container_name ][ 'volumes' ][] = [
'type' => 'bind' ,
'source' => $init_script ,
'target' => '/docker-entrypoint-initdb.d/' . basename ( $init_script ),
'read_only' => true ,
];
}
2023-08-08 12:35:01 +00:00
}
2025-01-29 12:30:45 +00:00
if ( $this -> database -> enable_ssl ) {
$docker_compose [ 'services' ][ $container_name ][ 'command' ] = [
'postgres' ,
'-c' ,
2025-01-30 11:58:48 +00:00
'ssl=on' ,
2025-01-29 12:30:45 +00:00
'-c' ,
'ssl_cert_file=/etc/postgresql/ssl/internal.crt' ,
'-c' ,
'ssl_key_file=/etc/postgresql/ssl/internal.key' ,
];
}
2024-12-18 09:44:56 +00:00
if ( filled ( $this -> database -> postgres_conf )) {
2023-11-08 11:40:05 +00:00
$docker_compose [ 'services' ][ $container_name ][ 'volumes' ][] = [
'type' => 'bind' ,
2024-08-27 18:47:17 +00:00
'source' => $this -> configuration_dir . '/custom-postgres.conf' ,
2023-11-08 11:40:05 +00:00
'target' => '/etc/postgresql/postgresql.conf' ,
'read_only' => true ,
];
$docker_compose [ 'services' ][ $container_name ][ 'command' ] = [
'postgres' ,
'-c' ,
'config_file=/etc/postgresql/postgresql.conf' ,
];
}
2024-08-16 11:56:47 +00:00
// Add custom docker run options
2024-11-06 09:13:40 +00:00
$docker_run_options = convertDockerRunToCompose ( $this -> database -> custom_docker_run_options );
$docker_compose = generateCustomDockerRunOptionsForDatabases ( $docker_run_options , $docker_compose , $container_name , $this -> database -> destination -> network );
2024-08-16 11:56:47 +00:00
2023-08-07 20:14:21 +00:00
$docker_compose = Yaml :: dump ( $docker_compose , 10 );
$docker_compose_base64 = base64_encode ( $docker_compose );
2024-04-17 08:49:34 +00:00
$this -> commands [] = " echo ' { $docker_compose_base64 } ' | base64 -d | tee $this->configuration_dir /docker-compose.yml > /dev/null " ;
2023-08-09 12:44:36 +00:00
$readme = generate_readme_file ( $this -> database -> name , now ());
$this -> commands [] = " echo ' { $readme } ' > $this->configuration_dir /README.md " ;
2025-01-07 14:31:43 +00:00
$this -> commands [] = " echo 'Pulling { $database -> image } image.' " ;
2023-11-13 14:27:33 +00:00
$this -> commands [] = " docker compose -f $this->configuration_dir /docker-compose.yml pull " ;
2023-08-09 12:44:36 +00:00
$this -> commands [] = " docker compose -f $this->configuration_dir /docker-compose.yml up -d " ;
2025-01-30 11:58:48 +00:00
if ( $this -> database -> enable_ssl ) {
$this -> commands [] = executeInDocker ( $this -> database -> uuid , " chown { $this -> database -> postgres_user } : { $this -> database -> postgres_user } /etc/postgresql/ssl/internal.key /etc/postgresql/ssl/internal.crt " );
}
2024-02-07 19:34:13 +00:00
$this -> commands [] = " echo 'Database started.' " ;
2024-06-10 20:43:34 +00:00
2025-01-07 14:31:43 +00:00
return remote_process ( $this -> commands , $database -> destination -> server , callEventOnFinish : 'DatabaseStatusChanged' );
2023-08-07 16:46:40 +00:00
}
2023-08-07 20:27:20 +00:00
private function generate_local_persistent_volumes ()
{
$local_persistent_volumes = [];
foreach ( $this -> database -> persistentStorages as $persistentStorage ) {
2024-04-11 11:20:46 +00:00
if ( $persistentStorage -> host_path !== '' && $persistentStorage -> host_path !== null ) {
2024-08-27 18:47:17 +00:00
$local_persistent_volumes [] = $persistentStorage -> host_path . ':' . $persistentStorage -> mount_path ;
2024-04-11 11:20:46 +00:00
} else {
$volume_name = $persistentStorage -> name ;
2024-08-27 18:47:17 +00:00
$local_persistent_volumes [] = $volume_name . ':' . $persistentStorage -> mount_path ;
2024-04-11 11:20:46 +00:00
}
2023-08-07 20:27:20 +00:00
}
2024-06-10 20:43:34 +00:00
2023-08-07 20:27:20 +00:00
return $local_persistent_volumes ;
}
2023-08-08 09:51:36 +00:00
2023-08-07 20:27:20 +00:00
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 ,
];
}
2024-06-10 20:43:34 +00:00
2023-08-07 20:27:20 +00:00
return $local_persistent_volumes_names ;
}
2023-08-08 09:51:36 +00:00
private function generate_environment_variables ()
{
$environment_variables = collect ();
foreach ( $this -> database -> runtime_environment_variables as $env ) {
2024-01-23 16:13:23 +00:00
$environment_variables -> push ( " $env->key = $env->real_value " );
2023-08-08 09:51:36 +00:00
}
2024-08-27 18:47:17 +00:00
if ( $environment_variables -> filter ( fn ( $env ) => str ( $env ) -> contains ( 'POSTGRES_USER' )) -> isEmpty ()) {
2023-08-08 09:51:36 +00:00
$environment_variables -> push ( " POSTGRES_USER= { $this -> database -> postgres_user } " );
}
2024-08-27 18:47:17 +00:00
if ( $environment_variables -> filter ( fn ( $env ) => str ( $env ) -> contains ( 'PGUSER' )) -> isEmpty ()) {
2023-10-19 09:58:12 +00:00
$environment_variables -> push ( " PGUSER= { $this -> database -> postgres_user } " );
}
2023-08-08 09:51:36 +00:00
2024-08-27 18:47:17 +00:00
if ( $environment_variables -> filter ( fn ( $env ) => str ( $env ) -> contains ( 'POSTGRES_PASSWORD' )) -> isEmpty ()) {
2023-08-08 09:51:36 +00:00
$environment_variables -> push ( " POSTGRES_PASSWORD= { $this -> database -> postgres_password } " );
}
2024-08-27 18:47:17 +00:00
if ( $environment_variables -> filter ( fn ( $env ) => str ( $env ) -> contains ( 'POSTGRES_DB' )) -> isEmpty ()) {
2023-08-08 09:51:36 +00:00
$environment_variables -> push ( " POSTGRES_DB= { $this -> database -> postgres_db } " );
}
2024-06-10 20:43:34 +00:00
2024-08-28 11:30:59 +00:00
add_coolify_default_environment_variables ( $this -> database , $environment_variables , $environment_variables );
2024-08-27 16:57:02 +00:00
2023-08-08 09:51:36 +00:00
return $environment_variables -> all ();
}
2023-08-08 12:35:01 +00:00
private function generate_init_scripts ()
{
2024-10-18 19:07:23 +00:00
$this -> commands [] = " rm -rf $this->configuration_dir /docker-entrypoint-initdb.d/* " ;
2024-12-18 09:44:56 +00:00
if ( blank ( $this -> database -> init_scripts ) || count ( $this -> database -> init_scripts ) === 0 ) {
2023-08-08 12:35:01 +00:00
return ;
}
2024-10-18 19:07:23 +00:00
2023-08-08 12:35:01 +00:00
foreach ( $this -> database -> init_scripts as $init_script ) {
$filename = data_get ( $init_script , 'filename' );
$content = data_get ( $init_script , 'content' );
$content_base64 = base64_encode ( $content );
2024-04-17 08:49:34 +00:00
$this -> commands [] = " echo ' { $content_base64 } ' | base64 -d | tee $this->configuration_dir /docker-entrypoint-initdb.d/ { $filename } > /dev/null " ;
2023-08-09 12:44:36 +00:00
$this -> init_scripts [] = " $this->configuration_dir /docker-entrypoint-initdb.d/ { $filename } " ;
2023-08-08 12:35:01 +00:00
}
}
2024-06-10 20:43:34 +00:00
2023-11-08 11:40:05 +00:00
private function add_custom_conf ()
{
2024-10-18 19:17:57 +00:00
$filename = 'custom-postgres.conf' ;
$config_file_path = " $this->configuration_dir / $filename " ;
2024-12-18 09:44:56 +00:00
if ( blank ( $this -> database -> postgres_conf )) {
2024-10-18 19:17:57 +00:00
$this -> commands [] = " rm -f $config_file_path " ;
2023-11-08 11:40:05 +00:00
return ;
}
2024-10-18 19:17:57 +00:00
2023-11-08 11:40:05 +00:00
$content = $this -> database -> postgres_conf ;
2024-06-10 20:43:34 +00:00
if ( ! str ( $content ) -> contains ( 'listen_addresses' )) {
2024-05-07 10:35:24 +00:00
$content .= " \n listen_addresses = '*' " ;
$this -> database -> postgres_conf = $content ;
$this -> database -> save ();
}
2023-11-08 11:40:05 +00:00
$content_base64 = base64_encode ( $content );
2024-10-18 19:17:57 +00:00
$this -> commands [] = " echo ' { $content_base64 } ' | base64 -d | tee $config_file_path > /dev/null " ;
2023-11-08 11:40:05 +00:00
}
2025-01-29 12:30:45 +00:00
private function addSslFilesToFileStorage ()
{
if ( ! $this -> ssl_certificate ) {
return ;
}
$this -> database -> fileStorages () -> create ([
'fs_path' => $this -> configuration_dir . '/ssl/internal.crt' ,
'mount_path' => '/etc/postgresql/ssl/internal.crt' ,
'content' => $this -> ssl_certificate -> ssl_certificate ,
'is_directory' => false ,
'chmod' => '644' ,
]);
$this -> database -> fileStorages () -> create ([
'fs_path' => $this -> configuration_dir . '/ssl/internal.key' ,
'mount_path' => '/etc/postgresql/ssl/internal.key' ,
'content' => $this -> ssl_certificate -> ssl_private_key ,
'is_directory' => false ,
'chmod' => '600' ,
]);
}
2023-08-07 20:14:21 +00:00
}