diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml
index 36321d96b..3709b5b46 100644
--- a/.github/workflows/production-build.yml
+++ b/.github/workflows/production-build.yml
@@ -3,6 +3,8 @@ name: Production Build (v4)
on:
push:
branches: ["main"]
+ paths-ignore:
+ - templates/service-templates.json
env:
REGISTRY: ghcr.io
diff --git a/app/Actions/Database/StartClickhouse.php b/app/Actions/Database/StartClickhouse.php
index fb38c32d5..d043da410 100644
--- a/app/Actions/Database/StartClickhouse.php
+++ b/app/Actions/Database/StartClickhouse.php
@@ -99,7 +99,7 @@ class StartClickhouse
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
- $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
+ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
diff --git a/app/Actions/Database/StartDatabaseProxy.php b/app/Actions/Database/StartDatabaseProxy.php
index 89bee1d5a..a1e47710c 100644
--- a/app/Actions/Database/StartDatabaseProxy.php
+++ b/app/Actions/Database/StartDatabaseProxy.php
@@ -149,9 +149,9 @@ class StartDatabaseProxy
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
instant_remote_process([
"mkdir -p $configuration_dir",
- "echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
- "echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
- "echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
+ "echo '{$dockerfile_base64}' | base64 -d | tee $configuration_dir/Dockerfile > /dev/null",
+ "echo '{$nginxconf_base64}' | base64 -d | tee $configuration_dir/nginx.conf > /dev/null",
+ "echo '{$dockercompose_base64}' | base64 -d | tee $configuration_dir/docker-compose.yaml > /dev/null",
"docker compose --project-directory {$configuration_dir} pull",
"docker compose --project-directory {$configuration_dir} up --build -d",
], $server);
diff --git a/app/Actions/Database/StartDragonfly.php b/app/Actions/Database/StartDragonfly.php
index 4fb75b3cb..bb71d8c48 100644
--- a/app/Actions/Database/StartDragonfly.php
+++ b/app/Actions/Database/StartDragonfly.php
@@ -100,7 +100,7 @@ class StartDragonfly
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
- $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
+ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
diff --git a/app/Actions/Database/StartKeydb.php b/app/Actions/Database/StartKeydb.php
index 7b4bbe124..489c74053 100644
--- a/app/Actions/Database/StartKeydb.php
+++ b/app/Actions/Database/StartKeydb.php
@@ -107,7 +107,7 @@ class StartKeydb
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
- $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
+ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php
index 755c47867..e02b28b2e 100644
--- a/app/Actions/Database/StartMariadb.php
+++ b/app/Actions/Database/StartMariadb.php
@@ -100,7 +100,7 @@ class StartMariadb
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
- $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
+ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -171,6 +171,6 @@ class StartMariadb
$filename = 'custom-config.cnf';
$content = $this->database->mariadb_conf;
$content_base64 = base64_encode($content);
- $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
+ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
}
diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php
index 180ae988a..7bb6cbcd0 100644
--- a/app/Actions/Database/StartMongodb.php
+++ b/app/Actions/Database/StartMongodb.php
@@ -116,7 +116,7 @@ class StartMongodb
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
- $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
+ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -184,13 +184,13 @@ class StartMongodb
$filename = 'mongod.conf';
$content = $this->database->mongo_conf;
$content_base64 = base64_encode($content);
- $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
+ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
private function add_default_database()
{
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
$content_base64 = base64_encode($content);
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
- $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js";
+ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null";
}
}
diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php
index 0e2e75719..b3f695d72 100644
--- a/app/Actions/Database/StartMysql.php
+++ b/app/Actions/Database/StartMysql.php
@@ -100,7 +100,7 @@ class StartMysql
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
- $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
+ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -171,6 +171,6 @@ class StartMysql
$filename = 'custom-config.cnf';
$content = $this->database->mysql_conf;
$content_base64 = base64_encode($content);
- $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
+ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
}
diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php
index 03b460edc..f19a8b036 100644
--- a/app/Actions/Database/StartPostgresql.php
+++ b/app/Actions/Database/StartPostgresql.php
@@ -122,7 +122,7 @@ class StartPostgresql
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
- $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
+ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -197,7 +197,7 @@ class StartPostgresql
$filename = data_get($init_script, 'filename');
$content = data_get($init_script, 'content');
$content_base64 = base64_encode($content);
- $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
+ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/{$filename} > /dev/null";
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
}
}
@@ -209,6 +209,6 @@ class StartPostgresql
$filename = 'custom-postgres.conf';
$content = $this->database->postgres_conf;
$content_base64 = base64_encode($content);
- $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
+ $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
}
diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php
index ff8c63aab..01e9a9bef 100644
--- a/app/Actions/Database/StartRedis.php
+++ b/app/Actions/Database/StartRedis.php
@@ -111,7 +111,7 @@ class StartRedis
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
- $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
+ $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
diff --git a/app/Actions/Proxy/CheckConfiguration.php b/app/Actions/Proxy/CheckConfiguration.php
index 208a6863a..1058e8b5f 100644
--- a/app/Actions/Proxy/CheckConfiguration.php
+++ b/app/Actions/Proxy/CheckConfiguration.php
@@ -16,11 +16,11 @@ class CheckConfiguration
return 'OK';
}
$proxy_path = $server->proxyPath();
-
- $proxy_configuration = instant_remote_process([
+ $payload = [
"mkdir -p $proxy_path",
"cat $proxy_path/docker-compose.yml",
- ], $server, false);
+ ];
+ $proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php
index d39b33a2a..9323ca1d1 100644
--- a/app/Actions/Proxy/CheckProxy.php
+++ b/app/Actions/Proxy/CheckProxy.php
@@ -13,8 +13,9 @@ class CheckProxy
if ($server->proxyType() === 'NONE') {
return false;
}
- if (!$server->validateConnection()) {
- throw new \Exception("Server Connection Error");
+ ['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
+ if (!$uptime) {
+ throw new \Exception($error);
}
if (!$server->isProxyShouldRun()) {
if ($fromUI) {
diff --git a/app/Actions/Proxy/SaveConfiguration.php b/app/Actions/Proxy/SaveConfiguration.php
index 5fb983d1a..4c413ca36 100644
--- a/app/Actions/Proxy/SaveConfiguration.php
+++ b/app/Actions/Proxy/SaveConfiguration.php
@@ -23,7 +23,7 @@ class SaveConfiguration
return instant_remote_process([
"mkdir -p $proxy_path",
- "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
+ "echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null",
], $server);
}
}
diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php
index ab635fc65..5b1ffa409 100644
--- a/app/Actions/Proxy/StartProxy.php
+++ b/app/Actions/Proxy/StartProxy.php
@@ -30,16 +30,18 @@ class StartProxy
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([
- "mkdir -p $proxy_path/dynamic && cd $proxy_path",
+ "mkdir -p $proxy_path/dynamic",
+ "cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'",
- "cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
+ "docker stack deploy -c docker-compose.yml coolify-proxy",
"echo 'Proxy started successfully.'"
]);
} else {
$caddfile = "import /dynamic/*.caddy";
$commands = $commands->merge([
- "mkdir -p $proxy_path/dynamic && cd $proxy_path",
+ "mkdir -p $proxy_path/dynamic",
+ "cd $proxy_path",
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
"echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",
diff --git a/app/Actions/Server/ConfigureCloudflared.php b/app/Actions/Server/ConfigureCloudflared.php
index 6b08b3de6..3221557ae 100644
--- a/app/Actions/Server/ConfigureCloudflared.php
+++ b/app/Actions/Server/ConfigureCloudflared.php
@@ -29,8 +29,9 @@ class ConfigureCloudflared
$config = Yaml::dump($config, 12, 2);
$docker_compose_yml_base64 = base64_encode($config);
$commands = collect([
- "mkdir -p /tmp/cloudflared && cd /tmp/cloudflared",
- "echo '$docker_compose_yml_base64' | base64 -d > docker-compose.yml",
+ "mkdir -p /tmp/cloudflared",
+ "cd /tmp/cloudflared",
+ "echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
"docker compose pull",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
"docker compose up -d --remove-orphans",
@@ -39,6 +40,11 @@ class ConfigureCloudflared
} catch (\Throwable $e) {
ray($e);
throw $e;
+ } finally {
+ $commands = collect([
+ "rm -fr /tmp/cloudflared",
+ ]);
+ instant_remote_process($commands, $server);
}
}
}
diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php
index 1db766a88..721d174b8 100644
--- a/app/Actions/Server/InstallDocker.php
+++ b/app/Actions/Server/InstallDocker.php
@@ -48,20 +48,28 @@ class InstallDocker
if ($supported_os_type->contains('debian')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
- "command -v jq >/dev/null || apt-get update -y",
- "command -v jq >/dev/null || apt install -y curl wget git jq",
-
+ "apt-get update -y",
+ "command -v curl >/dev/null || apt install -y curl",
+ "command -v wget >/dev/null || apt install -y wget",
+ "command -v git >/dev/null || apt install -y git",
+ "command -v jq >/dev/null || apt install -y jq",
]);
} else if ($supported_os_type->contains('rhel')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
- "command -v jq >/dev/null || dnf install -y curl wget git jq",
+ "command -v curl >/dev/null || dnf install -y curl",
+ "command -v wget >/dev/null || dnf install -y wget",
+ "command -v git >/dev/null || dnf install -y git",
+ "command -v jq >/dev/null || dnf install -y jq",
]);
} else if ($supported_os_type->contains('sles')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
- "command -v jq >/dev/null || zypper update -y",
- "command -v jq >/dev/null || zypper install -y curl wget git jq",
+ "zypper update -y",
+ "command -v curl >/dev/null || zypper install -y curl",
+ "command -v wget >/dev/null || zypper install -y wget",
+ "command -v git >/dev/null || zypper install -y git",
+ "command -v jq >/dev/null || zypper install -y jq",
]);
} else {
throw new \Exception('Unsupported OS');
@@ -70,10 +78,13 @@ class InstallDocker
"echo 'Installing Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
- "test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
- "echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
- "cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
- "cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
+ "test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-$(date +\"%Y%m%d-%H%M%S\")\"",
+ "test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null",
+ "echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null",
+ "jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null",
+ "mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify",
+ "jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null",
+ "mv /etc/docker/daemon.json.appended /etc/docker/daemon.json",
"echo 'Restarting Docker Engine...'",
"systemctl enable docker >/dev/null 2>&1 || true",
"systemctl restart docker",
diff --git a/app/Actions/Server/InstallLogDrain.php b/app/Actions/Server/InstallLogDrain.php
index 86ca69a6e..aa32d4c0b 100644
--- a/app/Actions/Server/InstallLogDrain.php
+++ b/app/Actions/Server/InstallLogDrain.php
@@ -168,10 +168,10 @@ Files:
$command = [
"echo 'Saving configuration'",
"mkdir -p $config_path",
- "echo '{$parsers}' | base64 -d > $parsers_config",
- "echo '{$config}' | base64 -d > $fluent_bit_config",
- "echo '{$compose}' | base64 -d > $compose_path",
- "echo '{$readme}' | base64 -d > $readme_path",
+ "echo '{$parsers}' | base64 -d | tee $parsers_config > /dev/null",
+ "echo '{$config}' | base64 -d | tee $fluent_bit_config > /dev/null",
+ "echo '{$compose}' | base64 -d | tee $compose_path > /dev/null",
+ "echo '{$readme}' | base64 -d | tee $readme_path > /dev/null",
"test -f $config_path/.env && rm $config_path/.env",
];
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 92cbf9c5c..31760f2fd 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -31,7 +31,7 @@ class Kernel extends ConsoleKernel
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
- $this->pull_helper_image($schedule);
+ // $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
} else {
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
index 2584df48b..5c8827085 100644
--- a/app/Exceptions/Handler.php
+++ b/app/Exceptions/Handler.php
@@ -56,7 +56,7 @@ class Handler extends ExceptionHandler
{
$this->reportable(function (Throwable $e) {
if (isDev()) {
- // return;
+ return;
}
if ($e instanceof RuntimeException) {
return;
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index 5cc5c4fb5..52afdd47e 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -49,6 +49,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private string $deployment_uuid;
private int $pull_request_id;
private string $commit;
+ private bool $rollback;
private bool $force_rebuild;
private bool $restart_only;
@@ -117,6 +118,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
$this->commit = $this->application_deployment_queue->commit;
+ $this->rollback = $this->application_deployment_queue->rollback;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->restart_only = $this->application_deployment_queue->restart_only;
$this->only_this_server = $this->application_deployment_queue->only_this_server;
@@ -322,7 +324,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->prepare_builder_image();
$this->execute_remote_command(
[
- executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > {$this->workdir}{$this->dockerfile_location}")
+ executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null")
],
);
$this->generate_image_names();
@@ -391,7 +393,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([
- executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true
+ executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true
]);
$this->save_environment_variables();
// Build new container to limit downtime.
@@ -565,7 +567,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"mkdir -p $this->configuration_dir"
],
[
- "echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
+ "echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
@@ -708,19 +710,78 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function save_environment_variables()
{
$envs = collect([]);
+ $ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
if ($this->pull_request_id !== 0) {
$this->env_filename = ".env-pr-$this->pull_request_id";
foreach ($this->application->environment_variables_preview as $env) {
- $envs->push($env->key . '=' . $env->real_value);
+ $real_value = $env->real_value;
+ if ($env->version === '4.0.0-beta.239') {
+ $real_value = $env->real_value;
+ } else {
+ $real_value = escapeEnvVariables($env->real_value);
+ }
+ if ($env->is_literal) {
+ $real_value = '\'' . $real_value . '\'';
+ }
+ $envs->push($env->key . '=' . $real_value);
}
+ // Add PORT if not exists, use the first port as default
+ if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
+ $envs->push("PORT={$ports[0]}");
+ }
+ // Add HOST if not exists
+ if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
+ $envs->push("HOST=0.0.0.0");
+ }
+ if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
+ if (!is_null($this->commit)) {
+ $envs->push("SOURCE_COMMIT={$this->commit}");
+ } else {
+ $envs->push("SOURCE_COMMIT=unknown");
+ }
+ }
+ $envs = $envs->sort(function ($a, $b) {
+ return strpos($a, '$') === false ? -1 : 1;
+ });
} else {
$this->env_filename = ".env";
foreach ($this->application->environment_variables as $env) {
- $envs->push($env->key . '=' . $env->real_value);
+ $real_value = $env->real_value;
+ if ($env->version === '4.0.0-beta.239') {
+ $real_value = $env->real_value;
+ } else {
+ $real_value = escapeEnvVariables($env->real_value);
+ }
+ if ($env->is_literal) {
+ $real_value = '\'' . $real_value . '\'';
+ }
+ $envs->push($env->key . '=' . $real_value);
}
+ // Add PORT if not exists, use the first port as default
+ if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
+ $envs->push("PORT={$ports[0]}");
+ }
+ // Add HOST if not exists
+ if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
+ $envs->push("HOST=0.0.0.0");
+ }
+ if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
+ if (!is_null($this->commit)) {
+ $envs->push("SOURCE_COMMIT={$this->commit}");
+ } else {
+ $envs->push("SOURCE_COMMIT=unknown");
+ }
+ }
+ $envs = $envs->sort(function ($a, $b) {
+ return strpos($a, '$') === false ? -1 : 1;
+ });
}
+
if ($envs->isEmpty()) {
$this->env_filename = null;
+ if ($this->use_build_server) {
+ $this->server = $this->original_server;
+ }
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
@@ -728,17 +789,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true
]
);
- return;
+ if ($this->use_build_server) {
+ $this->server = $this->build_server;
+ }
+ } else {
+ $envs_base64 = base64_encode($envs->implode("\n"));
+ $this->execute_remote_command(
+ [
+ executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null")
+ ],
+
+ );
+ if ($this->use_build_server) {
+ $this->server = $this->original_server;
+ }
+ $this->execute_remote_command(
+ [
+ "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
+ ]
+ );
+ if ($this->use_build_server) {
+ $this->server = $this->build_server;
+ }
}
- $envs_base64 = base64_encode($envs->implode("\n"));
- $this->execute_remote_command(
- [
- executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
- ],
- [
- "echo '$envs_base64' | base64 -d > $this->configuration_dir/{$this->env_filename}"
- ]
- );
+ // $this->execute_remote_command([
+ // executeInDocker($this->deployment_uuid, "cat $this->workdir/.env 2>/dev/null || true"),
+ // "hidden" => true,
+ // "save" => "dotenv"
+ // ]);
+ // if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
+ // $base64_dotenv = base64_encode($this->saved_outputs->get('dotenv')->value());
+ // $this->execute_remote_command(
+ // [
+ // "echo '{$base64_dotenv}' | base64 -d | tee $this->configuration_dir/.env > /dev/null"
+ // ]
+ // );
+ // } else {
+ // $this->execute_remote_command(
+ // [
+ // "command" => "rm -f $this->configuration_dir/.env",
+ // "hidden" => true,
+ // "ignore_errors" => true
+ // ]
+ // );
+ // }
+
}
@@ -973,7 +1068,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh")
],
[
- executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa")
+ executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null")
],
[
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa")
@@ -993,7 +1088,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
}
- if ($this->saved_outputs->get('git_commit_sha')) {
+ if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) {
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
}
}
@@ -1124,8 +1219,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
- $environment_variables = $this->generate_environment_variables($ports);
-
+ // $environment_variables = $this->generate_environment_variables($ports);
+ $this->save_environment_variables();
if (data_get($this->application, 'custom_labels')) {
$this->application->parseContainerLabels();
$labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels)));
@@ -1159,8 +1254,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->pull_request_id !== 0) {
$labels = collect(generateLabelsApplication($this->application, $this->preview));
}
+ $labels = $labels->map(function ($value, $key) {
+ return escapeDollarSign($value);
+ });
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
-
// Check for custom HEALTHCHECK
$this->custom_healthcheck_found = false;
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
@@ -1179,7 +1276,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'image' => $this->production_image_name,
'container_name' => $this->container_name,
'restart' => RESTART_MODE,
- 'environment' => $environment_variables,
'expose' => $ports,
'networks' => [
$this->destination->network,
@@ -1200,10 +1296,22 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
]
];
- if ($this->env_filename) {
- $docker_compose['services'][$this->container_name]['env_file'] = [
- $this->env_filename
- ];
+ // if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
+ // if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
+ // $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
+ // } else {
+ // $docker_compose['services'][$this->container_name]['env_file'] = ['.env'];
+ // }
+ // }
+ // if ($this->env_filename) {
+ // if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
+ // $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename;
+ // } else {
+ // $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
+ // }
+ // }
+ if (!is_null($this->env_filename)) {
+ $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
}
if (!$this->custom_healthcheck_found) {
$docker_compose['services'][$this->container_name]['healthcheck'] = [
@@ -1357,8 +1465,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose);
- $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
- $this->save_environment_variables();
+ $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), "hidden" => true]);
}
private function generate_local_persistent_volumes()
@@ -1415,6 +1522,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
+ if ($env->is_literal) {
+ $real_value = escapeDollarSign($real_value);
+ }
$environment_variables->push("$env->key=$real_value");
}
foreach ($this->application->nixpacks_environment_variables as $env) {
@@ -1423,6 +1533,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
+ if ($env->is_literal) {
+ $real_value = escapeDollarSign($real_value);
+ }
$environment_variables->push("$env->key=$real_value");
}
} else {
@@ -1432,6 +1545,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
+ if ($env->is_literal) {
+ $real_value = escapeDollarSign($real_value);
+ }
$environment_variables->push("$env->key=$real_value");
}
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
@@ -1440,6 +1556,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
+ if ($env->is_literal) {
+ $real_value = escapeDollarSign($real_value);
+ }
$environment_variables->push("$env->key=$real_value");
}
}
@@ -1539,7 +1658,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else {
if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
- $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]);
+ $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true
@@ -1560,7 +1679,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$this->execute_remote_command(
[
- executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
+ executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1595,13 +1714,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
- executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile")
+ executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null")
],
[
- executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
+ executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null")
],
[
- executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
+ executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1614,7 +1733,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
- executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
+ executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1623,7 +1742,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else {
if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
- $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]);
+ $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true
@@ -1644,7 +1763,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$this->execute_remote_command(
[
- executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
+ executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1762,7 +1881,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
$this->execute_remote_command([
- executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"),
+ executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"),
"hidden" => true
]);
}
diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php
index 333d46027..a9cec009b 100644
--- a/app/Jobs/ContainerStatusJob.php
+++ b/app/Jobs/ContainerStatusJob.php
@@ -47,7 +47,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
-
$applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($applications as $application) {
@@ -78,6 +77,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if (is_null($containers)) {
return;
}
+
$containers = format_docker_command_output_to_json($containers);
if ($containerReplicates) {
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
@@ -201,7 +201,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
// Notify user that this container should not be there.
}
}
-
}
if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0;
diff --git a/app/Jobs/ServerFilesFromServerJob.php b/app/Jobs/ServerFilesFromServerJob.php
index b195e2b6d..978a3dc19 100644
--- a/app/Jobs/ServerFilesFromServerJob.php
+++ b/app/Jobs/ServerFilesFromServerJob.php
@@ -2,6 +2,7 @@
namespace App\Jobs;
+use App\Models\Application;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Bus\Queueable;
@@ -16,11 +17,11 @@ class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct(public ServiceApplication|ServiceDatabase $service)
+ public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
{
}
public function handle()
{
- $this->service->getFilesFromServer(isInit: true);
+ $this->resource->getFilesFromServer(isInit: true);
}
}
diff --git a/app/Livewire/ActivityMonitor.php b/app/Livewire/ActivityMonitor.php
index 421992cb5..37bfc77bb 100644
--- a/app/Livewire/ActivityMonitor.php
+++ b/app/Livewire/ActivityMonitor.php
@@ -13,6 +13,7 @@ class ActivityMonitor extends Component
public $activityId;
public $eventToDispatch = 'activityFinished';
public $isPollingActive = false;
+ public bool $fullHeight = false;
public bool $showWaiting = false;
protected $activity;
diff --git a/app/Livewire/NewActivityMonitor.php b/app/Livewire/NewActivityMonitor.php
index 8f2dba0da..853115888 100644
--- a/app/Livewire/NewActivityMonitor.php
+++ b/app/Livewire/NewActivityMonitor.php
@@ -11,15 +11,17 @@ class NewActivityMonitor extends Component
public ?string $header = null;
public $activityId;
public $eventToDispatch = 'activityFinished';
+ public $eventData = null;
public $isPollingActive = false;
protected $activity;
protected $listeners = ['newActivityMonitor' => 'newMonitorActivity'];
- public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
+ public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished', $eventData = null)
{
$this->activityId = $activityId;
$this->eventToDispatch = $eventToDispatch;
+ $this->eventData = $eventData;
$this->hydrateActivity();
@@ -55,8 +57,12 @@ class NewActivityMonitor extends Component
}
return;
}
- $this->dispatch($this->eventToDispatch);
- ray('Dispatched event: ' . $this->eventToDispatch);
+ if (!is_null($this->eventData)) {
+ $this->dispatch($this->eventToDispatch, $this->eventData);
+ } else {
+ $this->dispatch($this->eventToDispatch);
+ }
+ ray('Dispatched event: ' . $this->eventToDispatch . ' with data: ' . $this->eventData);
}
}
}
diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php
index 7eb910533..022a7b710 100644
--- a/app/Livewire/Project/Application/General.php
+++ b/app/Livewire/Project/Application/General.php
@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
+use App\Models\LocalFileVolume;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Component;
@@ -124,7 +125,7 @@ class General extends Component
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes;
- $this->customLabels = $this->application->parseContainerLabels();
+ $this->customLabels = $this->application->parseContainerLabels();
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
@@ -156,8 +157,36 @@ class General extends Component
return;
}
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
+ $compose = $this->application->parseCompose();
+ $services = data_get($compose, 'services');
+ if ($services) {
+ $volumes = collect($services)->map(function ($service) {
+ return data_get($service, 'volumes');
+ })->flatten()->filter(function ($volume) {
+ return str($volume)->startsWith('/data/coolify');
+ })->unique()->values();
+ foreach ($volumes as $volume) {
+ $source = Str::of($volume)->before(':');
+ $target = Str::of($volume)->after(':')->beforeLast(':');
+
+ LocalFileVolume::updateOrCreate(
+ [
+ 'mount_path' => $target,
+ 'resource_id' => $this->application->id,
+ 'resource_type' => get_class($this->application)
+ ],
+ [
+ 'fs_path' => $source,
+ 'mount_path' => $target,
+ 'resource_id' => $this->application->id,
+ 'resource_type' => get_class($this->application)
+ ]
+ );
+ }
+ }
$this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded');
+ $this->dispatch('refresh_storages');
} catch (\Throwable $e) {
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
@@ -165,7 +194,6 @@ class General extends Component
return handleError($e, $this);
} finally {
$this->initLoadingCompose = false;
-
}
}
public function generateDomain(string $serviceName)
@@ -184,7 +212,14 @@ class General extends Component
$this->loadComposeFile();
}
}
- public function updatedApplicationFqdn() {
+ public function updatedApplicationFqdn()
+ {
+ $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
+ $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
+ $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
+ return str($domain)->trim()->lower();
+ });
+ $this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$this->resetDefaultLabels();
}
public function updatedApplicationBuildPack()
diff --git a/app/Livewire/Project/Application/Rollback.php b/app/Livewire/Project/Application/Rollback.php
index 7bd2d069b..f926b8e12 100644
--- a/app/Livewire/Project/Application/Rollback.php
+++ b/app/Livewire/Project/Application/Rollback.php
@@ -18,7 +18,6 @@ class Rollback extends Component
{
$this->parameters = get_route_parameters();
}
-
public function rollbackImage($commit)
{
$deployment_uuid = new Cuid2(7);
@@ -27,6 +26,7 @@ class Rollback extends Component
application: $this->application,
deployment_uuid: $deployment_uuid,
commit: $commit,
+ rollback: true,
force_rebuild: false,
);
return redirect()->route('project.application.deployment.show', [
diff --git a/app/Livewire/Project/Edit.php b/app/Livewire/Project/Edit.php
index 17cb6902b..d222917a6 100644
--- a/app/Livewire/Project/Edit.php
+++ b/app/Livewire/Project/Edit.php
@@ -25,6 +25,7 @@ class Edit extends Component
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
+ 'is_literal' => $data['is_literal'],
'type' => 'project',
'team_id' => currentTeam()->id,
]);
diff --git a/app/Livewire/Project/EnvironmentEdit.php b/app/Livewire/Project/EnvironmentEdit.php
index c5e4ccf11..173d946f3 100644
--- a/app/Livewire/Project/EnvironmentEdit.php
+++ b/app/Livewire/Project/EnvironmentEdit.php
@@ -29,6 +29,7 @@ class EnvironmentEdit extends Component
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
+ 'is_literal' => $data['is_literal'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);
diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php
index 8e1fcb5f1..8bbb5b052 100644
--- a/app/Livewire/Project/New/PublicGitRepository.php
+++ b/app/Livewire/Project/New/PublicGitRepository.php
@@ -89,9 +89,11 @@ class PublicGitRepository extends Component
public function load_branch()
{
try {
- $this->validate([
- 'repository_url' => 'required|url'
- ]);
+ if (str($this->repository_url)->startsWith('git@')) {
+ $github_instance = str($this->repository_url)->after('git@')->before(':');
+ $repository = str($this->repository_url)->after(':')->before('.git');
+ $this->repository_url = 'https://' . str($github_instance) . '/' . $repository;
+ }
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php
index 322360534..3705d6f93 100644
--- a/app/Livewire/Project/Resource/Create.php
+++ b/app/Livewire/Project/Resource/Create.php
@@ -40,7 +40,7 @@ class Create extends Component
$database = create_standalone_keydb($environment->id, $destination_uuid);
} else if ($type->value() === 'dragonfly') {
$database = create_standalone_dragonfly($environment->id, $destination_uuid);
- }else if ($type->value() === 'clickhouse') {
+ } else if ($type->value() === 'clickhouse') {
$database = create_standalone_clickhouse($environment->id, $destination_uuid);
}
return redirect()->route('project.database.configuration', [
@@ -60,14 +60,19 @@ class Create extends Component
}
if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
- $service = Service::create([
+ $service_payload = [
'name' => "$oneClickServiceName-" . str()->random(10),
'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id,
+ 'service_type' => $oneClickServiceName,
'server_id' => (int) $server_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
- ]);
+ ];
+ if ($oneClickServiceName === 'cloudflared') {
+ data_set($service_payload, 'connect_to_docker_network', true);
+ }
+ $service = Service::create($service_payload);
$service->name = "$oneClickServiceName-" . $service->uuid;
$service->save();
if ($oneClickDotEnvs?->count() > 0) {
diff --git a/app/Livewire/Project/Service/EditCompose.php b/app/Livewire/Project/Service/EditCompose.php
index 84cc45cf2..0f9c449f9 100644
--- a/app/Livewire/Project/Service/EditCompose.php
+++ b/app/Livewire/Project/Service/EditCompose.php
@@ -13,11 +13,13 @@ class EditCompose extends Component
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
];
- public function mount() {
+ public function mount()
+ {
$this->service = Service::find($this->serviceId);
}
- public function saveEditedCompose() {
+ public function saveEditedCompose()
+ {
$this->dispatch('info', "Saving new docker compose...");
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
}
diff --git a/app/Livewire/Project/Service/EditDomain.php b/app/Livewire/Project/Service/EditDomain.php
index 87a4a0546..a09d6aa38 100644
--- a/app/Livewire/Project/Service/EditDomain.php
+++ b/app/Livewire/Project/Service/EditDomain.php
@@ -41,7 +41,7 @@ class EditDomain extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
- $this->dispatch('generateDockerCompose');
+ $this->application->service->parse();
$this->dispatch('refresh');
$this->dispatch('configurationChanged');
}
diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php
index 0077f5cda..1703caf8f 100644
--- a/app/Livewire/Project/Service/FileStorage.php
+++ b/app/Livewire/Project/Service/FileStorage.php
@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Service;
+use App\Models\Application;
use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -12,7 +13,7 @@ use Illuminate\Support\Str;
class FileStorage extends Component
{
public LocalFileVolume $fileStorage;
- public ServiceApplication|ServiceDatabase|StandaloneClickhouse $resource;
+ public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource;
public string $fs_path;
public ?string $workdir = null;
@@ -33,6 +34,43 @@ class FileStorage extends Component
$this->fs_path = $this->fileStorage->fs_path;
}
}
+ public function convertToDirectory() {
+ try {
+ $this->fileStorage->deleteStorageOnServer();
+ $this->fileStorage->is_directory = true;
+ $this->fileStorage->content = null;
+ $this->fileStorage->save();
+ $this->fileStorage->saveStorageOnServer();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh_storages');
+ }
+ }
+ public function convertToFile() {
+ try {
+ $this->fileStorage->deleteStorageOnServer();
+ $this->fileStorage->is_directory = false;
+ $this->fileStorage->content = null;
+ $this->fileStorage->save();
+ $this->fileStorage->saveStorageOnServer();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh_storages');
+ }
+ }
+ public function delete() {
+ try {
+ $this->fileStorage->deleteStorageOnServer();
+ $this->fileStorage->delete();
+ $this->dispatch('success', 'File deleted.');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh_storages');
+ }
+ }
public function submit()
{
$original = $this->fileStorage->getOriginal();
diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php
index 9c98c4d9f..03bf99245 100644
--- a/app/Livewire/Project/Service/StackForm.php
+++ b/app/Livewire/Project/Service/StackForm.php
@@ -3,12 +3,13 @@
namespace App\Livewire\Project\Service;
use App\Models\Service;
+use Illuminate\Support\Collection;
use Livewire\Component;
class StackForm extends Component
{
public Service $service;
- public $fields = [];
+ public Collection $fields;
protected $listeners = ["saveCompose"];
public $rules = [
'service.docker_compose_raw' => 'required',
@@ -20,6 +21,7 @@ class StackForm extends Component
public $validationAttributes = [];
public function mount()
{
+ $this->fields = collect([]);
$extraFields = $this->service->extraFields();
foreach ($extraFields as $serviceName => $fields) {
foreach ($fields as $fieldKey => $field) {
@@ -27,18 +29,20 @@ class StackForm extends Component
$value = data_get($field, 'value');
$rules = data_get($field, 'rules', 'nullable');
$isPassword = data_get($field, 'isPassword');
- $this->fields[$key] = [
+ $this->fields->put($key, [
"serviceName" => $serviceName,
"key" => $key,
"name" => $fieldKey,
"value" => $value,
"isPassword" => $isPassword,
"rules" => $rules
- ];
+ ]);
+
$this->rules["fields.$key.value"] = $rules;
$this->validationAttributes["fields.$key.value"] = $fieldKey;
}
}
+ $this->fields = $this->fields->sortDesc();
}
public function saveCompose($raw)
{
diff --git a/app/Livewire/Project/Service/Storage.php b/app/Livewire/Project/Service/Storage.php
index 0accde787..1d40f1741 100644
--- a/app/Livewire/Project/Service/Storage.php
+++ b/app/Livewire/Project/Service/Storage.php
@@ -7,12 +7,13 @@ use Livewire\Component;
class Storage extends Component
{
- protected $listeners = ['addNewVolume'];
public $resource;
-
- public function render()
+ public function getListeners()
{
- return view('livewire.project.service.storage');
+ return [
+ 'addNewVolume',
+ 'refresh_storages' => '$refresh',
+ ];
}
public function addNewVolume($data)
{
@@ -27,9 +28,13 @@ class Storage extends Component
$this->resource->refresh();
$this->dispatch('success', 'Storage added successfully');
$this->dispatch('clearAddStorage');
- $this->dispatch('refreshStorages');
+ $this->dispatch('refresh_storages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
+ public function render()
+ {
+ return view('livewire.project.service.storage');
+ }
}
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
index c04242963..df808ba52 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
@@ -7,11 +7,13 @@ use Livewire\Component;
class Add extends Component
{
public $parameters;
+ public bool $shared = false;
public bool $is_preview = false;
public string $key;
public ?string $value = null;
public bool $is_build_time = false;
public bool $is_multiline = false;
+ public bool $is_literal = false;
protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [
@@ -19,12 +21,14 @@ class Add extends Component
'value' => 'nullable',
'is_build_time' => 'required|boolean',
'is_multiline' => 'required|boolean',
+ 'is_literal' => 'required|boolean',
];
protected $validationAttributes = [
'key' => 'key',
'value' => 'value',
'is_build_time' => 'build',
'is_multiline' => 'multiline',
+ 'is_literal' => 'literal',
];
public function mount()
@@ -47,6 +51,7 @@ class Add extends Component
'value' => $this->value,
'is_build_time' => $this->is_build_time,
'is_multiline' => $this->is_multiline,
+ 'is_literal' => $this->is_literal,
'is_preview' => $this->is_preview,
]);
$this->clear();
@@ -58,5 +63,6 @@ class Add extends Component
$this->value = '';
$this->is_build_time = false;
$this->is_multiline = false;
+ $this->is_literal = false;
}
}
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
index c4e14c905..6a6d94142 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
@@ -15,8 +15,10 @@ class All extends Component
public ?string $variables = null;
public ?string $variablesPreview = null;
public string $view = 'normal';
- protected $listeners = ['refreshEnvs', 'saveKey' => 'submit'];
-
+ protected $listeners = [
+ 'refreshEnvs',
+ 'saveKey' => 'submit',
+ ];
public function mount()
{
$resourceClass = get_class($this->resource);
@@ -161,6 +163,7 @@ class All extends Component
$environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time'];
$environment->is_multiline = $data['is_multiline'];
+ $environment->is_literal = $data['is_literal'];
$environment->is_preview = $data['is_preview'];
switch ($this->resource->type()) {
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
index 7903e8e51..9beb7cfab 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
@@ -16,12 +16,16 @@ class Show extends Component
public bool $isLocked = false;
public bool $isSharedVariable = false;
public string $type;
+ protected $listeners = [
+ "compose_loaded" => '$refresh',
+ ];
protected $rules = [
'env.key' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
'env.is_multiline' => 'required|boolean',
+ 'env.is_literal' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable',
];
@@ -30,6 +34,7 @@ class Show extends Component
'env.value' => 'Value',
'env.is_build_time' => 'Build Time',
'env.is_multiline' => 'Multiline',
+ 'env.is_literal' => 'Literal',
'env.is_shown_once' => 'Shown Once',
];
@@ -45,9 +50,9 @@ class Show extends Component
public function checkEnvs()
{
$this->isDisabled = false;
- if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) {
- $this->isDisabled = true;
- }
+ // if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) {
+ // $this->isDisabled = true;
+ // }
if ($this->env->is_shown_once) {
$this->isLocked = true;
}
diff --git a/app/Livewire/Project/Shared/Logs.php b/app/Livewire/Project/Shared/Logs.php
index a2aaebd2b..68e4e193e 100644
--- a/app/Livewire/Project/Shared/Logs.php
+++ b/app/Livewire/Project/Shared/Logs.php
@@ -44,7 +44,7 @@ class Logs extends Component
} else {
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
}
- $server->containers = $containers;
+ $server->containers = $containers->sort();
} catch (\Exception $e) {
return handleError($e, $this);
}
@@ -94,6 +94,7 @@ class Logs extends Component
$this->servers = $this->servers->push($this->resource->server);
}
}
+ $this->containers = $this->containers->sort();
} catch (\Exception $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Project/Shared/Storages/All.php b/app/Livewire/Project/Shared/Storages/All.php
index a17153343..14fa9b7b0 100644
--- a/app/Livewire/Project/Shared/Storages/All.php
+++ b/app/Livewire/Project/Shared/Storages/All.php
@@ -7,10 +7,5 @@ use Livewire\Component;
class All extends Component
{
public $resource;
- protected $listeners = ['refreshStorages'];
-
- public function refreshStorages()
- {
- $this->resource->refresh();
- }
+ protected $listeners = ['refresh_storages' => '$refresh'];
}
diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php
index 0f65aa91e..5e35b796a 100644
--- a/app/Livewire/Project/Shared/Storages/Show.php
+++ b/app/Livewire/Project/Shared/Storages/Show.php
@@ -39,6 +39,6 @@ class Show extends Component
public function delete()
{
$this->storage->delete();
- $this->dispatch('refreshStorages');
+ $this->dispatch('refresh_storages');
}
}
diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php
index e51aff8a3..14a2809c7 100644
--- a/app/Livewire/Server/Form.php
+++ b/app/Livewire/Server/Form.php
@@ -76,14 +76,14 @@ class Form extends Component
public function checkLocalhostConnection()
{
$this->submit();
- $uptime = $this->server->validateConnection();
+ ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_reachable = true;
$this->server->settings->is_usable = true;
$this->server->settings->save();
} else {
- $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.
Check this documentation for further help.');
+ $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.
Check this documentation for further help.
Error: ' . $error);
return;
}
}
diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php
index 8a029e6a1..82925c396 100644
--- a/app/Livewire/Server/Proxy/Deploy.php
+++ b/app/Livewire/Server/Proxy/Deploy.php
@@ -23,7 +23,8 @@ class Deploy extends Component
'proxyStatusUpdated',
'traefikDashboardAvailable',
'serverRefresh' => 'proxyStatusUpdated',
- "checkProxy", "startProxy"
+ "checkProxy",
+ "startProxy"
];
}
diff --git a/app/Livewire/Server/Proxy/NewDynamicConfiguration.php b/app/Livewire/Server/Proxy/NewDynamicConfiguration.php
index 06180d947..8110986a9 100644
--- a/app/Livewire/Server/Proxy/NewDynamicConfiguration.php
+++ b/app/Livewire/Server/Proxy/NewDynamicConfiguration.php
@@ -68,7 +68,7 @@ class NewDynamicConfiguration extends Component
}
$base64_value = base64_encode($this->value);
instant_remote_process([
- "echo '{$base64_value}' | base64 -d > {$file}",
+ "echo '{$base64_value}' | base64 -d | tee {$file} > /dev/null",
], $this->server);
if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy();
diff --git a/app/Livewire/Server/ShowPrivateKey.php b/app/Livewire/Server/ShowPrivateKey.php
index d44d2cb5f..e0474f2c4 100644
--- a/app/Livewire/Server/ShowPrivateKey.php
+++ b/app/Livewire/Server/ShowPrivateKey.php
@@ -35,10 +35,11 @@ class ShowPrivateKey extends Component
public function checkConnection()
{
try {
- $uptime = $this->server->validateConnection();
+ ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
} else {
+ ray($error);
$this->dispatch('error', 'Server is not reachable.
Please validate your configuration and connection.
Check this documentation for further help.');
return;
}
diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php
index 6d4173956..b148fe2c4 100644
--- a/app/Livewire/Server/ValidateAndInstall.php
+++ b/app/Livewire/Server/ValidateAndInstall.php
@@ -11,7 +11,7 @@ class ValidateAndInstall extends Component
{
public Server $server;
public int $number_of_tries = 0;
- public int $max_tries = 1;
+ public int $max_tries = 3;
public bool $install = true;
public $uptime = null;
public $supported_os_type = null;
@@ -32,9 +32,8 @@ class ValidateAndInstall extends Component
'refresh' => '$refresh',
];
- public function init(bool $install = true)
+ public function init(int $data = 0)
{
- $this->install = $install;
$this->uptime = null;
$this->supported_os_type = null;
$this->docker_installed = null;
@@ -42,7 +41,7 @@ class ValidateAndInstall extends Component
$this->docker_compose_installed = null;
$this->proxy_started = null;
$this->error = null;
- $this->number_of_tries = 0;
+ $this->number_of_tries = $data;
if (!$this->ask) {
$this->dispatch('validateConnection');
}
@@ -66,16 +65,15 @@ class ValidateAndInstall extends Component
} else {
$this->proxy_started = true;
}
-
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function validateConnection()
{
- $this->uptime = $this->server->validateConnection();
+ ['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (!$this->uptime) {
- $this->error = 'Server is not reachable. Please validate your configuration and connection.
Check this documentation for further help.';
+ $this->error = 'Server is not reachable. Please validate your configuration and connection.
Check this documentation for further help.
Error: ' . $error;
return;
}
$this->dispatch('validateOS');
@@ -99,10 +97,10 @@ class ValidateAndInstall extends Component
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: documentation.';
return;
} else {
- if ($this->number_of_tries == 0) {
+ if ($this->number_of_tries <= $this->max_tries) {
$activity = $this->server->installDocker();
$this->number_of_tries++;
- $this->dispatch('newActivityMonitor', $activity->id, 'init');
+ $this->dispatch('newActivityMonitor', $activity->id, 'init', $this->number_of_tries);
}
return;
}
diff --git a/app/Livewire/Settings/Configuration.php b/app/Livewire/Settings/Configuration.php
index fbc88785d..5281a1e01 100644
--- a/app/Livewire/Settings/Configuration.php
+++ b/app/Livewire/Settings/Configuration.php
@@ -69,13 +69,14 @@ class Configuration extends Component
}
$this->validate();
- if ($this->settings->is_dns_validation_enabled) {
+ if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
+ ray('asdf');
if (!validate_dns_entry($this->settings->fqdn, $this->server)) {
$this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.
Make sure you have added the DNS records correctly.
Check this documentation for further help.");
$error_show = true;
}
}
- check_domain_usage(domain: $this->settings->fqdn);
+ if ($this->settings->fqdn) check_domain_usage(domain: $this->settings->fqdn);
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
return str($dns)->trim()->lower();
diff --git a/app/Livewire/Subscription/PricingPlans.php b/app/Livewire/Subscription/PricingPlans.php
index 4e4dbadee..dddfa3a80 100644
--- a/app/Livewire/Subscription/PricingPlans.php
+++ b/app/Livewire/Subscription/PricingPlans.php
@@ -54,6 +54,7 @@ class PricingPlans extends Component
return;
}
$payload = [
+ 'allow_promotion_codes' => true,
'billing_address_collection' => 'required',
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
'line_items' => [[
diff --git a/app/Livewire/TeamSharedVariablesIndex.php b/app/Livewire/TeamSharedVariablesIndex.php
index dbb64ab65..eeee30ab9 100644
--- a/app/Livewire/TeamSharedVariablesIndex.php
+++ b/app/Livewire/TeamSharedVariablesIndex.php
@@ -21,6 +21,7 @@ class TeamSharedVariablesIndex extends Component
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
+ 'is_literal' => $data['is_literal'],
'type' => 'team',
'team_id' => currentTeam()->id,
]);
diff --git a/app/Models/Application.php b/app/Models/Application.php
index 204cb96b2..66e18565b 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -511,9 +511,9 @@ class Application extends BaseModel
{
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
- $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
} else {
- $newConfigHash .= json_encode($this->environment_variables_preview->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables_preview->get('value')->sort());
}
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
@@ -653,13 +653,13 @@ class Application extends BaseModel
if ($exec_in_docker) {
$commands = collect([
executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"),
- executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
+ executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
executeInDocker($deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
]);
} else {
$commands = collect([
"mkdir -p /root/.ssh",
- "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa",
+ "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null",
"chmod 600 /root/.ssh/id_rsa",
]);
}
@@ -954,4 +954,9 @@ class Application extends BaseModel
});
return $matches->count() > 0;
}
+
+ public function getFilesFromServer(bool $isInit = false)
+ {
+ getFilesystemVolumesFromServer($this, $isInit);
+ }
}
diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php
index 03930fba4..32bc04285 100644
--- a/app/Models/EnvironmentVariable.php
+++ b/app/Models/EnvironmentVariable.php
@@ -6,6 +6,7 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
+use Symfony\Component\Yaml\Yaml;
class EnvironmentVariable extends Model
{
@@ -81,6 +82,44 @@ class EnvironmentVariable extends Model
}
);
}
+ protected function isFoundInCompose(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ if (!$this->application_id) {
+ return true;
+ }
+ $found_in_compose = false;
+ $resource = $this->resource();
+ $compose = data_get($resource, 'docker_compose_raw');
+ if (!$compose) {
+ return true;
+ }
+ $yaml = Yaml::parse($compose);
+ $services = collect(data_get($yaml, 'services'));
+ if ($services->isEmpty()) {
+ return false;
+ }
+ foreach ($services as $service) {
+ $environments = collect(data_get($service, 'environment'));
+ if ($environments->isEmpty()) {
+ $found_in_compose = false;
+ break;
+ }
+ $found_in_compose = $environments->contains(function ($item) {
+ if (str($item)->contains('=')) {
+ $item = str($item)->before('=');
+ }
+ return strpos($item, $this->key) !== false;
+ });
+ if ($found_in_compose) {
+ break;
+ }
+ }
+ return $found_in_compose;
+ }
+ );
+ }
protected function isShared(): Attribute
{
return Attribute::make(
diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php
index 7ea0cd546..5595bbb13 100644
--- a/app/Models/LocalFileVolume.php
+++ b/app/Models/LocalFileVolume.php
@@ -20,6 +20,26 @@ class LocalFileVolume extends BaseModel
{
return $this->morphTo('resource');
}
+ public function deleteStorageOnServer()
+ {
+ $isService = data_get($this->resource, 'service');
+ if ($isService) {
+ $workdir = $this->resource->service->workdir();
+ $server = $this->resource->service->server;
+ } else {
+ $workdir = $this->resource->workdir();
+ $server = $this->resource->destination->server;
+ }
+ $commands = collect([
+ "cd $workdir"
+ ]);
+ $fs_path = data_get($this, 'fs_path');
+ if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
+ $commands->push("rm -rf $fs_path");
+ }
+ ray($commands);
+ return instant_remote_process($commands, $server);
+ }
public function saveStorageOnServer()
{
$isService = data_get($this->resource, 'service');
@@ -63,8 +83,7 @@ class LocalFileVolume extends BaseModel
$content = base64_encode($content);
$chmod = $fileVolume->chmod;
$chown = $fileVolume->chown;
- ray($content, $path, $chmod, $chown);
- $commands->push("echo '$content' | base64 -d > $path");
+ $commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
$commands->push("chmod +x $path");
if ($chown) {
$commands->push("chown $chown $path");
@@ -72,7 +91,6 @@ class LocalFileVolume extends BaseModel
if ($chmod) {
$commands->push("chmod $chmod $path");
}
-
}
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 79c98ccf6..bda044320 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -141,7 +141,7 @@ respond 404
$base64 = base64_encode($conf);
instant_remote_process([
"mkdir -p $dynamic_conf_path",
- "echo '$base64' | base64 -d > $default_redirect_file",
+ "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this);
$this->reloadCaddy();
return;
@@ -223,7 +223,7 @@ respond 404
instant_remote_process([
"mkdir -p $dynamic_conf_path",
- "echo '$base64' | base64 -d > $default_redirect_file",
+ "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this);
if (config('app.env') == 'local') {
@@ -349,7 +349,7 @@ respond 404
$base64 = base64_encode($yaml);
instant_remote_process([
"mkdir -p $dynamic_config_path",
- "echo '$base64' | base64 -d > $file",
+ "echo '$base64' | base64 -d | tee $file > /dev/null",
], $this);
if (config('app.env') == 'local') {
@@ -376,7 +376,7 @@ $schema://$host {
}";
$base64 = base64_encode($caddy_file);
instant_remote_process([
- "echo '$base64' | base64 -d > $file",
+ "echo '$base64' | base64 -d | tee $file > /dev/null",
], $this);
$this->reloadCaddy();
}
@@ -481,8 +481,8 @@ $schema://$host {
// ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
// ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
- $result = $this->validateConnection();
- if ($result) {
+ ['uptime' => $uptime] = $this->validateConnection();
+ if ($uptime) {
if ($this->unreachable_notification_sent === true) {
$this->update(['unreachable_notification_sent' => false]);
}
@@ -551,7 +551,7 @@ $schema://$host {
public function loadUnmanagedContainers()
{
if ($this->isFunctional()) {
- $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
+ $containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
$containers = format_docker_command_output_to_json($containers);
$containers = $containers->map(function ($container) {
$labels = data_get($container, 'Labels');
@@ -748,34 +748,31 @@ $schema://$host {
$server = Server::find($this->id);
if (!$server) {
- return false;
+ return ['uptime' => false, 'error' => 'Server not found.'];
}
if ($server->skipServer()) {
- return false;
+ return ['uptime' => false, 'error' => 'Server skipped.'];
}
- // EC2 does not have `uptime` command, lol
-
- $uptime = instant_remote_process(['ls /'], $server, false);
- if (!$uptime) {
- $server->settings()->update([
- 'is_reachable' => false,
- ]);
- return false;
- } else {
+ try {
+ // EC2 does not have `uptime` command, lol
+ instant_remote_process(['ls /'], $server);
$server->settings()->update([
'is_reachable' => true,
]);
$server->update([
'unreachable_count' => 0,
]);
+ if (data_get($server, 'unreachable_notification_sent') === true) {
+ $server->team?->notify(new Revived($server));
+ $server->update(['unreachable_notification_sent' => false]);
+ }
+ return ['uptime' => true, 'error' => null];
+ } catch (\Throwable $e) {
+ $server->settings()->update([
+ 'is_reachable' => false,
+ ]);
+ return ['uptime' => false, 'error' => $e->getMessage()];
}
-
- if (data_get($server, 'unreachable_notification_sent') === true) {
- $server->team?->notify(new Revived($server));
- $server->update(['unreachable_notification_sent' => false]);
- }
-
- return true;
}
public function installDocker()
{
@@ -784,7 +781,7 @@ $schema://$host {
}
public function validateDockerEngine($throwError = false)
{
- $dockerBinary = instant_remote_process(["command -v docker"], $this, false);
+ $dockerBinary = instant_remote_process(["command -v docker"], $this, false, no_sudo: true);
if (is_null($dockerBinary)) {
$this->settings->is_usable = false;
$this->settings->save();
@@ -861,4 +858,11 @@ $schema://$host {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
}
}
+ public function isNonRoot()
+ {
+ if ($this->user instanceof Stringable) {
+ return $this->user->value() !== 'root';
+ }
+ return $this->user !== 'root';
+ }
}
diff --git a/app/Models/Service.php b/app/Models/Service.php
index 9f0d2b67e..5de1a9745 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -14,20 +14,20 @@ class Service extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
- $domains = $this->applications()->get()->pluck('fqdn')->toArray();
+ $domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
$domains = implode(',', $domains);
- $applicationImages = $this->applications()->get()->pluck('image');
- $databaseImages = $this->databases()->get()->pluck('image');
+ $applicationImages = $this->applications()->get()->pluck('image')->sort();
+ $databaseImages = $this->databases()->get()->pluck('image')->sort();
$images = $applicationImages->merge($databaseImages);
$images = implode(',', $images->toArray());
- $applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten();
- $databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten();
+ $applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
+ $databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
$storages = $applicationStorages->merge($databaseStorages)->implode('updated_at');
$newConfigHash = $images . $domains . $images . $storages;
- $newConfigHash .= json_encode($this->environment_variables()->get('value'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -150,6 +150,76 @@ class Service extends BaseModel
foreach ($applications as $application) {
$image = str($application->image)->before(':')->value();
switch ($image) {
+ case str($image)?->contains('tolgee'):
+ $data = collect([]);
+ $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
+ $data = $data->merge([
+ 'Admin User' => [
+ 'key' => 'TOLGEE_AUTHENTICATION_INITIAL_USERNAME',
+ 'value' => 'admin',
+ 'readonly' => true,
+ 'rules' => 'required',
+ ],
+ ]);
+ if ($admin_password) {
+ $data = $data->merge([
+ 'Admin Password' => [
+ 'key' => 'SERVICE_PASSWORD_TOLGEE',
+ 'value' => data_get($admin_password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ $fields->put('Tolgee', $data);
+ break;
+ case str($image)?->contains('logto'):
+ $data = collect([]);
+ $logto_endpoint = $this->environment_variables()->where('key', 'LOGTO_ENDPOINT')->first();
+ $logto_admin_endpoint = $this->environment_variables()->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
+ if ($logto_endpoint) {
+ $data = $data->merge([
+ 'Endpoint' => [
+ 'key' => data_get($logto_endpoint, 'key'),
+ 'value' => data_get($logto_endpoint, 'value'),
+ 'rules' => 'required|url',
+ ],
+ ]);
+ }
+ if ($logto_admin_endpoint) {
+ $data = $data->merge([
+ 'Admin Endpoint' => [
+ 'key' => data_get($logto_admin_endpoint, 'key'),
+ 'value' => data_get($logto_admin_endpoint, 'value'),
+ 'rules' => 'required|url',
+ ],
+ ]);
+ }
+ $fields->put('Logto', $data);
+ break;
+ case str($image)?->contains('unleash-server'):
+ $data = collect([]);
+ $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UNLEASH')->first();
+ $data = $data->merge([
+ 'Admin User' => [
+ 'key' => 'SERVICE_USER_UNLEASH',
+ 'value' => 'admin',
+ 'readonly' => true,
+ 'rules' => 'required',
+ ],
+ ]);
+ if ($admin_password) {
+ $data = $data->merge([
+ 'Admin Password' => [
+ 'key' => 'SERVICE_PASSWORD_UNLEASH',
+ 'value' => data_get($admin_password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ $fields->put('Unleash', $data);
+ break;
case str($image)?->contains('grafana'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
@@ -618,7 +688,7 @@ class Service extends BaseModel
$commands[] = "cd $workdir";
$docker_compose_base64 = base64_encode($this->docker_compose);
- $commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
+ $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null";
$envs = $this->environment_variables()->get();
$commands[] = "rm -f .env || true";
foreach ($envs as $env) {
diff --git a/app/Models/SharedEnvironmentVariable.php b/app/Models/SharedEnvironmentVariable.php
index 260f16afb..5fad8fd96 100644
--- a/app/Models/SharedEnvironmentVariable.php
+++ b/app/Models/SharedEnvironmentVariable.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class SharedEnvironmentVariable extends Model
diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php
index a3a7cea8f..2197d51df 100644
--- a/app/Models/StandaloneClickhouse.php
+++ b/app/Models/StandaloneClickhouse.php
@@ -45,7 +45,7 @@ class StandaloneClickhouse extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings;
- $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index 593b63506..7b18666b8 100644
--- a/app/Models/StandaloneDragonfly.php
+++ b/app/Models/StandaloneDragonfly.php
@@ -44,7 +44,7 @@ class StandaloneDragonfly extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings;
- $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -167,9 +167,9 @@ class StandaloneDragonfly extends BaseModel
public function get_db_url(bool $useInternal = false): string
{
if ($this->is_public && !$useInternal) {
- return "redis://{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
+ return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
} else {
- return "redis://{$this->dragonfly_password}@{$this->uuid}:6379/0";
+ return "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0";
}
}
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index be9e66992..1dc55228a 100644
--- a/app/Models/StandaloneKeydb.php
+++ b/app/Models/StandaloneKeydb.php
@@ -44,7 +44,7 @@ class StandaloneKeydb extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->keydb_conf;
- $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index 4c143d9b8..5e18bbfde 100644
--- a/app/Models/StandaloneMariadb.php
+++ b/app/Models/StandaloneMariadb.php
@@ -46,7 +46,7 @@ class StandaloneMariadb extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->mariadb_conf;
- $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index c23854376..8e4d327a3 100644
--- a/app/Models/StandaloneMongodb.php
+++ b/app/Models/StandaloneMongodb.php
@@ -49,7 +49,7 @@ class StandaloneMongodb extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->mongo_conf;
- $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index 7885b8441..eede451d7 100644
--- a/app/Models/StandaloneMysql.php
+++ b/app/Models/StandaloneMysql.php
@@ -46,7 +46,7 @@ class StandaloneMysql extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->mysql_conf;
- $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index b42dbdcb8..cf449a815 100644
--- a/app/Models/StandalonePostgresql.php
+++ b/app/Models/StandalonePostgresql.php
@@ -58,7 +58,7 @@ class StandalonePostgresql extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->postgres_initdb_args . $this->postgres_host_auth_method;
- $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index e6231be3c..da4701df9 100644
--- a/app/Models/StandaloneRedis.php
+++ b/app/Models/StandaloneRedis.php
@@ -41,7 +41,7 @@ class StandaloneRedis extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->redis_conf;
- $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
+ $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index a6ded1dc7..a1b6beb6e 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -20,9 +20,9 @@ class EventServiceProvider extends ServiceProvider
MaintenanceModeDisabledNotification::class,
],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
- \SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle',
+ \SocialiteProviders\Azure\AzureExtendSocialite::class . '@handle',
],
- ProxyStarted::class => [
+ ProxyStarted::class => [
ProxyStartedNotification::class,
],
];
diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php
index 529dacd7a..028dbaadc 100644
--- a/app/Traits/ExecuteRemoteCommand.php
+++ b/app/Traits/ExecuteRemoteCommand.php
@@ -34,7 +34,13 @@ trait ExecuteRemoteCommand
$ignore_errors = data_get($single_command, 'ignore_errors', false);
$append = data_get($single_command, 'append', true);
$this->save = data_get($single_command, 'save');
-
+ if ($this->server->isNonRoot()) {
+ if (str($command)->startsWith('docker exec')) {
+ $command = str($command)->replace('docker exec', 'sudo docker exec');
+ } else {
+ $command = parseLineForSudo($command, $this->server);
+ }
+ }
$remote_command = generateSshCommand($this->server, $command);
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) {
$output = Str::of($output)->trim();
diff --git a/app/View/Components/Services/Links.php b/app/View/Components/Services/Links.php
index b3953c174..4cc19b518 100644
--- a/app/View/Components/Services/Links.php
+++ b/app/View/Components/Services/Links.php
@@ -18,7 +18,10 @@ class Links extends Component
$service->applications()->get()->map(function ($application) {
$type = $application->serviceType();
if ($type) {
- $links = generateServiceSpecificFqdns($application, false);
+ $links = generateServiceSpecificFqdns($application);
+ $links = $links->map(function ($link) {
+ return getFqdnWithoutPort($link);
+ });
$this->links = $this->links->merge($links);
} else {
if ($application->fqdn) {
diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php
index 8d810da0f..c0aaf4abf 100644
--- a/bootstrap/helpers/applications.php
+++ b/bootstrap/helpers/applications.php
@@ -9,7 +9,7 @@ use App\Models\StandaloneDocker;
use Illuminate\Support\Collection;
use Spatie\Url\Url;
-function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false)
+function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false)
{
$application_id = $application->id;
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
@@ -38,6 +38,7 @@ function queue_application_deployment(Application $application, string $deployme
'is_webhook' => $is_webhook,
'restart_only' => $restart_only,
'commit' => $commit,
+ 'rollback' => $rollback,
'git_type' => $git_type,
'only_this_server' => $only_this_server
]);
diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php
index c3ef9a694..11cfc3df2 100644
--- a/bootstrap/helpers/constants.php
+++ b/bootstrap/helpers/constants.php
@@ -32,6 +32,7 @@ const DATABASE_DOCKER_IMAGES = [
];
const SPECIFIC_SERVICES = [
'quay.io/minio/minio',
+ 'svhd/logto'
];
// Based on /etc/os-release
diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php
index afcc4622b..a70c85a72 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -169,7 +169,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
}
return $labels;
}
-function generateServiceSpecificFqdns(ServiceApplication|Application $resource, $forTraefik = false)
+function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
{
if ($resource->getMorphClass() === 'App\Models\ServiceApplication') {
$uuid = $resource->uuid;
@@ -182,6 +182,9 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
$environment_variables = $resource->environment_variables;
$type = $resource->serviceType();
}
+ if (is_null($server) || is_null($type)) {
+ return collect([]);
+ }
$variables = collect($environment_variables);
$payload = collect([]);
switch ($type) {
@@ -201,17 +204,31 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
"value" => generateFqdn($server, 'minio-' . $uuid)
]);
}
- if ($forTraefik) {
- $payload = collect([
- $MINIO_BROWSER_REDIRECT_URL->value . ':9001',
- $MINIO_SERVER_URL->value . ':9000',
- ]);
- } else {
- $payload = collect([
- $MINIO_BROWSER_REDIRECT_URL->value,
- $MINIO_SERVER_URL->value,
+ $payload = collect([
+ $MINIO_BROWSER_REDIRECT_URL->value . ':9001',
+ $MINIO_SERVER_URL->value . ':9000',
+ ]);
+ break;
+ case $type?->contains('logto'):
+ $LOGTO_ENDPOINT = $variables->where('key', 'LOGTO_ENDPOINT')->first();
+ $LOGTO_ADMIN_ENDPOINT = $variables->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
+ if (is_null($LOGTO_ENDPOINT) || is_null($LOGTO_ADMIN_ENDPOINT)) {
+ return $payload;
+ }
+ if (is_null($LOGTO_ENDPOINT?->value)) {
+ $LOGTO_ENDPOINT?->update([
+ "value" => generateFqdn($server, 'logto-' . $uuid)
]);
}
+ if (is_null($LOGTO_ADMIN_ENDPOINT?->value)) {
+ $LOGTO_ADMIN_ENDPOINT?->update([
+ "value" => generateFqdn($server, 'logto-admin-' . $uuid)
+ ]);
+ }
+ $payload = collect([
+ $LOGTO_ENDPOINT->value . ':3001',
+ $LOGTO_ADMIN_ENDPOINT->value . ':3002',
+ ]);
break;
}
return $payload;
@@ -565,7 +582,7 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
$server = Server::findOrFail($server_id);
$base64_compose = base64_encode($compose);
$output = instant_remote_process([
- "echo {$base64_compose} | base64 -d > /tmp/{$uuid}.yml",
+ "echo {$base64_compose} | base64 -d | tee /tmp/{$uuid}.yml > /dev/null",
"docker compose -f /tmp/{$uuid}.yml config",
], $server);
ray($output);
@@ -586,3 +603,9 @@ function escapeEnvVariables($value)
$replace = array("\\\\", "\\r", "\\t", "\\0", '\"', "\'");
return str_replace($search, $replace, $value);
}
+function escapeDollarSign($value)
+{
+ $search = array('$');
+ $replace = array('$$');
+ return str_replace($search, $replace, $value);
+}
diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php
index 6365a6e45..85533550b 100644
--- a/bootstrap/helpers/remoteProcess.php
+++ b/bootstrap/helpers/remoteProcess.php
@@ -32,6 +32,9 @@ function remote_process(
if ($command instanceof Collection) {
$command = $command->toArray();
}
+ if ($server->isNonRoot()) {
+ $command = parseCommandsByLineForSudo(collect($command), $server);
+ }
$command_string = implode("\n", $command);
if (auth()->user()) {
$teams = auth()->user()->teams->pluck('id');
@@ -133,17 +136,17 @@ function generateSshCommand(Server $server, string $command)
$timeout = config('constants.ssh.command_timeout');
$connectionTimeout = config('constants.ssh.connection_timeout');
$serverInterval = config('constants.ssh.server_interval');
+ $muxPersistTime = config('constants.ssh.mux_persist_time');
$ssh_command = "timeout $timeout ssh ";
if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) {
- $ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
+ $ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ";
}
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
}
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
-
$delimiter = Hash::make($command);
$command = str_replace($delimiter, '', $command);
$ssh_command .= "-i {$privateKeyLocation} "
@@ -159,17 +162,19 @@ function generateSshCommand(Server $server, string $command)
. $command . PHP_EOL
. $delimiter;
// ray($ssh_command);
- // ray($delimiter);
return $ssh_command;
}
-function instant_remote_process(Collection|array $command, Server $server, $throwError = true)
+function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false)
{
$timeout = config('constants.ssh.command_timeout');
if ($command instanceof Collection) {
$command = $command->toArray();
}
+ if ($server->isNonRoot() && !$no_sudo) {
+ $command = parseCommandsByLineForSudo(collect($command), $server);
+ }
$command_string = implode("\n", $command);
- $ssh_command = generateSshCommand($server, $command_string);
+ $ssh_command = generateSshCommand($server, $command_string, $no_sudo);
$process = Process::timeout($timeout)->run($ssh_command);
$output = trim($process->output());
$exitCode = $process->exitCode();
diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php
index b690659dd..8e3c0337e 100644
--- a/bootstrap/helpers/services.php
+++ b/bootstrap/helpers/services.php
@@ -1,7 +1,7 @@
replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', '');
}
-function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService, bool $isInit = false)
+function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false)
{
- // TODO: make this async
try {
- $workdir = $oneService->service->workdir();
- $server = $oneService->service->server;
+ if ($oneService->getMorphClass() === 'App\Models\Application') {
+ $workdir = $oneService->workdir();
+ $server = $oneService->destination->server;
+ } else{
+ $workdir = $oneService->service->workdir();
+ $server = $oneService->service->server;
+ }
$fileVolumes = $oneService->fileStorages()->get();
$commands = collect([
"mkdir -p $workdir > /dev/null 2>&1 || true",
- "cd "
+ "cd $workdir"
]);
instant_remote_process($commands, $server);
foreach ($fileVolumes as $fileVolume) {
@@ -67,7 +71,7 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneS
$dir = Str::of($fileLocation)->dirname();
instant_remote_process([
"mkdir -p $dir",
- "echo '$content' | base64 -d > $fileLocation"
+ "echo '$content' | base64 -d | tee $fileLocation"
], $server);
} else if ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) {
$fileVolume->content = null;
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index 4533d098b..966b1db59 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -29,6 +29,7 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
+use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
@@ -656,6 +657,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if (str(data_get($service, 'image'))->contains('glitchtip')) {
$tempServiceName = 'glitchtip';
}
+ if ($serviceName === 'supabase-kong') {
+ $tempServiceName = 'supabase';
+ }
$serviceDefinition = data_get($allServices, $tempServiceName);
$predefinedPort = data_get($serviceDefinition, 'port');
if ($serviceName === 'plausible') {
@@ -978,12 +982,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
// Caddy needs exact port in some cases.
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) {
+ $fqdns_exploded = str($savedService->fqdn)->explode(',');
+ if ($fqdns_exploded->count() > 1) {
+ continue;
+ }
if ($resource->server->proxyType() === 'CADDY') {
$env = EnvironmentVariable::where([
'key' => $key,
'service_id' => $resource->id,
])->first();
if ($env) {
+
$env_url = Url::fromString($savedService->fqdn);
$env_port = $env_url->getPort();
if ($env_port !== $predefinedPort) {
@@ -1045,6 +1054,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
// Caddy needs exact port in some cases.
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') {
+ $fqdns_exploded = str($savedService->fqdn)->explode(',');
+ if ($fqdns_exploded->count() > 1) {
+ continue;
+ }
$env = EnvironmentVariable::where([
'key' => $key,
'service_id' => $resource->id,
@@ -1109,10 +1122,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
}
}
-
// Add labels to the service
if ($savedService->serviceType()) {
- $fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true);
+ $fqdns = generateServiceSpecificFqdns($savedService);
} else {
$fqdns = collect(data_get($savedService, 'fqdns'))->filter();
}
@@ -1482,7 +1494,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'application_id' => $resource->id,
])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
- ray($command, $generatedValue);
if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) {
@@ -1567,7 +1578,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
// Add labels to the service
if ($resource->serviceType()) {
- $fqdns = generateServiceSpecificFqdns($resource, forTraefik: true);
+ $fqdns = generateServiceSpecificFqdns($resource);
} else {
$domains = collect(json_decode($resource->docker_compose_domains)) ?? [];
if ($domains) {
@@ -1939,3 +1950,57 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
}
}
}
+
+function parseCommandsByLineForSudo(Collection $commands, Server $server): array
+{
+ $commands = $commands->map(function ($line) {
+ if (!str($line)->startsWith('cd') && !str($line)->startsWith('command') && !str($line)->startsWith('echo') && !str($line)->startsWith('true')) {
+ return "sudo $line";
+ }
+ return $line;
+ });
+ $commands = $commands->map(function ($line) use ($server) {
+ if (Str::startsWith($line, 'sudo mkdir -p')) {
+ return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p');
+ }
+ return $line;
+ });
+ $commands = $commands->map(function ($line) {
+ $line = str($line);
+ if (str($line)->contains('$(')) {
+ $line = $line->replace('$(', '$(sudo ');
+ }
+ if (str($line)->contains('||')) {
+ $line = $line->replace('||', '|| sudo');
+ }
+ if (str($line)->contains('&&')) {
+ $line = $line->replace('&&', '&& sudo');
+ }
+ if (str($line)->contains(' | ')) {
+ $line = $line->replace(' | ', ' | sudo ');
+ }
+ return $line->value();
+ });
+
+ return $commands->toArray();
+}
+function parseLineForSudo(string $command, Server $server): string
+{
+ if (!str($command)->startSwith('cd') && !str($command)->startSwith('command')) {
+ $command = "sudo $command";
+ }
+ if (Str::startsWith($command, 'sudo mkdir -p')) {
+ $command = "$command && sudo chown -R $server->user:$server->user " . Str::after($command, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($command, 'sudo mkdir -p');
+ }
+ if (str($command)->contains('$(') || str($command)->contains('`')) {
+ $command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value();
+ }
+ if (str($command)->contains('||')) {
+ $command = str($command)->replace('||', '|| sudo ')->value();
+ }
+ if (str($command)->contains('&&')) {
+ $command = str($command)->replace('&&', '&& sudo ')->value();
+ }
+
+ return $command;
+}
diff --git a/config/app.php b/config/app.php
index b95224fdc..fc5d1e26f 100644
--- a/config/app.php
+++ b/config/app.php
@@ -141,8 +141,8 @@ return [
*/
'maintenance' => [
- 'driver' => 'file',
- // 'store' => 'redis',
+ 'driver' => 'cache',
+ 'store' => 'redis',
],
/*
diff --git a/config/constants.php b/config/constants.php
index 54c451310..091c60996 100644
--- a/config/constants.php
+++ b/config/constants.php
@@ -5,6 +5,7 @@ return [
'contact' => 'https://coolify.io/docs/contact',
],
'ssh' => [
+ 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', "1m"),
'connection_timeout' => 10,
'server_interval' => 20,
'command_timeout' => 7200,
diff --git a/config/horizon.php b/config/horizon.php
index 49df24b57..15f7f5696 100644
--- a/config/horizon.php
+++ b/config/horizon.php
@@ -183,9 +183,7 @@ return [
's6' => [
'connection' => 'redis',
'queue' => ['default'],
- 'balance' => 'auto',
- // 'autoScalingStrategy' => 'time',
- // 'maxProcesses' => 1,
+ 'balance' => env('HORIZON_BALANCE', 'auto'),
'maxTime' => 0,
'maxJobs' => 0,
'memory' => 128,
diff --git a/config/livewire.php b/config/livewire.php
index 83229fcea..cf9bcd206 100644
--- a/config/livewire.php
+++ b/config/livewire.php
@@ -146,4 +146,5 @@ return [
*/
'pagination_theme' => 'tailwind',
+ 'lazy_placeholder' => 'components.page-loading',
];
diff --git a/config/sentry.php b/config/sentry.php
index c399f7933..7ae7ccfda 100644
--- a/config/sentry.php
+++ b/config/sentry.php
@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
- 'release' => '4.0.0-beta.258',
+ 'release' => '4.0.0-beta.266',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),
diff --git a/config/version.php b/config/version.php
index d0fd506d3..7001afde8 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,3 +1,3 @@
boolean('is_literal')->default(false);
+ });
+ Schema::table('shared_environment_variables', function (Blueprint $table) {
+ $table->boolean('is_literal')->default(false);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('environment_variables', function (Blueprint $table) {
+ $table->dropColumn('is_literal');
+ });
+ Schema::table('shared_environment_variables', function (Blueprint $table) {
+ $table->dropColumn('is_literal');
+ });
+ }
+};
diff --git a/database/migrations/2024_04_16_083919_add_service_type_on_creation.php b/database/migrations/2024_04_16_083919_add_service_type_on_creation.php
new file mode 100644
index 000000000..ce91bddbb
--- /dev/null
+++ b/database/migrations/2024_04_16_083919_add_service_type_on_creation.php
@@ -0,0 +1,28 @@
+string('service_type')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('services', function (Blueprint $table) {
+ $table->dropColumn('service_type');
+ });
+ }
+};
diff --git a/database/migrations/2024_04_17_132541_add_rollback_queues.php b/database/migrations/2024_04_17_132541_add_rollback_queues.php
new file mode 100644
index 000000000..27865cf0d
--- /dev/null
+++ b/database/migrations/2024_04_17_132541_add_rollback_queues.php
@@ -0,0 +1,28 @@
+boolean('rollback')->default(false);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('application_deployment_queues', function (Blueprint $table) {
+ $table->dropColumn('rollback');
+ });
+ }
+};
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
index 4e85836d2..f68b2c41c 100644
--- a/docker-compose.prod.yml
+++ b/docker-compose.prod.yml
@@ -29,6 +29,7 @@ services:
- QUEUE_CONNECTION
- REDIS_HOST
- REDIS_PASSWORD
+ - HORIZON_BALANCE
- HORIZON_MAX_PROCESSES
- HORIZON_BALANCE_MAX_SHIFT
- HORIZON_BALANCE_COOLDOWN
@@ -47,6 +48,7 @@ services:
- PUSHER_APP_SECRET
- AUTOUPDATE
- SELF_HOSTED
+ - SSH_MUX_PERSIST_TIME
- FEEDBACK_DISCORD_WEBHOOK
- WAITLIST
- SUBSCRIPTION_PROVIDER
@@ -93,7 +95,7 @@ services:
expose:
- "${APP_PORT:-8000}"
healthcheck:
- test: curl --fail http://localhost:80/api/health || exit 1
+ test: curl --fail http://127.0.0.1:80/api/health || exit 1
interval: 5s
retries: 10
timeout: 2s
@@ -140,7 +142,7 @@ services:
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
healthcheck:
- test: wget -qO- http://localhost:6001/ready || exit 1
+ test: wget -qO- http://127.0.0.1:6001/ready || exit 1
interval: 5s
retries: 10
timeout: 2s
diff --git a/public/svgs/activepieces.png b/public/svgs/activepieces.png
new file mode 100644
index 000000000..373c4fad0
Binary files /dev/null and b/public/svgs/activepieces.png differ
diff --git a/public/svgs/authentik.png b/public/svgs/authentik.png
new file mode 100644
index 000000000..8f945aad8
Binary files /dev/null and b/public/svgs/authentik.png differ
diff --git a/public/svgs/classicpress.svg b/public/svgs/classicpress.svg
new file mode 100644
index 000000000..341be300e
--- /dev/null
+++ b/public/svgs/classicpress.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/svgs/cloudflared.svg b/public/svgs/cloudflared.svg
new file mode 100644
index 000000000..f9ff79f96
--- /dev/null
+++ b/public/svgs/cloudflared.svg
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/public/svgs/logto_dark.svg b/public/svgs/logto_dark.svg
new file mode 100644
index 000000000..c5f3fa0f6
--- /dev/null
+++ b/public/svgs/logto_dark.svg
@@ -0,0 +1,14 @@
+
diff --git a/public/svgs/rxresume.svg b/public/svgs/rxresume.svg
new file mode 100644
index 000000000..6a7ffcae0
--- /dev/null
+++ b/public/svgs/rxresume.svg
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/public/svgs/unleash.svg b/public/svgs/unleash.svg
new file mode 100644
index 000000000..b79f91703
--- /dev/null
+++ b/public/svgs/unleash.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/css/app.css b/resources/css/app.css
index a8a55f1e8..a10103ce8 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -41,7 +41,7 @@ option {
}
.button {
- @apply flex items-center justify-center gap-2 px-2 py-1 text-sm text-black normal-case border rounded cursor-pointer bg-neutral-200/50 border-neutral-300 hover:bg-neutral-300 dark:bg-coolgray-200 dark:text-white dark:hover:text-white dark:hover:bg-coolgray-500 dark:border-black hover:text-black disabled:bg-coolgray-100/10 disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600;
+ @apply flex items-center justify-center gap-2 px-2 py-1 text-sm text-black normal-case border rounded cursor-pointer bg-neutral-200/50 border-neutral-300 hover:bg-neutral-300 dark:bg-coolgray-200 dark:text-white dark:hover:text-white dark:hover:bg-coolgray-500 dark:border-black hover:text-black disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600 disabled:border-none disabled:hover:bg-transparent disabled:bg-transparent disabled:text-neutral-300;
}
button[isError]:not(:disabled) {
diff --git a/resources/views/components/modal-input.blade.php b/resources/views/components/modal-input.blade.php
index b0747d4a9..13331736f 100644
--- a/resources/views/components/modal-input.blade.php
+++ b/resources/views/components/modal-input.blade.php
@@ -5,6 +5,7 @@
'disabled' => false,
'action' => 'delete',
'content' => null,
+ 'closeOutside' => true
])
503
Service Unavailable. Be right back. Thanks for your +
Service Unavailable. Be right back. Thanks for your patience.