diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml index 36321d96b..3709b5b46 100644 --- a/.github/workflows/production-build.yml +++ b/.github/workflows/production-build.yml @@ -3,6 +3,8 @@ name: Production Build (v4) on: push: branches: ["main"] + paths-ignore: + - templates/service-templates.json env: REGISTRY: ghcr.io diff --git a/app/Actions/Database/StartClickhouse.php b/app/Actions/Database/StartClickhouse.php index fb38c32d5..d043da410 100644 --- a/app/Actions/Database/StartClickhouse.php +++ b/app/Actions/Database/StartClickhouse.php @@ -99,7 +99,7 @@ class StartClickhouse } $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); - $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; diff --git a/app/Actions/Database/StartDatabaseProxy.php b/app/Actions/Database/StartDatabaseProxy.php index 89bee1d5a..a1e47710c 100644 --- a/app/Actions/Database/StartDatabaseProxy.php +++ b/app/Actions/Database/StartDatabaseProxy.php @@ -149,9 +149,9 @@ class StartDatabaseProxy instant_remote_process(["docker rm -f $proxyContainerName"], $server, false); instant_remote_process([ "mkdir -p $configuration_dir", - "echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile", - "echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf", - "echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml", + "echo '{$dockerfile_base64}' | base64 -d | tee $configuration_dir/Dockerfile > /dev/null", + "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", "docker compose --project-directory {$configuration_dir} pull", "docker compose --project-directory {$configuration_dir} up --build -d", ], $server); diff --git a/app/Actions/Database/StartDragonfly.php b/app/Actions/Database/StartDragonfly.php index 4fb75b3cb..bb71d8c48 100644 --- a/app/Actions/Database/StartDragonfly.php +++ b/app/Actions/Database/StartDragonfly.php @@ -100,7 +100,7 @@ class StartDragonfly } $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); - $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; diff --git a/app/Actions/Database/StartKeydb.php b/app/Actions/Database/StartKeydb.php index 7b4bbe124..489c74053 100644 --- a/app/Actions/Database/StartKeydb.php +++ b/app/Actions/Database/StartKeydb.php @@ -107,7 +107,7 @@ class StartKeydb } $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); - $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index 755c47867..e02b28b2e 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -100,7 +100,7 @@ class StartMariadb } $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); - $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; @@ -171,6 +171,6 @@ class StartMariadb $filename = 'custom-config.cnf'; $content = $this->database->mariadb_conf; $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; + $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; } } diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 180ae988a..7bb6cbcd0 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -116,7 +116,7 @@ class StartMongodb $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); - $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; @@ -184,13 +184,13 @@ class StartMongodb $filename = 'mongod.conf'; $content = $this->database->mongo_conf; $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; + $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; } 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 > $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js"; + $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null"; } } diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index 0e2e75719..b3f695d72 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -100,7 +100,7 @@ class StartMysql } $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); - $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; @@ -171,6 +171,6 @@ class StartMysql $filename = 'custom-config.cnf'; $content = $this->database->mysql_conf; $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; + $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; } } diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 03b460edc..f19a8b036 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -122,7 +122,7 @@ class StartPostgresql } $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); - $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; @@ -197,7 +197,7 @@ class StartPostgresql $filename = data_get($init_script, 'filename'); $content = data_get($init_script, 'content'); $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; + $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/{$filename} > /dev/null"; $this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; } } @@ -209,6 +209,6 @@ class StartPostgresql $filename = 'custom-postgres.conf'; $content = $this->database->postgres_conf; $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; + $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; } } diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index ff8c63aab..01e9a9bef 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -111,7 +111,7 @@ class StartRedis } $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); - $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $readme = generate_readme_file($this->database->name, now()); $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; diff --git a/app/Actions/Proxy/CheckConfiguration.php b/app/Actions/Proxy/CheckConfiguration.php index 208a6863a..1058e8b5f 100644 --- a/app/Actions/Proxy/CheckConfiguration.php +++ b/app/Actions/Proxy/CheckConfiguration.php @@ -16,11 +16,11 @@ class CheckConfiguration return 'OK'; } $proxy_path = $server->proxyPath(); - - $proxy_configuration = instant_remote_process([ + $payload = [ "mkdir -p $proxy_path", "cat $proxy_path/docker-compose.yml", - ], $server, false); + ]; + $proxy_configuration = instant_remote_process($payload, $server, false); if ($reset || !$proxy_configuration || is_null($proxy_configuration)) { $proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value; diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index d39b33a2a..9323ca1d1 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -13,8 +13,9 @@ class CheckProxy if ($server->proxyType() === 'NONE') { return false; } - if (!$server->validateConnection()) { - throw new \Exception("Server Connection Error"); + ['uptime' => $uptime, 'error' => $error] = $server->validateConnection(); + if (!$uptime) { + throw new \Exception($error); } if (!$server->isProxyShouldRun()) { if ($fromUI) { diff --git a/app/Actions/Proxy/SaveConfiguration.php b/app/Actions/Proxy/SaveConfiguration.php index 5fb983d1a..4c413ca36 100644 --- a/app/Actions/Proxy/SaveConfiguration.php +++ b/app/Actions/Proxy/SaveConfiguration.php @@ -23,7 +23,7 @@ class SaveConfiguration return instant_remote_process([ "mkdir -p $proxy_path", - "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", + "echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null", ], $server); } } diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index ab635fc65..5b1ffa409 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -30,16 +30,18 @@ class StartProxy $server->save(); if ($server->isSwarm()) { $commands = $commands->merge([ - "mkdir -p $proxy_path/dynamic && cd $proxy_path", + "mkdir -p $proxy_path/dynamic", + "cd $proxy_path", "echo 'Creating required Docker Compose file.'", "echo 'Starting coolify-proxy.'", - "cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy", + "docker stack deploy -c docker-compose.yml coolify-proxy", "echo 'Proxy started successfully.'" ]); } else { $caddfile = "import /dynamic/*.caddy"; $commands = $commands->merge([ - "mkdir -p $proxy_path/dynamic && cd $proxy_path", + "mkdir -p $proxy_path/dynamic", + "cd $proxy_path", "echo '$caddfile' > $proxy_path/dynamic/Caddyfile", "echo 'Creating required Docker Compose file.'", "echo 'Pulling docker image.'", diff --git a/app/Actions/Server/ConfigureCloudflared.php b/app/Actions/Server/ConfigureCloudflared.php index 6b08b3de6..3221557ae 100644 --- a/app/Actions/Server/ConfigureCloudflared.php +++ b/app/Actions/Server/ConfigureCloudflared.php @@ -29,8 +29,9 @@ class ConfigureCloudflared $config = Yaml::dump($config, 12, 2); $docker_compose_yml_base64 = base64_encode($config); $commands = collect([ - "mkdir -p /tmp/cloudflared && cd /tmp/cloudflared", - "echo '$docker_compose_yml_base64' | base64 -d > docker-compose.yml", + "mkdir -p /tmp/cloudflared", + "cd /tmp/cloudflared", + "echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null", "docker compose pull", "docker compose down -v --remove-orphans > /dev/null 2>&1", "docker compose up -d --remove-orphans", @@ -39,6 +40,11 @@ class ConfigureCloudflared } catch (\Throwable $e) { ray($e); throw $e; + } finally { + $commands = collect([ + "rm -fr /tmp/cloudflared", + ]); + instant_remote_process($commands, $server); } } } diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index 1db766a88..721d174b8 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -48,20 +48,28 @@ class InstallDocker if ($supported_os_type->contains('debian')) { $command = $command->merge([ "echo 'Installing Prerequisites...'", - "command -v jq >/dev/null || apt-get update -y", - "command -v jq >/dev/null || apt install -y curl wget git jq", - + "apt-get update -y", + "command -v curl >/dev/null || apt install -y curl", + "command -v wget >/dev/null || apt install -y wget", + "command -v git >/dev/null || apt install -y git", + "command -v jq >/dev/null || apt install -y jq", ]); } else if ($supported_os_type->contains('rhel')) { $command = $command->merge([ "echo 'Installing Prerequisites...'", - "command -v jq >/dev/null || dnf install -y curl wget git jq", + "command -v curl >/dev/null || dnf install -y curl", + "command -v wget >/dev/null || dnf install -y wget", + "command -v git >/dev/null || dnf install -y git", + "command -v jq >/dev/null || dnf install -y jq", ]); } else if ($supported_os_type->contains('sles')) { $command = $command->merge([ "echo 'Installing Prerequisites...'", - "command -v jq >/dev/null || zypper update -y", - "command -v jq >/dev/null || zypper install -y curl wget git jq", + "zypper update -y", + "command -v curl >/dev/null || zypper install -y curl", + "command -v wget >/dev/null || zypper install -y wget", + "command -v git >/dev/null || zypper install -y git", + "command -v jq >/dev/null || zypper install -y jq", ]); } else { throw new \Exception('Unsupported OS'); @@ -70,10 +78,13 @@ class InstallDocker "echo 'Installing Docker Engine...'", "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\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json", - "echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify", - "cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify", - "cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json", + "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", + "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", + "mv /etc/docker/daemon.json.appended /etc/docker/daemon.json", "echo 'Restarting Docker Engine...'", "systemctl enable docker >/dev/null 2>&1 || true", "systemctl restart docker", diff --git a/app/Actions/Server/InstallLogDrain.php b/app/Actions/Server/InstallLogDrain.php index 86ca69a6e..aa32d4c0b 100644 --- a/app/Actions/Server/InstallLogDrain.php +++ b/app/Actions/Server/InstallLogDrain.php @@ -168,10 +168,10 @@ Files: $command = [ "echo 'Saving configuration'", "mkdir -p $config_path", - "echo '{$parsers}' | base64 -d > $parsers_config", - "echo '{$config}' | base64 -d > $fluent_bit_config", - "echo '{$compose}' | base64 -d > $compose_path", - "echo '{$readme}' | base64 -d > $readme_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", "test -f $config_path/.env && rm $config_path/.env", ]; diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 92cbf9c5c..31760f2fd 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -31,7 +31,7 @@ class Kernel extends ConsoleKernel $this->check_scheduled_backups($schedule); $this->check_resources($schedule); $this->check_scheduled_backups($schedule); - $this->pull_helper_image($schedule); + // $this->pull_helper_image($schedule); $this->check_scheduled_tasks($schedule); $schedule->command('uploads:clear')->everyTwoMinutes(); } else { diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 2584df48b..5c8827085 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -56,7 +56,7 @@ class Handler extends ExceptionHandler { $this->reportable(function (Throwable $e) { if (isDev()) { - // return; + return; } if ($e instanceof RuntimeException) { return; diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 5cc5c4fb5..52afdd47e 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -49,6 +49,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private string $deployment_uuid; private int $pull_request_id; private string $commit; + private bool $rollback; private bool $force_rebuild; private bool $restart_only; @@ -117,6 +118,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; $this->pull_request_id = $this->application_deployment_queue->pull_request_id; $this->commit = $this->application_deployment_queue->commit; + $this->rollback = $this->application_deployment_queue->rollback; $this->force_rebuild = $this->application_deployment_queue->force_rebuild; $this->restart_only = $this->application_deployment_queue->restart_only; $this->only_this_server = $this->application_deployment_queue->only_this_server; @@ -322,7 +324,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->prepare_builder_image(); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > {$this->workdir}{$this->dockerfile_location}") + executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null") ], ); $this->generate_image_names(); @@ -391,7 +393,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } $this->docker_compose_base64 = base64_encode($yaml); $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true + executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true ]); $this->save_environment_variables(); // Build new container to limit downtime. @@ -565,7 +567,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted "mkdir -p $this->configuration_dir" ], [ - "echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName", + "echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null", ], [ "echo '{$readme}' > $this->configuration_dir/README.md", @@ -708,19 +710,78 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private function save_environment_variables() { $envs = collect([]); + $ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array; if ($this->pull_request_id !== 0) { $this->env_filename = ".env-pr-$this->pull_request_id"; foreach ($this->application->environment_variables_preview as $env) { - $envs->push($env->key . '=' . $env->real_value); + $real_value = $env->real_value; + if ($env->version === '4.0.0-beta.239') { + $real_value = $env->real_value; + } else { + $real_value = escapeEnvVariables($env->real_value); + } + if ($env->is_literal) { + $real_value = '\'' . $real_value . '\''; + } + $envs->push($env->key . '=' . $real_value); } + // Add PORT if not exists, use the first port as default + if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) { + $envs->push("PORT={$ports[0]}"); + } + // Add HOST if not exists + if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) { + $envs->push("HOST=0.0.0.0"); + } + if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) { + if (!is_null($this->commit)) { + $envs->push("SOURCE_COMMIT={$this->commit}"); + } else { + $envs->push("SOURCE_COMMIT=unknown"); + } + } + $envs = $envs->sort(function ($a, $b) { + return strpos($a, '$') === false ? -1 : 1; + }); } else { $this->env_filename = ".env"; foreach ($this->application->environment_variables as $env) { - $envs->push($env->key . '=' . $env->real_value); + $real_value = $env->real_value; + if ($env->version === '4.0.0-beta.239') { + $real_value = $env->real_value; + } else { + $real_value = escapeEnvVariables($env->real_value); + } + if ($env->is_literal) { + $real_value = '\'' . $real_value . '\''; + } + $envs->push($env->key . '=' . $real_value); } + // Add PORT if not exists, use the first port as default + if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) { + $envs->push("PORT={$ports[0]}"); + } + // Add HOST if not exists + if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) { + $envs->push("HOST=0.0.0.0"); + } + if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) { + if (!is_null($this->commit)) { + $envs->push("SOURCE_COMMIT={$this->commit}"); + } else { + $envs->push("SOURCE_COMMIT=unknown"); + } + } + $envs = $envs->sort(function ($a, $b) { + return strpos($a, '$') === false ? -1 : 1; + }); } + if ($envs->isEmpty()) { $this->env_filename = null; + if ($this->use_build_server) { + $this->server = $this->original_server; + } $this->execute_remote_command( [ "command" => "rm -f $this->configuration_dir/{$this->env_filename}", @@ -728,17 +789,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted "ignore_errors" => true ] ); - return; + if ($this->use_build_server) { + $this->server = $this->build_server; + } + } 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") + ], + + ); + 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" + ] + ); + if ($this->use_build_server) { + $this->server = $this->build_server; + } } - $envs_base64 = base64_encode($envs->implode("\n")); - $this->execute_remote_command( - [ - executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env") - ], - [ - "echo '$envs_base64' | base64 -d > $this->configuration_dir/{$this->env_filename}" - ] - ); + // $this->execute_remote_command([ + // executeInDocker($this->deployment_uuid, "cat $this->workdir/.env 2>/dev/null || true"), + // "hidden" => true, + // "save" => "dotenv" + // ]); + // if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) { + // $base64_dotenv = base64_encode($this->saved_outputs->get('dotenv')->value()); + // $this->execute_remote_command( + // [ + // "echo '{$base64_dotenv}' | base64 -d | tee $this->configuration_dir/.env > /dev/null" + // ] + // ); + // } else { + // $this->execute_remote_command( + // [ + // "command" => "rm -f $this->configuration_dir/.env", + // "hidden" => true, + // "ignore_errors" => true + // ] + // ); + // } + } @@ -973,7 +1068,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh") ], [ - executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa") + 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") @@ -993,7 +1088,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ], ); } - if ($this->saved_outputs->get('git_commit_sha')) { + if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) { $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); } } @@ -1124,8 +1219,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } $persistent_storages = $this->generate_local_persistent_volumes(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); - $environment_variables = $this->generate_environment_variables($ports); - + // $environment_variables = $this->generate_environment_variables($ports); + $this->save_environment_variables(); if (data_get($this->application, 'custom_labels')) { $this->application->parseContainerLabels(); $labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels))); @@ -1159,8 +1254,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if ($this->pull_request_id !== 0) { $labels = collect(generateLabelsApplication($this->application, $this->preview)); } + $labels = $labels->map(function ($value, $key) { + return escapeDollarSign($value); + }); $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray(); - // Check for custom HEALTHCHECK $this->custom_healthcheck_found = false; if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) { @@ -1179,7 +1276,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted 'image' => $this->production_image_name, 'container_name' => $this->container_name, 'restart' => RESTART_MODE, - 'environment' => $environment_variables, 'expose' => $ports, 'networks' => [ $this->destination->network, @@ -1200,10 +1296,22 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ] ] ]; - if ($this->env_filename) { - $docker_compose['services'][$this->container_name]['env_file'] = [ - $this->env_filename - ]; + // if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) { + // if (data_get($docker_compose, "services.{$this->container_name}.env_file")) { + // $docker_compose['services'][$this->container_name]['env_file'][] = '.env'; + // } else { + // $docker_compose['services'][$this->container_name]['env_file'] = ['.env']; + // } + // } + // if ($this->env_filename) { + // if (data_get($docker_compose, "services.{$this->container_name}.env_file")) { + // $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename; + // } else { + // $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; + // } + // } + if (!is_null($this->env_filename)) { + $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; } if (!$this->custom_healthcheck_found) { $docker_compose['services'][$this->container_name]['healthcheck'] = [ @@ -1357,8 +1465,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $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 > {$this->workdir}/docker-compose.yml"), "hidden" => true]); - $this->save_environment_variables(); + $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), "hidden" => true]); } private function generate_local_persistent_volumes() @@ -1415,6 +1522,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } else { $real_value = escapeEnvVariables($env->real_value); } + if ($env->is_literal) { + $real_value = escapeDollarSign($real_value); + } $environment_variables->push("$env->key=$real_value"); } foreach ($this->application->nixpacks_environment_variables as $env) { @@ -1423,6 +1533,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } else { $real_value = escapeEnvVariables($env->real_value); } + if ($env->is_literal) { + $real_value = escapeDollarSign($real_value); + } $environment_variables->push("$env->key=$real_value"); } } else { @@ -1432,6 +1545,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } else { $real_value = escapeEnvVariables($env->real_value); } + if ($env->is_literal) { + $real_value = escapeDollarSign($real_value); + } $environment_variables->push("$env->key=$real_value"); } foreach ($this->application->nixpacks_environment_variables_preview as $env) { @@ -1440,6 +1556,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } else { $real_value = escapeEnvVariables($env->real_value); } + if ($env->is_literal) { + $real_value = escapeDollarSign($real_value); + } $environment_variables->push("$env->key=$real_value"); } } @@ -1539,7 +1658,7 @@ 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 > /artifacts/thegameplan.json"), "hidden" => true]); + $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]); 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}"), "hidden" => true @@ -1560,7 +1679,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true ], [ executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true @@ -1595,13 +1714,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 > {$this->workdir}/Dockerfile") + executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null") ], [ - executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf") + executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null") ], [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true ], [ executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true @@ -1614,7 +1733,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 > /artifacts/build.sh"), "hidden" => true + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true ], [ executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true @@ -1623,7 +1742,7 @@ 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 > /artifacts/thegameplan.json"), "hidden" => true]); + $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]); 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}"), "hidden" => true @@ -1644,7 +1763,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true ], [ executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true @@ -1762,7 +1881,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 > {$this->workdir}{$this->dockerfile_location}"), + executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"), "hidden" => true ]); } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 333d46027..a9cec009b 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -47,7 +47,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted if (!$this->server->isFunctional()) { return 'Server is not ready.'; }; - $applications = $this->server->applications(); $skip_these_applications = collect([]); foreach ($applications as $application) { @@ -78,6 +77,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted if (is_null($containers)) { return; } + $containers = format_docker_command_output_to_json($containers); if ($containerReplicates) { $containerReplicates = format_docker_command_output_to_json($containerReplicates); @@ -201,7 +201,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted // Notify user that this container should not be there. } } - } if (data_get($container, 'Name') === '/coolify-db') { $foundDatabases[] = 0; diff --git a/app/Jobs/ServerFilesFromServerJob.php b/app/Jobs/ServerFilesFromServerJob.php index b195e2b6d..978a3dc19 100644 --- a/app/Jobs/ServerFilesFromServerJob.php +++ b/app/Jobs/ServerFilesFromServerJob.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Models\Application; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; use Illuminate\Bus\Queueable; @@ -16,11 +17,11 @@ class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public ServiceApplication|ServiceDatabase $service) + public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) { } public function handle() { - $this->service->getFilesFromServer(isInit: true); + $this->resource->getFilesFromServer(isInit: true); } } diff --git a/app/Livewire/ActivityMonitor.php b/app/Livewire/ActivityMonitor.php index 421992cb5..37bfc77bb 100644 --- a/app/Livewire/ActivityMonitor.php +++ b/app/Livewire/ActivityMonitor.php @@ -13,6 +13,7 @@ class ActivityMonitor extends Component public $activityId; public $eventToDispatch = 'activityFinished'; public $isPollingActive = false; + public bool $fullHeight = false; public bool $showWaiting = false; protected $activity; diff --git a/app/Livewire/NewActivityMonitor.php b/app/Livewire/NewActivityMonitor.php index 8f2dba0da..853115888 100644 --- a/app/Livewire/NewActivityMonitor.php +++ b/app/Livewire/NewActivityMonitor.php @@ -11,15 +11,17 @@ class NewActivityMonitor extends Component public ?string $header = null; public $activityId; public $eventToDispatch = 'activityFinished'; + public $eventData = null; public $isPollingActive = false; protected $activity; protected $listeners = ['newActivityMonitor' => 'newMonitorActivity']; - public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished') + public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished', $eventData = null) { $this->activityId = $activityId; $this->eventToDispatch = $eventToDispatch; + $this->eventData = $eventData; $this->hydrateActivity(); @@ -55,8 +57,12 @@ class NewActivityMonitor extends Component } return; } - $this->dispatch($this->eventToDispatch); - ray('Dispatched event: ' . $this->eventToDispatch); + if (!is_null($this->eventData)) { + $this->dispatch($this->eventToDispatch, $this->eventData); + } else { + $this->dispatch($this->eventToDispatch); + } + ray('Dispatched event: ' . $this->eventToDispatch . ' with data: ' . $this->eventData); } } } diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 7eb910533..022a7b710 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -3,6 +3,7 @@ namespace App\Livewire\Project\Application; use App\Models\Application; +use App\Models\LocalFileVolume; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Livewire\Component; @@ -124,7 +125,7 @@ class General extends Component } $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; $this->ports_exposes = $this->application->ports_exposes; - $this->customLabels = $this->application->parseContainerLabels(); + $this->customLabels = $this->application->parseContainerLabels(); if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') { $this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n"); $this->application->custom_labels = base64_encode($this->customLabels); @@ -156,8 +157,36 @@ class General extends Component return; } ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit); + $compose = $this->application->parseCompose(); + $services = data_get($compose, 'services'); + if ($services) { + $volumes = collect($services)->map(function ($service) { + return data_get($service, 'volumes'); + })->flatten()->filter(function ($volume) { + return str($volume)->startsWith('/data/coolify'); + })->unique()->values(); + foreach ($volumes as $volume) { + $source = Str::of($volume)->before(':'); + $target = Str::of($volume)->after(':')->beforeLast(':'); + + LocalFileVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $this->application->id, + 'resource_type' => get_class($this->application) + ], + [ + 'fs_path' => $source, + 'mount_path' => $target, + 'resource_id' => $this->application->id, + 'resource_type' => get_class($this->application) + ] + ); + } + } $this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('compose_loaded'); + $this->dispatch('refresh_storages'); } catch (\Throwable $e) { $this->application->docker_compose_location = $this->initialDockerComposeLocation; $this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation; @@ -165,7 +194,6 @@ class General extends Component return handleError($e, $this); } finally { $this->initLoadingCompose = false; - } } public function generateDomain(string $serviceName) @@ -184,7 +212,14 @@ class General extends Component $this->loadComposeFile(); } } - public function updatedApplicationFqdn() { + public function updatedApplicationFqdn() + { + $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); + $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); + $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { + return str($domain)->trim()->lower(); + }); + $this->application->fqdn = $this->application->fqdn->unique()->implode(','); $this->resetDefaultLabels(); } public function updatedApplicationBuildPack() diff --git a/app/Livewire/Project/Application/Rollback.php b/app/Livewire/Project/Application/Rollback.php index 7bd2d069b..f926b8e12 100644 --- a/app/Livewire/Project/Application/Rollback.php +++ b/app/Livewire/Project/Application/Rollback.php @@ -18,7 +18,6 @@ class Rollback extends Component { $this->parameters = get_route_parameters(); } - public function rollbackImage($commit) { $deployment_uuid = new Cuid2(7); @@ -27,6 +26,7 @@ class Rollback extends Component application: $this->application, deployment_uuid: $deployment_uuid, commit: $commit, + rollback: true, force_rebuild: false, ); return redirect()->route('project.application.deployment.show', [ diff --git a/app/Livewire/Project/Edit.php b/app/Livewire/Project/Edit.php index 17cb6902b..d222917a6 100644 --- a/app/Livewire/Project/Edit.php +++ b/app/Livewire/Project/Edit.php @@ -25,6 +25,7 @@ class Edit extends Component 'key' => $data['key'], 'value' => $data['value'], 'is_multiline' => $data['is_multiline'], + 'is_literal' => $data['is_literal'], 'type' => 'project', 'team_id' => currentTeam()->id, ]); diff --git a/app/Livewire/Project/EnvironmentEdit.php b/app/Livewire/Project/EnvironmentEdit.php index c5e4ccf11..173d946f3 100644 --- a/app/Livewire/Project/EnvironmentEdit.php +++ b/app/Livewire/Project/EnvironmentEdit.php @@ -29,6 +29,7 @@ class EnvironmentEdit extends Component 'key' => $data['key'], 'value' => $data['value'], 'is_multiline' => $data['is_multiline'], + 'is_literal' => $data['is_literal'], 'type' => 'environment', 'team_id' => currentTeam()->id, ]); diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php index 8e1fcb5f1..8bbb5b052 100644 --- a/app/Livewire/Project/New/PublicGitRepository.php +++ b/app/Livewire/Project/New/PublicGitRepository.php @@ -89,9 +89,11 @@ class PublicGitRepository extends Component public function load_branch() { try { - $this->validate([ - 'repository_url' => 'required|url' - ]); + if (str($this->repository_url)->startsWith('git@')) { + $github_instance = str($this->repository_url)->after('git@')->before(':'); + $repository = str($this->repository_url)->after(':')->before('.git'); + $this->repository_url = 'https://' . str($github_instance) . '/' . $repository; + } } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index 322360534..3705d6f93 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -40,7 +40,7 @@ class Create extends Component $database = create_standalone_keydb($environment->id, $destination_uuid); } else if ($type->value() === 'dragonfly') { $database = create_standalone_dragonfly($environment->id, $destination_uuid); - }else if ($type->value() === 'clickhouse') { + } else if ($type->value() === 'clickhouse') { $database = create_standalone_clickhouse($environment->id, $destination_uuid); } return redirect()->route('project.database.configuration', [ @@ -60,14 +60,19 @@ class Create extends Component } if ($oneClickService) { $destination = StandaloneDocker::whereUuid($destination_uuid)->first(); - $service = Service::create([ + $service_payload = [ 'name' => "$oneClickServiceName-" . str()->random(10), 'docker_compose_raw' => base64_decode($oneClickService), 'environment_id' => $environment->id, + 'service_type' => $oneClickServiceName, 'server_id' => (int) $server_id, 'destination_id' => $destination->id, 'destination_type' => $destination->getMorphClass(), - ]); + ]; + if ($oneClickServiceName === 'cloudflared') { + data_set($service_payload, 'connect_to_docker_network', true); + } + $service = Service::create($service_payload); $service->name = "$oneClickServiceName-" . $service->uuid; $service->save(); if ($oneClickDotEnvs?->count() > 0) { diff --git a/app/Livewire/Project/Service/EditCompose.php b/app/Livewire/Project/Service/EditCompose.php index 84cc45cf2..0f9c449f9 100644 --- a/app/Livewire/Project/Service/EditCompose.php +++ b/app/Livewire/Project/Service/EditCompose.php @@ -13,11 +13,13 @@ class EditCompose extends Component 'service.docker_compose_raw' => 'required', 'service.docker_compose' => 'required', ]; - public function mount() { + public function mount() + { $this->service = Service::find($this->serviceId); } - public function saveEditedCompose() { + public function saveEditedCompose() + { $this->dispatch('info', "Saving new docker compose..."); $this->dispatch('saveCompose', $this->service->docker_compose_raw); } diff --git a/app/Livewire/Project/Service/EditDomain.php b/app/Livewire/Project/Service/EditDomain.php index 87a4a0546..a09d6aa38 100644 --- a/app/Livewire/Project/Service/EditDomain.php +++ b/app/Livewire/Project/Service/EditDomain.php @@ -41,7 +41,7 @@ class EditDomain extends Component } catch (\Throwable $e) { return handleError($e, $this); } finally { - $this->dispatch('generateDockerCompose'); + $this->application->service->parse(); $this->dispatch('refresh'); $this->dispatch('configurationChanged'); } diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php index 0077f5cda..1703caf8f 100644 --- a/app/Livewire/Project/Service/FileStorage.php +++ b/app/Livewire/Project/Service/FileStorage.php @@ -2,6 +2,7 @@ namespace App\Livewire\Project\Service; +use App\Models\Application; use App\Models\LocalFileVolume; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; @@ -12,7 +13,7 @@ use Illuminate\Support\Str; class FileStorage extends Component { public LocalFileVolume $fileStorage; - public ServiceApplication|ServiceDatabase|StandaloneClickhouse $resource; + public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource; public string $fs_path; public ?string $workdir = null; @@ -33,6 +34,43 @@ class FileStorage extends Component $this->fs_path = $this->fileStorage->fs_path; } } + public function convertToDirectory() { + try { + $this->fileStorage->deleteStorageOnServer(); + $this->fileStorage->is_directory = true; + $this->fileStorage->content = null; + $this->fileStorage->save(); + $this->fileStorage->saveStorageOnServer(); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->dispatch('refresh_storages'); + } + } + public function convertToFile() { + try { + $this->fileStorage->deleteStorageOnServer(); + $this->fileStorage->is_directory = false; + $this->fileStorage->content = null; + $this->fileStorage->save(); + $this->fileStorage->saveStorageOnServer(); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->dispatch('refresh_storages'); + } + } + public function delete() { + try { + $this->fileStorage->deleteStorageOnServer(); + $this->fileStorage->delete(); + $this->dispatch('success', 'File deleted.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->dispatch('refresh_storages'); + } + } public function submit() { $original = $this->fileStorage->getOriginal(); diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php index 9c98c4d9f..03bf99245 100644 --- a/app/Livewire/Project/Service/StackForm.php +++ b/app/Livewire/Project/Service/StackForm.php @@ -3,12 +3,13 @@ namespace App\Livewire\Project\Service; use App\Models\Service; +use Illuminate\Support\Collection; use Livewire\Component; class StackForm extends Component { public Service $service; - public $fields = []; + public Collection $fields; protected $listeners = ["saveCompose"]; public $rules = [ 'service.docker_compose_raw' => 'required', @@ -20,6 +21,7 @@ class StackForm extends Component public $validationAttributes = []; public function mount() { + $this->fields = collect([]); $extraFields = $this->service->extraFields(); foreach ($extraFields as $serviceName => $fields) { foreach ($fields as $fieldKey => $field) { @@ -27,18 +29,20 @@ class StackForm extends Component $value = data_get($field, 'value'); $rules = data_get($field, 'rules', 'nullable'); $isPassword = data_get($field, 'isPassword'); - $this->fields[$key] = [ + $this->fields->put($key, [ "serviceName" => $serviceName, "key" => $key, "name" => $fieldKey, "value" => $value, "isPassword" => $isPassword, "rules" => $rules - ]; + ]); + $this->rules["fields.$key.value"] = $rules; $this->validationAttributes["fields.$key.value"] = $fieldKey; } } + $this->fields = $this->fields->sortDesc(); } public function saveCompose($raw) { diff --git a/app/Livewire/Project/Service/Storage.php b/app/Livewire/Project/Service/Storage.php index 0accde787..1d40f1741 100644 --- a/app/Livewire/Project/Service/Storage.php +++ b/app/Livewire/Project/Service/Storage.php @@ -7,12 +7,13 @@ use Livewire\Component; class Storage extends Component { - protected $listeners = ['addNewVolume']; public $resource; - - public function render() + public function getListeners() { - return view('livewire.project.service.storage'); + return [ + 'addNewVolume', + 'refresh_storages' => '$refresh', + ]; } public function addNewVolume($data) { @@ -27,9 +28,13 @@ class Storage extends Component $this->resource->refresh(); $this->dispatch('success', 'Storage added successfully'); $this->dispatch('clearAddStorage'); - $this->dispatch('refreshStorages'); + $this->dispatch('refresh_storages'); } catch (\Throwable $e) { return handleError($e, $this); } } + public function render() + { + return view('livewire.project.service.storage'); + } } diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php index c04242963..df808ba52 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php @@ -7,11 +7,13 @@ use Livewire\Component; class Add extends Component { public $parameters; + public bool $shared = false; public bool $is_preview = false; public string $key; public ?string $value = null; public bool $is_build_time = false; public bool $is_multiline = false; + public bool $is_literal = false; protected $listeners = ['clearAddEnv' => 'clear']; protected $rules = [ @@ -19,12 +21,14 @@ class Add extends Component 'value' => 'nullable', 'is_build_time' => 'required|boolean', 'is_multiline' => 'required|boolean', + 'is_literal' => 'required|boolean', ]; protected $validationAttributes = [ 'key' => 'key', 'value' => 'value', 'is_build_time' => 'build', 'is_multiline' => 'multiline', + 'is_literal' => 'literal', ]; public function mount() @@ -47,6 +51,7 @@ class Add extends Component 'value' => $this->value, 'is_build_time' => $this->is_build_time, 'is_multiline' => $this->is_multiline, + 'is_literal' => $this->is_literal, 'is_preview' => $this->is_preview, ]); $this->clear(); @@ -58,5 +63,6 @@ class Add extends Component $this->value = ''; $this->is_build_time = false; $this->is_multiline = false; + $this->is_literal = false; } } diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index c4e14c905..6a6d94142 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -15,8 +15,10 @@ class All extends Component public ?string $variables = null; public ?string $variablesPreview = null; public string $view = 'normal'; - protected $listeners = ['refreshEnvs', 'saveKey' => 'submit']; - + protected $listeners = [ + 'refreshEnvs', + 'saveKey' => 'submit', + ]; public function mount() { $resourceClass = get_class($this->resource); @@ -161,6 +163,7 @@ class All extends Component $environment->value = $data['value']; $environment->is_build_time = $data['is_build_time']; $environment->is_multiline = $data['is_multiline']; + $environment->is_literal = $data['is_literal']; $environment->is_preview = $data['is_preview']; switch ($this->resource->type()) { diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 7903e8e51..9beb7cfab 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -16,12 +16,16 @@ class Show extends Component public bool $isLocked = false; public bool $isSharedVariable = false; public string $type; + protected $listeners = [ + "compose_loaded" => '$refresh', + ]; protected $rules = [ 'env.key' => 'required|string', 'env.value' => 'nullable', 'env.is_build_time' => 'required|boolean', 'env.is_multiline' => 'required|boolean', + 'env.is_literal' => 'required|boolean', 'env.is_shown_once' => 'required|boolean', 'env.real_value' => 'nullable', ]; @@ -30,6 +34,7 @@ class Show extends Component 'env.value' => 'Value', 'env.is_build_time' => 'Build Time', 'env.is_multiline' => 'Multiline', + 'env.is_literal' => 'Literal', 'env.is_shown_once' => 'Shown Once', ]; @@ -45,9 +50,9 @@ class Show extends Component public function checkEnvs() { $this->isDisabled = false; - if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) { - $this->isDisabled = true; - } + // if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) { + // $this->isDisabled = true; + // } if ($this->env->is_shown_once) { $this->isLocked = true; } diff --git a/app/Livewire/Project/Shared/Logs.php b/app/Livewire/Project/Shared/Logs.php index a2aaebd2b..68e4e193e 100644 --- a/app/Livewire/Project/Shared/Logs.php +++ b/app/Livewire/Project/Shared/Logs.php @@ -44,7 +44,7 @@ class Logs extends Component } else { $containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true); } - $server->containers = $containers; + $server->containers = $containers->sort(); } catch (\Exception $e) { return handleError($e, $this); } @@ -94,6 +94,7 @@ class Logs extends Component $this->servers = $this->servers->push($this->resource->server); } } + $this->containers = $this->containers->sort(); } catch (\Exception $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Shared/Storages/All.php b/app/Livewire/Project/Shared/Storages/All.php index a17153343..14fa9b7b0 100644 --- a/app/Livewire/Project/Shared/Storages/All.php +++ b/app/Livewire/Project/Shared/Storages/All.php @@ -7,10 +7,5 @@ use Livewire\Component; class All extends Component { public $resource; - protected $listeners = ['refreshStorages']; - - public function refreshStorages() - { - $this->resource->refresh(); - } + protected $listeners = ['refresh_storages' => '$refresh']; } diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php index 0f65aa91e..5e35b796a 100644 --- a/app/Livewire/Project/Shared/Storages/Show.php +++ b/app/Livewire/Project/Shared/Storages/Show.php @@ -39,6 +39,6 @@ class Show extends Component public function delete() { $this->storage->delete(); - $this->dispatch('refreshStorages'); + $this->dispatch('refresh_storages'); } } diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index e51aff8a3..14a2809c7 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -76,14 +76,14 @@ class Form extends Component public function checkLocalhostConnection() { $this->submit(); - $uptime = $this->server->validateConnection(); + ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(); if ($uptime) { $this->dispatch('success', 'Server is reachable.'); $this->server->settings->is_reachable = true; $this->server->settings->is_usable = true; $this->server->settings->save(); } else { - $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.

Check this documentation for further help.'); + $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.

Check this documentation for further help.

Error: ' . $error); return; } } diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php index 8a029e6a1..82925c396 100644 --- a/app/Livewire/Server/Proxy/Deploy.php +++ b/app/Livewire/Server/Proxy/Deploy.php @@ -23,7 +23,8 @@ class Deploy extends Component 'proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated', - "checkProxy", "startProxy" + "checkProxy", + "startProxy" ]; } diff --git a/app/Livewire/Server/Proxy/NewDynamicConfiguration.php b/app/Livewire/Server/Proxy/NewDynamicConfiguration.php index 06180d947..8110986a9 100644 --- a/app/Livewire/Server/Proxy/NewDynamicConfiguration.php +++ b/app/Livewire/Server/Proxy/NewDynamicConfiguration.php @@ -68,7 +68,7 @@ class NewDynamicConfiguration extends Component } $base64_value = base64_encode($this->value); instant_remote_process([ - "echo '{$base64_value}' | base64 -d > {$file}", + "echo '{$base64_value}' | base64 -d | tee {$file} > /dev/null", ], $this->server); if ($proxy_type === 'CADDY') { $this->server->reloadCaddy(); diff --git a/app/Livewire/Server/ShowPrivateKey.php b/app/Livewire/Server/ShowPrivateKey.php index d44d2cb5f..e0474f2c4 100644 --- a/app/Livewire/Server/ShowPrivateKey.php +++ b/app/Livewire/Server/ShowPrivateKey.php @@ -35,10 +35,11 @@ class ShowPrivateKey extends Component public function checkConnection() { try { - $uptime = $this->server->validateConnection(); + ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(); if ($uptime) { $this->dispatch('success', 'Server is reachable.'); } else { + ray($error); $this->dispatch('error', 'Server is not reachable.
Please validate your configuration and connection.

Check this documentation for further help.'); return; } diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php index 6d4173956..b148fe2c4 100644 --- a/app/Livewire/Server/ValidateAndInstall.php +++ b/app/Livewire/Server/ValidateAndInstall.php @@ -11,7 +11,7 @@ class ValidateAndInstall extends Component { public Server $server; public int $number_of_tries = 0; - public int $max_tries = 1; + public int $max_tries = 3; public bool $install = true; public $uptime = null; public $supported_os_type = null; @@ -32,9 +32,8 @@ class ValidateAndInstall extends Component 'refresh' => '$refresh', ]; - public function init(bool $install = true) + public function init(int $data = 0) { - $this->install = $install; $this->uptime = null; $this->supported_os_type = null; $this->docker_installed = null; @@ -42,7 +41,7 @@ class ValidateAndInstall extends Component $this->docker_compose_installed = null; $this->proxy_started = null; $this->error = null; - $this->number_of_tries = 0; + $this->number_of_tries = $data; if (!$this->ask) { $this->dispatch('validateConnection'); } @@ -66,16 +65,15 @@ class ValidateAndInstall extends Component } else { $this->proxy_started = true; } - } catch (\Throwable $e) { return handleError($e, $this); } } public function validateConnection() { - $this->uptime = $this->server->validateConnection(); + ['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection(); if (!$this->uptime) { - $this->error = 'Server is not reachable. Please validate your configuration and connection.

Check this documentation for further help.'; + $this->error = 'Server is not reachable. Please validate your configuration and connection.

Check this documentation for further help.

Error: ' . $error; return; } $this->dispatch('validateOS'); @@ -99,10 +97,10 @@ class ValidateAndInstall extends Component $this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: documentation.'; return; } else { - if ($this->number_of_tries == 0) { + if ($this->number_of_tries <= $this->max_tries) { $activity = $this->server->installDocker(); $this->number_of_tries++; - $this->dispatch('newActivityMonitor', $activity->id, 'init'); + $this->dispatch('newActivityMonitor', $activity->id, 'init', $this->number_of_tries); } return; } diff --git a/app/Livewire/Settings/Configuration.php b/app/Livewire/Settings/Configuration.php index fbc88785d..5281a1e01 100644 --- a/app/Livewire/Settings/Configuration.php +++ b/app/Livewire/Settings/Configuration.php @@ -69,13 +69,14 @@ class Configuration extends Component } $this->validate(); - if ($this->settings->is_dns_validation_enabled) { + if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) { + ray('asdf'); if (!validate_dns_entry($this->settings->fqdn, $this->server)) { $this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.

Make sure you have added the DNS records correctly.

Check this documentation for further help."); $error_show = true; } } - check_domain_usage(domain: $this->settings->fqdn); + if ($this->settings->fqdn) check_domain_usage(domain: $this->settings->fqdn); $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim(); $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) { return str($dns)->trim()->lower(); diff --git a/app/Livewire/Subscription/PricingPlans.php b/app/Livewire/Subscription/PricingPlans.php index 4e4dbadee..dddfa3a80 100644 --- a/app/Livewire/Subscription/PricingPlans.php +++ b/app/Livewire/Subscription/PricingPlans.php @@ -54,6 +54,7 @@ class PricingPlans extends Component return; } $payload = [ + 'allow_promotion_codes' => true, 'billing_address_collection' => 'required', 'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id, 'line_items' => [[ diff --git a/app/Livewire/TeamSharedVariablesIndex.php b/app/Livewire/TeamSharedVariablesIndex.php index dbb64ab65..eeee30ab9 100644 --- a/app/Livewire/TeamSharedVariablesIndex.php +++ b/app/Livewire/TeamSharedVariablesIndex.php @@ -21,6 +21,7 @@ class TeamSharedVariablesIndex extends Component 'key' => $data['key'], 'value' => $data['value'], 'is_multiline' => $data['is_multiline'], + 'is_literal' => $data['is_literal'], 'type' => 'team', 'team_id' => currentTeam()->id, ]); diff --git a/app/Models/Application.php b/app/Models/Application.php index 204cb96b2..66e18565b 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -511,9 +511,9 @@ class Application extends BaseModel { $newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build; if ($this->pull_request_id === 0 || $this->pull_request_id === null) { - $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); } else { - $newConfigHash .= json_encode($this->environment_variables_preview->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables_preview->get('value')->sort()); } $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); @@ -653,13 +653,13 @@ class Application extends BaseModel if ($exec_in_docker) { $commands = collect([ executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"), - executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d > /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 { $commands = collect([ "mkdir -p /root/.ssh", - "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa", + "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null", "chmod 600 /root/.ssh/id_rsa", ]); } @@ -954,4 +954,9 @@ class Application extends BaseModel }); return $matches->count() > 0; } + + public function getFilesFromServer(bool $isInit = false) + { + getFilesystemVolumesFromServer($this, $isInit); + } } diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 03930fba4..32bc04285 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -6,6 +6,7 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; +use Symfony\Component\Yaml\Yaml; class EnvironmentVariable extends Model { @@ -81,6 +82,44 @@ class EnvironmentVariable extends Model } ); } + protected function isFoundInCompose(): Attribute + { + return Attribute::make( + get: function () { + if (!$this->application_id) { + return true; + } + $found_in_compose = false; + $resource = $this->resource(); + $compose = data_get($resource, 'docker_compose_raw'); + if (!$compose) { + return true; + } + $yaml = Yaml::parse($compose); + $services = collect(data_get($yaml, 'services')); + if ($services->isEmpty()) { + return false; + } + foreach ($services as $service) { + $environments = collect(data_get($service, 'environment')); + if ($environments->isEmpty()) { + $found_in_compose = false; + break; + } + $found_in_compose = $environments->contains(function ($item) { + if (str($item)->contains('=')) { + $item = str($item)->before('='); + } + return strpos($item, $this->key) !== false; + }); + if ($found_in_compose) { + break; + } + } + return $found_in_compose; + } + ); + } protected function isShared(): Attribute { return Attribute::make( diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php index 7ea0cd546..5595bbb13 100644 --- a/app/Models/LocalFileVolume.php +++ b/app/Models/LocalFileVolume.php @@ -20,6 +20,26 @@ class LocalFileVolume extends BaseModel { return $this->morphTo('resource'); } + public function deleteStorageOnServer() + { + $isService = data_get($this->resource, 'service'); + if ($isService) { + $workdir = $this->resource->service->workdir(); + $server = $this->resource->service->server; + } else { + $workdir = $this->resource->workdir(); + $server = $this->resource->destination->server; + } + $commands = collect([ + "cd $workdir" + ]); + $fs_path = data_get($this, 'fs_path'); + if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') { + $commands->push("rm -rf $fs_path"); + } + ray($commands); + return instant_remote_process($commands, $server); + } public function saveStorageOnServer() { $isService = data_get($this->resource, 'service'); @@ -63,8 +83,7 @@ class LocalFileVolume extends BaseModel $content = base64_encode($content); $chmod = $fileVolume->chmod; $chown = $fileVolume->chown; - ray($content, $path, $chmod, $chown); - $commands->push("echo '$content' | base64 -d > $path"); + $commands->push("echo '$content' | base64 -d | tee $path > /dev/null"); $commands->push("chmod +x $path"); if ($chown) { $commands->push("chown $chown $path"); @@ -72,7 +91,6 @@ class LocalFileVolume extends BaseModel if ($chmod) { $commands->push("chmod $chmod $path"); } - } } else if ($isDir == 'NOK' && $fileVolume->is_directory) { $commands->push("mkdir -p $path > /dev/null 2>&1 || true"); diff --git a/app/Models/Server.php b/app/Models/Server.php index 79c98ccf6..bda044320 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -141,7 +141,7 @@ respond 404 $base64 = base64_encode($conf); instant_remote_process([ "mkdir -p $dynamic_conf_path", - "echo '$base64' | base64 -d > $default_redirect_file", + "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", ], $this); $this->reloadCaddy(); return; @@ -223,7 +223,7 @@ respond 404 instant_remote_process([ "mkdir -p $dynamic_conf_path", - "echo '$base64' | base64 -d > $default_redirect_file", + "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", ], $this); if (config('app.env') == 'local') { @@ -349,7 +349,7 @@ respond 404 $base64 = base64_encode($yaml); instant_remote_process([ "mkdir -p $dynamic_config_path", - "echo '$base64' | base64 -d > $file", + "echo '$base64' | base64 -d | tee $file > /dev/null", ], $this); if (config('app.env') == 'local') { @@ -376,7 +376,7 @@ $schema://$host { }"; $base64 = base64_encode($caddy_file); instant_remote_process([ - "echo '$base64' | base64 -d > $file", + "echo '$base64' | base64 -d | tee $file > /dev/null", ], $this); $this->reloadCaddy(); } @@ -481,8 +481,8 @@ $schema://$host { // ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber); // ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax); - $result = $this->validateConnection(); - if ($result) { + ['uptime' => $uptime] = $this->validateConnection(); + if ($uptime) { if ($this->unreachable_notification_sent === true) { $this->update(['unreachable_notification_sent' => false]); } @@ -551,7 +551,7 @@ $schema://$host { public function loadUnmanagedContainers() { if ($this->isFunctional()) { - $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); + $containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this); $containers = format_docker_command_output_to_json($containers); $containers = $containers->map(function ($container) { $labels = data_get($container, 'Labels'); @@ -748,34 +748,31 @@ $schema://$host { $server = Server::find($this->id); if (!$server) { - return false; + return ['uptime' => false, 'error' => 'Server not found.']; } if ($server->skipServer()) { - return false; + return ['uptime' => false, 'error' => 'Server skipped.']; } - // EC2 does not have `uptime` command, lol - - $uptime = instant_remote_process(['ls /'], $server, false); - if (!$uptime) { - $server->settings()->update([ - 'is_reachable' => false, - ]); - return false; - } else { + try { + // EC2 does not have `uptime` command, lol + instant_remote_process(['ls /'], $server); $server->settings()->update([ 'is_reachable' => true, ]); $server->update([ 'unreachable_count' => 0, ]); + if (data_get($server, 'unreachable_notification_sent') === true) { + $server->team?->notify(new Revived($server)); + $server->update(['unreachable_notification_sent' => false]); + } + return ['uptime' => true, 'error' => null]; + } catch (\Throwable $e) { + $server->settings()->update([ + 'is_reachable' => false, + ]); + return ['uptime' => false, 'error' => $e->getMessage()]; } - - if (data_get($server, 'unreachable_notification_sent') === true) { - $server->team?->notify(new Revived($server)); - $server->update(['unreachable_notification_sent' => false]); - } - - return true; } public function installDocker() { @@ -784,7 +781,7 @@ $schema://$host { } public function validateDockerEngine($throwError = false) { - $dockerBinary = instant_remote_process(["command -v docker"], $this, false); + $dockerBinary = instant_remote_process(["command -v docker"], $this, false, no_sudo: true); if (is_null($dockerBinary)) { $this->settings->is_usable = false; $this->settings->save(); @@ -861,4 +858,11 @@ $schema://$host { return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); } } + public function isNonRoot() + { + if ($this->user instanceof Stringable) { + return $this->user->value() !== 'root'; + } + return $this->user !== 'root'; + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 9f0d2b67e..5de1a9745 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -14,20 +14,20 @@ class Service extends BaseModel public function isConfigurationChanged(bool $save = false) { - $domains = $this->applications()->get()->pluck('fqdn')->toArray(); + $domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray(); $domains = implode(',', $domains); - $applicationImages = $this->applications()->get()->pluck('image'); - $databaseImages = $this->databases()->get()->pluck('image'); + $applicationImages = $this->applications()->get()->pluck('image')->sort(); + $databaseImages = $this->databases()->get()->pluck('image')->sort(); $images = $applicationImages->merge($databaseImages); $images = implode(',', $images->toArray()); - $applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten(); - $databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten(); + $applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten()->sortBy('id'); + $databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten()->sortBy('id'); $storages = $applicationStorages->merge($databaseStorages)->implode('updated_at'); $newConfigHash = $images . $domains . $images . $storages; - $newConfigHash .= json_encode($this->environment_variables()->get('value')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { @@ -150,6 +150,76 @@ class Service extends BaseModel foreach ($applications as $application) { $image = str($application->image)->before(':')->value(); switch ($image) { + case str($image)?->contains('tolgee'): + $data = collect([]); + $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first(); + $data = $data->merge([ + 'Admin User' => [ + 'key' => 'TOLGEE_AUTHENTICATION_INITIAL_USERNAME', + 'value' => 'admin', + 'readonly' => true, + 'rules' => 'required', + ], + ]); + if ($admin_password) { + $data = $data->merge([ + 'Admin Password' => [ + 'key' => 'SERVICE_PASSWORD_TOLGEE', + 'value' => data_get($admin_password, 'value'), + 'rules' => 'required', + 'isPassword' => true, + ], + ]); + } + $fields->put('Tolgee', $data); + break; + case str($image)?->contains('logto'): + $data = collect([]); + $logto_endpoint = $this->environment_variables()->where('key', 'LOGTO_ENDPOINT')->first(); + $logto_admin_endpoint = $this->environment_variables()->where('key', 'LOGTO_ADMIN_ENDPOINT')->first(); + if ($logto_endpoint) { + $data = $data->merge([ + 'Endpoint' => [ + 'key' => data_get($logto_endpoint, 'key'), + 'value' => data_get($logto_endpoint, 'value'), + 'rules' => 'required|url', + ], + ]); + } + if ($logto_admin_endpoint) { + $data = $data->merge([ + 'Admin Endpoint' => [ + 'key' => data_get($logto_admin_endpoint, 'key'), + 'value' => data_get($logto_admin_endpoint, 'value'), + 'rules' => 'required|url', + ], + ]); + } + $fields->put('Logto', $data); + break; + case str($image)?->contains('unleash-server'): + $data = collect([]); + $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UNLEASH')->first(); + $data = $data->merge([ + 'Admin User' => [ + 'key' => 'SERVICE_USER_UNLEASH', + 'value' => 'admin', + 'readonly' => true, + 'rules' => 'required', + ], + ]); + if ($admin_password) { + $data = $data->merge([ + 'Admin Password' => [ + 'key' => 'SERVICE_PASSWORD_UNLEASH', + 'value' => data_get($admin_password, 'value'), + 'rules' => 'required', + 'isPassword' => true, + ], + ]); + } + $fields->put('Unleash', $data); + break; case str($image)?->contains('grafana'): $data = collect([]); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first(); @@ -618,7 +688,7 @@ class Service extends BaseModel $commands[] = "cd $workdir"; $docker_compose_base64 = base64_encode($this->docker_compose); - $commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml"; + $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null"; $envs = $this->environment_variables()->get(); $commands[] = "rm -f .env || true"; foreach ($envs as $env) { diff --git a/app/Models/SharedEnvironmentVariable.php b/app/Models/SharedEnvironmentVariable.php index 260f16afb..5fad8fd96 100644 --- a/app/Models/SharedEnvironmentVariable.php +++ b/app/Models/SharedEnvironmentVariable.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; class SharedEnvironmentVariable extends Model diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index a3a7cea8f..2197d51df 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -45,7 +45,7 @@ class StandaloneClickhouse extends BaseModel public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->image . $this->ports_mappings; - $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index 593b63506..7b18666b8 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -44,7 +44,7 @@ class StandaloneDragonfly extends BaseModel public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->image . $this->ports_mappings; - $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { @@ -167,9 +167,9 @@ class StandaloneDragonfly extends BaseModel public function get_db_url(bool $useInternal = false): string { if ($this->is_public && !$useInternal) { - return "redis://{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; + return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; } else { - return "redis://{$this->dragonfly_password}@{$this->uuid}:6379/0"; + return "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0"; } } diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index be9e66992..1dc55228a 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -44,7 +44,7 @@ class StandaloneKeydb extends BaseModel public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->image . $this->ports_mappings . $this->keydb_conf; - $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 4c143d9b8..5e18bbfde 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -46,7 +46,7 @@ class StandaloneMariadb extends BaseModel public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->image . $this->ports_mappings . $this->mariadb_conf; - $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index c23854376..8e4d327a3 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -49,7 +49,7 @@ class StandaloneMongodb extends BaseModel public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->image . $this->ports_mappings . $this->mongo_conf; - $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index 7885b8441..eede451d7 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -46,7 +46,7 @@ class StandaloneMysql extends BaseModel public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->image . $this->ports_mappings . $this->mysql_conf; - $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index b42dbdcb8..cf449a815 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -58,7 +58,7 @@ class StandalonePostgresql extends BaseModel public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->image . $this->ports_mappings . $this->postgres_initdb_args . $this->postgres_host_auth_method; - $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index e6231be3c..da4701df9 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -41,7 +41,7 @@ class StandaloneRedis extends BaseModel public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->image . $this->ports_mappings . $this->redis_conf; - $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); + $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index a6ded1dc7..a1b6beb6e 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -20,9 +20,9 @@ class EventServiceProvider extends ServiceProvider MaintenanceModeDisabledNotification::class, ], \SocialiteProviders\Manager\SocialiteWasCalled::class => [ - \SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle', + \SocialiteProviders\Azure\AzureExtendSocialite::class . '@handle', ], - ProxyStarted::class => [ + ProxyStarted::class => [ ProxyStartedNotification::class, ], ]; diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php index 529dacd7a..028dbaadc 100644 --- a/app/Traits/ExecuteRemoteCommand.php +++ b/app/Traits/ExecuteRemoteCommand.php @@ -34,7 +34,13 @@ trait ExecuteRemoteCommand $ignore_errors = data_get($single_command, 'ignore_errors', false); $append = data_get($single_command, 'append', true); $this->save = data_get($single_command, 'save'); - + if ($this->server->isNonRoot()) { + if (str($command)->startsWith('docker exec')) { + $command = str($command)->replace('docker exec', 'sudo docker exec'); + } else { + $command = parseLineForSudo($command, $this->server); + } + } $remote_command = generateSshCommand($this->server, $command); $process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) { $output = Str::of($output)->trim(); diff --git a/app/View/Components/Services/Links.php b/app/View/Components/Services/Links.php index b3953c174..4cc19b518 100644 --- a/app/View/Components/Services/Links.php +++ b/app/View/Components/Services/Links.php @@ -18,7 +18,10 @@ class Links extends Component $service->applications()->get()->map(function ($application) { $type = $application->serviceType(); if ($type) { - $links = generateServiceSpecificFqdns($application, false); + $links = generateServiceSpecificFqdns($application); + $links = $links->map(function ($link) { + return getFqdnWithoutPort($link); + }); $this->links = $this->links->merge($links); } else { if ($application->fqdn) { diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 8d810da0f..c0aaf4abf 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -9,7 +9,7 @@ use App\Models\StandaloneDocker; use Illuminate\Support\Collection; use Spatie\Url\Url; -function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false) +function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false) { $application_id = $application->id; $deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}"); @@ -38,6 +38,7 @@ function queue_application_deployment(Application $application, string $deployme 'is_webhook' => $is_webhook, 'restart_only' => $restart_only, 'commit' => $commit, + 'rollback' => $rollback, 'git_type' => $git_type, 'only_this_server' => $only_this_server ]); diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index c3ef9a694..11cfc3df2 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -32,6 +32,7 @@ const DATABASE_DOCKER_IMAGES = [ ]; const SPECIFIC_SERVICES = [ 'quay.io/minio/minio', + 'svhd/logto' ]; // Based on /etc/os-release diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index afcc4622b..a70c85a72 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -169,7 +169,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica } return $labels; } -function generateServiceSpecificFqdns(ServiceApplication|Application $resource, $forTraefik = false) +function generateServiceSpecificFqdns(ServiceApplication|Application $resource) { if ($resource->getMorphClass() === 'App\Models\ServiceApplication') { $uuid = $resource->uuid; @@ -182,6 +182,9 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource, $environment_variables = $resource->environment_variables; $type = $resource->serviceType(); } + if (is_null($server) || is_null($type)) { + return collect([]); + } $variables = collect($environment_variables); $payload = collect([]); switch ($type) { @@ -201,17 +204,31 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource, "value" => generateFqdn($server, 'minio-' . $uuid) ]); } - if ($forTraefik) { - $payload = collect([ - $MINIO_BROWSER_REDIRECT_URL->value . ':9001', - $MINIO_SERVER_URL->value . ':9000', - ]); - } else { - $payload = collect([ - $MINIO_BROWSER_REDIRECT_URL->value, - $MINIO_SERVER_URL->value, + $payload = collect([ + $MINIO_BROWSER_REDIRECT_URL->value . ':9001', + $MINIO_SERVER_URL->value . ':9000', + ]); + break; + case $type?->contains('logto'): + $LOGTO_ENDPOINT = $variables->where('key', 'LOGTO_ENDPOINT')->first(); + $LOGTO_ADMIN_ENDPOINT = $variables->where('key', 'LOGTO_ADMIN_ENDPOINT')->first(); + if (is_null($LOGTO_ENDPOINT) || is_null($LOGTO_ADMIN_ENDPOINT)) { + return $payload; + } + if (is_null($LOGTO_ENDPOINT?->value)) { + $LOGTO_ENDPOINT?->update([ + "value" => generateFqdn($server, 'logto-' . $uuid) ]); } + if (is_null($LOGTO_ADMIN_ENDPOINT?->value)) { + $LOGTO_ADMIN_ENDPOINT?->update([ + "value" => generateFqdn($server, 'logto-admin-' . $uuid) + ]); + } + $payload = collect([ + $LOGTO_ENDPOINT->value . ':3001', + $LOGTO_ADMIN_ENDPOINT->value . ':3002', + ]); break; } return $payload; @@ -565,7 +582,7 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable $server = Server::findOrFail($server_id); $base64_compose = base64_encode($compose); $output = instant_remote_process([ - "echo {$base64_compose} | base64 -d > /tmp/{$uuid}.yml", + "echo {$base64_compose} | base64 -d | tee /tmp/{$uuid}.yml > /dev/null", "docker compose -f /tmp/{$uuid}.yml config", ], $server); ray($output); @@ -586,3 +603,9 @@ function escapeEnvVariables($value) $replace = array("\\\\", "\\r", "\\t", "\\0", '\"', "\'"); return str_replace($search, $replace, $value); } +function escapeDollarSign($value) +{ + $search = array('$'); + $replace = array('$$'); + return str_replace($search, $replace, $value); +} diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index 6365a6e45..85533550b 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -32,6 +32,9 @@ function remote_process( if ($command instanceof Collection) { $command = $command->toArray(); } + if ($server->isNonRoot()) { + $command = parseCommandsByLineForSudo(collect($command), $server); + } $command_string = implode("\n", $command); if (auth()->user()) { $teams = auth()->user()->teams->pluck('id'); @@ -133,17 +136,17 @@ function generateSshCommand(Server $server, string $command) $timeout = config('constants.ssh.command_timeout'); $connectionTimeout = config('constants.ssh.connection_timeout'); $serverInterval = config('constants.ssh.server_interval'); + $muxPersistTime = config('constants.ssh.mux_persist_time'); $ssh_command = "timeout $timeout ssh "; if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) { - $ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r '; + $ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r "; } if (data_get($server, 'settings.is_cloudflare_tunnel')) { $ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" '; } $command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command"; - $delimiter = Hash::make($command); $command = str_replace($delimiter, '', $command); $ssh_command .= "-i {$privateKeyLocation} " @@ -159,17 +162,19 @@ function generateSshCommand(Server $server, string $command) . $command . PHP_EOL . $delimiter; // ray($ssh_command); - // ray($delimiter); return $ssh_command; } -function instant_remote_process(Collection|array $command, Server $server, $throwError = true) +function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false) { $timeout = config('constants.ssh.command_timeout'); if ($command instanceof Collection) { $command = $command->toArray(); } + if ($server->isNonRoot() && !$no_sudo) { + $command = parseCommandsByLineForSudo(collect($command), $server); + } $command_string = implode("\n", $command); - $ssh_command = generateSshCommand($server, $command_string); + $ssh_command = generateSshCommand($server, $command_string, $no_sudo); $process = Process::timeout($timeout)->run($ssh_command); $output = trim($process->output()); $exitCode = $process->exitCode(); diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index b690659dd..8e3c0337e 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -1,7 +1,7 @@ replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', ''); } -function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService, bool $isInit = false) +function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false) { - // TODO: make this async try { - $workdir = $oneService->service->workdir(); - $server = $oneService->service->server; + if ($oneService->getMorphClass() === 'App\Models\Application') { + $workdir = $oneService->workdir(); + $server = $oneService->destination->server; + } else{ + $workdir = $oneService->service->workdir(); + $server = $oneService->service->server; + } $fileVolumes = $oneService->fileStorages()->get(); $commands = collect([ "mkdir -p $workdir > /dev/null 2>&1 || true", - "cd " + "cd $workdir" ]); instant_remote_process($commands, $server); foreach ($fileVolumes as $fileVolume) { @@ -67,7 +71,7 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneS $dir = Str::of($fileLocation)->dirname(); instant_remote_process([ "mkdir -p $dir", - "echo '$content' | base64 -d > $fileLocation" + "echo '$content' | base64 -d | tee $fileLocation" ], $server); } else if ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) { $fileVolume->content = null; diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 4533d098b..966b1db59 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -29,6 +29,7 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Mail\Message; use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; @@ -656,6 +657,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if (str(data_get($service, 'image'))->contains('glitchtip')) { $tempServiceName = 'glitchtip'; } + if ($serviceName === 'supabase-kong') { + $tempServiceName = 'supabase'; + } $serviceDefinition = data_get($allServices, $tempServiceName); $predefinedPort = data_get($serviceDefinition, 'port'); if ($serviceName === 'plausible') { @@ -978,12 +982,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } // Caddy needs exact port in some cases. if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) { + $fqdns_exploded = str($savedService->fqdn)->explode(','); + if ($fqdns_exploded->count() > 1) { + continue; + } if ($resource->server->proxyType() === 'CADDY') { $env = EnvironmentVariable::where([ 'key' => $key, 'service_id' => $resource->id, ])->first(); if ($env) { + $env_url = Url::fromString($savedService->fqdn); $env_port = $env_url->getPort(); if ($env_port !== $predefinedPort) { @@ -1045,6 +1054,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } // Caddy needs exact port in some cases. if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') { + $fqdns_exploded = str($savedService->fqdn)->explode(','); + if ($fqdns_exploded->count() > 1) { + continue; + } $env = EnvironmentVariable::where([ 'key' => $key, 'service_id' => $resource->id, @@ -1109,10 +1122,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } } - // Add labels to the service if ($savedService->serviceType()) { - $fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true); + $fqdns = generateServiceSpecificFqdns($savedService); } else { $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); } @@ -1482,7 +1494,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'application_id' => $resource->id, ])->first(); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); - ray($command, $generatedValue); if (!is_null($command)) { if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if (Str::lower($forService) === $serviceName) { @@ -1567,7 +1578,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } // Add labels to the service if ($resource->serviceType()) { - $fqdns = generateServiceSpecificFqdns($resource, forTraefik: true); + $fqdns = generateServiceSpecificFqdns($resource); } else { $domains = collect(json_decode($resource->docker_compose_domains)) ?? []; if ($domains) { @@ -1939,3 +1950,57 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null } } } + +function parseCommandsByLineForSudo(Collection $commands, Server $server): array +{ + $commands = $commands->map(function ($line) { + if (!str($line)->startsWith('cd') && !str($line)->startsWith('command') && !str($line)->startsWith('echo') && !str($line)->startsWith('true')) { + return "sudo $line"; + } + return $line; + }); + $commands = $commands->map(function ($line) use ($server) { + if (Str::startsWith($line, 'sudo mkdir -p')) { + return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p'); + } + return $line; + }); + $commands = $commands->map(function ($line) { + $line = str($line); + if (str($line)->contains('$(')) { + $line = $line->replace('$(', '$(sudo '); + } + if (str($line)->contains('||')) { + $line = $line->replace('||', '|| sudo'); + } + if (str($line)->contains('&&')) { + $line = $line->replace('&&', '&& sudo'); + } + if (str($line)->contains(' | ')) { + $line = $line->replace(' | ', ' | sudo '); + } + return $line->value(); + }); + + return $commands->toArray(); +} +function parseLineForSudo(string $command, Server $server): string +{ + if (!str($command)->startSwith('cd') && !str($command)->startSwith('command')) { + $command = "sudo $command"; + } + if (Str::startsWith($command, 'sudo mkdir -p')) { + $command = "$command && sudo chown -R $server->user:$server->user " . Str::after($command, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($command, 'sudo mkdir -p'); + } + if (str($command)->contains('$(') || str($command)->contains('`')) { + $command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value(); + } + if (str($command)->contains('||')) { + $command = str($command)->replace('||', '|| sudo ')->value(); + } + if (str($command)->contains('&&')) { + $command = str($command)->replace('&&', '&& sudo ')->value(); + } + + return $command; +} diff --git a/config/app.php b/config/app.php index b95224fdc..fc5d1e26f 100644 --- a/config/app.php +++ b/config/app.php @@ -141,8 +141,8 @@ return [ */ 'maintenance' => [ - 'driver' => 'file', - // 'store' => 'redis', + 'driver' => 'cache', + 'store' => 'redis', ], /* diff --git a/config/constants.php b/config/constants.php index 54c451310..091c60996 100644 --- a/config/constants.php +++ b/config/constants.php @@ -5,6 +5,7 @@ return [ 'contact' => 'https://coolify.io/docs/contact', ], 'ssh' => [ + 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', "1m"), 'connection_timeout' => 10, 'server_interval' => 20, 'command_timeout' => 7200, diff --git a/config/horizon.php b/config/horizon.php index 49df24b57..15f7f5696 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -183,9 +183,7 @@ return [ 's6' => [ 'connection' => 'redis', 'queue' => ['default'], - 'balance' => 'auto', - // 'autoScalingStrategy' => 'time', - // 'maxProcesses' => 1, + 'balance' => env('HORIZON_BALANCE', 'auto'), 'maxTime' => 0, 'maxJobs' => 0, 'memory' => 128, diff --git a/config/livewire.php b/config/livewire.php index 83229fcea..cf9bcd206 100644 --- a/config/livewire.php +++ b/config/livewire.php @@ -146,4 +146,5 @@ return [ */ 'pagination_theme' => 'tailwind', + 'lazy_placeholder' => 'components.page-loading', ]; diff --git a/config/sentry.php b/config/sentry.php index c399f7933..7ae7ccfda 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.258', + 'release' => '4.0.0-beta.266', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index d0fd506d3..7001afde8 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ boolean('is_literal')->default(false); + }); + Schema::table('shared_environment_variables', function (Blueprint $table) { + $table->boolean('is_literal')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('environment_variables', function (Blueprint $table) { + $table->dropColumn('is_literal'); + }); + Schema::table('shared_environment_variables', function (Blueprint $table) { + $table->dropColumn('is_literal'); + }); + } +}; diff --git a/database/migrations/2024_04_16_083919_add_service_type_on_creation.php b/database/migrations/2024_04_16_083919_add_service_type_on_creation.php new file mode 100644 index 000000000..ce91bddbb --- /dev/null +++ b/database/migrations/2024_04_16_083919_add_service_type_on_creation.php @@ -0,0 +1,28 @@ +string('service_type')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('services', function (Blueprint $table) { + $table->dropColumn('service_type'); + }); + } +}; diff --git a/database/migrations/2024_04_17_132541_add_rollback_queues.php b/database/migrations/2024_04_17_132541_add_rollback_queues.php new file mode 100644 index 000000000..27865cf0d --- /dev/null +++ b/database/migrations/2024_04_17_132541_add_rollback_queues.php @@ -0,0 +1,28 @@ +boolean('rollback')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('rollback'); + }); + } +}; diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 4e85836d2..f68b2c41c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -29,6 +29,7 @@ services: - QUEUE_CONNECTION - REDIS_HOST - REDIS_PASSWORD + - HORIZON_BALANCE - HORIZON_MAX_PROCESSES - HORIZON_BALANCE_MAX_SHIFT - HORIZON_BALANCE_COOLDOWN @@ -47,6 +48,7 @@ services: - PUSHER_APP_SECRET - AUTOUPDATE - SELF_HOSTED + - SSH_MUX_PERSIST_TIME - FEEDBACK_DISCORD_WEBHOOK - WAITLIST - SUBSCRIPTION_PROVIDER @@ -93,7 +95,7 @@ services: expose: - "${APP_PORT:-8000}" healthcheck: - test: curl --fail http://localhost:80/api/health || exit 1 + test: curl --fail http://127.0.0.1:80/api/health || exit 1 interval: 5s retries: 10 timeout: 2s @@ -140,7 +142,7 @@ services: SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}" SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}" healthcheck: - test: wget -qO- http://localhost:6001/ready || exit 1 + test: wget -qO- http://127.0.0.1:6001/ready || exit 1 interval: 5s retries: 10 timeout: 2s diff --git a/public/svgs/activepieces.png b/public/svgs/activepieces.png new file mode 100644 index 000000000..373c4fad0 Binary files /dev/null and b/public/svgs/activepieces.png differ diff --git a/public/svgs/authentik.png b/public/svgs/authentik.png new file mode 100644 index 000000000..8f945aad8 Binary files /dev/null and b/public/svgs/authentik.png differ diff --git a/public/svgs/classicpress.svg b/public/svgs/classicpress.svg new file mode 100644 index 000000000..341be300e --- /dev/null +++ b/public/svgs/classicpress.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/cloudflared.svg b/public/svgs/cloudflared.svg new file mode 100644 index 000000000..f9ff79f96 --- /dev/null +++ b/public/svgs/cloudflared.svg @@ -0,0 +1,26 @@ + + + + + Cloudflare logo + + + + + \ No newline at end of file diff --git a/public/svgs/logto_dark.svg b/public/svgs/logto_dark.svg new file mode 100644 index 000000000..c5f3fa0f6 --- /dev/null +++ b/public/svgs/logto_dark.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/svgs/rxresume.svg b/public/svgs/rxresume.svg new file mode 100644 index 000000000..6a7ffcae0 --- /dev/null +++ b/public/svgs/rxresume.svg @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/public/svgs/unleash.svg b/public/svgs/unleash.svg new file mode 100644 index 000000000..b79f91703 --- /dev/null +++ b/public/svgs/unleash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index a8a55f1e8..a10103ce8 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -41,7 +41,7 @@ option { } .button { - @apply flex items-center justify-center gap-2 px-2 py-1 text-sm text-black normal-case border rounded cursor-pointer bg-neutral-200/50 border-neutral-300 hover:bg-neutral-300 dark:bg-coolgray-200 dark:text-white dark:hover:text-white dark:hover:bg-coolgray-500 dark:border-black hover:text-black disabled:bg-coolgray-100/10 disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600; + @apply flex items-center justify-center gap-2 px-2 py-1 text-sm text-black normal-case border rounded cursor-pointer bg-neutral-200/50 border-neutral-300 hover:bg-neutral-300 dark:bg-coolgray-200 dark:text-white dark:hover:text-white dark:hover:bg-coolgray-500 dark:border-black hover:text-black disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600 disabled:border-none disabled:hover:bg-transparent disabled:bg-transparent disabled:text-neutral-300; } button[isError]:not(:disabled) { diff --git a/resources/views/components/modal-input.blade.php b/resources/views/components/modal-input.blade.php index b0747d4a9..13331736f 100644 --- a/resources/views/components/modal-input.blade.php +++ b/resources/views/components/modal-input.blade.php @@ -5,6 +5,7 @@ 'disabled' => false, 'action' => 'delete', 'content' => null, + 'closeOutside' => true ])
@if ($content) @@ -27,7 +28,7 @@ x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="absolute inset-0 w-full h-full bg-black bg-opacity-20 backdrop-blur-sm">
-
"Loading..."]) +
+ @if (isset($text)) +
{{ $text }}
+ @endif + + + + + +
diff --git a/resources/views/components/resource-view.blade.php b/resources/views/components/resource-view.blade.php index d6cc12026..a01e5ab53 100644 --- a/resources/views/components/resource-view.blade.php +++ b/resources/views/components/resource-view.blade.php @@ -12,7 +12,7 @@ @if ($upgrade)
{{ $upgrade }}
@else -
+
{{ $description }}
@endif diff --git a/resources/views/errors/503.blade.php b/resources/views/errors/503.blade.php index d3a15fb45..e5ad5fb08 100644 --- a/resources/views/errors/503.blade.php +++ b/resources/views/errors/503.blade.php @@ -3,7 +3,7 @@

503

We are working on serious things.

-

Service Unavailable. Be right back. Thanks for your +

Service Unavailable. Be right back. Thanks for your patience.

diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 50ff2deec..4d6f6596b 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -6,9 +6,13 @@ - + - + + + + + Coolify @env('local') diff --git a/resources/views/livewire/activity-monitor.blade.php b/resources/views/livewire/activity-monitor.blade.php index 08ed57458..78f7c59a0 100644 --- a/resources/views/livewire/activity-monitor.blade.php +++ b/resources/views/livewire/activity-monitor.blade.php @@ -9,8 +9,11 @@ @endif
@endif -
+
$fullHeight, + 'max-h-96' => !$fullHeight, + ])>
{{ RunRemoteProcess::decodeOutput($this->activity) }}
@else diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index cc90f784a..55177d4b7 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -103,7 +103,8 @@ @else
No servers found.
-
+
+ your first server or diff --git a/resources/views/livewire/layout-popups.blade.php b/resources/views/livewire/layout-popups.blade.php index 7674afe26..3f4436b8b 100644 --- a/resources/views/livewire/layout-popups.blade.php +++ b/resources/views/livewire/layout-popups.blade.php @@ -36,7 +36,10 @@ Coolify could not connect to its real-time service.
This will cause unusual problems on the UI if - not fixed!

Please check the + not fixed!

+ Please ensure that you have opened the + required ports, + check the related documentation or get help on Discord.
diff --git a/resources/views/livewire/project/application/advanced.blade.php b/resources/views/livewire/project/application/advanced.blade.php index 0b1cf72dd..75a55c5b9 100644 --- a/resources/views/livewire/project/application/advanced.blade.php +++ b/resources/views/livewire/project/application/advanced.blade.php @@ -4,7 +4,7 @@

Advanced

Advanced configuration for your application.
-
+

General

@if ($application->git_based()) build_pack !== 'dockercompose')
@if ($application->settings->is_gpu_enabled)
GPU Settings
@@ -64,7 +64,7 @@
diff --git a/resources/views/livewire/project/application/configuration.blade.php b/resources/views/livewire/project/application/configuration.blade.php index 719c60bc1..4aaf96569 100644 --- a/resources/views/livewire/project/application/configuration.blade.php +++ b/resources/views/livewire/project/application/configuration.blade.php @@ -19,7 +19,7 @@ href="#">Environment Variables @endif - @if ($application->build_pack !== 'static' && $application->build_pack !== 'dockercompose') + @if ($application->build_pack !== 'static') Storages diff --git a/resources/views/livewire/project/application/deployment/show.blade.php b/resources/views/livewire/project/application/deployment/show.blade.php index 8249a1029..a8d4f3847 100644 --- a/resources/views/livewire/project/application/deployment/show.blade.php +++ b/resources/views/livewire/project/application/deployment/show.blade.php @@ -89,7 +89,7 @@ 'dark:text-warning whitespace-pre-line' => $line['hidden'], 'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr', ])>[{{ $line['timestamp'] }}] @if ($line['hidden']) -

COMMAND: {{ $line['command'] }}

OUTPUT : +

[COMMAND] {{ $line['command'] }}
[OUTPUT] @endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://')) @php $line['output'] = preg_replace( @@ -100,8 +100,8 @@ @endphp {!! $line['output'] !!} @else {{ $line['output'] }} + @endif -
@endforeach @else diff --git a/resources/views/livewire/project/application/heading.blade.php b/resources/views/livewire/project/application/heading.blade.php index 2b9506cb6..4375d6cfb 100644 --- a/resources/views/livewire/project/application/heading.blade.php +++ b/resources/views/livewire/project/application/heading.blade.php @@ -17,81 +17,96 @@ @endif
- @if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw)) -
Please load a Compose file.
- @else - @if (!$application->destination->server->isSwarm()) - - @endif -
- @if (!str($application->status)->startsWith('exited')) - @if (!$application->destination->server->isSwarm()) - - - - - - - - Redeploy - - @endif - @if ($application->build_pack !== 'dockercompose') - @if ($application->destination->server->isSwarm()) - - - - - - +
+ @if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw)) +
Please load a Compose file.
+ @else + @if (!$application->destination->server->isSwarm()) + + @endif +
+ @if (!str($application->status)->startsWith('exited')) + @if (!$application->destination->server->isSwarm()) + + + + + + - Update Service - - @else - - - - - - - - Restart + Redeploy @endif + @if ($application->build_pack !== 'dockercompose') + @if ($application->destination->server->isSwarm()) + + + + + + + + Update Service + + @else + + + + + + + + Restart + + @endif + @endif + + + + + + + + + + Stop + + This application will be stopped.
Please think again. +
+ @else + + + + + + Deploy + @endif - - - - - - - - - Stop - - @else - - - - - - Deploy - - @endif -
- @endif +
+ @endif +
+ @script + + @endscript diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 6d1eb8614..487f2cccd 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -57,7 +57,7 @@
Previews
@foreach ($application->previews as $preview) -
+
PR #{{ data_get($preview, 'pull_request_id') }} | @if (Str::of(data_get($preview, 'status'))->startsWith('running')) @@ -86,18 +86,20 @@ Redeploy @endif - - - Deployment Logs - - - - - Application Logs - - + @if (count($parameters) > 0) + + + Deployment Logs + + + + + Application Logs + + + @endif Delete diff --git a/resources/views/livewire/project/application/rollback.blade.php b/resources/views/livewire/project/application/rollback.blade.php index 0e92fd574..6501155da 100644 --- a/resources/views/livewire/project/application/rollback.blade.php +++ b/resources/views/livewire/project/application/rollback.blade.php @@ -9,7 +9,7 @@
@forelse ($images as $image)
-
+
@if (data_get($image, 'is_current')) diff --git a/resources/views/livewire/project/edit.blade.php b/resources/views/livewire/project/edit.blade.php index c8fd27f9c..9fd47a70a 100644 --- a/resources/views/livewire/project/edit.blade.php +++ b/resources/views/livewire/project/edit.blade.php @@ -17,7 +17,7 @@

Shared Variables

- +
diff --git a/resources/views/livewire/project/environment-edit.blade.php b/resources/views/livewire/project/environment-edit.blade.php index 02fbb1c69..23bb8748b 100644 --- a/resources/views/livewire/project/environment-edit.blade.php +++ b/resources/views/livewire/project/environment-edit.blade.php @@ -45,7 +45,7 @@

Shared Variables

- +
You can use these variables anywhere with @{{environment.VARIABLENAME}}Create a new Application
You can deploy an existing Docker Image from any Registry.
-
+

Docker Image

Save
diff --git a/resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php b/resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php index f8733c742..7635d3cec 100644 --- a/resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php +++ b/resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php @@ -1,32 +1,33 @@

Create a new Application

Deploy any public or private Git repositories through a Deploy Key.
-
+
@if ($current_step === 'private_keys') -
    -
  • Select a Private Key
  • -
  • Select a Repository, Branch & Save
  • -
-
+

Select a private key

+
@forelse ($private_keys as $key) @if ($private_key_id == $key->id) -
-
-
+
+
{{ $key->name }}
+
+ {{ $key->description }}
@else -
-
-
+
+
{{ $key->name }}
+
+ {{ $key->description }}
@@ -45,12 +46,10 @@
@endif @if ($current_step === 'repository') -
    -
  • Select a Private Key
  • -
  • Select a Repository, Branch & Save
  • -
+

Select a repository

- +
diff --git a/resources/views/livewire/project/new/github-private-repository.blade.php b/resources/views/livewire/project/new/github-private-repository.blade.php index ef2f4c00e..21ffb2f67 100644 --- a/resources/views/livewire/project/new/github-private-repository.blade.php +++ b/resources/views/livewire/project/new/github-private-repository.blade.php @@ -14,24 +14,23 @@ @endif
Deploy any public or private Git repositories through a GitHub App.
+ @if ($github_apps->count() !== 0) -
+

Select a Github App

+
@if ($current_step === 'github_apps') -
+
@foreach ($github_apps as $ghapp) -
-
+
{{ data_get($ghapp, 'name') }}
-
+
{{ data_get($ghapp, 'html_url') }}
-
-
@endforeach @@ -40,7 +39,7 @@ @if ($current_step === 'repository') @if ($repositories->count() > 0)
- @foreach ($repositories as $repo) @if ($loop->first) @@ -54,7 +53,6 @@ @endforeach Load Repository -
@else
No repositories found. Check your GitHub App configuration.
diff --git a/resources/views/livewire/project/new/public-git-repository.blade.php b/resources/views/livewire/project/new/public-git-repository.blade.php index 1edc481ec..da09c1dd6 100644 --- a/resources/views/livewire/project/new/public-git-repository.blade.php +++ b/resources/views/livewire/project/new/public-git-repository.blade.php @@ -5,27 +5,19 @@
- + Check repository
@if (!$branch_found)
-
-
Public:
-
https://..
-
-
-
Private:
-
git@..
-
Preselect branch (eg: main):
https://github.com/coollabsio/coolify-examples/tree/main
- For example application deployments, checkout Coolify Examples.
diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index 2c41aaa64..33984ac93 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -587,10 +587,10 @@ @forelse($servers as $server)
-
+
{{ $server->name }}
-
+
{{ $server->description }}
@@ -626,10 +626,10 @@ @foreach ($standaloneDockers as $standaloneDocker)
-
+
Standalone Docker ({{ $standaloneDocker->name }})
-
+
Network: {{ $standaloneDocker->network }}
diff --git a/resources/views/livewire/project/service/configuration.blade.php b/resources/views/livewire/project/service/configuration.blade.php index 3d85d9ee5..3c0e113e4 100644 --- a/resources/views/livewire/project/service/configuration.blade.php +++ b/resources/views/livewire/project/service/configuration.blade.php @@ -42,6 +42,7 @@
+

Services

@foreach ($applications as $application)
- + applicationId="{{ $application->id }}" + wire:key="edit-domain-{{ $application->id }}" /> @endif - {{--
{{ $application->status }}
--}} +
{{ $application->status }}
-
- Show Deployable Compose + Show Deployable Compose
- Show Source + Show Source Compose
+
Save diff --git a/resources/views/livewire/project/service/file-storage.blade.php b/resources/views/livewire/project/service/file-storage.blade.php index 116d8a06a..07e219b10 100644 --- a/resources/views/livewire/project/service/file-storage.blade.php +++ b/resources/views/livewire/project/service/file-storage.blade.php @@ -1,17 +1,32 @@ -
+

{{ data_get($resource, 'name', 'unknown') }}

{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}
+ @if ($fileStorage->is_directory) +
Directory
+ @else +
File
+ @endif
-
- -
- -
- @if (!$fileStorage->is_directory) - - Save + +
+ @if ($fileStorage->is_directory) + + This will delete all files in this directory. It is not reversible.
Please think again. +
+ @else + + This will convert this to a directory. If it was a file, it will be deleted. It is not reversible.
Please think again. +
@endif - -
+ + This file / directory will be deleted. It is not reversible.
Please think again. +
+
+ @if (!$fileStorage->is_directory) + + Save + @endif + +
diff --git a/resources/views/livewire/project/service/navbar.blade.php b/resources/views/livewire/project/service/navbar.blade.php index f716b54f6..817efd2d7 100644 --- a/resources/views/livewire/project/service/navbar.blade.php +++ b/resources/views/livewire/project/service/navbar.blade.php @@ -3,7 +3,7 @@ Service Startup - +

Configuration

@@ -15,102 +15,104 @@
- @if (str($service->status())->contains('running')) - - - - - - - - - +
+ @if (str($service->status())->contains('running')) + - - - - - - - - + Pull Latest Images & Restart + + + + + + + + + + + Stop + + This service will be stopped.
Please think again. +
+ @elseif (str($service->status())->contains('degraded')) + - - @else - - - - - - - - + Restart Degraded Services + + + + + + + + + + + Stop + + This service will be stopped.
Please think again. +
+ @elseif (str($service->status())->contains('exited')) + - @endif + Force Cleanup Containers + + + @else + + + + + + + + + + Stop + + This service will be stopped.
Please think again. +
+ + @endif +
@script