diff --git a/app/Actions/Database/StartClickhouse.php b/app/Actions/Database/StartClickhouse.php index f218fcabb..7be727f55 100644 --- a/app/Actions/Database/StartClickhouse.php +++ b/app/Actions/Database/StartClickhouse.php @@ -99,8 +99,12 @@ class StartClickhouse $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); $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"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $docker_compose, + 'destination' => "$this->configuration_dir/docker-compose.yml", + ], + ]; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; diff --git a/app/Actions/Database/StartDatabaseProxy.php b/app/Actions/Database/StartDatabaseProxy.php index 12fd92792..d90eebc17 100644 --- a/app/Actions/Database/StartDatabaseProxy.php +++ b/app/Actions/Database/StartDatabaseProxy.php @@ -52,8 +52,9 @@ class StartDatabaseProxy } $configuration_dir = database_proxy_dir($database->uuid); + $volume_configuration_dir = $configuration_dir; if (isDev()) { - $configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$database->uuid.'/proxy'; + $volume_configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$database->uuid.'/proxy'; } $nginxconf = << [ [ 'type' => 'bind', - 'source' => "$configuration_dir/nginx.conf", + 'source' => "$volume_configuration_dir/nginx.conf", 'target' => '/etc/nginx/nginx.conf', ], ], @@ -115,8 +116,18 @@ class StartDatabaseProxy instant_remote_process(["docker rm -f $proxyContainerName"], $server, false); instant_remote_process([ "mkdir -p $configuration_dir", - "echo '{$nginxconf_base64}' | base64 -d | tee $configuration_dir/nginx.conf > /dev/null", - "echo '{$dockercompose_base64}' | base64 -d | tee $configuration_dir/docker-compose.yaml > /dev/null", + [ + 'transfer_file' => [ + 'content' => base64_decode($nginxconf_base64), + 'destination' => "$configuration_dir/nginx.conf", + ], + ], + [ + 'transfer_file' => [ + 'content' => base64_decode($dockercompose_base64), + 'destination' => "$configuration_dir/docker-compose.yaml", + ], + ], "docker compose --project-directory {$configuration_dir} pull", "docker compose --project-directory {$configuration_dir} up -d", ], $server); diff --git a/app/Actions/Database/StartDragonfly.php b/app/Actions/Database/StartDragonfly.php index 38ad99d2e..579c6841d 100644 --- a/app/Actions/Database/StartDragonfly.php +++ b/app/Actions/Database/StartDragonfly.php @@ -183,8 +183,12 @@ class StartDragonfly $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); $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"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $docker_compose, + 'destination' => "$this->configuration_dir/docker-compose.yml", + ], + ]; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; diff --git a/app/Actions/Database/StartKeydb.php b/app/Actions/Database/StartKeydb.php index 59bcd4123..e1d4e43c1 100644 --- a/app/Actions/Database/StartKeydb.php +++ b/app/Actions/Database/StartKeydb.php @@ -199,8 +199,12 @@ class StartKeydb $docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); $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"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $docker_compose, + 'destination' => "$this->configuration_dir/docker-compose.yml", + ], + ]; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index 13dba4b43..3f7d22245 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -203,8 +203,12 @@ class StartMariadb } $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"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $docker_compose, + 'destination' => "$this->configuration_dir/docker-compose.yml", + ], + ]; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; @@ -284,7 +288,11 @@ class StartMariadb } $filename = 'custom-config.cnf'; $content = $this->database->mariadb_conf; - $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $content, + 'destination' => "$this->configuration_dir/{$filename}", + ], + ]; } } diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 870b5b7e5..0372cd64f 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -18,6 +18,8 @@ class StartMongodb public string $configuration_dir; + public string $volume_configuration_dir; + private ?SslCertificate $ssl_certificate = null; public function handle(StandaloneMongodb $database) @@ -27,9 +29,9 @@ class StartMongodb $startCommand = 'mongod'; $container_name = $this->database->uuid; - $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $this->volume_configuration_dir = $this->configuration_dir = database_configuration_dir().'/'.$container_name; if (isDev()) { - $this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name; + $this->volume_configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name; } $this->commands = [ @@ -176,7 +178,7 @@ class StartMongodb $docker_compose['services'][$container_name]['volumes'] ?? [], [[ 'type' => 'bind', - 'source' => $this->configuration_dir.'/mongod.conf', + 'source' => $this->volume_configuration_dir.'/mongod.conf', 'target' => '/etc/mongo/mongod.conf', 'read_only' => true, ]] @@ -190,7 +192,7 @@ class StartMongodb $docker_compose['services'][$container_name]['volumes'] ?? [], [[ 'type' => 'bind', - 'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d', + 'source' => $this->volume_configuration_dir.'/docker-entrypoint-initdb.d', 'target' => '/docker-entrypoint-initdb.d', 'read_only' => true, ]] @@ -254,8 +256,12 @@ class StartMongodb } $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"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $docker_compose, + 'destination' => "$this->volume_configuration_dir/docker-compose.yml", + ], + ]; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; @@ -332,15 +338,22 @@ class StartMongodb } $filename = 'mongod.conf'; $content = $this->database->mongo_conf; - $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $content, + 'destination' => "$this->configuration_dir/{$filename}", + ], + ]; } private function add_default_database() { $content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});"; - $content_base64 = base64_encode($content); - $this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d"; - $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $content, + 'destination' => "$this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js", + ], + ]; } } diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index 5d5611e07..5f453f80a 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -204,8 +204,12 @@ class StartMysql } $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"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $docker_compose, + 'destination' => "$this->configuration_dir/docker-compose.yml", + ], + ]; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; @@ -287,7 +291,11 @@ class StartMysql } $filename = 'custom-config.cnf'; $content = $this->database->mysql_conf; - $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $content, + 'destination' => "$this->configuration_dir/{$filename}", + ], + ]; } } diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 4314ccd2f..80860bda2 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -20,6 +20,8 @@ class StartPostgresql public string $configuration_dir; + public string $volume_configuration_dir; + private ?SslCertificate $ssl_certificate = null; public function handle(StandalonePostgresql $database) @@ -27,8 +29,9 @@ class StartPostgresql $this->database = $database; $container_name = $this->database->uuid; $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $this->volume_configuration_dir = $this->configuration_dir; if (isDev()) { - $this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name; + $this->volume_configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name; } $this->commands = [ @@ -192,7 +195,7 @@ class StartPostgresql $docker_compose['services'][$container_name]['volumes'], [[ 'type' => 'bind', - 'source' => $this->configuration_dir.'/custom-postgres.conf', + 'source' => $this->volume_configuration_dir.'/custom-postgres.conf', 'target' => '/etc/postgresql/postgresql.conf', 'read_only' => true, ]] @@ -217,8 +220,12 @@ class StartPostgresql } $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"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $docker_compose, + 'destination' => "$this->volume_configuration_dir/docker-compose.yml", + ], + ]; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; @@ -302,8 +309,12 @@ class StartPostgresql 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); - $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/{$filename} > /dev/null"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $content, + 'destination' => "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}", + ], + ]; $this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; } } @@ -325,7 +336,11 @@ class StartPostgresql $this->database->postgres_conf = $content; $this->database->save(); } - $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $config_file_path > /dev/null"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $content, + 'destination' => $config_file_path, + ], + ]; } } diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index 68a1f3fe3..b5962b165 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -196,8 +196,12 @@ class StartRedis $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); $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"; + $this->commands[] = [ + 'transfer_file' => [ + 'content' => $docker_compose, + 'destination' => "$this->configuration_dir/docker-compose.yml", + ], + ]; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; diff --git a/app/Actions/Proxy/SaveConfiguration.php b/app/Actions/Proxy/SaveConfiguration.php index f2de2b3f5..25887d15e 100644 --- a/app/Actions/Proxy/SaveConfiguration.php +++ b/app/Actions/Proxy/SaveConfiguration.php @@ -22,7 +22,12 @@ class SaveConfiguration return instant_remote_process([ "mkdir -p $proxy_path", - "echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null", + [ + 'transfer_file' => [ + 'content' => base64_decode($docker_compose_yml_base64), + 'destination' => "$proxy_path/docker-compose.yml", + ], + ], ], $server); } } diff --git a/app/Actions/Server/ConfigureCloudflared.php b/app/Actions/Server/ConfigureCloudflared.php index d21622bc5..e66e7eecb 100644 --- a/app/Actions/Server/ConfigureCloudflared.php +++ b/app/Actions/Server/ConfigureCloudflared.php @@ -40,7 +40,12 @@ class ConfigureCloudflared $commands = collect([ 'mkdir -p /tmp/cloudflared', 'cd /tmp/cloudflared', - "echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null", + [ + 'transfer_file' => [ + 'content' => base64_decode($docker_compose_yml_base64), + 'destination' => '/tmp/cloudflared/docker-compose.yml', + ], + ], 'echo Pulling latest Cloudflare Tunnel image.', 'docker compose pull', 'echo Stopping existing Cloudflare Tunnel container.', diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index 5410b1cbd..33c22b484 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -14,6 +14,7 @@ class InstallDocker public function handle(Server $server) { + ray('install docker'); $dockerVersion = config('constants.docker.minimum_required_version'); $supported_os_type = $server->validateOS(); if (! $supported_os_type) { @@ -103,8 +104,15 @@ class InstallDocker "curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}", "echo 'Configuring Docker Engine (merging existing configuration with the required)...'", 'test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json "/etc/docker/daemon.json.original-$(date +"%Y%m%d-%H%M%S")"', - "test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null", - "echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null", + [ + 'transfer_file' => [ + 'content' => base64_decode($config), + 'destination' => '/tmp/daemon.json.new', + ], + ], + 'test ! -s /etc/docker/daemon.json && cp /tmp/daemon.json.new /etc/docker/daemon.json', + 'cp /tmp/daemon.json.new /etc/docker/daemon.json.coolify', + 'rm -f /tmp/daemon.json.new', 'jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null', 'mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify', "jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null", diff --git a/app/Actions/Server/StartLogDrain.php b/app/Actions/Server/StartLogDrain.php index f72f23696..3e1dad1c2 100644 --- a/app/Actions/Server/StartLogDrain.php +++ b/app/Actions/Server/StartLogDrain.php @@ -180,10 +180,30 @@ Files: $command = [ "echo 'Saving configuration'", "mkdir -p $config_path", - "echo '{$parsers}' | base64 -d | tee $parsers_config > /dev/null", - "echo '{$config}' | base64 -d | tee $fluent_bit_config > /dev/null", - "echo '{$compose}' | base64 -d | tee $compose_path > /dev/null", - "echo '{$readme}' | base64 -d | tee $readme_path > /dev/null", + [ + 'transfer_file' => [ + 'content' => base64_decode($parsers), + 'destination' => $parsers_config, + ], + ], + [ + 'transfer_file' => [ + 'content' => base64_decode($config), + 'destination' => $fluent_bit_config, + ], + ], + [ + 'transfer_file' => [ + 'content' => base64_decode($compose), + 'destination' => $compose_path, + ], + ], + [ + 'transfer_file' => [ + 'content' => base64_decode($readme), + 'destination' => $readme_path, + ], + ], "test -f $config_path/.env && rm $config_path/.env", ]; if ($type === 'newrelic') { diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 9037fa3e5..d77adebb9 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -388,11 +388,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $dockerfile_base64 = base64_encode($this->application->dockerfile); $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}."); $this->prepare_builder_image(); - $this->execute_remote_command( - [ - executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"), - ], - ); + $dockerfile_content = base64_decode($dockerfile_base64); + transfer_file_to_container($dockerfile_content, "{$this->workdir}{$this->dockerfile_location}", $this->deployment_uuid, $this->server); $this->generate_image_names(); $this->generate_compose_file(); $this->generate_build_env_variables(); @@ -497,10 +494,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $yaml = Yaml::dump(convertToArray($composeFile), 10); } $this->docker_compose_base64 = base64_encode($yaml); - $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), - 'hidden' => true, - ]); + transfer_file_to_container($yaml, "{$this->workdir}{$this->docker_compose_location}", $this->deployment_uuid, $this->server); // Build new container to limit downtime. $this->application_deployment_queue->addLogEntry('Pulling & building required images.'); @@ -715,13 +709,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $composeFileName = "$mainDir/docker-compose-pr-{$this->pull_request_id}.yaml"; $this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml"; } + $this->execute_remote_command([ + "mkdir -p $mainDir", + ]); + $docker_compose_content = base64_decode($this->docker_compose_base64); + transfer_file_to_server($docker_compose_content, $composeFileName, $this->server); $this->execute_remote_command( - [ - "mkdir -p $mainDir", - ], - [ - "echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null", - ], [ "echo '{$readme}' > $mainDir/README.md", ] @@ -1013,27 +1006,15 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue ); } } else { - $envs_base64 = base64_encode($envs->implode("\n")); - $this->execute_remote_command( - [ - executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null"), - ], + $envs_content = $envs->implode("\n"); + transfer_file_to_container($envs_content, "$this->workdir/{$this->env_filename}", $this->deployment_uuid, $this->server); - ); if ($this->use_build_server) { $this->server = $this->original_server; - $this->execute_remote_command( - [ - "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null", - ] - ); + transfer_file_to_server($envs_content, "$this->configuration_dir/{$this->env_filename}", $this->server); $this->server = $this->build_server; } else { - $this->execute_remote_command( - [ - "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null", - ] - ); + transfer_file_to_server($envs_content, "$this->configuration_dir/{$this->env_filename}", $this->server); } } $this->environment_variables = $envs; @@ -1444,13 +1425,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $private_key = data_get($this->application, 'private_key.private_key'); if ($private_key) { $private_key = base64_encode($private_key); + $this->execute_remote_command([ + executeInDocker($this->deployment_uuid, 'mkdir -p /root/.ssh'), + ]); + $key_content = base64_decode($private_key); + transfer_file_to_container($key_content, '/root/.ssh/id_rsa', $this->deployment_uuid, $this->server); $this->execute_remote_command( - [ - executeInDocker($this->deployment_uuid, 'mkdir -p /root/.ssh'), - ], - [ - executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"), - ], [ executeInDocker($this->deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'), ], @@ -1993,7 +1973,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->docker_compose = Yaml::dump($docker_compose, 10); $this->docker_compose_base64 = base64_encode($this->docker_compose); - $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yaml > /dev/null"), 'hidden' => true]); + transfer_file_to_container(base64_decode($this->docker_compose_base64), "{$this->workdir}/docker-compose.yaml", $this->deployment_uuid, $this->server); } private function generate_local_persistent_volumes() @@ -2121,7 +2101,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } else { if ($this->application->build_pack === 'nixpacks') { $this->nixpacks_plan = base64_encode($this->nixpacks_plan); - $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]); + $nixpacks_content = base64_decode($this->nixpacks_plan); + transfer_file_to_container($nixpacks_content, '/artifacts/thegameplan.json', $this->deployment_uuid, $this->server); if ($this->force_rebuild) { $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), @@ -2139,7 +2120,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), 'hidden' => true, ], [ @@ -2162,7 +2143,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), 'hidden' => true, ], [ @@ -2194,13 +2175,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null"), + transfer_file_to_container(base64_decode($dockerfile), "{$this->workdir}/Dockerfile", $this->deployment_uuid, $this->server), ], [ - executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null"), + transfer_file_to_container(base64_decode($nginx_config), "{$this->workdir}/nginx.conf", $this->deployment_uuid, $this->server), ], [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), 'hidden' => true, ], [ @@ -2223,7 +2204,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), 'hidden' => true, ], [ @@ -2238,7 +2219,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } else { if ($this->application->build_pack === 'nixpacks') { $this->nixpacks_plan = base64_encode($this->nixpacks_plan); - $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]); + $nixpacks_content = base64_decode($this->nixpacks_plan); + transfer_file_to_container($nixpacks_content, '/artifacts/thegameplan.json', $this->deployment_uuid, $this->server); if ($this->force_rebuild) { $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), @@ -2255,7 +2237,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), 'hidden' => true, ], [ @@ -2278,7 +2260,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), 'hidden' => true, ], [ @@ -2405,7 +2387,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } $dockerfile_base64 = base64_encode($dockerfile->implode("\n")); $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"), + transfer_file_to_container(base64_decode($dockerfile_base64), "{$this->workdir}{$this->dockerfile_location}", $this->deployment_uuid, $this->server), 'hidden' => true, ]); } diff --git a/app/Models/Server.php b/app/Models/Server.php index 736a59be4..0fba5da4b 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1082,7 +1082,6 @@ $schema://$host { public function validateConnection(bool $justCheckingNewKey = false) { - ray('validateConnection', $this->id); $this->disableSshMux(); if ($this->skipServer()) { @@ -1320,7 +1319,6 @@ $schema://$host { public function generateCaCertificate() { try { - ray('Generating CA certificate for server', $this->id); SslHelper::generateSslCertificate( commonName: 'Coolify CA Certificate', serverId: $this->id, @@ -1328,7 +1326,6 @@ $schema://$host { validityDays: 10 * 365 ); $caCertificate = SslCertificate::where('server_id', $this->id)->where('is_ca_certificate', true)->first(); - ray('CA certificate generated', $caCertificate); if ($caCertificate) { $certificateContent = $caCertificate->ssl_certificate; $caCertPath = config('constants.coolify.base_config_path').'/ssl/'; diff --git a/app/Models/Service.php b/app/Models/Service.php index 43cb32d85..bd185b355 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -1281,8 +1281,10 @@ class Service extends BaseModel if ($envs->count() === 0) { $commands[] = 'touch .env'; } else { - $envs_base64 = base64_encode($envs->implode("\n")); - $commands[] = "echo '$envs_base64' | base64 -d | tee .env > /dev/null"; + $envs_content = $envs->implode("\n"); + transfer_file_to_server($envs_content, $this->workdir().'/.env', $this->server); + + return; } instant_remote_process($commands, $this->server); diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index b5bdeff49..fd73de653 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -29,11 +29,31 @@ function remote_process( $type = $type ?? ActivityTypes::INLINE->value; $command = $command instanceof Collection ? $command->toArray() : $command; - if ($server->isNonRoot()) { - $command = parseCommandsByLineForSudo(collect($command), $server); + // Process commands and handle file transfers + $processed_commands = []; + foreach ($command as $cmd) { + if (is_array($cmd) && isset($cmd['transfer_file'])) { + // Handle file transfer command + $transfer_data = $cmd['transfer_file']; + $content = $transfer_data['content']; + $destination = $transfer_data['destination']; + + // Execute file transfer immediately + transfer_file_to_server($content, $destination, $server, ! $ignore_errors); + + // Add a comment to the command log for visibility + $processed_commands[] = "# File transferred via SCP: $destination"; + } else { + // Regular string command + $processed_commands[] = $cmd; + } } - $command_string = implode("\n", $command); + if ($server->isNonRoot()) { + $processed_commands = parseCommandsByLineForSudo(collect($processed_commands), $server); + } + + $command_string = implode("\n", $processed_commands); if (Auth::check()) { $teams = Auth::user()->teams->pluck('id'); @@ -84,6 +104,66 @@ function instant_scp(string $source, string $dest, Server $server, $throwError = ); } +function transfer_file_to_container(string $content, string $container_path, string $deployment_uuid, Server $server, bool $throwError = true): ?string +{ + $temp_file = tempnam(sys_get_temp_dir(), 'coolify_env_'); + + try { + // Write content to temporary file + file_put_contents($temp_file, $content); + + // Generate unique filename for server transfer + $server_temp_file = '/tmp/coolify_env_'.uniqid().'_'.$deployment_uuid; + + // Transfer file to server + instant_scp($temp_file, $server_temp_file, $server, $throwError); + + // Ensure parent directory exists in container, then copy file + $parent_dir = dirname($container_path); + $commands = []; + if ($parent_dir !== '.' && $parent_dir !== '/') { + $commands[] = executeInDocker($deployment_uuid, "mkdir -p \"$parent_dir\""); + } + $commands[] = "docker cp $server_temp_file $deployment_uuid:$container_path"; + $commands[] = "rm -f $server_temp_file"; // Cleanup server temp file + + return instant_remote_process_with_timeout($commands, $server, $throwError); + + } finally { + ray($temp_file); + // Always cleanup local temp file + if (file_exists($temp_file)) { + unlink($temp_file); + } + } +} + +function transfer_file_to_server(string $content, string $server_path, Server $server, bool $throwError = true): ?string +{ + $temp_file = tempnam(sys_get_temp_dir(), 'coolify_env_'); + + try { + // Write content to temporary file + file_put_contents($temp_file, $content); + + // Ensure parent directory exists on server + $parent_dir = dirname($server_path); + if ($parent_dir !== '.' && $parent_dir !== '/') { + instant_remote_process_with_timeout(["mkdir -p \"$parent_dir\""], $server, $throwError); + } + + // Transfer file directly to server destination + return instant_scp($temp_file, $server_path, $server, $throwError); + + } finally { + ray($temp_file); + // Always cleanup local temp file + if (file_exists($temp_file)) { + unlink($temp_file); + } + } +} + function instant_remote_process_with_timeout(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string { $command = $command instanceof Collection ? $command->toArray() : $command; @@ -121,10 +201,31 @@ function instant_remote_process_with_timeout(Collection|array $command, Server $ function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string { $command = $command instanceof Collection ? $command->toArray() : $command; - if ($server->isNonRoot() && ! $no_sudo) { - $command = parseCommandsByLineForSudo(collect($command), $server); + + // Process commands and handle file transfers + $processed_commands = []; + foreach ($command as $cmd) { + if (is_array($cmd) && isset($cmd['transfer_file'])) { + // Handle file transfer command + $transfer_data = $cmd['transfer_file']; + $content = $transfer_data['content']; + $destination = $transfer_data['destination']; + + // Execute file transfer immediately + transfer_file_to_server($content, $destination, $server, $throwError); + + // Add a comment to the command log for visibility + $processed_commands[] = "# File transferred via SCP: $destination"; + } else { + // Regular string command + $processed_commands[] = $cmd; + } } - $command_string = implode("\n", $command); + + if ($server->isNonRoot() && ! $no_sudo) { + $processed_commands = parseCommandsByLineForSudo(collect($processed_commands), $server); + } + $command_string = implode("\n", $processed_commands); return \App\Helpers\SshRetryHandler::retry( function () use ($server, $command_string) {