Merge pull request #1982 from coollabsio/next

v4.0.0-beta.259
This commit is contained in:
Andras Bacsai 2024-04-17 11:43:10 +02:00 committed by GitHub
commit 74e8ae4d79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
147 changed files with 2067 additions and 698 deletions

View File

@ -3,6 +3,8 @@ name: Production Build (v4)
on:
push:
branches: ["main"]
paths-ignore:
- templates/service-templates.json
env:
REGISTRY: ghcr.io

View File

@ -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.'";

View File

@ -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);

View File

@ -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.'";

View File

@ -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.'";

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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.'";

View File

@ -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;

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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.'",

View File

@ -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);
}
}
}

View File

@ -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",

View File

@ -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",
];

View File

@ -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 {
@ -46,7 +46,7 @@ class Kernel extends ConsoleKernel
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->pull_helper_image($schedule);
// $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('cleanup:database --yes')->daily();

View File

@ -322,7 +322,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 +391,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 +565,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 +708,68 @@ 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");
}
}
} else {
$this->env_filename = ".env";
$this->env_filename = ".env-coolify";
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");
}
}
}
if ($envs->isEmpty()) {
$this->env_filename = null;
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
@ -728,15 +777,28 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true
]
);
$this->env_filename = null;
return;
}
$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()) {
$this->execute_remote_command(
[
"echo '{$this->saved_outputs->get('dotenv')->value()}' | tee $this->configuration_dir/.env > /dev/null"
]
);
}
$envs_base64 = base64_encode($envs->implode("\n"));
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null")
],
[
"echo '$envs_base64' | base64 -d > $this->configuration_dir/{$this->env_filename}"
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
]
);
}
@ -902,6 +964,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage.");
$this->execute_remote_command(
[
"command" => "docker pull -q {$helperImage}",
"hidden" => true
],
[
"command" => "docker rm -f {$this->deployment_uuid}",
"ignore_errors" => true,
@ -973,7 +1039,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")
@ -1124,8 +1190,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)));
@ -1179,7 +1245,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,11 +1265,21 @@ 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 (!$this->custom_healthcheck_found) {
$docker_compose['services'][$this->container_name]['healthcheck'] = [
'test' => [
@ -1357,8 +1432,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 +1489,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 +1500,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 +1512,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 +1523,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 +1625,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 +1646,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 +1681,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 +1700,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 +1709,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 +1730,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 +1848,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
]);
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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()

View File

@ -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,
]);

View File

@ -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,
]);

View File

@ -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);
}

View File

@ -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) {

View File

@ -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);
}

View File

@ -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');
}

View File

@ -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();

View File

@ -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->sort();
}
public function saveCompose($raw)
{

View File

@ -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');
}
}

View File

@ -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;
}
}

View File

@ -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()) {

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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'];
}

View File

@ -39,6 +39,6 @@ class Show extends Component
public function delete()
{
$this->storage->delete();
$this->dispatch('refreshStorages');
$this->dispatch('refresh_storages');
}
}

View File

@ -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.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.');
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error);
return;
}
}

View File

@ -23,7 +23,8 @@ class Deploy extends Component
'proxyStatusUpdated',
'traefikDashboardAvailable',
'serverRefresh' => 'proxyStatusUpdated',
"checkProxy", "startProxy"
"checkProxy",
"startProxy"
];
}

View File

@ -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();

View File

@ -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.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.');
return;
}

View File

@ -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.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.';
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>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: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
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;
}

View File

@ -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' => [[

View File

@ -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,
]);

View File

@ -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);
}
}

View File

@ -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(

View File

@ -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");

View File

@ -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,8 @@ $schema://$host {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
}
}
public function isNonRoot()
{
return $this->user !== 'root';
}
}

View File

@ -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,53 @@ class Service extends BaseModel
foreach ($applications as $application) {
$image = str($application->image)->before(':')->value();
switch ($image) {
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 +665,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) {

View File

@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class SharedEnvironmentVariable extends Model

View File

@ -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) {

View File

@ -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";
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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,
],
];

View File

@ -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();

View File

@ -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) {

View File

@ -32,6 +32,7 @@ const DATABASE_DOCKER_IMAGES = [
];
const SPECIFIC_SERVICES = [
'quay.io/minio/minio',
'svhd/logto'
];
// Based on /etc/os-release

View File

@ -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);
}

View File

@ -32,6 +32,10 @@ function remote_process(
if ($command instanceof Collection) {
$command = $command->toArray();
}
if ($server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $server);
}
ray($command);
$command_string = implode("\n", $command);
if (auth()->user()) {
$teams = auth()->user()->teams->pluck('id');
@ -133,17 +137,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 +163,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();

View File

@ -1,7 +1,7 @@
<?php
use App\Models\Application;
use App\Models\EnvironmentVariable;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Str;
@ -21,16 +21,20 @@ function replaceVariables($variable)
return $variable->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;

View File

@ -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;
}

View File

@ -141,8 +141,8 @@ return [
*/
'maintenance' => [
'driver' => 'file',
// 'store' => 'redis',
'driver' => 'cache',
'store' => 'redis',
],
/*

View File

@ -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,

View File

@ -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,

View File

@ -146,4 +146,5 @@ return [
*/
'pagination_theme' => 'tailwind',
'lazy_placeholder' => 'components.page-loading',
];

View File

@ -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.259',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.258';
return '4.0.0-beta.259';

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->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');
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('services', function (Blueprint $table) {
$table->string('service_type')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('services', function (Blueprint $table) {
$table->dropColumn('service_type');
});
}
};

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/svgs/authentik.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 120 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M111.108,9.531l-0.006,-0.011c-0.435,-0.618 -1.151,-0.981 -1.906,-0.966c-13.039,0.734 -34.043,8.376 -34.929,8.701c-0.512,0.188 -0.932,0.566 -1.174,1.054l-2.96,5.911l-2.181,-2.183c-0.658,-0.664 -1.681,-0.819 -2.508,-0.381c-2.497,1.305 -15.121,7.998 -17.807,11.256c-1.559,1.885 -2.764,6.031 -3.581,9.685l-1.427,-2.846c-0.295,-0.595 -0.851,-1.018 -1.503,-1.144c-0.652,-0.135 -1.33,0.048 -1.826,0.492c-0.755,0.663 -16.512,14.842 -13.232,32.52c12.607,-17.973 27.758,-33.089 52.542,-45.254c0.295,-0.146 0.619,-0.221 0.948,-0.221c1.175,-0 2.143,0.967 2.143,2.144c-0,0.814 -0.465,1.561 -1.195,1.922l-0.007,-0c-1.442,0.713 -2.842,1.433 -4.216,2.162c-0.35,0.184 -0.693,0.372 -1.038,0.558c-1.055,0.571 -2.093,1.148 -3.113,1.73c-0.333,0.191 -0.662,0.378 -0.99,0.569c-2.629,1.532 -5.141,3.108 -7.534,4.727c-0.197,0.132 -0.39,0.268 -0.59,0.406c-1.042,0.713 -2.064,1.436 -3.067,2.166c-0.179,0.129 -0.35,0.257 -0.526,0.387c-23.388,17.253 -35.764,39.115 -50.59,65.336c-0.182,0.322 -0.279,0.685 -0.279,1.055c0,1.174 0.967,2.14 2.141,2.14l0.005,0c0.772,0.003 1.487,-0.413 1.865,-1.085c5.905,-10.442 11.416,-20.177 17.392,-29.187c2.434,2.967 6.13,4.62 9.964,4.46c16.21,-0 42.981,-23.712 45.677,-31.349c0.081,-0.23 0.123,-0.472 0.123,-0.715c0,-0.98 -0.672,-1.841 -1.623,-2.077l-7.145,-1.789l15.555,-2.592c0.677,-0.111 1.261,-0.543 1.564,-1.16l17.152,-34.3c0.328,-0.679 0.283,-1.482 -0.118,-2.121Z" style="fill:#fff;fill-rule:nonzero;"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 101.4 33.5">
<defs>
<style>
.a {
fill: #fff;
}
.b {
fill: #f48120;
}
.c {
fill: #faad3f;
}
.d {
fill: #404041;
}
</style>
</defs>
<title>Cloudflare logo</title>
<path class="a" d="M94.7,10.6,89.1,9.3l-1-.4-25.7.2V21.5l32.3.1Z"/>
<path class="b" d="M84.2,20.4a2.85546,2.85546,0,0,0-.3-2.6,3.09428,3.09428,0,0,0-2.1-1.1l-17.4-.2c-.1,0-.2-.1-.3-.1a.1875.1875,0,0,1,0-.3c.1-.2.2-.3.4-.3L82,15.6a6.29223,6.29223,0,0,0,5.1-3.8l1-2.6c0-.1.1-.2,0-.3A11.39646,11.39646,0,0,0,66.2,7.7a5.45941,5.45941,0,0,0-3.6-1A5.20936,5.20936,0,0,0,58,11.3a5.46262,5.46262,0,0,0,.1,1.8A7.30177,7.30177,0,0,0,51,20.4a4.102,4.102,0,0,0,.1,1.1.3193.3193,0,0,0,.3.3H83.5c.2,0,.4-.1.4-.3Z"/>
<path class="c" d="M89.7,9.2h-.5c-.1,0-.2.1-.3.2l-.7,2.4a2.85546,2.85546,0,0,0,.3,2.6,3.09428,3.09428,0,0,0,2.1,1.1l3.7.2c.1,0,.2.1.3.1a.1875.1875,0,0,1,0,.3c-.1.2-.2.3-.4.3l-3.8.2a6.29223,6.29223,0,0,0-5.1,3.8l-.2.9c-.1.1,0,.3.2.3H98.5a.26517.26517,0,0,0,.3-.3,10.87184,10.87184,0,0,0,.4-2.6,9.56045,9.56045,0,0,0-9.5-9.5"/>
<path class="d" d="M100.5,27.2a.9.9,0,1,1,.9-.9.89626.89626,0,0,1-.9.9m0-1.6a.7.7,0,1,0,.7.7.68354.68354,0,0,0-.7-.7m.4,1.2h-.2l-.2-.3h-.2v.3h-.2v-.9h.5a.26517.26517,0,0,1,.3.3c0,.1-.1.2-.2.3l.2.3Zm-.3-.5c.1,0,.1,0,.1-.1a.09794.09794,0,0,0-.1-.1h-.3v.3h.3Zm-89.7-.9h2.2v6h3.8v1.9h-6Zm8.3,3.9a4.10491,4.10491,0,0,1,4.3-4.1,4.02,4.02,0,0,1,4.2,4.1,4.10491,4.10491,0,0,1-4.3,4.1,4.07888,4.07888,0,0,1-4.2-4.1m6.3,0a2.05565,2.05565,0,0,0-2-2.2,2.1025,2.1025,0,0,0,0,4.2c1.2.2,2-.8,2-2m4.9.5V25.4h2.2v4.4c0,1.1.6,1.7,1.5,1.7a1.39926,1.39926,0,0,0,1.5-1.6V25.4h2.2v4.4c0,2.6-1.5,3.7-3.7,3.7-2.3-.1-3.7-1.2-3.7-3.7m10.7-4.4h3.1c2.8,0,4.5,1.6,4.5,3.9s-1.7,4-4.5,4h-3V25.4Zm3.1,5.9a2.00909,2.00909,0,1,0,0-4h-.9v4Zm7.6-5.9h6.3v1.9H54v1.3h3.7v1.8H54v2.9H51.8Zm9.4,0h2.2v6h3.8v1.9h-6Zm11.7-.1h2.2l3.4,8H76.1l-.6-1.4H72.4l-.6,1.4H69.5Zm2,4.9L74,28l-.9,2.2Zm6.4-4.8H85a3.41818,3.41818,0,0,1,2.6.9,2.62373,2.62373,0,0,1-.9,4.2l1.9,2.8H86.1l-1.6-2.4h-1v2.4H81.3Zm3.6,3.8c.7,0,1.2-.4,1.2-.9,0-.6-.5-.9-1.2-.9H83.5v1.9h1.4Zm6.5-3.8h6.4v1.8H93.6v1.2h3.8v1.8H93.6v1.2h4.3v1.9H91.4ZM6.1,30.3a1.97548,1.97548,0,0,1-1.8,1.2,2.1025,2.1025,0,0,1,0-4.2,2.0977,2.0977,0,0,1,1.9,1.3H8.5a4.13459,4.13459,0,0,0-4.2-3.3A4.1651,4.1651,0,0,0,0,29.4a4.07888,4.07888,0,0,0,4.2,4.1,4.31812,4.31812,0,0,0,4.2-3.2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,14 @@
<svg viewBox="0 0 154 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 7.49503C2 6.1159 2 5.42633 2.26742 4.87131C2.50309 4.38219 2.8821 3.96763 3.35931 3.67702C3.90082 3.34726 4.62076 3.24937 6.06063 3.05361L26.5958 0.261598C28.4598 0.00816784 29.3918 -0.118547 30.1161 0.149002C30.7518 0.383867 31.2824 0.81992 31.6179 1.38326C32 2.025 32 2.91768 32 4.70302V43.297C32 45.0823 32 45.975 31.6179 46.6167C31.2824 47.1801 30.7518 47.6161 30.1161 47.851C29.3918 48.1185 28.4598 47.9918 26.5959 47.7384H26.5959H26.5958L6.06063 44.9464L6.06057 44.9464C4.62074 44.7506 3.90081 44.6527 3.35931 44.323C2.8821 44.0324 2.50309 43.6178 2.26742 43.1287C2 42.5737 2 41.8841 2 40.505V7.49503ZM24 20.5078C24 20.0879 24 19.8779 24.0705 19.7119C24.1326 19.5657 24.2323 19.4436 24.3567 19.3611C24.4979 19.2674 24.6847 19.2484 25.0583 19.2104L26.731 19.0402C27.1712 18.9954 27.3912 18.973 27.5611 19.0552C27.7104 19.1273 27.8338 19.2535 27.9115 19.4134C28 19.5954 28 19.8428 28 20.3376V27.6624C28 28.1572 28 28.4046 27.9115 28.5866C27.8338 28.7465 27.7104 28.8727 27.5611 28.9448C27.3912 29.027 27.1712 29.0046 26.731 28.9598L25.0583 28.7896C24.6847 28.7516 24.4979 28.7326 24.3567 28.6389C24.2323 28.5564 24.1326 28.4343 24.0705 28.2881C24 28.1221 24 27.9121 24 27.4922V20.5078ZM37.6444 5H35V44H37.6444C39.169 44 39.9313 44 40.5136 43.7024C41.0259 43.4407 41.4423 43.0231 41.7033 42.5094C42 41.9254 42 41.1609 42 39.632V9.368C42 7.83906 42 7.07458 41.7033 6.49061C41.4423 5.97692 41.0259 5.55929 40.5136 5.29755C39.9313 5 39.169 5 37.6444 5Z" fill="url(#paint0_linear_865_5913)"/>
<path d="M85.5299 36.8007C83.4751 36.8007 81.7004 36.3737 80.206 35.5198C78.7116 34.6658 77.5641 33.4783 76.7635 31.9572C75.963 30.4094 75.5627 28.6215 75.5627 26.5933C75.5627 24.5118 75.9763 22.7105 76.8036 21.1894C77.6308 19.6683 78.7917 18.4808 80.2861 17.6268C81.8072 16.7729 83.5818 16.3459 85.6099 16.3459C87.6648 16.3459 89.426 16.7729 90.8938 17.6268C92.3882 18.4808 93.549 19.6683 94.3763 21.1894C95.2036 22.7105 95.6172 24.4985 95.6172 26.5533C95.6172 28.5814 95.1902 30.3694 94.3363 31.9172C93.509 33.465 92.3482 34.6658 90.8537 35.5198C89.3593 36.3471 87.5847 36.774 85.5299 36.8007ZM85.5699 32.9179C86.6907 32.9179 87.6514 32.6377 88.452 32.0773C89.2526 31.5169 89.8664 30.7563 90.2933 29.7956C90.747 28.8349 90.9738 27.7542 90.9738 26.5533C90.9738 25.3257 90.7603 24.2316 90.3334 23.2709C89.9064 22.3102 89.2926 21.563 88.492 21.0293C87.6914 20.4689 86.7307 20.1887 85.6099 20.1887C84.5425 20.1887 83.5951 20.4689 82.7679 21.0293C81.9406 21.5897 81.3001 22.3502 80.8465 23.3109C80.4195 24.2716 80.1927 25.3524 80.166 26.5533C80.166 27.7808 80.3795 28.875 80.8065 29.8357C81.2334 30.7964 81.8606 31.5569 82.6878 32.1173C83.5151 32.651 84.4758 32.9179 85.5699 32.9179Z" fill="white"/>
<path d="M116.582 17.0751C116.582 16.9598 116.488 16.8663 116.373 16.8663H112.268C112.152 16.8663 112.059 16.9598 112.059 17.0751V37.5613C112.059 38.9222 111.552 39.7675 110.538 40.5147C109.55 41.2619 108.44 41.5062 106.865 41.5329C105.771 41.5062 104.744 41.4088 103.837 41.1152C102.986 40.8649 102.055 40.4971 101.084 40.012C100.978 39.9592 100.849 40.0031 100.799 40.1101L99.3229 43.2556C99.2803 43.3465 99.3081 43.4551 99.3911 43.5116C99.8414 43.8181 100.368 44.0919 100.971 44.333C101.638 44.5998 102.318 44.8133 103.012 44.9734C103.733 45.1602 104.477 45.3161 105.118 45.3961C105.785 45.4762 106.415 45.487 106.815 45.487C108.843 45.487 110.578 45.1668 112.019 44.5263C113.486 43.8858 114.607 42.9785 115.381 41.8043C116.182 40.6302 116.582 39.2291 116.582 37.6013V17.0751ZM113.314 30.3339C113.253 30.1892 113.059 30.1611 112.956 30.2796C112.171 31.183 111.373 31.823 110.618 32.2374C109.817 32.6911 108.896 32.9179 107.856 32.9179C106.788 32.9179 105.854 32.651 105.054 32.1173C104.28 31.5569 103.693 30.7964 103.292 29.8357C102.892 28.875 102.692 27.7675 102.692 26.5133C102.692 25.3124 102.892 24.2449 103.292 23.3109C103.693 22.3502 104.28 21.5897 105.054 21.0293C105.854 20.4689 106.802 20.1887 107.896 20.1887C108.91 20.1887 109.71 20.4022 110.538 20.8291C111.31 21.2153 112.132 22.0971 112.839 23.0181C112.938 23.1464 113.139 23.1225 113.202 22.9736L114.338 20.2934C114.366 20.2281 114.359 20.1533 114.319 20.0952C112.885 18.0383 110.461 16.3459 107.7 16.3459C105.699 16.3459 104.12 16.7729 102.652 17.6268C101.184 18.4808 100.05 19.6683 99.2494 21.1894C98.4755 22.7105 98.0886 24.4851 98.0886 26.5133C98.0886 28.5948 98.4755 30.4094 99.2494 31.9572C100.023 33.4783 101.131 34.6658 102.572 35.5198C104.04 36.3737 105.774 36.8007 107.776 36.8007C108.523 36.8007 109.297 36.6539 110.097 36.3604C110.925 36.0935 111.725 35.6932 112.499 35.1595C113.239 34.6489 113.894 34.0405 114.464 33.3345C114.511 33.2753 114.522 33.1947 114.493 33.1247L113.314 30.3339Z" fill="white"/>
<path d="M132.061 17.2352C132.061 17.1199 131.968 17.0264 131.852 17.0264H127.386C127.271 17.0264 127.178 16.9329 127.178 16.8176V11.3577C127.178 11.2424 127.084 11.1489 126.969 11.1489H122.903C122.788 11.1489 122.694 11.2424 122.694 11.3577V16.8176C122.694 16.9329 122.601 17.0264 122.486 17.0264H119.766C119.65 17.0264 119.556 17.1212 119.557 17.2374L119.593 20.6225C119.594 20.737 119.687 20.8291 119.801 20.8291H122.486C122.601 20.8291 122.694 20.9226 122.694 21.038V30.4761C122.694 31.8905 122.975 33.0914 123.535 34.0787C124.122 35.0394 124.923 35.7733 125.937 36.2803C126.951 36.7874 128.178 37.0409 129.619 37.0409C130.02 37.0409 130.447 36.9875 130.9 36.8808C131.381 36.774 131.845 36.6617 132.245 36.5015C132.602 36.3675 133.299 36.0182 133.654 35.8124C133.748 35.7576 133.777 35.6383 133.724 35.5431L132.072 32.631C132.02 32.5386 131.907 32.5 131.808 32.5378C131.541 32.6396 131.265 32.7234 130.98 32.7891C130.633 32.8692 130.286 32.9092 129.94 32.9092C129.006 32.9092 128.312 32.8112 127.858 32.1974C127.404 31.5569 127.178 30.743 127.178 29.7556V21.038C127.178 20.9226 127.271 20.8291 127.386 20.8291H131.852C131.968 20.8291 132.061 20.7356 132.061 20.6203V17.2352Z" fill="white"/>
<path d="M143.913 36.7907C141.858 36.7907 140.083 36.3638 138.589 35.5098C137.094 34.6559 135.947 33.4683 135.146 31.9472C134.346 30.3994 133.945 28.6115 133.945 26.5833C133.945 24.5018 134.359 22.7005 135.186 21.1794C136.014 19.6583 137.174 18.4708 138.669 17.6168C140.19 16.7629 141.965 16.3359 143.993 16.3359C146.048 16.3359 147.809 16.7629 149.277 17.6168C150.771 18.4708 151.932 19.6583 152.759 21.1794C153.586 22.7005 154 24.4885 154 26.5433C154 28.5715 153.573 30.3594 152.719 31.9072C151.892 33.455 150.731 34.6559 149.237 35.5098C147.742 36.3371 145.968 36.7641 143.913 36.7907ZM143.953 32.9079C145.074 32.9079 146.034 32.6277 146.835 32.0673C147.635 31.5069 148.249 30.7464 148.676 29.7857C149.13 28.825 149.357 27.7442 149.357 26.5433C149.357 25.3158 149.143 24.2216 148.716 23.2609C148.289 22.3002 147.675 21.553 146.875 21.0193C146.074 20.4589 145.114 20.1787 143.993 20.1787C142.925 20.1787 141.978 20.4589 141.151 21.0193C140.323 21.5797 139.683 22.3403 139.229 23.301C138.802 24.2617 138.575 25.3424 138.549 26.5433C138.549 27.7709 138.762 28.865 139.189 29.8257C139.616 30.7864 140.243 31.5469 141.071 32.1074C141.898 32.6411 142.859 32.9079 143.953 32.9079Z" fill="white"/>
<path d="M73.6634 32.7578C73.6634 32.6425 73.5699 32.549 73.4546 32.549H62.652C62.5367 32.549 62.4432 32.4555 62.4432 32.3401V11.3136C62.4432 11.1982 62.3497 11.1047 62.2344 11.1047H58.2088C58.0935 11.1047 58 11.1982 58 11.3136V36.5919C58 36.7072 58.0935 36.8007 58.2088 36.8007H73.4546C73.5699 36.8007 73.6634 36.7072 73.6634 36.5919V32.7578Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_865_5913" x1="-10.381" y1="33.1953" x2="38.8896" y2="13.0537" gradientUnits="userSpaceOnUse">
<stop stop-color="#4B2EFB"/>
<stop offset="1" stop-color="#E65FFC"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

18
public/svgs/rxresume.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

1
public/svgs/unleash.svg Normal file
View File

@ -0,0 +1 @@
<svg id="bg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 251.43 251.03"><defs><style>.cls-1{fill:#1a4049;}.cls-2{fill:#fff;}.cls-3{fill:#817afe;}</style></defs><circle class="cls-1" cx="125.71" cy="125.31" r="80"/><polygon class="cls-2" points="137.14 91.03 137.14 113.88 137.14 136.74 160 136.74 160 113.88 160 91.03 137.14 91.03"/><polygon class="cls-2" points="114.29 113.88 114.29 91.03 91.43 91.03 91.43 113.88 91.43 136.74 91.43 159.6 114.29 159.6 137.14 159.6 137.14 136.74 114.29 136.74 114.29 113.88"/><rect class="cls-3" x="137.14" y="136.74" width="22.86" height="22.86"/></svg>

After

Width:  |  Height:  |  Size: 593 B

View File

@ -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) {

View File

@ -5,6 +5,7 @@
'disabled' => false,
'action' => 'delete',
'content' => null,
'closeOutside' => true
])
<div x-data="{ modalOpen: false }" :class="{ 'z-40': modalOpen }" @keydown.window.escape="modalOpen=false" class="relative w-auto h-auto">
@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"></div>
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" @click.outside="modalOpen=false" x-transition:enter="ease-out duration-100"
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" @if ($closeOutside) @click.outside="modalOpen=false" @endif x-transition:enter="ease-out duration-100"
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-100"

View File

@ -0,0 +1,13 @@
@props(['text' => "Loading..."])
<div class="inline-flex items-center justify-center" {{ $attributes }}>
@if (isset($text))
<div>{{ $text }}</div>
@endif
<svg class="w-4 h-4 mx-1 ml-3 text-coollabs dark:text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
</svg>
</div>

View File

@ -12,7 +12,7 @@
@if ($upgrade)
<div>{{ $upgrade }}</div>
@else
<div class="text-xs dark:text-neutral-500 group-hover:dark:text-neutral-300">
<div class="text-xs font-bold dark:text-neutral-500 group-hover:dark:text-neutral-300">
{{ $description }}
</div>
@endif

View File

@ -3,7 +3,7 @@
<div>
<p class="font-mono font-semibold text-7xl dark:text-warning">503</p>
<h1 class="mt-4 font-bold tracking-tight dark:text-white">We are working on serious things.</h1>
<p class="text-base leading-7 text-neutral-300">Service Unavailable. Be right back. Thanks for your
<p class="text-base leading-7 text-black dark:text-neutral-300">Service Unavailable. Be right back. Thanks for your
patience.
</p>
<div class="flex items-center mt-10 gap-x-6">

View File

@ -6,9 +6,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
<link rel="dns-prefetch" href="https://api.fonts.coollabs.io" />
<link rel="preload" href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" as="style" />
<link rel="preload" href="https://api.fonts.coollabs.io/css2?family=Inter:wght@400;500;600;700;800&display=swap" as="style" />
<link rel="preload" href="https://cdn.fonts.coollabs.io/inter/normal/400.woff2" as="style" />
<link href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" rel="stylesheet">
<link rel="preload" href="https://cdn.fonts.coollabs.io/inter/normal/500.woff2" as="style" />
<link rel="preload" href="https://cdn.fonts.coollabs.io/inter/normal/600.woff2" as="style" />
<link rel="preload" href="https://cdn.fonts.coollabs.io/inter/normal/700.woff2" as="style" />
<link rel="preload" href="https://cdn.fonts.coollabs.io/inter/normal/800.woff2" as="style" />
<link href="https://api.fonts.coollabs.io/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<meta name="robots" content="noindex">
<title>Coolify</title>
@env('local')

View File

@ -9,8 +9,11 @@
@endif
</div>
@endif
<div
class="flex flex-col-reverse w-full px-4 py-2 overflow-y-auto bg-white border border-solid rounded dark:text-white dark:bg-coolgray-100 scrollbar border-neutral-300 dark:border-coolgray-300 max-h-96">
<div @class([
'flex flex-col-reverse w-full px-4 py-2 overflow-y-auto bg-white border border-solid rounded dark:text-white dark:bg-coolgray-100 scrollbar border-neutral-300 dark:border-coolgray-300',
'max-h-[48rem]' => $fullHeight,
'max-h-96' => !$fullHeight,
])>
<pre class="font-mono whitespace-pre-wrap" @if ($isPollingActive) wire:poll.1000ms="polling" @endif>{{ RunRemoteProcess::decodeOutput($this->activity) }}</pre>
</div>
@else

View File

@ -103,7 +103,8 @@
@else
<div class="flex flex-col gap-1">
<div class='font-bold dark:text-warning'>No servers found.</div>
<div class="flex items-center gap-1"><x-modal-input buttonTitle="Add" title="New Server">
<div class="flex items-center gap-1">
<x-modal-input buttonTitle="Add" title="New Server" :closeOutside="false">
<livewire:server.create />
</x-modal-input> your first server
or

View File

@ -36,7 +36,10 @@
<x-slot:description>
<span>Coolify could not connect to its real-time service.<br>This will cause unusual problems on the UI
if
not fixed! <br><br>Please check the
not fixed! <br><br>
Please ensure that you have opened the
<a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall' target='_blank'>required ports</a>,
check the
related <a class="underline" href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels'
target='_blank'>documentation</a> or get
help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>. </span>

View File

@ -4,7 +4,7 @@
<h2>Advanced</h2>
</div>
<div>Advanced configuration for your application.</div>
<div class="pt-4 w-96">
<div class="flex flex-col gap-1 pt-4 md:w-96">
<h3>General</h3>
@if ($application->git_based())
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
@ -49,7 +49,7 @@
@if ($application->build_pack !== 'dockercompose')
<div class="w-96">
<x-forms.checkbox
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='dark:text-white underline' target='_blank'>here</a>."
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='underline dark:text-white' target='_blank'>here</a>."
instantSave id="application.settings.is_gpu_enabled" label="Attach GPU" />
@if ($application->settings->is_gpu_enabled)
<h5>GPU Settings</h5>
@ -64,7 +64,7 @@
<x-forms.input label="GPU Count" placeholder="empty means use all GPUs"
id="application.settings.gpu_count"> </x-forms.input>
<x-forms.input label="GPU Device Ids" placeholder="0,2"
helper="Comma separated list of device ids. More info <a href='https://docs.docker.com/compose/gpu-support/#access-specific-devices' class='dark:text-white underline' target='_blank'>here</a>."
helper="Comma separated list of device ids. More info <a href='https://docs.docker.com/compose/gpu-support/#access-specific-devices' class='underline dark:text-white' target='_blank'>here</a>."
id="application.settings.gpu_device_ids"> </x-forms.input>
</div>

View File

@ -19,7 +19,7 @@
href="#">Environment
Variables</a>
@endif
@if ($application->build_pack !== 'static' && $application->build_pack !== 'dockercompose')
@if ($application->build_pack !== 'static')
<a class="menu-item" :class="activeTab === 'storages' && 'menu-item-active'"
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
</a>
@ -83,48 +83,48 @@
<livewire:project.application.general :application="$application" />
</div>
<div x-cloak x-show="activeTab === 'swarm'" class="h-full">
<livewire:project.application.swarm :application="$application" />
<livewire:project.application.swarm :application="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'advanced'" class="h-full">
<livewire:project.application.advanced :application="$application" />
<livewire:project.application.advanced :application="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'environment-variables'">
<livewire:project.shared.environment-variable.all :resource="$application" />
<livewire:project.shared.environment-variable.all :resource="$application" lazy />
</div>
@if ($application->git_based())
<div x-cloak x-show="activeTab === 'source'">
<livewire:project.application.source :application="$application" />
<livewire:project.application.source :application="$application" lazy />
</div>
@endif
<div x-cloak x-show="activeTab === 'servers'">
<livewire:project.shared.destination :resource="$application" :servers="$servers" />
<livewire:project.shared.destination :resource="$application" :servers="$servers" lazy />
</div>
<div x-cloak x-show="activeTab === 'storages'">
<livewire:project.service.storage :resource="$application" />
<livewire:project.service.storage :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'webhooks'">
<livewire:project.shared.webhooks :resource="$application" />
<livewire:project.shared.webhooks :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'previews'">
<livewire:project.application.previews :application="$application" />
<livewire:project.application.previews :application="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'health'">
<livewire:project.shared.health-checks :resource="$application" />
<livewire:project.shared.health-checks :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'rollback'">
<livewire:project.application.rollback :application="$application" />
<livewire:project.application.rollback :application="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'resource-limits'">
<livewire:project.shared.resource-limits :resource="$application" />
<livewire:project.shared.resource-limits :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'scheduled-tasks'">
<livewire:project.shared.scheduled-task.all :resource="$application" />
<livewire:project.shared.scheduled-task.all :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'resource-operations'">
<livewire:project.shared.resource-operations :resource="$application" />
<livewire:project.shared.resource-operations :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'tags'">
<livewire:project.shared.tags :resource="$application" />
<livewire:project.shared.tags :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'danger'">
<livewire:project.shared.danger :resource="$application" />

View File

@ -87,9 +87,9 @@
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
<span @class([
'dark:text-warning whitespace-pre-line' => $line['hidden'],
'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr',
'text-red-500 font-bold' => $line['type'] == 'stderr',
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
<br><br>COMMAND: {{ $line['command'] }}<br><br>OUTPUT :
<br><br>[COMMAND] {{ $line['command'] }}<br>[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
<br>
</span>
@endforeach
@else

View File

@ -17,81 +17,96 @@
@endif
<x-applications.links :application="$application" />
<div class="flex-1"></div>
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
<div>Please load a Compose file.</div>
@else
@if (!$application->destination->server->isSwarm())
<x-applications.advanced :application="$application" />
@endif
<div class="flex gap-2">
@if (!str($application->status)->startsWith('exited'))
@if (!$application->destination->server->isSwarm())
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
</path>
<path d="M7.05 11.038v-3.988"></path>
</svg>
Redeploy
</x-forms.button>
@endif
@if ($application->build_pack !== 'dockercompose')
@if ($application->destination->server->isSwarm())
<x-forms.button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
<div class="flex items-center gap-2">
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
<div>Please load a Compose file.</div>
@else
@if (!$application->destination->server->isSwarm())
<x-applications.advanced :application="$application" />
@endif
<div class="flex gap-2">
@if (!str($application->status)->startsWith('exited'))
@if (!$application->destination->server->isSwarm())
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
</path>
<path d="M7.05 11.038v-3.988"></path>
</svg>
Update Service
</x-forms.button>
@else
<x-forms.button title="Restart without rebuilding" wire:click='restart'>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg>
Restart
Redeploy
</x-forms.button>
@endif
@if ($application->build_pack !== 'dockercompose')
@if ($application->destination->server->isSwarm())
<x-forms.button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg>
Update Service
</x-forms.button>
@else
<x-forms.button title="Restart without rebuilding" wire:click='restart'>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg>
Restart
</x-forms.button>
@endif
@endif
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
<path
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
</svg>
Stop
</x-slot:button-title>
This application will be stopped. <br>Please think again.
</x-modal-confirmation>
@else
<x-forms.button wire:click='deploy'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
Deploy
</x-forms.button>
@endif
<x-forms.button wire:click='stop'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
</svg>
Stop
</x-forms.button>
@else
<x-forms.button wire:click='deploy'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
Deploy
</x-forms.button>
@endif
</div>
@endif
</div>
@endif
</div>
</div>
@script
<script>
$wire.$on('stopEvent', () => {
$wire.$dispatch('info', 'Stopping application.');
$wire.$call('stop');
});
</script>
@endscript
</nav>

View File

@ -17,7 +17,7 @@
<div class="flex gap-2">
<h2>Shared Variables</h2>
<x-modal-input buttonTitle="+ Add" title="New Shared Variable">
<livewire:project.shared.environment-variable.add />
<livewire:project.shared.environment-variable.add :shared="true" />
</x-modal-input>
</div>
<div class="pb-4 lg:flex lg:gap-1">

View File

@ -45,7 +45,7 @@
<div class="flex gap-2 pt-10">
<h2>Shared Variables</h2>
<x-modal-input buttonTitle="+ Add" title="New Shared Variable">
<livewire:project.shared.environment-variable.add />
<livewire:project.shared.environment-variable.add :shared="true" />
</x-modal-input>
</div>
<div class="flex items-center gap-2 pb-4">You can use these variables anywhere with <span class="dark:text-warning text-coollabs">@{{environment.VARIABLENAME}}</span><x-helper

View File

@ -2,7 +2,7 @@
<h1>Create a new Application</h1>
<div class="pb-4">You can deploy an existing Docker Image from any Registry.</div>
<form wire:submit="submit">
<div class="flex gap-2 pb-1">
<div class="flex gap-2 pt-4 pb-1">
<h2>Docker Image</h2>
<x-forms.button type="submit">Save</x-forms.button>
</div>

Some files were not shown because too many files have changed in this diff Show More