diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php
index 41f961b59..5a1ae56cf 100644
--- a/app/Actions/Proxy/CheckProxy.php
+++ b/app/Actions/Proxy/CheckProxy.php
@@ -46,14 +46,14 @@ class CheckProxy
$port443 = is_resource($connection443) && fclose($connection443);
if ($port80) {
if ($fromUI) {
- throw new \Exception("Port 80 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
+ throw new \Exception("Port 80 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
} else {
return false;
}
}
if ($port443) {
if ($fromUI) {
- throw new \Exception("Port 443 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
+ throw new \Exception("Port 443 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
} else {
return false;
}
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index 129df7814..b31239393 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -1128,9 +1128,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->custom_healthcheck_found = false;
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
$this->execute_remote_command([
- executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile', "ignore_errors" => true
+ executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile_from_repo', "ignore_errors" => true
]);
- $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
+ $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
if (str($dockerfile)->contains('HEALTHCHECK')) {
$this->custom_healthcheck_found = true;
}
@@ -1359,23 +1359,58 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$environment_variables = collect();
if ($this->pull_request_id === 0) {
foreach ($this->application->runtime_environment_variables as $env) {
- $environment_variables->push("$env->key=$env->real_value");
+ // This is necessary because we have to escape the value of the environment variable
+ // but only if the environment variable is created after the 15th of March 2024
+ // when I implemented the escaping feature.
+
+ // Old environment variables are not escaped, because it could break the application
+ // as the application could expect the unescaped value.
+
+ // Yes, I worked on March 15th, 2024, and I implemented this feature.
+ // It was a national holiday in Hungary.
+
+ // Welcome to the life of a solopreneur.
+ if ($env->created_at > '2024-03-15T20:42:42.000000Z') {
+ $real_value = escapeEnvVariables($env->real_value);
+ } else {
+ $real_value = $env->value;
+ }
+ $environment_variables->push("$env->key=$real_value");
}
foreach ($this->application->nixpacks_environment_variables as $env) {
- $environment_variables->push("$env->key=$env->real_value");
+ if ($env->created_at > '2024-03-15T20:42:42.000000Z') {
+ $real_value = escapeEnvVariables($env->real_value);
+ } else {
+ $real_value = $env->value;
+ }
+ $environment_variables->push("$env->key=$real_value");
}
} else {
foreach ($this->application->runtime_environment_variables_preview as $env) {
- $environment_variables->push("$env->key=$env->real_value");
+ if ($env->created_at > '2024-03-15T20:42:42.000000Z') {
+ $real_value = escapeEnvVariables($env->real_value);
+ } else {
+ $real_value = $env->value;
+ }
+ $environment_variables->push("$env->key=$real_value");
}
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
- $environment_variables->push("$env->key=$env->real_value");
+ if ($env->created_at > '2024-03-15T20:42:42.000000Z') {
+ $real_value = escapeEnvVariables($env->real_value);
+ } else {
+ $real_value = $env->value;
+ }
+ $environment_variables->push("$env->key=$real_value");
}
}
// Add PORT if not exists, use the first port as default
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$environment_variables->push("PORT={$ports[0]}");
}
+ // Add HOST if not exists
+ if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
+ $environment_variables->push("HOST=0.0.0.0");
+ }
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$environment_variables->push("SOURCE_COMMIT={$this->commit}");
@@ -1383,6 +1418,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$environment_variables->push("SOURCE_COMMIT=unknown");
}
}
+ ray($environment_variables->all());
return $environment_variables->all();
}
diff --git a/app/Livewire/Project/Edit.php b/app/Livewire/Project/Edit.php
index b2cda7a47..a80b1af76 100644
--- a/app/Livewire/Project/Edit.php
+++ b/app/Livewire/Project/Edit.php
@@ -17,9 +17,14 @@ class Edit extends Component
public function saveKey($data)
{
try {
+ $found = $this->project->environment_variables()->where('key', $data['key'])->first();
+ if ($found) {
+ throw new \Exception('Variable already exists.');
+ }
$this->project->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
+ 'is_multiline' => $data['is_multiline'],
'type' => 'project',
'team_id' => currentTeam()->id,
]);
diff --git a/app/Livewire/Project/EnvironmentEdit.php b/app/Livewire/Project/EnvironmentEdit.php
index 276aa58df..c5e4ccf11 100644
--- a/app/Livewire/Project/EnvironmentEdit.php
+++ b/app/Livewire/Project/EnvironmentEdit.php
@@ -21,9 +21,14 @@ class EnvironmentEdit extends Component
public function saveKey($data)
{
try {
+ $found = $this->environment->environment_variables()->where('key', $data['key'])->first();
+ if ($found) {
+ throw new \Exception('Variable already exists.');
+ }
$this->environment->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
+ 'is_multiline' => $data['is_multiline'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
index 923f0d455..c04242963 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
@@ -11,17 +11,20 @@ class Add extends Component
public string $key;
public ?string $value = null;
public bool $is_build_time = false;
+ public bool $is_multiline = false;
protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [
'key' => 'required|string',
'value' => 'nullable',
'is_build_time' => 'required|boolean',
+ 'is_multiline' => 'required|boolean',
];
protected $validationAttributes = [
'key' => 'key',
'value' => 'value',
'is_build_time' => 'build',
+ 'is_multiline' => 'multiline',
];
public function mount()
@@ -43,6 +46,7 @@ class Add extends Component
'key' => $this->key,
'value' => $this->value,
'is_build_time' => $this->is_build_time,
+ 'is_multiline' => $this->is_multiline,
'is_preview' => $this->is_preview,
]);
$this->clear();
@@ -53,5 +57,6 @@ class Add extends Component
$this->key = '';
$this->value = '';
$this->is_build_time = false;
+ $this->is_multiline = false;
}
}
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
index 3a304d3e9..53dd0ffe3 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
@@ -11,7 +11,7 @@ class All extends Component
{
public $resource;
public bool $showPreview = false;
- public string|null $modalId = null;
+ public ?string $modalId = null;
public ?string $variables = null;
public ?string $variablesPreview = null;
public string $view = 'normal';
@@ -34,6 +34,9 @@ class All extends Component
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
+ if ($item->is_multiline) {
+ return "$item->key=(multiline, edit in normal view)";
+ }
return "$item->key=$item->value";
})->sort()->join('
');
@@ -42,6 +45,9 @@ class All extends Component
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
+ if ($item->is_multiline) {
+ return "$item->key=(multiline, edit in normal view)";
+ }
return "$item->key=$item->value";
})->sort()->join('
');
@@ -67,7 +73,7 @@ class All extends Component
$found = $this->resource->environment_variables()->where('key', $key)->first();
}
if ($found) {
- if ($found->is_shown_once) {
+ if ($found->is_shown_once || $found->is_multiline) {
continue;
}
$found->value = $variable;
@@ -144,6 +150,7 @@ class All extends Component
$environment->key = $data['key'];
$environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time'];
+ $environment->is_multiline = $data['is_multiline'];
$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 9252c44f8..7903e8e51 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
@@ -21,6 +21,7 @@ class Show extends Component
'env.key' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
+ 'env.is_multiline' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable',
];
@@ -28,6 +29,7 @@ class Show extends Component
'env.key' => 'Key',
'env.value' => 'Value',
'env.is_build_time' => 'Build Time',
+ 'env.is_multiline' => 'Multiline',
'env.is_shown_once' => 'Shown Once',
];
diff --git a/app/Livewire/Project/Shared/GetLogs.php b/app/Livewire/Project/Shared/GetLogs.php
index 5a2053fc4..6d1aaefc1 100644
--- a/app/Livewire/Project/Shared/GetLogs.php
+++ b/app/Livewire/Project/Shared/GetLogs.php
@@ -71,6 +71,9 @@ class GetLogs extends Component
}
public function getLogs($refresh = false)
{
+ if (!$this->server->isFunctional()) {
+ return;
+ }
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
@@ -79,6 +82,9 @@ class GetLogs extends Component
}
}
if (!$refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) return;
+ if (!$this->numberOfLines) {
+ $this->numberOfLines = 1000;
+ }
if ($this->container) {
if ($this->showTimeStamps) {
if ($this->server->isSwarm()) {
diff --git a/app/Livewire/Project/Shared/Logs.php b/app/Livewire/Project/Shared/Logs.php
index 2958a31fd..d200ca69e 100644
--- a/app/Livewire/Project/Shared/Logs.php
+++ b/app/Livewire/Project/Shared/Logs.php
@@ -29,6 +29,9 @@ class Logs extends Component
{
try {
$server = $this->servers->firstWhere('id', $server_id);
+ if (!$server->isFunctional()) {
+ return;
+ }
if ($server->isSwarm()) {
$containers = collect([
[
@@ -96,7 +99,6 @@ class Logs extends Component
$this->resource->databases()->get()->each(function ($database) {
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
});
-
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}
diff --git a/app/Livewire/TeamSharedVariablesIndex.php b/app/Livewire/TeamSharedVariablesIndex.php
index d2776e8b2..dbb64ab65 100644
--- a/app/Livewire/TeamSharedVariablesIndex.php
+++ b/app/Livewire/TeamSharedVariablesIndex.php
@@ -13,9 +13,14 @@ class TeamSharedVariablesIndex extends Component
public function saveKey($data)
{
try {
+ $found = $this->team->environment_variables()->where('key', $data['key'])->first();
+ if ($found) {
+ throw new \Exception('Variable already exists.');
+ }
$this->team->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
+ 'is_multiline' => $data['is_multiline'],
'type' => 'team',
'team_id' => currentTeam()->id,
]);
diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php
index a3611a0c6..41fe40c58 100644
--- a/app/Models/EnvironmentVariable.php
+++ b/app/Models/EnvironmentVariable.php
@@ -49,7 +49,7 @@ class EnvironmentVariable extends Model
set: fn (?string $value = null) => $this->set_environment_variables($value),
);
}
- public function realValue(): Attribute
+ public function resource()
{
$resource = null;
if ($this->application_id) {
@@ -71,9 +71,19 @@ class EnvironmentVariable extends Model
}
}
}
+ return $resource;
+ }
+ public function realValue(): Attribute
+ {
+ $resource = $this->resource();
return Attribute::make(
get: function () use ($resource) {
- return $this->get_real_environment_variables($this->value, $resource);
+ $env = $this->get_real_environment_variables($this->value, $resource);
+ return data_get($env, 'value', $env);
+ if (is_string($env)) {
+ return $env;
+ }
+ return $env->value;
}
);
}
@@ -89,7 +99,7 @@ class EnvironmentVariable extends Model
}
);
}
- private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null
+ private function get_real_environment_variables(?string $environment_variable = null, $resource = null)
{
if (!$environment_variable || !$resource) {
return null;
@@ -112,7 +122,7 @@ class EnvironmentVariable extends Model
}
$environment_variable_found = SharedEnvironmentVariable::where("type", $type)->where('key', $variable)->where('team_id', $resource->team()->id)->where("{$type}_id", $id)->first();
if ($environment_variable_found) {
- return $environment_variable_found->value;
+ return $environment_variable_found;
}
}
return $environment_variable;
diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php
index 09cee0338..93598cea5 100644
--- a/app/View/Components/Forms/Input.php
+++ b/app/View/Components/Forms/Input.php
@@ -10,17 +10,17 @@ use Visus\Cuid2\Cuid2;
class Input extends Component
{
public function __construct(
- public string|null $id = null,
- public string|null $name = null,
- public string|null $type = 'text',
- public string|null $value = null,
- public string|null $label = null,
- public bool $required = false,
- public bool $disabled = false,
- public bool $readonly = false,
- public string|null $helper = null,
- public bool $allowToPeak = true,
- public string $defaultClass = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
+ public ?string $id = null,
+ public ?string $name = null,
+ public ?string $type = 'text',
+ public ?string $value = null,
+ public ?string $label = null,
+ public bool $required = false,
+ public bool $disabled = false,
+ public bool $readonly = false,
+ public ?string $helper = null,
+ public bool $allowToPeak = true,
+ public string $defaultClass = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
) {
}
diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php
index 50ffe77f7..8c50af533 100644
--- a/app/View/Components/Forms/Textarea.php
+++ b/app/View/Components/Forms/Textarea.php
@@ -4,7 +4,6 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
-use Illuminate\Support\Str;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
@@ -14,18 +13,20 @@ class Textarea extends Component
* Create a new component instance.
*/
public function __construct(
- public string|null $id = null,
- public string|null $name = null,
- public string|null $type = 'text',
- public string|null $value = null,
- public string|null $label = null,
- public string|null $placeholder = null,
- public bool $required = false,
- public bool $disabled = false,
- public bool $readonly = false,
- public string|null $helper = null,
- public bool $realtimeValidation = false,
- public string $defaultClass = "textarea leading-normal bg-coolgray-100 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
+ public ?string $id = null,
+ public ?string $name = null,
+ public ?string $type = 'text',
+ public ?string $value = null,
+ public ?string $label = null,
+ public ?string $placeholder = null,
+ public bool $required = false,
+ public bool $disabled = false,
+ public bool $readonly = false,
+ public ?string $helper = null,
+ public bool $realtimeValidation = false,
+ public bool $allowToPeak = true,
+ public string $defaultClass = "textarea leading-normal bg-coolgray-100 rounded text-white w-full scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50",
+ public string $defaultClassInput = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
) {
//
}
diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php
index 898789adf..aed77a7bb 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -557,7 +557,8 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
return $compose_options->toArray();
}
-function validateComposeFile(string $compose, int $server_id): string|Throwable {
+function validateComposeFile(string $compose, int $server_id): string|Throwable
+{
return 'OK';
try {
$uuid = Str::random(10);
@@ -578,3 +579,10 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
], $server);
}
}
+
+function escapeEnvVariables($value)
+{
+ $search = array("\\", "\r", "\t", "\x0", '"', "'", "$");
+ $replace = array("\\\\", "\\r", "\\t", "\\0", '\"', "\'", "$$");
+ return str_replace($search, $replace, $value);
+}
diff --git a/config/sentry.php b/config/sentry.php
index 150b055c3..5d46ba7f3 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.238',
+ 'release' => '4.0.0-beta.240',
// 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 6a881ff74..c7e1008fc 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,3 +1,3 @@
boolean('is_multiline')->default(false);
+ });
+ Schema::table('shared_environment_variables', function (Blueprint $table) {
+ $table->boolean('is_multiline')->default(false);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('environment_variables', function (Blueprint $table) {
+ $table->dropColumn('is_multiline');
+ });
+ Schema::table('shared_environment_variables', function (Blueprint $table) {
+ $table->dropColumn('is_multiline');
+ });
+ }
+};
diff --git a/resources/views/components/forms/input.blade.php b/resources/views/components/forms/input.blade.php
index c13de00c1..5cabb3c70 100644
--- a/resources/views/components/forms/input.blade.php
+++ b/resources/views/components/forms/input.blade.php
@@ -1,4 +1,4 @@
-