Merge pull request #6616 from coollabsio/next

v4.0.0-beta.428
This commit is contained in:
Andras Bacsai
2025-09-15 18:52:24 +02:00
committed by GitHub
29 changed files with 298 additions and 321 deletions

View File

@@ -99,12 +99,8 @@ class StartClickhouse
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$this->commands[] = [ $docker_compose_base64 = base64_encode($docker_compose);
'transfer_file' => [ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
'content' => $docker_compose,
'destination' => "$this->configuration_dir/docker-compose.yml",
],
];
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -52,9 +52,8 @@ class StartDatabaseProxy
} }
$configuration_dir = database_proxy_dir($database->uuid); $configuration_dir = database_proxy_dir($database->uuid);
$volume_configuration_dir = $configuration_dir;
if (isDev()) { if (isDev()) {
$volume_configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$database->uuid.'/proxy'; $configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$database->uuid.'/proxy';
} }
$nginxconf = <<<EOF $nginxconf = <<<EOF
user nginx; user nginx;
@@ -87,7 +86,7 @@ class StartDatabaseProxy
'volumes' => [ 'volumes' => [
[ [
'type' => 'bind', 'type' => 'bind',
'source' => "$volume_configuration_dir/nginx.conf", 'source' => "$configuration_dir/nginx.conf",
'target' => '/etc/nginx/nginx.conf', 'target' => '/etc/nginx/nginx.conf',
], ],
], ],
@@ -116,18 +115,8 @@ class StartDatabaseProxy
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false); instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
instant_remote_process([ instant_remote_process([
"mkdir -p $configuration_dir", "mkdir -p $configuration_dir",
[ "echo '{$nginxconf_base64}' | base64 -d | tee $configuration_dir/nginx.conf > /dev/null",
'transfer_file' => [ "echo '{$dockercompose_base64}' | base64 -d | tee $configuration_dir/docker-compose.yaml > /dev/null",
'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} pull",
"docker compose --project-directory {$configuration_dir} up -d", "docker compose --project-directory {$configuration_dir} up -d",
], $server); ], $server);

View File

@@ -183,12 +183,8 @@ class StartDragonfly
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$this->commands[] = [ $docker_compose_base64 = base64_encode($docker_compose);
'transfer_file' => [ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
'content' => $docker_compose,
'destination' => "$this->configuration_dir/docker-compose.yml",
],
];
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -199,12 +199,8 @@ class StartKeydb
$docker_run_options = convertDockerRunToCompose($this->database->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); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$this->commands[] = [ $docker_compose_base64 = base64_encode($docker_compose);
'transfer_file' => [ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
'content' => $docker_compose,
'destination' => "$this->configuration_dir/docker-compose.yml",
],
];
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -203,12 +203,8 @@ class StartMariadb
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$this->commands[] = [ $docker_compose_base64 = base64_encode($docker_compose);
'transfer_file' => [ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
'content' => $docker_compose,
'destination' => "$this->configuration_dir/docker-compose.yml",
],
];
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -288,11 +284,7 @@ class StartMariadb
} }
$filename = 'custom-config.cnf'; $filename = 'custom-config.cnf';
$content = $this->database->mariadb_conf; $content = $this->database->mariadb_conf;
$this->commands[] = [ $content_base64 = base64_encode($content);
'transfer_file' => [ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
'content' => $content,
'destination' => "$this->configuration_dir/{$filename}",
],
];
} }
} }

View File

@@ -28,6 +28,9 @@ class StartMongodb
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name; $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->commands = [ $this->commands = [
"echo 'Starting database.'", "echo 'Starting database.'",
@@ -251,12 +254,8 @@ class StartMongodb
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$this->commands[] = [ $docker_compose_base64 = base64_encode($docker_compose);
'transfer_file' => [ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
'content' => $docker_compose,
'destination' => "$this->configuration_dir/docker-compose.yml",
],
];
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -333,22 +332,15 @@ class StartMongodb
} }
$filename = 'mongod.conf'; $filename = 'mongod.conf';
$content = $this->database->mongo_conf; $content = $this->database->mongo_conf;
$this->commands[] = [ $content_base64 = base64_encode($content);
'transfer_file' => [ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
'content' => $content,
'destination' => "$this->configuration_dir/{$filename}",
],
];
} }
private function add_default_database() 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 = "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}\"}]});";
$this->commands[] = [ $content_base64 = base64_encode($content);
'transfer_file' => [ $this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
'content' => $content, $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null";
'destination' => "$this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js",
],
];
} }
} }

View File

@@ -204,12 +204,8 @@ class StartMysql
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$this->commands[] = [ $docker_compose_base64 = base64_encode($docker_compose);
'transfer_file' => [ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
'content' => $docker_compose,
'destination' => "$this->configuration_dir/docker-compose.yml",
],
];
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -291,11 +287,7 @@ class StartMysql
} }
$filename = 'custom-config.cnf'; $filename = 'custom-config.cnf';
$content = $this->database->mysql_conf; $content = $this->database->mysql_conf;
$this->commands[] = [ $content_base64 = base64_encode($content);
'transfer_file' => [ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
'content' => $content,
'destination' => "$this->configuration_dir/{$filename}",
],
];
} }
} }

View File

@@ -27,6 +27,9 @@ class StartPostgresql
$this->database = $database; $this->database = $database;
$container_name = $this->database->uuid; $container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name; $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->commands = [ $this->commands = [
"echo 'Starting database.'", "echo 'Starting database.'",
@@ -214,12 +217,8 @@ class StartPostgresql
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$this->commands[] = [ $docker_compose_base64 = base64_encode($docker_compose);
'transfer_file' => [ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
'content' => $docker_compose,
'destination' => "$this->configuration_dir/docker-compose.yml",
],
];
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -305,12 +304,8 @@ class StartPostgresql
foreach ($this->database->init_scripts as $init_script) { foreach ($this->database->init_scripts as $init_script) {
$filename = data_get($init_script, 'filename'); $filename = data_get($init_script, 'filename');
$content = data_get($init_script, 'content'); $content = data_get($init_script, 'content');
$this->commands[] = [ $content_base64 = base64_encode($content);
'transfer_file' => [ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/{$filename} > /dev/null";
'content' => $content,
'destination' => "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}",
],
];
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; $this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
} }
} }
@@ -332,11 +327,7 @@ class StartPostgresql
$this->database->postgres_conf = $content; $this->database->postgres_conf = $content;
$this->database->save(); $this->database->save();
} }
$this->commands[] = [ $content_base64 = base64_encode($content);
'transfer_file' => [ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $config_file_path > /dev/null";
'content' => $content,
'destination' => $config_file_path,
],
];
} }
} }

View File

@@ -196,12 +196,8 @@ class StartRedis
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$this->commands[] = [ $docker_compose_base64 = base64_encode($docker_compose);
'transfer_file' => [ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
'content' => $docker_compose,
'destination' => "$this->configuration_dir/docker-compose.yml",
],
];
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -21,12 +21,7 @@ class SaveProxyConfiguration
// Transfer the configuration file to the server // Transfer the configuration file to the server
instant_remote_process([ instant_remote_process([
"mkdir -p $proxy_path", "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); ], $server);
} }
} }

View File

@@ -40,12 +40,7 @@ class ConfigureCloudflared
$commands = collect([ $commands = collect([
'mkdir -p /tmp/cloudflared', 'mkdir -p /tmp/cloudflared',
'cd /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.', 'echo Pulling latest Cloudflare Tunnel image.',
'docker compose pull', 'docker compose pull',
'echo Stopping existing Cloudflare Tunnel container.', 'echo Stopping existing Cloudflare Tunnel container.',

View File

@@ -14,7 +14,6 @@ class InstallDocker
public function handle(Server $server) public function handle(Server $server)
{ {
ray('install docker');
$dockerVersion = config('constants.docker.minimum_required_version'); $dockerVersion = config('constants.docker.minimum_required_version');
$supported_os_type = $server->validateOS(); $supported_os_type = $server->validateOS();
if (! $supported_os_type) { if (! $supported_os_type) {
@@ -104,15 +103,8 @@ class InstallDocker
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}", "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)...'", "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 && 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",
'transfer_file' => [ "echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null",
'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', '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', '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", "jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null",

View File

@@ -180,30 +180,10 @@ Files:
$command = [ $command = [
"echo 'Saving configuration'", "echo 'Saving configuration'",
"mkdir -p $config_path", "mkdir -p $config_path",
[ "echo '{$parsers}' | base64 -d | tee $parsers_config > /dev/null",
'transfer_file' => [ "echo '{$config}' | base64 -d | tee $fluent_bit_config > /dev/null",
'content' => base64_decode($parsers), "echo '{$compose}' | base64 -d | tee $compose_path > /dev/null",
'destination' => $parsers_config, "echo '{$readme}' | base64 -d | tee $readme_path > /dev/null",
],
],
[
'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", "test -f $config_path/.env && rm $config_path/.env",
]; ];
if ($type === 'newrelic') { if ($type === 'newrelic') {

View File

@@ -388,8 +388,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$dockerfile_base64 = base64_encode($this->application->dockerfile); $dockerfile_base64 = base64_encode($this->application->dockerfile);
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}."); $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}.");
$this->prepare_builder_image(); $this->prepare_builder_image();
$dockerfile_content = base64_decode($dockerfile_base64); $this->execute_remote_command(
transfer_file_to_container($dockerfile_content, "{$this->workdir}{$this->dockerfile_location}", $this->deployment_uuid, $this->server); [
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"),
],
);
$this->generate_image_names(); $this->generate_image_names();
$this->generate_compose_file(); $this->generate_compose_file();
$this->generate_build_env_variables(); $this->generate_build_env_variables();
@@ -479,7 +482,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if (filled($this->env_filename)) { if (filled($this->env_filename)) {
$services = collect(data_get($composeFile, 'services', [])); $services = collect(data_get($composeFile, 'services', []));
$services = $services->map(function ($service, $name) { $services = $services->map(function ($service, $name) {
$service['env_file'] = ["/artifacts/{$this->env_filename}"]; $service['env_file'] = [$this->env_filename];
return $service; return $service;
}); });
@@ -494,7 +497,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$yaml = Yaml::dump(convertToArray($composeFile), 10); $yaml = Yaml::dump(convertToArray($composeFile), 10);
} }
$this->docker_compose_base64 = base64_encode($yaml); $this->docker_compose_base64 = base64_encode($yaml);
transfer_file_to_container($yaml, "{$this->workdir}{$this->docker_compose_location}", $this->deployment_uuid, $this->server); $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,
]);
// Build new container to limit downtime. // Build new container to limit downtime.
$this->application_deployment_queue->addLogEntry('Pulling & building required images.'); $this->application_deployment_queue->addLogEntry('Pulling & building required images.');
@@ -505,7 +511,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} else { } else {
$command = "{$this->coolify_variables} docker compose"; $command = "{$this->coolify_variables} docker compose";
if (filled($this->env_filename)) { if (filled($this->env_filename)) {
$command .= " --env-file /artifacts/{$this->env_filename}"; $command .= " --env-file {$this->workdir}/{$this->env_filename}";
} }
if ($this->force_rebuild) { if ($this->force_rebuild) {
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull --no-cache"; $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull --no-cache";
@@ -551,7 +557,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$command = "{$this->coolify_variables} docker compose"; $command = "{$this->coolify_variables} docker compose";
if (filled($this->env_filename)) { if (filled($this->env_filename)) {
$command .= " --env-file /artifacts/{$this->env_filename}"; $command .= " --env-file {$server_workdir}/{$this->env_filename}";
} }
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"; $command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command( $this->execute_remote_command(
@@ -568,7 +574,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$command = "{$this->coolify_variables} docker compose"; $command = "{$this->coolify_variables} docker compose";
if ($this->preserveRepository) { if ($this->preserveRepository) {
if (filled($this->env_filename)) { if (filled($this->env_filename)) {
$command .= " --env-file /artifacts/{$this->env_filename}"; $command .= " --env-file {$server_workdir}/{$this->env_filename}";
} }
$command .= " --project-name {$this->application->uuid} --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"; $command .= " --project-name {$this->application->uuid} --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->write_deployment_configurations(); $this->write_deployment_configurations();
@@ -578,7 +584,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
); );
} else { } else {
if (filled($this->env_filename)) { if (filled($this->env_filename)) {
$command .= " --env-file /artifacts/{$this->env_filename}"; $command .= " --env-file {$this->workdir}/{$this->env_filename}";
} }
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"; $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command( $this->execute_remote_command(
@@ -709,12 +715,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$composeFileName = "$mainDir/".addPreviewDeploymentSuffix('docker-compose', $this->pull_request_id).'.yaml'; $composeFileName = "$mainDir/".addPreviewDeploymentSuffix('docker-compose', $this->pull_request_id).'.yaml';
$this->docker_compose_location = '/'.addPreviewDeploymentSuffix('docker-compose', $this->pull_request_id).'.yaml'; $this->docker_compose_location = '/'.addPreviewDeploymentSuffix('docker-compose', $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( $this->execute_remote_command(
[
"mkdir -p $mainDir",
],
[
"echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
],
[ [
"echo '{$readme}' > $mainDir/README.md", "echo '{$readme}' > $mainDir/README.md",
] ]
@@ -911,24 +918,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}); });
if ($this->pull_request_id === 0) { if ($this->pull_request_id === 0) {
$this->env_filename = '.env'; $this->env_filename = '.env';
// Filter out buildtime-only variables from runtime environment
$runtime_environment_variables = $sorted_environment_variables->filter(function ($env) {
return ! $env->is_buildtime_only;
});
foreach ($runtime_environment_variables as $env) {
$envs->push($env->key.'='.$env->real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->build_pack !== 'dockercompose') {
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
}
// Add HOST if not exists
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
$envs->push('HOST=0.0.0.0');
}
// Generate SERVICE_ variables first for dockercompose
if ($this->build_pack === 'dockercompose') { if ($this->build_pack === 'dockercompose') {
$domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect([]); $domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect([]);
@@ -957,26 +948,38 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$envs->push('SERVICE_NAME_'.str($serviceName)->upper().'='.$serviceName); $envs->push('SERVICE_NAME_'.str($serviceName)->upper().'='.$serviceName);
} }
} }
} else {
$this->env_filename = '.env'; // Filter out buildtime-only variables from runtime environment
// Filter out buildtime-only variables from runtime environment for preview $runtime_environment_variables = $sorted_environment_variables->filter(function ($env) {
$runtime_environment_variables_preview = $sorted_environment_variables_preview->filter(function ($env) {
return ! $env->is_buildtime_only; return ! $env->is_buildtime_only;
}); });
foreach ($runtime_environment_variables_preview as $env) {
// Sort runtime environment variables: those referencing SERVICE_ variables come after others
$runtime_environment_variables = $runtime_environment_variables->sortBy(function ($env) {
if (str($env->value)->startsWith('$SERVICE_') || str($env->value)->contains('${SERVICE_')) {
return 2;
}
return 1;
});
foreach ($runtime_environment_variables as $env) {
$envs->push($env->key.'='.$env->real_value); $envs->push($env->key.'='.$env->real_value);
} }
// Add PORT if not exists, use the first port as default // Add PORT if not exists, use the first port as default
if ($this->build_pack !== 'dockercompose') { if ($this->build_pack !== 'dockercompose') {
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) { if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}"); $envs->push("PORT={$ports[0]}");
} }
} }
// Add HOST if not exists // Add HOST if not exists
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) { if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
$envs->push('HOST=0.0.0.0'); $envs->push('HOST=0.0.0.0');
} }
} else {
$this->env_filename = '.env';
// Generate SERVICE_ variables first for dockercompose preview
if ($this->build_pack === 'dockercompose') { if ($this->build_pack === 'dockercompose') {
$domains = collect(json_decode(data_get($this->preview, 'docker_compose_domains'))) ?? collect([]); $domains = collect(json_decode(data_get($this->preview, 'docker_compose_domains'))) ?? collect([]);
@@ -1001,6 +1004,34 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$envs->push('SERVICE_NAME_'.str($rawServiceName)->upper().'='.addPreviewDeploymentSuffix($rawServiceName, $this->pull_request_id)); $envs->push('SERVICE_NAME_'.str($rawServiceName)->upper().'='.addPreviewDeploymentSuffix($rawServiceName, $this->pull_request_id));
} }
} }
// Filter out buildtime-only variables from runtime environment for preview
$runtime_environment_variables_preview = $sorted_environment_variables_preview->filter(function ($env) {
return ! $env->is_buildtime_only;
});
// Sort runtime environment variables: those referencing SERVICE_ variables come after others
$runtime_environment_variables_preview = $runtime_environment_variables_preview->sortBy(function ($env) {
if (str($env->value)->startsWith('$SERVICE_') || str($env->value)->contains('${SERVICE_')) {
return 2;
}
return 1;
});
foreach ($runtime_environment_variables_preview as $env) {
$envs->push($env->key.'='.$env->real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->build_pack !== 'dockercompose') {
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
}
// Add HOST if not exists
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
$envs->push('HOST=0.0.0.0');
}
} }
if ($envs->isEmpty()) { if ($envs->isEmpty()) {
if ($this->env_filename) { if ($this->env_filename) {
@@ -1033,17 +1064,27 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} }
$this->env_filename = null; $this->env_filename = null;
} else { } else {
$envs_content = $envs->implode("\n"); $envs_base64 = base64_encode($envs->implode("\n"));
transfer_file_to_container($envs_content, "/artifacts/{$this->env_filename}", $this->deployment_uuid, $this->server); $this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null"),
],
// Save the env filename with preview deployment suffix );
$env_filename = addPreviewDeploymentSuffix($this->env_filename, $this->pull_request_id);
if ($this->use_build_server) { if ($this->use_build_server) {
$this->server = $this->original_server; $this->server = $this->original_server;
transfer_file_to_server($envs_content, "$this->configuration_dir/{$env_filename}", $this->server); $this->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null",
]
);
$this->server = $this->build_server; $this->server = $this->build_server;
} else { } else {
transfer_file_to_server($envs_content, "$this->configuration_dir/{$env_filename}", $this->server); $this->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null",
]
);
} }
} }
$this->environment_variables = $envs; $this->environment_variables = $envs;
@@ -1436,11 +1477,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} }
$private_key = data_get($this->application, 'private_key.private_key'); $private_key = data_get($this->application, 'private_key.private_key');
if ($private_key) { if ($private_key) {
$this->execute_remote_command([ $private_key = base64_encode($private_key);
executeInDocker($this->deployment_uuid, 'mkdir -p /root/.ssh'),
]);
transfer_file_to_container($private_key, '/root/.ssh/id_rsa', $this->deployment_uuid, $this->server);
$this->execute_remote_command( $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'), executeInDocker($this->deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'),
], ],
@@ -1845,7 +1889,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
], ],
]; ];
if (filled($this->env_filename)) { if (filled($this->env_filename)) {
$docker_compose['services'][$this->container_name]['env_file'] = ["/artifacts/{$this->env_filename}"]; $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
} }
$docker_compose['services'][$this->container_name]['healthcheck'] = [ $docker_compose['services'][$this->container_name]['healthcheck'] = [
'test' => [ 'test' => [
@@ -2002,7 +2046,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->docker_compose = Yaml::dump($docker_compose, 10); $this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose); $this->docker_compose_base64 = base64_encode($this->docker_compose);
transfer_file_to_container(base64_decode($this->docker_compose_base64), "{$this->workdir}/docker-compose.yaml", $this->deployment_uuid, $this->server); $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]);
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()
@@ -2130,8 +2174,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else { } else {
if ($this->application->build_pack === 'nixpacks') { if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan); $this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$nixpacks_content = base64_decode($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]);
transfer_file_to_container($nixpacks_content, '/artifacts/thegameplan.json', $this->deployment_uuid, $this->server);
if ($this->force_rebuild) { if ($this->force_rebuild) {
$this->execute_remote_command([ $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}"), 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}"),
@@ -2155,7 +2198,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
$this->execute_remote_command( $this->execute_remote_command(
[ [
transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true, 'hidden' => true,
], ],
[ [
@@ -2178,7 +2221,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$this->execute_remote_command( $this->execute_remote_command(
[ [
transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true, 'hidden' => true,
], ],
[ [
@@ -2210,13 +2253,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
$this->execute_remote_command( $this->execute_remote_command(
[ [
transfer_file_to_container(base64_decode($dockerfile), "{$this->workdir}/Dockerfile", $this->deployment_uuid, $this->server), executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null"),
], ],
[ [
transfer_file_to_container(base64_decode($nginx_config), "{$this->workdir}/nginx.conf", $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($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true, 'hidden' => true,
], ],
[ [
@@ -2239,7 +2282,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
$this->execute_remote_command( $this->execute_remote_command(
[ [
transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true, 'hidden' => true,
], ],
[ [
@@ -2254,8 +2297,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else { } else {
if ($this->application->build_pack === 'nixpacks') { if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan); $this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$nixpacks_content = base64_decode($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]);
transfer_file_to_container($nixpacks_content, '/artifacts/thegameplan.json', $this->deployment_uuid, $this->server);
if ($this->force_rebuild) { if ($this->force_rebuild) {
$this->execute_remote_command([ $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}"), 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}"),
@@ -2278,7 +2320,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
$this->execute_remote_command( $this->execute_remote_command(
[ [
transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true, 'hidden' => true,
], ],
[ [
@@ -2301,7 +2343,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$this->execute_remote_command( $this->execute_remote_command(
[ [
transfer_file_to_container(base64_decode($base64_build_command), '/artifacts/build.sh', $this->deployment_uuid, $this->server), executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true, 'hidden' => true,
], ],
[ [
@@ -2434,7 +2476,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$dockerfile_base64 = base64_encode($dockerfile->implode("\n")); $dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
$this->execute_remote_command([ $this->execute_remote_command([
transfer_file_to_container(base64_decode($dockerfile_base64), "{$this->workdir}{$this->dockerfile_location}", $this->deployment_uuid, $this->server), executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"),
'hidden' => true, 'hidden' => true,
]); ]);
} }

View File

@@ -232,12 +232,8 @@ EOD;
break; break;
} }
$this->importCommands[] = [ $restoreCommandBase64 = base64_encode($restoreCommand);
'transfer_file' => [ $this->importCommands[] = "echo \"{$restoreCommandBase64}\" | base64 -d > {$scriptPath}";
'content' => $restoreCommand,
'destination' => $scriptPath,
],
];
$this->importCommands[] = "chmod +x {$scriptPath}"; $this->importCommands[] = "chmod +x {$scriptPath}";
$this->importCommands[] = "docker cp {$scriptPath} {$this->container}:{$scriptPath}"; $this->importCommands[] = "docker cp {$scriptPath} {$this->container}:{$scriptPath}";

View File

@@ -78,7 +78,10 @@ class NewDynamicConfiguration extends Component
$yaml = Yaml::dump($yaml, 10, 2); $yaml = Yaml::dump($yaml, 10, 2);
$this->value = $yaml; $this->value = $yaml;
} }
transfer_file_to_server($this->value, $file, $this->server); $base64_value = base64_encode($this->value);
instant_remote_process([
"echo '{$base64_value}' | base64 -d | tee {$file} > /dev/null",
], $this->server);
if ($proxy_type === 'CADDY') { if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy(); $this->server->reloadCaddy();
} }

View File

@@ -1073,20 +1073,26 @@ class Application extends BaseModel
if (is_null($private_key)) { if (is_null($private_key)) {
throw new RuntimeException('Private key not found. Please add a private key to the application and try again.'); throw new RuntimeException('Private key not found. Please add a private key to the application and try again.');
} }
$private_key = base64_encode($private_key);
$base_comamnd = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$base_command} {$customRepository}"; $base_comamnd = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$base_command} {$customRepository}";
$commands = collect([]); if ($exec_in_docker) {
$commands = collect([
executeInDocker($deployment_uuid, 'mkdir -p /root/.ssh'),
executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
executeInDocker($deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'),
]);
} else {
$commands = collect([
'mkdir -p /root/.ssh',
"echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null",
'chmod 600 /root/.ssh/id_rsa',
]);
}
if ($exec_in_docker) { if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, 'mkdir -p /root/.ssh'));
// SSH key transfer handled by ApplicationDeploymentJob, assume key is already in container
$commands->push(executeInDocker($deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'));
$commands->push(executeInDocker($deployment_uuid, $base_comamnd)); $commands->push(executeInDocker($deployment_uuid, $base_comamnd));
} else { } else {
$server = $this->destination->server;
$commands->push('mkdir -p /root/.ssh');
transfer_file_to_server($private_key, '/root/.ssh/id_rsa', $server);
$commands->push('chmod 600 /root/.ssh/id_rsa');
$commands->push($base_comamnd); $commands->push($base_comamnd);
} }
@@ -1212,6 +1218,7 @@ class Application extends BaseModel
if (is_null($private_key)) { if (is_null($private_key)) {
throw new RuntimeException('Private key not found. Please add a private key to the application and try again.'); throw new RuntimeException('Private key not found. Please add a private key to the application and try again.');
} }
$private_key = base64_encode($private_key);
$escapedCustomRepository = escapeshellarg($customRepository); $escapedCustomRepository = escapeshellarg($customRepository);
$git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}"; $git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
if ($only_checkout) { if ($only_checkout) {
@@ -1219,18 +1226,18 @@ class Application extends BaseModel
} else { } else {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base); $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base);
} }
$commands = collect([]);
if ($exec_in_docker) { if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, 'mkdir -p /root/.ssh')); $commands = collect([
// SSH key transfer handled by ApplicationDeploymentJob, assume key is already in container executeInDocker($deployment_uuid, 'mkdir -p /root/.ssh'),
$commands->push(executeInDocker($deployment_uuid, 'chmod 600 /root/.ssh/id_rsa')); executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
executeInDocker($deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'),
]);
} else { } else {
$server = $this->destination->server; $commands = collect([
$commands->push('mkdir -p /root/.ssh'); 'mkdir -p /root/.ssh',
transfer_file_to_server($private_key, '/root/.ssh/id_rsa', $server); "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null",
$commands->push('chmod 600 /root/.ssh/id_rsa'); 'chmod 600 /root/.ssh/id_rsa',
]);
} }
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
if ($git_type === 'gitlab') { if ($git_type === 'gitlab') {
@@ -1563,7 +1570,19 @@ class Application extends BaseModel
if (is_null($this->watch_paths)) { if (is_null($this->watch_paths)) {
return false; return false;
} }
$watch_paths = collect(explode("\n", $this->watch_paths)); $watch_paths = collect(explode("\n", $this->watch_paths))
->map(function (string $path): string {
return trim($path);
})
->filter(function (string $path): bool {
return strlen($path) > 0;
});
// If no valid patterns after filtering, don't trigger
if ($watch_paths->isEmpty()) {
return false;
}
$matches = $modified_files->filter(function ($file) use ($watch_paths) { $matches = $modified_files->filter(function ($file) use ($watch_paths) {
return $watch_paths->contains(function ($glob) use ($file) { return $watch_paths->contains(function ($glob) use ($file) {
return fnmatch($glob, $file); return fnmatch($glob, $file);

View File

@@ -159,7 +159,8 @@ class LocalFileVolume extends BaseModel
$chmod = data_get($this, 'chmod'); $chmod = data_get($this, 'chmod');
$chown = data_get($this, 'chown'); $chown = data_get($this, 'chown');
if ($content) { if ($content) {
transfer_file_to_server($content, $path, $server); $content = base64_encode($content);
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
} else { } else {
$commands->push("touch $path"); $commands->push("touch $path");
} }
@@ -174,10 +175,8 @@ class LocalFileVolume extends BaseModel
$commands->push("mkdir -p $path > /dev/null 2>&1 || true"); $commands->push("mkdir -p $path > /dev/null 2>&1 || true");
} }
if ($commands->count() > 0) {
return instant_remote_process($commands, $server); return instant_remote_process($commands, $server);
} }
}
// Accessor for convenient access // Accessor for convenient access
protected function plainMountPath(): Attribute protected function plainMountPath(): Attribute

View File

@@ -309,7 +309,10 @@ class Server extends BaseModel
$conf = Yaml::dump($dynamic_conf, 12, 2); $conf = Yaml::dump($dynamic_conf, 12, 2);
} }
$conf = $banner.$conf; $conf = $banner.$conf;
transfer_file_to_server($conf, $default_redirect_file, $this); $base64 = base64_encode($conf);
instant_remote_process([
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this);
} }
if ($proxy_type === 'CADDY') { if ($proxy_type === 'CADDY') {
@@ -443,10 +446,11 @@ class Server extends BaseModel
"# Do not edit it manually (only if you know what are you doing).\n\n". "# Do not edit it manually (only if you know what are you doing).\n\n".
$yaml; $yaml;
$base64 = base64_encode($yaml);
instant_remote_process([ instant_remote_process([
"mkdir -p $dynamic_config_path", "mkdir -p $dynamic_config_path",
"echo '$base64' | base64 -d | tee $file > /dev/null",
], $this); ], $this);
transfer_file_to_server($yaml, $file, $this);
} }
} elseif ($this->proxyType() === 'CADDY') { } elseif ($this->proxyType() === 'CADDY') {
$file = "$dynamic_config_path/coolify.caddy"; $file = "$dynamic_config_path/coolify.caddy";
@@ -469,7 +473,10 @@ $schema://$host {
} }
reverse_proxy coolify:8080 reverse_proxy coolify:8080
}"; }";
transfer_file_to_server($caddy_file, $file, $this); $base64 = base64_encode($caddy_file);
instant_remote_process([
"echo '$base64' | base64 -d | tee $file > /dev/null",
], $this);
$this->reloadCaddy(); $this->reloadCaddy();
} }
} }
@@ -1075,6 +1082,7 @@ $schema://$host {
public function validateConnection(bool $justCheckingNewKey = false) public function validateConnection(bool $justCheckingNewKey = false)
{ {
ray('validateConnection', $this->id);
$this->disableSshMux(); $this->disableSshMux();
if ($this->skipServer()) { if ($this->skipServer()) {
@@ -1312,6 +1320,7 @@ $schema://$host {
public function generateCaCertificate() public function generateCaCertificate()
{ {
try { try {
ray('Generating CA certificate for server', $this->id);
SslHelper::generateSslCertificate( SslHelper::generateSslCertificate(
commonName: 'Coolify CA Certificate', commonName: 'Coolify CA Certificate',
serverId: $this->id, serverId: $this->id,
@@ -1319,6 +1328,7 @@ $schema://$host {
validityDays: 10 * 365 validityDays: 10 * 365
); );
$caCertificate = SslCertificate::where('server_id', $this->id)->where('is_ca_certificate', true)->first(); $caCertificate = SslCertificate::where('server_id', $this->id)->where('is_ca_certificate', true)->first();
ray('CA certificate generated', $caCertificate);
if ($caCertificate) { if ($caCertificate) {
$certificateContent = $caCertificate->ssl_certificate; $certificateContent = $caCertificate->ssl_certificate;
$caCertPath = config('constants.coolify.base_config_path').'/ssl/'; $caCertPath = config('constants.coolify.base_config_path').'/ssl/';

View File

@@ -1280,10 +1280,8 @@ class Service extends BaseModel
if ($envs->count() === 0) { if ($envs->count() === 0) {
$commands[] = 'touch .env'; $commands[] = 'touch .env';
} else { } else {
$envs_content = $envs->implode("\n"); $envs_base64 = base64_encode($envs->implode("\n"));
transfer_file_to_server($envs_content, $this->workdir().'/.env', $this->server); $commands[] = "echo '$envs_base64' | base64 -d | tee .env > /dev/null";
return;
} }
instant_remote_process($commands, $this->server); instant_remote_process($commands, $this->server);

View File

@@ -1069,9 +1069,9 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
} }
} }
} }
$compose_content = Yaml::dump($yaml_compose); $base64_compose = base64_encode(Yaml::dump($yaml_compose));
transfer_file_to_server($compose_content, "/tmp/{$uuid}.yml", $server);
instant_remote_process([ instant_remote_process([
"echo {$base64_compose} | base64 -d | tee /tmp/{$uuid}.yml > /dev/null",
"chmod 600 /tmp/{$uuid}.yml", "chmod 600 /tmp/{$uuid}.yml",
"docker compose -f /tmp/{$uuid}.yml config --no-interpolate --no-path-resolution -q", "docker compose -f /tmp/{$uuid}.yml config --no-interpolate --no-path-resolution -q",
"rm /tmp/{$uuid}.yml", "rm /tmp/{$uuid}.yml",

View File

@@ -29,31 +29,11 @@ function remote_process(
$type = $type ?? ActivityTypes::INLINE->value; $type = $type ?? ActivityTypes::INLINE->value;
$command = $command instanceof Collection ? $command->toArray() : $command; $command = $command instanceof Collection ? $command->toArray() : $command;
// 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;
}
}
if ($server->isNonRoot()) { if ($server->isNonRoot()) {
$processed_commands = parseCommandsByLineForSudo(collect($processed_commands), $server); $command = parseCommandsByLineForSudo(collect($command), $server);
} }
$command_string = implode("\n", $processed_commands); $command_string = implode("\n", $command);
if (Auth::check()) { if (Auth::check()) {
$teams = Auth::user()->teams->pluck('id'); $teams = Auth::user()->teams->pluck('id');
@@ -200,30 +180,10 @@ function instant_remote_process(Collection|array $command, Server $server, bool
{ {
$command = $command instanceof Collection ? $command->toArray() : $command; $command = $command instanceof Collection ? $command->toArray() : $command;
// 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;
}
}
if ($server->isNonRoot() && ! $no_sudo) { if ($server->isNonRoot() && ! $no_sudo) {
$processed_commands = parseCommandsByLineForSudo(collect($processed_commands), $server); $command = parseCommandsByLineForSudo(collect($command), $server);
} }
$command_string = implode("\n", $processed_commands); $command_string = implode("\n", $command);
return \App\Helpers\SshRetryHandler::retry( return \App\Helpers\SshRetryHandler::retry(
function () use ($server, $command_string) { function () use ($server, $command_string) {

View File

@@ -69,11 +69,12 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
$fileVolume->content = $content; $fileVolume->content = $content;
$fileVolume->is_directory = false; $fileVolume->is_directory = false;
$fileVolume->save(); $fileVolume->save();
$content = base64_encode($content);
$dir = str($fileLocation)->dirname(); $dir = str($fileLocation)->dirname();
instant_remote_process([ instant_remote_process([
"mkdir -p $dir", "mkdir -p $dir",
"echo '$content' | base64 -d | tee $fileLocation",
], $server); ], $server);
transfer_file_to_server($content, $fileLocation, $server);
} elseif ($isFile === 'NOK' && $isDir === 'NOK' && $fileVolume->is_directory && $isInit) { } elseif ($isFile === 'NOK' && $isDir === 'NOK' && $fileVolume->is_directory && $isInit) {
// Does not exists (no dir or file), flagged as directory, is init // Does not exists (no dir or file), flagged as directory, is init
$fileVolume->content = null; $fileVolume->content = null;

View File

@@ -1125,30 +1125,77 @@ function get_public_ips()
function isAnyDeploymentInprogress() function isAnyDeploymentInprogress()
{ {
$runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get(); $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get();
$basicDetails = $runningJobs->map(function ($job) {
return [ if ($runningJobs->isEmpty()) {
'id' => $job->id, echo "No deployments in progress.\n";
'created_at' => $job->created_at, exit(0);
'application_id' => $job->application_id, }
'server_id' => $job->server_id,
'horizon_job_id' => $job->horizon_job_id,
'status' => $job->status,
];
});
echo 'Running jobs: '.json_encode($basicDetails)."\n";
$horizonJobIds = []; $horizonJobIds = [];
$deploymentDetails = [];
foreach ($runningJobs as $runningJob) { foreach ($runningJobs as $runningJob) {
$horizonJobStatus = getJobStatus($runningJob->horizon_job_id); $horizonJobStatus = getJobStatus($runningJob->horizon_job_id);
if ($horizonJobStatus === 'unknown' || $horizonJobStatus === 'reserved') { if ($horizonJobStatus === 'unknown' || $horizonJobStatus === 'reserved') {
$horizonJobIds[] = $runningJob->horizon_job_id; $horizonJobIds[] = $runningJob->horizon_job_id;
// Get application and team information
$application = Application::find($runningJob->application_id);
$teamMembers = [];
$deploymentUrl = '';
if ($application) {
// Get team members through the application's project
$team = $application->team();
if ($team) {
$teamMembers = $team->members()->pluck('email')->toArray();
}
// Construct the full deployment URL
if ($runningJob->deployment_url) {
$baseUrl = base_url();
$deploymentUrl = $baseUrl.$runningJob->deployment_url;
} }
} }
$deploymentDetails[] = [
'id' => $runningJob->id,
'application_name' => $runningJob->application_name ?? 'Unknown',
'server_name' => $runningJob->server_name ?? 'Unknown',
'deployment_url' => $deploymentUrl,
'team_members' => $teamMembers,
'created_at' => $runningJob->created_at->format('Y-m-d H:i:s'),
'horizon_job_id' => $runningJob->horizon_job_id,
];
}
}
if (count($horizonJobIds) === 0) { if (count($horizonJobIds) === 0) {
echo "No deployments in progress.\n"; echo "No active deployments in progress (all jobs completed or failed).\n";
exit(0); exit(0);
} }
$horizonJobIds = collect($horizonJobIds)->unique()->toArray();
echo 'There are '.count($horizonJobIds)." deployments in progress.\n"; // Display enhanced deployment information
echo "\n=== Running Deployments ===\n";
echo 'Total active deployments: '.count($horizonJobIds)."\n\n";
foreach ($deploymentDetails as $index => $deployment) {
echo 'Deployment #'.($index + 1).":\n";
echo ' Application: '.$deployment['application_name']."\n";
echo ' Server: '.$deployment['server_name']."\n";
echo ' Started: '.$deployment['created_at']."\n";
if ($deployment['deployment_url']) {
echo ' URL: '.$deployment['deployment_url']."\n";
}
if (! empty($deployment['team_members'])) {
echo ' Team members: '.implode(', ', $deployment['team_members'])."\n";
} else {
echo " Team members: No team members found\n";
}
echo ' Horizon Job ID: '.$deployment['horizon_job_id']."\n";
echo "\n";
}
exit(1); exit(1);
} }

View File

@@ -2,8 +2,8 @@
return [ return [
'coolify' => [ 'coolify' => [
'version' => '4.0.0-beta.427', 'version' => '4.0.0-beta.428',
'helper_version' => '1.0.10', 'helper_version' => '1.0.11',
'realtime_version' => '1.0.10', 'realtime_version' => '1.0.10',
'self_hosted' => env('SELF_HOSTED', true), 'self_hosted' => env('SELF_HOSTED', true),
'autoupdate' => env('AUTOUPDATE'), 'autoupdate' => env('AUTOUPDATE'),

View File

@@ -7,7 +7,7 @@
"dependencies": { "dependencies": {
"@xterm/addon-fit": "0.10.0", "@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "5.5.0", "@xterm/xterm": "5.5.0",
"axios": "1.8.4", "axios": "1.12.0",
"cookie": "1.0.2", "cookie": "1.0.2",
"dotenv": "16.5.0", "dotenv": "16.5.0",
"node-pty": "1.0.0", "node-pty": "1.0.0",
@@ -36,13 +36,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.8.4", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.4",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },

View File

@@ -5,7 +5,7 @@
"@xterm/addon-fit": "0.10.0", "@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "5.5.0", "@xterm/xterm": "5.5.0",
"cookie": "1.0.2", "cookie": "1.0.2",
"axios": "1.8.4", "axios": "1.12.0",
"dotenv": "16.5.0", "dotenv": "16.5.0",
"node-pty": "1.0.0", "node-pty": "1.0.0",
"ws": "8.18.1" "ws": "8.18.1"

View File

@@ -1,13 +1,13 @@
{ {
"coolify": { "coolify": {
"v4": { "v4": {
"version": "4.0.0-beta.427"
},
"nightly": {
"version": "4.0.0-beta.428" "version": "4.0.0-beta.428"
}, },
"nightly": {
"version": "4.0.0-beta.429"
},
"helper": { "helper": {
"version": "1.0.10" "version": "1.0.11"
}, },
"realtime": { "realtime": {
"version": "1.0.10" "version": "1.0.10"

View File

@@ -1,13 +1,13 @@
{ {
"coolify": { "coolify": {
"v4": { "v4": {
"version": "4.0.0-beta.427"
},
"nightly": {
"version": "4.0.0-beta.428" "version": "4.0.0-beta.428"
}, },
"nightly": {
"version": "4.0.0-beta.429"
},
"helper": { "helper": {
"version": "1.0.10" "version": "1.0.11"
}, },
"realtime": { "realtime": {
"version": "1.0.10" "version": "1.0.10"