Merge pull request #1946 from coollabsio/next

v4.0.0-beta.252
This commit is contained in:
Andras Bacsai 2024-04-09 09:28:29 +02:00 committed by GitHub
commit 35c76c8e2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 453 additions and 246 deletions

View File

@ -13,6 +13,9 @@ public function handle(Server $server, $fromUI = false)
if ($server->proxyType() === 'NONE') {
return false;
}
if (!$server->validateConnection()) {
throw new \Exception("Server Connection Error");
}
if (!$server->isProxyShouldRun()) {
if ($fromUI) {
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");

View File

@ -45,7 +45,6 @@ public function handle(bool $force = false, bool $async = false)
}
$this->update();
}
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());
@ -83,6 +82,7 @@ private function update()
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server);
}
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
return;
}
}

View File

@ -179,6 +179,11 @@ public function __construct(int $application_deployment_queue_id)
public function handle(): void
{
if (!$this->server->isFunctional()) {
$this->application_deployment_queue->addLogEntry("Server is not functional.");
$this->fail("Server is not functional.");
return;
}
try {
// Generate custom host<->ip mapping
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
@ -646,21 +651,21 @@ private function generate_image_names()
{
if ($this->application->dockerfile) {
if ($this->application->docker_registry_image_name) {
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:build");
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:latest");
$this->build_image_name = "{$this->application->docker_registry_image_name}:build";
$this->production_image_name = "{$this->application->docker_registry_image_name}:latest";
} else {
$this->build_image_name = Str::lower("{$this->application->uuid}:build");
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
$this->build_image_name = "{$this->application->uuid}:build";
$this->production_image_name = "{$this->application->uuid}:latest";
}
} else if ($this->application->build_pack === 'dockerimage') {
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
$this->production_image_name = "{$this->dockerImage}:{$this->dockerImageTag}";
} else if ($this->pull_request_id !== 0) {
if ($this->application->docker_registry_image_name) {
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build");
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}");
$this->build_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build";
$this->production_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}";
} else {
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
$this->build_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}-build";
$this->production_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}";
}
} else {
$this->dockerImageTag = str($this->commit)->substr(0, 128);
@ -668,11 +673,11 @@ private function generate_image_names()
$this->dockerImageTag = $this->application->docker_registry_image_tag;
}
if ($this->application->docker_registry_image_name) {
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build");
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}");
$this->build_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build";
$this->production_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}";
} else {
$this->build_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}-build");
$this->production_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}");
$this->build_image_name = "{$this->application->uuid}:{$this->dockerImageTag}-build";
$this->production_image_name = "{$this->application->uuid}:{$this->dockerImageTag}";
}
}
}
@ -1001,11 +1006,6 @@ private function generate_git_import_commands()
return $commands;
}
private function set_git_import_settings($git_clone_command)
{
return $this->application->setGitImportSettings($this->deployment_uuid, $git_clone_command);
}
private function cleanup_git()
{
$this->execute_remote_command(
@ -1814,7 +1814,6 @@ private function next(string $status)
public function failed(Throwable $exception): void
{
$this->next(ApplicationDeploymentStatus::FAILED->value);
$this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr');
if (str($exception->getMessage())->isNotEmpty()) {

View File

@ -163,18 +163,16 @@ public function loadComposeFile($isInit = false)
}
public function generateDomain(string $serviceName)
{
$domain = $this->parsedServiceDomains[$serviceName]['domain'] ?? null;
if (!$domain) {
$uuid = new Cuid2(7);
$domain = generateFqdn($this->application->destination->server, $uuid);
$this->parsedServiceDomains[$serviceName]['domain'] = $domain;
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->application->save();
}
$uuid = new Cuid2(7);
$domain = generateFqdn($this->application->destination->server, $uuid);
$this->parsedServiceDomains[$serviceName]['domain'] = $domain;
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->application->save();
$this->dispatch('success', 'Domain generated.');
return $domain;
}
public function updatedApplicationBaseDirectory() {
raY('asdf');
public function updatedApplicationBaseDirectory()
{
if ($this->application->build_pack === 'dockercompose') {
$this->loadComposeFile();
}
@ -206,30 +204,47 @@ public function getWildcardDomain()
$fqdn = generateFqdn($server, $this->application->uuid);
$this->application->fqdn = $fqdn;
$this->application->save();
$this->updatedApplicationFqdn();
$this->dispatch('success', 'Wildcard domain generated.');
}
}
public function resetDefaultLabels($showToaster = true)
public function resetDefaultLabels()
{
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->ports_exposes = $this->application->ports_exposes;
$this->submit($showToaster);
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
}
public function updatedApplicationFqdn()
public function checkFqdns($showToaster = true)
{
$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->application->save();
$this->resetDefaultLabels(false);
if (data_get($this->application, 'fqdn')) {
$domains = str($this->application->fqdn)->trim()->explode(',');
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
}
}
}
check_domain_usage(resource: $this->application);
$this->application->fqdn = $domains->implode(',');
}
}
public function submit($showToaster = true)
{
try {
$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->checkFqdns();
$this->application->save();
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);
@ -241,25 +256,14 @@ public function submit($showToaster = true)
}
$this->validate();
if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false);
$this->resetDefaultLabels();
}
if (data_get($this->application, 'build_pack') === 'dockerimage') {
$this->validate([
'application.docker_registry_image_name' => 'required',
]);
}
if (data_get($this->application, 'fqdn')) {
$domains = str($this->application->fqdn)->trim()->explode(',');
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
}
}
}
check_fqdn_usage($this->application);
$this->application->fqdn = $domains->implode(',');
}
if (data_get($this->application, 'custom_docker_run_options')) {
$this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim();
}
@ -277,6 +281,15 @@ public function submit($showToaster = true)
}
if ($this->application->build_pack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
foreach ($this->parsedServiceDomains as $serviceName => $service) {
$domain = data_get($service, 'domain');
if ($domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
}
check_domain_usage(resource: $this->application);
}
}
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
} else {

View File

@ -10,7 +10,7 @@
class Index extends Component
{
public Service $service;
public ?Service $service = null;
public ?ServiceApplication $serviceApplication = null;
public ?ServiceDatabase $serviceDatabase = null;
public array $parameters;
@ -26,7 +26,10 @@ public function mount()
$this->services = collect([]);
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->first();
if (!$this->service) {
return redirect()->route('dashboard');
}
$service = $this->service->applications()->whereUuid($this->parameters['stack_service_uuid'])->first();
if ($service) {
$this->serviceApplication = $service;

View File

@ -65,12 +65,12 @@ public function mount()
public function submit()
{
try {
check_fqdn_usage($this->application);
check_domain_usage(resource: $this->application);
$this->validate();
$this->application->save();
updateCompose($this->application);
if (str($this->application->fqdn)->contains(',')) {
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.');
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.');
} else {
$this->dispatch('success', 'Service saved.');
}

View File

@ -59,23 +59,37 @@ public function instantSave()
public function submit()
{
$this->resetErrorBag();
if ($this->settings->public_port_min > $this->settings->public_port_max) {
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
return;
try {
$error_show = false;
$this->server = Server::findOrFail(0);
$this->resetErrorBag();
if ($this->settings->public_port_min > $this->settings->public_port_max) {
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
return;
}
$this->validate();
if ($this->settings->is_dns_validation_enabled) {
if (!validate_dns_entry($this->settings->fqdn, $this->server)) {
$this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.<br><br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$error_show = true;
}
}
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();
});
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
$this->settings->save();
$this->server->setupDynamicProxyConfiguration();
if (!$error_show) {
$this->dispatch('success', 'Instance settings updated successfully!');
}
} catch (\Exception $e) {
return handleError($e, $this);
}
$this->validate();
$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();
});
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
$this->settings->save();
$this->server = Server::findOrFail(0);
$this->server->setupDynamicProxyConfiguration();
$this->dispatch('success', 'Instance settings updated successfully!');
}
}

View File

@ -32,10 +32,10 @@ public function checkUpdate()
public function upgrade()
{
try {
$this->rateLimit(1, 30);
if ($this->showProgress) {
return;
}
$this->rateLimit(1, 30);
$this->showProgress = true;
UpdateCoolify::run(force: true, async: true);
$this->dispatch('success', "Updating Coolify to {$this->latestVersion} version...");

View File

@ -598,21 +598,30 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id
} else {
$github_access_token = generate_github_installation_token($this->source);
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git {$baseDir}"));
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git {$baseDir}";
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git";
} else {
$commands->push("{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}");
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}";
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
}
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
} else {
$commands->push($git_clone_command);
}
}
if ($pull_request_id !== 0) {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
$git_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name);
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "cd {$baseDir} && git fetch origin {$branch} && git checkout $pr_branch_name"));
$commands->push(executeInDocker($deployment_uuid, "cd {$baseDir} && git fetch origin {$branch} && $git_checkout_command"));
} else {
$commands->push("cd {$baseDir} && git fetch origin {$branch} && git checkout $pr_branch_name");
$commands->push("cd {$baseDir} && git fetch origin {$branch} && $git_checkout_command");
}
}
ray($commands);
return [
'commands' => $commands->implode(' && '),
'branch' => $branch,
@ -654,7 +663,7 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
} else if ($git_type === 'github') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
@ -662,14 +671,14 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
} else if ($git_type === 'bitbucket') {
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
}
}
@ -697,7 +706,7 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
} else if ($git_type === 'github') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
@ -705,14 +714,14 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
} else if ($git_type === 'bitbucket') {
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
}
}
@ -904,6 +913,16 @@ public function fqdns(): Attribute
: explode(',', $this->fqdn),
);
}
protected function buildGitCheckoutCommand($target): string
{
$command = "git checkout $target";
if ($this->settings->is_git_submodules_enabled) {
$command .= " && git submodule update --init --recursive";
}
return $command;
}
public function watchPaths(): Attribute
{
return Attribute::make(

View File

@ -550,21 +550,21 @@ public function startUnmanaged($id)
}
public function loadUnmanagedContainers()
{
if ($this->isFunctional()) {
$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');
if (!str($labels)->contains("coolify.managed")) {
return $container;
}
return null;
});
$containers = $containers->filter();
return collect($containers);
} else {
return collect([]);
}
if ($this->isFunctional()) {
$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');
if (!str($labels)->contains("coolify.managed")) {
return $container;
}
return null;
});
$containers = $containers->filter();
return collect($containers);
} else {
return collect([]);
}
}
public function hasDefinedResources()
{
@ -690,7 +690,13 @@ public function isProxyShouldRun()
}
public function isFunctional()
{
return $this->settings->is_reachable && $this->settings->is_usable && !$this->settings->force_disabled;
$isFunctional = $this->settings->is_reachable && $this->settings->is_usable && !$this->settings->force_disabled;
['private_key_filename' => $private_key_filename, 'mux_filename' => $mux_filename] = server_ssh_configuration($this);
if (!$isFunctional) {
Storage::disk('ssh-keys')->delete($private_key_filename);
Storage::disk('ssh-mux')->delete($mux_filename);
}
return $isFunctional;
}
public function isLogDrainEnabled()
{

View File

@ -2,6 +2,7 @@
namespace App\Notifications\Server;
use App\Jobs\ContainerStatusJob;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use App\Notifications\Channels\DiscordChannel;
@ -21,6 +22,7 @@ public function __construct(public Server $server)
if ($this->server->unreachable_notification_sent === false) {
return;
}
dispatch(new ContainerStatusJob($server));
}
public function via(object $notifiable): array

View File

@ -37,7 +37,7 @@
// Based on /etc/os-release
const SUPPORTED_OS = [
'ubuntu debian raspbian',
'centos fedora rhel ol rocky',
'centos fedora rhel ol rocky amzn',
'sles opensuse-leap opensuse-tumbleweed'
];

View File

@ -55,17 +55,30 @@ function remote_process(
),
])();
}
function server_ssh_configuration(Server $server)
{
$uuid = data_get($server, 'uuid');
if (is_null($uuid)) {
throw new \Exception("Server does not have a uuid");
}
$private_key_filename = "id.root@{$server->uuid}";
$location = '/var/www/html/storage/app/ssh/keys/' . $private_key_filename;
$mux_filename = '/var/www/html/storage/app/ssh/mux/' . $server->muxFilename();
return [
'location' => $location,
'mux_filename' => $mux_filename,
'private_key_filename' => $private_key_filename
];
}
function savePrivateKeyToFs(Server $server)
{
if (data_get($server, 'privateKey.private_key') === null) {
throw new \Exception("Server {$server->name} does not have a private key");
}
$sshKeyFileLocation = "id.root@{$server->uuid}";
['location' => $location, 'private_key_filename' => $private_key_filename] = server_ssh_configuration($server);
Storage::disk('ssh-keys')->makeDirectory('.');
Storage::disk('ssh-mux')->makeDirectory('.');
Storage::disk('ssh-keys')->put($sshKeyFileLocation, $server->privateKey->private_key);
$location = '/var/www/html/storage/app/ssh/keys/' . $sshKeyFileLocation;
Storage::disk('ssh-keys')->put($private_key_filename, $server->privateKey->private_key);
return $location;
}
@ -223,6 +236,13 @@ function remove_iip($text)
$text = preg_replace('/x-access-token:.*?(?=@)/', "x-access-token:" . REDACTED, $text);
return preg_replace('/\x1b\[[0-9;]*m/', '', $text);
}
function remove_mux_and_private_key(Server $server)
{
$muxFilename = $server->muxFilename();
$privateKeyLocation = savePrivateKeyToFs($server);
Storage::disk('ssh-mux')->delete($muxFilename);
Storage::disk('ssh-keys')->delete($privateKeyLocation);
}
function refresh_server_connection(?PrivateKey $private_key = null)
{
if (is_null($private_key)) {

View File

@ -88,33 +88,106 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$dockerCompose = Yaml::parse($dockerComposeRaw);
// Switch Image
$image = data_get($resource, 'image');
data_set($dockerCompose, "services.{$name}.image", $image);
$dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2);
$resource->service->docker_compose_raw = $dockerComposeRaw;
$resource->service->save();
if ($resource->fqdn && !str($resource->fqdn)->contains(',')) {
// Update FQDN
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper();
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$fqdn = Url::fromString($resource->fqdn);
$fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost();
if ($generatedEnv) {
$generatedEnv->value = $fqdn;
$generatedEnv->save();
}
$variableName = "SERVICE_URL_" . Str::of($resource->name)->upper();
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$url = Url::fromString($resource->fqdn);
$url = $url->getHost();
if ($generatedEnv) {
$url = Str::of($resource->fqdn)->after('://');
$generatedEnv->value = $url;
$generatedEnv->save();
$updatedImage = data_get_str($resource, 'image');
$currentImage = data_get_str($dockerCompose, "services.{$name}.image");
if ($currentImage !== $updatedImage) {
data_set($dockerCompose, "services.{$name}.image", $updatedImage->value());
$dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2);
$resource->service->docker_compose_raw = $dockerComposeRaw;
$resource->service->save();
$resource->image = $updatedImage;
$resource->save();
}
if ($resource->fqdn) {
$resourceFqdns = str($resource->fqdn)->explode(',');
if ($resourceFqdns->count() === 1) {
$resourceFqdns = $resourceFqdns->first();
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper()->replace('-', '');
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$fqdn = Url::fromString($resourceFqdns);
$port = $fqdn->getPort();
$fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost();
if ($generatedEnv) {
$generatedEnv->value = $fqdn;
$generatedEnv->save();
}
if ($port) {
$variableName = $variableName . "_$port";
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
$generatedEnv->value = $fqdn . ':' . $port;
$generatedEnv->save();
}
}
$variableName = "SERVICE_URL_" . Str::of($resource->name)->upper()->replace('-', '');
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$url = Url::fromString($fqdn);
$port = $url->getPort();
$url = $url->getHost();
if ($generatedEnv) {
$url = Str::of($fqdn)->after('://');
$generatedEnv->value = $url;
$generatedEnv->save();
}
if ($port) {
$variableName = $variableName . "_$port";
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
$generatedEnv->value = $url . ':' . $port;
$generatedEnv->save();
}
}
} else if ($resourceFqdns->count() > 1) {
foreach ($resourceFqdns as $fqdn) {
$host = Url::fromString($fqdn);
$port = $host->getPort();
$url = $host->getHost();
$host = $host->getScheme() . '://' . $host->getHost();
if ($port) {
$port_envs = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_FQDN_%_$port")->get();
foreach ($port_envs as $port_env) {
$service_fqdn = str($port_env->key)->beforeLast('_')->after('SERVICE_FQDN_');
$env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_FQDN_' . $service_fqdn)->first();
if ($env) {
$env->value = $host;
$env->save();
}
$port_env->value = $host . ':' . $port;
$port_env->save();
}
$port_envs_url = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_URL_%_$port")->get();
foreach ($port_envs_url as $port_env_url) {
$service_url = str($port_env_url->key)->beforeLast('_')->after('SERVICE_URL_');
$env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_URL_' . $service_url)->first();
if ($env) {
$env->value = $url;
$env->save();
}
$port_env_url->value = $url . ':' . $port;
$port_env_url->save();
}
} else {
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper()->replace('-', '');
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$fqdn = Url::fromString($fqdn);
$fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost();
if ($generatedEnv) {
$generatedEnv->value = $fqdn;
$generatedEnv->save();
}
$variableName = "SERVICE_URL_" . Str::of($resource->name)->upper()->replace('-', '');
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$url = Url::fromString($fqdn);
$url = $url->getHost();
if ($generatedEnv) {
$url = Str::of($fqdn)->after('://');
$generatedEnv->value = $url;
$generatedEnv->save();
}
}
}
}
}
} catch (\Throwable $e) {
return handleError($e);
}

View File

@ -443,7 +443,7 @@ function sslip(Server $server)
function getServiceTemplates()
{
if (!isDev()) {
if (isDev()) {
$services = File::get(base_path('templates/service-templates.json'));
$services = collect(json_decode($services))->sortKeys();
} else {
@ -1168,6 +1168,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// });
// ray($withoutServiceEnvs);
// data_set($service, 'environment', $withoutServiceEnvs->toArray());
updateCompose($savedService);
return $service;
});
$finalServices = [
@ -1856,13 +1857,26 @@ function ip_match($ip, $cidrs, &$match = null)
}
return false;
}
function check_fqdn_usage(ServiceApplication|Application $own_resource)
function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
{
$domains = collect($own_resource->fqdns)->map(function ($domain) {
if ($resource) {
if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') {
$domains = data_get(json_decode($resource->docker_compose_domains, true), "*.domain");
ray($domains);
$domains = collect($domains);
} else {
$domains = collect($resource->fqdns);
}
} else if ($domain) {
$domains = collect($domain);
} else {
throw new \RuntimeException("No resource or FQDN provided.");
}
$domains = $domains->map(function ($domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
return str($domain)->replace('http://', '')->replace('https://', '');
return str($domain);
});
$apps = Application::all();
foreach ($apps as $app) {
@ -1871,10 +1885,15 @@ function check_fqdn_usage(ServiceApplication|Application $own_resource)
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
$naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value();
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
if ($app->uuid !== $own_resource->uuid) {
throw new \RuntimeException("Domain $naked_domain is already in use by another resource:<br> {$app->name}.");
if (data_get($resource, 'uuid')) {
ray($resource->uuid, $app->uuid);
if ($resource->uuid !== $app->uuid) {
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
}
} else if ($domain) {
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
}
}
}
@ -1886,12 +1905,29 @@ function check_fqdn_usage(ServiceApplication|Application $own_resource)
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
$naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value();
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
if ($app->uuid !== $own_resource->uuid) {
throw new \RuntimeException("Domain $naked_domain is already in use by another resource.");
if (data_get($resource, 'uuid')) {
if ($resource->uuid !== $app->uuid) {
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
}
} else if ($domain) {
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
}
}
}
}
if ($resource) {
$settings = InstanceSettings::get();
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
throw new \RuntimeException("Domain $naked_domain is already in use by this Coolify instance.");
}
}
}
}

View File

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

View File

@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.251';
return '4.0.0-beta.252';

34
package-lock.json generated
View File

@ -6,8 +6,8 @@
"": {
"dependencies": {
"@tailwindcss/forms": "0.5.7",
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.7",
"@tailwindcss/typography": "0.5.12",
"alpinejs": "3.13.8",
"ioredis": "5.3.2",
"tailwindcss-scrollbar": "0.1.0"
},
@ -19,8 +19,8 @@
"laravel-vite-plugin": "0.8.1",
"postcss": "8.4.38",
"pusher-js": "8.4.0-rc2",
"tailwindcss": "3.4.1",
"vite": "4.5.2",
"tailwindcss": "3.4.3",
"vite": "4.5.3",
"vue": "3.4.21"
}
},
@ -496,9 +496,9 @@
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
"integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
"version": "0.5.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.12.tgz",
"integrity": "sha512-CNwpBpconcP7ppxmuq3qvaCxiRWnbhANpY/ruH4L5qs2GCiVDJXde/pjj2HWPV1+Q4G9+V/etrwUYopdcjAlyg==",
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
@ -683,9 +683,9 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
},
"node_modules/alpinejs": {
"version": "3.13.7",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.7.tgz",
"integrity": "sha512-rcTyjTANbsePq1hb7eSekt3qjI94HLGeO6JaRjCssCVbIIc+qBrc7pO5S/+2JB6oojIibjM6FA+xRI3zhGPZIg==",
"version": "3.13.8",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.8.tgz",
"integrity": "sha512-XolbBJryCndomtaHd/KHQjQeD/L72FJxy/YhLLFD4Lr7zzGcpcbg+UgXteMR2pYg1KhRUr6V4O3GfN1zJAmRWw==",
"dependencies": {
"@vue/reactivity": "~3.1.1"
}
@ -1900,9 +1900,9 @@
}
},
"node_modules/tailwindcss": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
"integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
"integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@ -1912,7 +1912,7 @@
"fast-glob": "^3.3.0",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.19.1",
"jiti": "^1.21.0",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
@ -2020,9 +2020,9 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/vite": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",

View File

@ -13,14 +13,14 @@
"laravel-vite-plugin": "0.8.1",
"postcss": "8.4.38",
"pusher-js": "8.4.0-rc2",
"tailwindcss": "3.4.1",
"vite": "4.5.2",
"tailwindcss": "3.4.3",
"vite": "4.5.3",
"vue": "3.4.21"
},
"dependencies": {
"@tailwindcss/forms": "0.5.7",
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.7",
"@tailwindcss/typography": "0.5.12",
"alpinejs": "3.13.8",
"ioredis": "5.3.2",
"tailwindcss-scrollbar": "0.1.0"
}

View File

@ -66,7 +66,7 @@
</div>
@endif
@foreach ($enabled_oauth_providers as $provider_setting)
<x-forms.button type="button"
<x-forms.button class="w-full" type="button"
onclick="document.location.href='/auth/{{ $provider_setting->provider }}/redirect'">
{{ __("auth.login.$provider_setting->provider") }}
</x-forms.button>

View File

@ -1,4 +1,4 @@
<nav class="flex flex-col flex-1 pl-2 bg-white border-r dark:border-coolgray-200 dark:bg-base" x-data="{
<nav class="flex flex-col flex-1 bg-white border-r dark:border-coolgray-200 dark:bg-base" x-data="{
switchWidth() {
if (this.full === 'full') {
localStorage.removeItem('pageWidth');

View File

@ -19,22 +19,17 @@
@if ($projects->count() > 0)
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@foreach ($projects as $project)
<div class="gap-2 border border-transparent cursor-pointer box group">
@if (data_get($project, 'environments')->count() === 1)
<a class="flex flex-col justify-center flex-1 mx-6"
href="{{ route('project.resource.index', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<div class="box-title">{{ $project->name }}</div>
<div class="box-description"> {{ $project->description }}</div>
</a>
@else
<a class="flex flex-col justify-center flex-1 mx-6"
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
<div class="box-title">{{ $project->name }}</div>
<div class="box-description">
{{ $project->description }}</div>
</a>
@endif
<div class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
<div class="gap-2 border border-transparent cursor-pointer box group"
@if (data_get($project, 'environments')->count() === 1) onclick="gotoProject('{{ data_get($project, 'uuid') }}', '{{ data_get($project, 'environments.0.name', 'production') }}')"
@else
onclick="window.location.href = '{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}'" @endif>
<div class="flex flex-col justify-center flex-1 mx-6">
<div class="box-title">{{ $project->name }}</div>
<div class="box-description">
{{ $project->description }}</div>
</div>
<span
class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
<a class="hover:underline"
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="p-2 font-bold">+
@ -44,7 +39,7 @@
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
Settings
</a>
</div>
</span>
</div>
@endforeach
</div>

View File

@ -81,15 +81,14 @@ class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g>
</svg></button>
<div id="logs" class="flex flex-col">
<div id="logs" class="flex flex-col font-mono">
@if (decode_remote_command_output($application_deployment_queue)->count() > 0)
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
<div @class([
'font-mono',
'dark:text-warning whitespace-pre-line' => $line['hidden'],
'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr',
<span @class([
'dark:text-warning' => $line['hidden'],
'text-red-500 font-bold' => $line['type'] == 'stderr',
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
<br>COMMAND: {{ $line['command'] }}<br>OUTPUT :
@endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://'))
@php
$line['output'] = preg_replace(
@ -101,7 +100,7 @@ class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox
@else
{{ $line['output'] }}
@endif
</div>
</span>
@endforeach
@else
<span class="font-mono text-neutral-400">No logs yet.</span>

View File

@ -59,10 +59,8 @@
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
label="Domains for {{ str($serviceName)->headline() }}"
id="parsedServiceDomains.{{ $serviceName }}.domain"></x-forms.input>
@if (!data_get($parsedServiceDomains, "$serviceName.domain"))
<x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate
Domain</x-forms.button>
@endif
<x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate
Domain</x-forms.button>
</div>
@endif
@endforeach
@ -205,10 +203,10 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
</div>
@if ($this->application->is_github_based() && !$this->application->is_public_repository())
<div class="pb-4">
<x-forms.textarea helper="Gitignore-style rules to filter Git based webhook deployments."
placeholder="src/pages/**" id="application.watch_paths" label="Watch Paths" />
</div>
<div class="pb-4">
<x-forms.textarea helper="Gitignore-style rules to filter Git based webhook deployments."
placeholder="src/pages/**" id="application.watch_paths" label="Watch Paths" />
</div>
@endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"

View File

@ -25,14 +25,16 @@
</div>
<div class="pb-4">Code source of your application.</div>
<div class="flex gap-2">
<x-forms.input placeholder="coollabsio/coolify-example" id="application.git_repository"
label="Repository" />
<x-forms.input placeholder="main" id="application.git_branch" label="Branch" />
</div>
<div class="flex items-end gap-2">
<x-forms.input placeholder="HEAD" id="application.git_commit_sha" placeholder="HEAD" label="Commit SHA" />
<div class="flex flex-col gap-2">
<div class="flex gap-2">
<x-forms.input placeholder="coollabsio/coolify-example" id="application.git_repository"
label="Repository" />
<x-forms.input placeholder="main" id="application.git_branch" label="Branch" />
</div>
<div class="flex items-end gap-2">
<x-forms.input placeholder="HEAD" id="application.git_commit_sha" placeholder="HEAD"
label="Commit SHA" />
</div>
</div>
@isset($application->private_key_id)
<h3 class="pt-4">Deploy Key</h3>

View File

@ -27,7 +27,7 @@
helper="More info <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/environment-variables#shared-variables' target='_blank'>here</a>."></x-helper>
</div>
<div class="flex flex-col gap-2">
@forelse ($project->environment_variables->sort()->sortBy('real_value') as $env)
@forelse ($project->environment_variables->sort()->sortBy('key') as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" type="project" />
@empty

View File

@ -52,7 +52,7 @@
helper="More info <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/environment-variables#shared-variables' target='_blank'>here</a>."></x-helper>
</div>
<div class="flex flex-col gap-2">
@forelse ($environment->environment_variables->sort()->sortBy('real_value') as $env)
@forelse ($environment->environment_variables->sort()->sortBy('key') as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" type="environment" />
@empty

View File

@ -1,27 +1,17 @@
<div tabindex="0" x-data="{ open: false }"
class="transition border rounded cursor-pointer collapse collapse-arrow border-coolgray-200"
:class="open ? 'collapse-open' : 'collapse-close'">
<div class="flex flex-col justify-center text-sm select-text collapse-title" x-on:click="open = !open">
<div class="p-4 transition border rounded cursor-pointer border-coolgray-200">
<div class="flex flex-col justify-center pb-4 text-sm select-text">
<h2>{{ $service->name }}</h2>
<div>{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}</div>
</div>
<div class="collapse-content">
<div>
<form wire:submit='submit' class="flex flex-col gap-2">
<div class="w-64">
<x-forms.checkbox instantSave label="Is directory?" id="fileStorage.is_directory"></x-forms.checkbox>
</div>
{{-- @if ($fileStorage->is_directory)
<x-forms.input readonly label="Directory on Filesystem (save files here)" id="fs_path"></x-forms.input>
@else --}}
{{-- <div class="flex gap-2">
<x-forms.input readonly label="File in Docker Compose file" id="fileStorage.fs_path"></x-forms.input>
<x-forms.input readonly label="File on Filesystem (save files here)" id="fs_path"></x-forms.input>
</div>
<x-forms.input readonly label="Mount (in container)" id="fileStorage.mount_path"></x-forms.input> --}}
@if (!$fileStorage->is_directory)
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea>
<x-forms.button type="submit">Save</x-forms.button>
@endif
{{-- @endif --}}
</form>
</div>
</div>

View File

@ -32,7 +32,7 @@
@endif
@endif
@else
@if ($resource->persistentStorages()->get()->count() > 0 || $resource->fileStorages()->get()->count() > 0)
@if ($resource->persistentStorages()->get()->count() > 0)
<h3 class="pt-4">{{ Str::headline($resource->name) }} </h3>
@endif
@if ($resource->persistentStorages()->get()->count() > 0)

View File

@ -16,7 +16,7 @@
@endif
</div>
@if ($view === 'normal')
@forelse ($resource->environment_variables->sort()->sortBy('real_value') as $env)
@forelse ($resource->environment_variables->sort()->sortBy('key') as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" :type="$resource->type()" />
@empty
@ -27,7 +27,7 @@
<h3>Preview Deployments</h3>
<div>Environment (secrets) variables for Preview Deployments.</div>
</div>
@foreach ($resource->environment_variables_preview->sort()->sortBy('real_value') as $env)
@foreach ($resource->environment_variables_preview->sort()->sortBy('key') as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" :type="$resource->type()" />
@endforeach

View File

@ -12,7 +12,7 @@ class="dark:text-warning text-coollabs">@{{ team.VARIABLENAME }}</span> <x-helpe
</div>
<div class="flex flex-col gap-2">
@forelse ($team->environment_variables->sort()->sortBy('real_value') as $env)
@forelse ($team->environment_variables->sort()->sortBy('key') as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" type="team" />
@empty

View File

@ -6,7 +6,7 @@ set -e # Exit immediately if a command exits with a non-zero status
#set -u # Treat unset variables as an error and exit
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
VERSION="1.2.3"
VERSION="1.3.0"
DOCKER_VERSION="24.0"
CDN="https://cdn.coollabs.io/coolify"
@ -18,6 +18,11 @@ else
OS_VERSION=$(grep -w "VERSION_ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
fi
# Install xargs on Amazon Linux 2023 - lol
if [ "$OS_TYPE" = 'amzn' ]; then
dnf install -y findutils >/dev/null 2>&1
fi
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | sed -n '2p' | xargs | awk '{print $2}' | tr -d ',')
DATE=$(date +"%Y%m%d-%H%M%S")
@ -27,7 +32,7 @@ if [ $EUID != 0 ]; then
fi
case "$OS_TYPE" in
arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux) ;;
arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn) ;;
*)
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
exit
@ -64,8 +69,12 @@ ubuntu | debian | raspbian)
apt update -y >/dev/null 2>&1
apt install -y curl wget git jq >/dev/null 2>&1
;;
centos | fedora | rhel | ol | rocky | almalinux)
dnf install -y curl wget git jq >/dev/null 2>&1
centos | fedora | rhel | ol | rocky | almalinux | amzn)
if [ "$OS_TYPE" = "amzn" ]; then
dnf install -y wget git jq >/dev/null 2>&1
else
dnf install -y curl wget git jq >/dev/null 2>&1
fi
;;
sles | opensuse-leap | opensuse-tumbleweed)
zypper refresh >/dev/null 2>&1
@ -133,6 +142,7 @@ if [ -x "$(command -v snap)" ]; then
fi
if ! [ -x "$(command -v docker)" ]; then
# Almalinux
if [ "$OS_TYPE" == 'almalinux' ]; then
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
@ -146,6 +156,7 @@ if ! [ -x "$(command -v docker)" ]; then
set +e
if ! [ -x "$(command -v docker)" ]; then
echo "Docker is not installed. Installing Docker."
# Arch Linux
if [ "$OS_TYPE" = "arch" ]; then
pacman -Sy docker docker-compose --noconfirm
systemctl enable docker.service
@ -157,19 +168,38 @@ if ! [ -x "$(command -v docker)" ]; then
exit
fi
else
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with Rancher script. Trying with official script."
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
# Amazon Linux 2023
if [ "$OS_TYPE" = "amzn" ]; then
dnf install docker -y
DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
systemctl start docker
systemctl enable docker
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with official script."
echo "Maybe your OS is not supported?"
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
echo "Failed to install Docker with pacman. Try to install it manually."
echo "Please visit https://wiki.archlinux.org/title/docker for more information."
exit
fi
else
# Automated Docker installation
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with Rancher script. Trying with official script."
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with official script."
echo "Maybe your OS is not supported?"
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
fi
fi
fi
fi

View File

@ -15,3 +15,8 @@ services:
environment:
- SERVICE_FQDN_SPDF_8080
- DOCKER_ENABLE_SECURITY=false
healthcheck:
test: 'curl --fail -I http://localhost:8080 || exit 1'
interval: 5s
timeout: 20s
retries: 10

View File

@ -15,7 +15,7 @@ services:
condition: service_healthy
environment:
- SERVICE_FQDN_SUPABASE_8000
- JWT_SERCET=${SERVICE_PASSWORD_JWT}
- JWT_SECRET=${SERVICE_PASSWORD_JWT}
- KONG_DATABASE=off
- KONG_DECLARATIVE_CONFIG=/home/kong/kong.yml
# https://github.com/supabase/cli/issues/14

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.251"
"version": "4.0.0-beta.252"
}
}
}