Error: '.$error);
return;
}
} catch (\Throwable $e) {
return handleError($e, $this);
+ } finally {
+ $this->dispatch('refreshServerShow');
+ $this->server->refresh();
}
}
- public function mount()
- {
- $this->parameters = get_route_parameters();
- }
+
}
diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php
index c52970258..eb492e691 100644
--- a/app/Livewire/Settings/Index.php
+++ b/app/Livewire/Settings/Index.php
@@ -28,6 +28,7 @@ class Index extends Component
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
protected Server $server;
+ public $timezones;
protected $rules = [
'settings.fqdn' => 'nullable',
@@ -53,14 +54,14 @@ class Index extends Component
'settings.is_auto_update_enabled' => 'Auto Update Enabled',
'auto_update_frequency' => 'Auto Update Frequency',
'update_check_frequency' => 'Update Check Frequency',
+ 'settings.instance_timezone' => 'Instance Timezone',
];
- public $timezones;
public function mount()
{
if (isInstanceAdmin()) {
- $this->settings = InstanceSettings::get();
+ $this->settings = instanceSettings();
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
@@ -162,7 +163,7 @@ class Index extends Component
{
CheckForUpdatesJob::dispatchSync();
$this->dispatch('updateAvailable');
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
if ($settings->new_version_available) {
$this->dispatch('success', 'New version available!');
} else {
@@ -170,12 +171,6 @@ class Index extends Component
}
}
- public function updatedSettingsInstanceTimezone($value)
- {
- $this->settings->instance_timezone = $value;
- $this->settings->save();
- $this->dispatch('success', 'Instance timezone updated.');
- }
public function render()
{
diff --git a/app/Livewire/Settings/License.php b/app/Livewire/Settings/License.php
index f9402fd7b..ca0c9c1ae 100644
--- a/app/Livewire/Settings/License.php
+++ b/app/Livewire/Settings/License.php
@@ -29,7 +29,7 @@ class License extends Component
abort(404);
}
$this->instance_id = config('app.id');
- $this->settings = \App\Models\InstanceSettings::get();
+ $this->settings = instanceSettings();
}
public function render()
diff --git a/app/Livewire/SettingsBackup.php b/app/Livewire/SettingsBackup.php
index 99b8f8d49..9240aa96d 100644
--- a/app/Livewire/SettingsBackup.php
+++ b/app/Livewire/SettingsBackup.php
@@ -42,7 +42,7 @@ class SettingsBackup extends Component
public function mount()
{
if (isInstanceAdmin()) {
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$this->database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($this->database) {
diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php
index 3eb8ea646..4515df9a7 100644
--- a/app/Livewire/SettingsEmail.php
+++ b/app/Livewire/SettingsEmail.php
@@ -43,7 +43,7 @@ class SettingsEmail extends Component
public function mount()
{
if (isInstanceAdmin()) {
- $this->settings = InstanceSettings::get();
+ $this->settings = instanceSettings();
$this->emails = auth()->user()->email;
} else {
return redirect()->route('dashboard');
diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php
index 75d7fd04a..193b650ff 100644
--- a/app/Livewire/Source/Github/Change.php
+++ b/app/Livewire/Source/Github/Change.php
@@ -99,7 +99,7 @@ class Change extends Component
return redirect()->route('source.all');
}
$this->applications = $this->github_app->applications;
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->name = str($this->github_app->name)->kebab();
diff --git a/app/Livewire/Source/Github/Create.php b/app/Livewire/Source/Github/Create.php
index f85e8646e..103c5c9fb 100644
--- a/app/Livewire/Source/Github/Create.php
+++ b/app/Livewire/Source/Github/Create.php
@@ -23,7 +23,7 @@ class Create extends Component
public function mount()
{
- $this->name = generate_random_name();
+ $this->name = substr(generate_random_name(), 0, 34); // GitHub Apps names can only be 34 characters long
}
public function createGitHubApp()
diff --git a/app/Livewire/Storage/Create.php b/app/Livewire/Storage/Create.php
index a05834ecc..c5250e1e3 100644
--- a/app/Livewire/Storage/Create.php
+++ b/app/Livewire/Storage/Create.php
@@ -43,15 +43,17 @@ class Create extends Component
'endpoint' => 'Endpoint',
];
- public function mount()
+ public function updatedEndpoint($value)
{
- if (isDev()) {
- $this->name = 'Local MinIO';
- $this->description = 'Local MinIO';
- $this->key = 'minioadmin';
- $this->secret = 'minioadmin';
- $this->bucket = 'local';
- $this->endpoint = 'http://coolify-minio:9000';
+ if (! str($value)->startsWith('https://') && ! str($value)->startsWith('http://')) {
+ $this->endpoint = 'https://'.$value;
+ $value = $this->endpoint;
+ }
+
+ if (str($value)->contains('your-objectstorage.com') && ! isset($this->bucket)) {
+ $this->bucket = str($value)->after('//')->before('.');
+ } elseif (str($value)->contains('your-objectstorage.com')) {
+ $this->bucket = $this->bucket ?: str($value)->after('//')->before('.');
}
}
diff --git a/app/Livewire/Subscription/Index.php b/app/Livewire/Subscription/Index.php
index c278bf58e..df450cf7e 100644
--- a/app/Livewire/Subscription/Index.php
+++ b/app/Livewire/Subscription/Index.php
@@ -23,7 +23,7 @@ class Index extends Component
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
return redirect()->route('subscription.show');
}
- $this->settings = \App\Models\InstanceSettings::get();
+ $this->settings = instanceSettings();
$this->alreadySubscribed = currentTeam()->subscription()->exists();
}
diff --git a/app/Livewire/Team/AdminView.php b/app/Livewire/Team/AdminView.php
index 97d4fcdbf..3026cb297 100644
--- a/app/Livewire/Team/AdminView.php
+++ b/app/Livewire/Team/AdminView.php
@@ -4,6 +4,8 @@ namespace App\Livewire\Team;
use App\Models\Team;
use App\Models\User;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class AdminView extends Component
@@ -73,8 +75,13 @@ class AdminView extends Component
$team->delete();
}
- public function delete($id)
+ public function delete($id, $password)
{
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
+
+ return;
+ }
if (! auth()->user()->isInstanceAdmin()) {
return $this->dispatch('error', 'You are not authorized to delete users');
}
diff --git a/app/Livewire/Terminal/Index.php b/app/Livewire/Terminal/Index.php
new file mode 100644
index 000000000..945b25714
--- /dev/null
+++ b/app/Livewire/Terminal/Index.php
@@ -0,0 +1,76 @@
+user()->isAdmin()) {
+ abort(403);
+ }
+ $this->servers = Server::isReachable()->get();
+ $this->containers = $this->getAllActiveContainers();
+ }
+
+ private function getAllActiveContainers()
+ {
+ return collect($this->servers)->flatMap(function ($server) {
+ if (! $server->isFunctional()) {
+ return [];
+ }
+
+ return $server->loadAllContainers()->map(function ($container) use ($server) {
+ $state = data_get_str($container, 'State')->lower();
+ if ($state->contains('running')) {
+ return [
+ 'name' => data_get($container, 'Names'),
+ 'connection_name' => data_get($container, 'Names'),
+ 'uuid' => data_get($container, 'Names'),
+ 'status' => data_get_str($container, 'State')->lower(),
+ 'server' => $server,
+ 'server_uuid' => $server->uuid,
+ ];
+ }
+
+ return null;
+ })->filter();
+ });
+ }
+
+ public function updatedSelectedUuid()
+ {
+ $this->connectToContainer();
+ }
+
+ #[On('connectToContainer')]
+ public function connectToContainer()
+ {
+ if ($this->selected_uuid === 'default') {
+ $this->dispatch('error', 'Please select a server or a container.');
+
+ return;
+ }
+ $container = collect($this->containers)->firstWhere('uuid', $this->selected_uuid);
+ $this->dispatch('send-terminal-command',
+ isset($container),
+ $container['connection_name'] ?? $this->selected_uuid,
+ $container['server_uuid'] ?? $this->selected_uuid
+ );
+ }
+
+ public function render()
+ {
+ return view('livewire.terminal.index');
+ }
+}
diff --git a/app/Models/Application.php b/app/Models/Application.php
index d0cc34a06..846d7df4c 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -6,7 +6,10 @@ use App\Enums\ApplicationDeploymentStatus;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Process\InvokedProcess;
use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Process;
+use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use OpenApi\Attributes as OA;
use RuntimeException;
@@ -102,7 +105,7 @@ class Application extends BaseModel
{
use SoftDeletes;
- private static $parserVersion = '3';
+ private static $parserVersion = '4';
protected $guarded = [];
@@ -141,6 +144,9 @@ class Application extends BaseModel
}
$application->tags()->detach();
$application->previews()->delete();
+ foreach ($application->deployment_queue as $deployment) {
+ $deployment->delete();
+ }
});
}
@@ -149,12 +155,64 @@ class Application extends BaseModel
return Application::whereRelation('environment.project.team', 'id', $teamId)->orderBy('name');
}
+ public function getContainersToStop(bool $previewDeployments = false): array
+ {
+ $containers = $previewDeployments
+ ? getCurrentApplicationContainerStatus($this->destination->server, $this->id, includePullrequests: true)
+ : getCurrentApplicationContainerStatus($this->destination->server, $this->id, 0);
+
+ return $containers->pluck('Names')->toArray();
+ }
+
+ public function stopContainers(array $containerNames, $server, int $timeout = 600)
+ {
+ $processes = [];
+ foreach ($containerNames as $containerName) {
+ $processes[$containerName] = $this->stopContainer($containerName, $server, $timeout);
+ }
+
+ $startTime = time();
+ while (count($processes) > 0) {
+ $finishedProcesses = array_filter($processes, function ($process) {
+ return ! $process->running();
+ });
+ foreach ($finishedProcesses as $containerName => $process) {
+ unset($processes[$containerName]);
+ $this->removeContainer($containerName, $server);
+ }
+
+ if (time() - $startTime >= $timeout) {
+ $this->forceStopRemainingContainers(array_keys($processes), $server);
+ break;
+ }
+
+ usleep(100000);
+ }
+ }
+
+ public function stopContainer(string $containerName, $server, int $timeout): InvokedProcess
+ {
+ return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
+ }
+
+ public function removeContainer(string $containerName, $server)
+ {
+ instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
+ }
+
+ public function forceStopRemainingContainers(array $containerNames, $server)
+ {
+ foreach ($containerNames as $containerName) {
+ instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
+ $this->removeContainer($containerName, $server);
+ }
+ }
+
public function delete_configurations()
{
$server = data_get($this, 'destination.server');
$workdir = $this->workdir();
if (str($workdir)->endsWith($this->uuid)) {
- ray('Deleting workdir');
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
}
}
@@ -176,6 +234,13 @@ class Application extends BaseModel
}
}
+ public function delete_connected_networks($uuid)
+ {
+ $server = data_get($this, 'destination.server');
+ instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
+ instant_remote_process(["docker network rm {$uuid}"], $server, false);
+ }
+
public function additional_servers()
{
return $this->belongsToMany(Server::class, 'additional_destinations')
@@ -243,7 +308,7 @@ class Application extends BaseModel
'application_uuid' => data_get($this, 'uuid'),
'task_uuid' => $task_uuid,
]);
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$url = Url::fromString($route);
$url = $url->withPort(null);
@@ -649,6 +714,11 @@ class Application extends BaseModel
return $this->hasMany(ApplicationPreview::class);
}
+ public function deployment_queue()
+ {
+ return $this->hasMany(ApplicationDeploymentQueue::class);
+ }
+
public function destination()
{
return $this->morphTo();
@@ -1034,6 +1104,7 @@ class Application extends BaseModel
throw new \Exception($e->getMessage());
}
$services = data_get($yaml, 'services');
+
$commands = collect([]);
$services = collect($services)->map(function ($service) use ($commands) {
$serviceVolumes = collect(data_get($service, 'volumes', []));
@@ -1088,7 +1159,7 @@ class Application extends BaseModel
public function parse(int $pull_request_id = 0, ?int $preview_id = null)
{
- if ($this->compose_parsing_version === '3') {
+ if ((int) $this->compose_parsing_version >= 3) {
return newParser($this, $pull_request_id, $preview_id);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
@@ -1166,7 +1237,6 @@ class Application extends BaseModel
} else {
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile
Check if you used the right extension (.yaml or .yml) in the compose file name.");
}
-
}
public function parseContainerLabels(?ApplicationPreview $preview = null)
@@ -1330,13 +1400,21 @@ class Application extends BaseModel
return [];
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (isDev() && $server->id === 0) {
+ $process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/container/{$container_name}/cpu/history?from=$from");
+ if ($process->failed()) {
+ throw new \Exception($process->errorOutput());
+ }
+ $metrics = $process->output();
+ } else {
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ }
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -1345,17 +1423,109 @@ class Application extends BaseModel
}
throw new \Exception($error);
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
});
return $parsedCollection->toArray();
}
}
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ if (isDev() && $server->id === 0) {
+ $process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/container/{$container_name}/memory/history?from=$from");
+ if ($process->failed()) {
+ throw new \Exception($process->errorOutput());
+ }
+ $metrics = $process->output();
+ } else {
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ }
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
+
+ public function generateConfig($is_json = false)
+ {
+ $config = collect([]);
+ if ($this->build_pack = 'nixpacks') {
+ $config = collect([
+ 'build_pack' => 'nixpacks',
+ 'docker_registry_image_name' => $this->docker_registry_image_name,
+ 'docker_registry_image_tag' => $this->docker_registry_image_tag,
+ 'install_command' => $this->install_command,
+ 'build_command' => $this->build_command,
+ 'start_command' => $this->start_command,
+ 'base_directory' => $this->base_directory,
+ 'publish_directory' => $this->publish_directory,
+ 'custom_docker_run_options' => $this->custom_docker_run_options,
+ 'ports_exposes' => $this->ports_exposes,
+ 'ports_mappings' => $this->ports_mapping,
+ 'settings' => collect([
+ 'is_static' => $this->settings->is_static,
+ ]),
+ ]);
+ }
+ $config = $config->filter(function ($value) {
+ return str($value)->isNotEmpty();
+ });
+ if ($is_json) {
+ return json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ }
+
+ return $config;
+ }
+
+ public function setConfig($config)
+ {
+
+ $config = $config;
+ $validator = Validator::make(['config' => $config], [
+ 'config' => 'required|json',
+ ]);
+ if ($validator->fails()) {
+ throw new \Exception('Invalid JSON format');
+ }
+ $config = json_decode($config, true);
+
+ $deepValidator = Validator::make(['config' => $config], [
+ 'config.build_pack' => 'required|string',
+ 'config.base_directory' => 'required|string',
+ 'config.publish_directory' => 'required|string',
+ 'config.ports_exposes' => 'required|string',
+ 'config.settings.is_static' => 'required|boolean',
+ ]);
+ if ($deepValidator->fails()) {
+ throw new \Exception('Invalid data');
+ }
+ $config = $deepValidator->validated()['config'];
+
+ try {
+ $settings = data_get($config, 'settings', []);
+ data_forget($config, 'settings');
+ $this->update($config);
+ $this->settings()->update($settings);
+ } catch (\Exception $e) {
+ throw new \Exception('Failed to update application settings');
+ }
+ }
}
diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php
index 90d7608cc..c261c30c6 100644
--- a/app/Models/ApplicationDeploymentQueue.php
+++ b/app/Models/ApplicationDeploymentQueue.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use OpenApi\Attributes as OA;
@@ -39,6 +40,20 @@ class ApplicationDeploymentQueue extends Model
{
protected $guarded = [];
+ public function application(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => Application::find($this->application_id),
+ );
+ }
+
+ public function server(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => Server::find($this->server_id),
+ );
+ }
+
public function setStatus(string $status)
{
$this->update([
diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php
index 138775aba..531c8fa40 100644
--- a/app/Models/EnvironmentVariable.php
+++ b/app/Models/EnvironmentVariable.php
@@ -44,7 +44,7 @@ class EnvironmentVariable extends Model
'version' => 'string',
];
- protected $appends = ['real_value', 'is_shared'];
+ protected $appends = ['real_value', 'is_shared', 'is_really_required'];
protected static function booted()
{
@@ -126,15 +126,17 @@ class EnvironmentVariable extends Model
$env = $this->get_real_environment_variables($this->value, $resource);
return data_get($env, 'value', $env);
- if (is_string($env)) {
- return $env;
- }
-
- return $env->value;
}
);
}
+ protected function isReallyRequired(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => $this->is_required && str($this->real_value)->isEmpty(),
+ );
+ }
+
protected function isShared(): Attribute
{
return Attribute::make(
diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php
index 27a181ee4..3ee142050 100644
--- a/app/Models/InstanceSettings.php
+++ b/app/Models/InstanceSettings.php
@@ -21,6 +21,7 @@ class InstanceSettings extends Model implements SendsEmail
'is_auto_update_enabled' => 'boolean',
'auto_update_frequency' => 'string',
'update_check_frequency' => 'string',
+ 'sentinel_token' => 'encrypted',
];
public function fqdn(): Attribute
@@ -85,4 +86,17 @@ class InstanceSettings extends Model implements SendsEmail
return "[{$instanceName}]";
}
+
+ public function helperVersion(): Attribute
+ {
+ return Attribute::make(
+ get: function ($value) {
+ if (isDev()) {
+ return 'latest';
+ }
+
+ return $value;
+ }
+ );
+ }
}
diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php
index 45bc6bc84..065746ede 100644
--- a/app/Models/PrivateKey.php
+++ b/app/Models/PrivateKey.php
@@ -2,6 +2,9 @@
namespace App\Models;
+use DanHarrin\LivewireRateLimiting\WithRateLimiting;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Validation\ValidationException;
use OpenApi\Attributes as OA;
use phpseclib3\Crypt\PublicKeyLoader;
@@ -22,48 +25,144 @@ use phpseclib3\Crypt\PublicKeyLoader;
)]
class PrivateKey extends BaseModel
{
+ use WithRateLimiting;
+
protected $fillable = [
'name',
'description',
'private_key',
'is_git_related',
'team_id',
+ 'fingerprint',
+ ];
+
+ protected $casts = [
+ 'private_key' => 'encrypted',
];
protected static function booted()
{
static::saving(function ($key) {
- $privateKey = data_get($key, 'private_key');
- if (substr($privateKey, -1) !== "\n") {
- $key->private_key = $privateKey."\n";
+ $key->private_key = formatPrivateKey($key->private_key);
+
+ if (! self::validatePrivateKey($key->private_key)) {
+ throw ValidationException::withMessages([
+ 'private_key' => ['The private key is invalid.'],
+ ]);
+ }
+
+ $key->fingerprint = self::generateFingerprint($key->private_key);
+ if (self::fingerprintExists($key->fingerprint, $key->id)) {
+ throw ValidationException::withMessages([
+ 'private_key' => ['This private key already exists.'],
+ ]);
}
});
+ static::deleted(function ($key) {
+ self::deleteFromStorage($key);
+ });
+ }
+
+ public function getPublicKey()
+ {
+ return self::extractPublicKeyFromPrivate($this->private_key) ?? 'Error loading private key';
}
public static function ownedByCurrentTeam(array $select = ['*'])
{
$selectArray = collect($select)->concat(['id']);
- return PrivateKey::whereTeamId(currentTeam()->id)->select($selectArray->all());
+ return self::whereTeamId(currentTeam()->id)->select($selectArray->all());
}
- public function publicKey()
+ public static function validatePrivateKey($privateKey)
{
try {
- return PublicKeyLoader::load($this->private_key)->getPublicKey()->toString('OpenSSH', ['comment' => '']);
+ PublicKeyLoader::load($privateKey);
+
+ return true;
} catch (\Throwable $e) {
- return 'Error loading private key';
+ return false;
}
}
- public function isEmpty()
+ public static function createAndStore(array $data)
{
- if ($this->servers()->count() === 0 && $this->applications()->count() === 0 && $this->githubApps()->count() === 0 && $this->gitlabApps()->count() === 0) {
- return true;
- }
+ $privateKey = new self($data);
+ $privateKey->save();
+ $privateKey->storeInFileSystem();
- return false;
+ return $privateKey;
+ }
+
+ public static function generateNewKeyPair($type = 'rsa')
+ {
+ try {
+ $instance = new self;
+ $instance->rateLimit(10);
+ $name = generate_random_name();
+ $description = 'Created by Coolify';
+ $keyPair = generateSSHKey($type === 'ed25519' ? 'ed25519' : 'rsa');
+
+ return [
+ 'name' => $name,
+ 'description' => $description,
+ 'private_key' => $keyPair['private'],
+ 'public_key' => $keyPair['public'],
+ ];
+ } catch (\Throwable $e) {
+ throw new \Exception("Failed to generate new {$type} key: ".$e->getMessage());
+ }
+ }
+
+ public static function extractPublicKeyFromPrivate($privateKey)
+ {
+ try {
+ $key = PublicKeyLoader::load($privateKey);
+
+ return $key->getPublicKey()->toString('OpenSSH', ['comment' => '']);
+ } catch (\Throwable $e) {
+ return null;
+ }
+ }
+
+ public static function validateAndExtractPublicKey($privateKey)
+ {
+ $isValid = self::validatePrivateKey($privateKey);
+ $publicKey = $isValid ? self::extractPublicKeyFromPrivate($privateKey) : '';
+
+ return [
+ 'isValid' => $isValid,
+ 'publicKey' => $publicKey,
+ ];
+ }
+
+ public function storeInFileSystem()
+ {
+ $filename = "ssh_key@{$this->uuid}";
+ Storage::disk('ssh-keys')->put($filename, $this->private_key);
+
+ return "/var/www/html/storage/app/ssh/keys/{$filename}";
+ }
+
+ public static function deleteFromStorage(self $privateKey)
+ {
+ $filename = "ssh_key@{$privateKey->uuid}";
+ Storage::disk('ssh-keys')->delete($filename);
+ }
+
+ public function getKeyLocation()
+ {
+ return "/var/www/html/storage/app/ssh/keys/ssh_key@{$this->uuid}";
+ }
+
+ public function updatePrivateKey(array $data)
+ {
+ $this->update($data);
+ $this->storeInFileSystem();
+
+ return $this;
}
public function servers()
@@ -85,4 +184,53 @@ class PrivateKey extends BaseModel
{
return $this->hasMany(GitlabApp::class);
}
+
+ public function isInUse()
+ {
+ return $this->servers()->exists()
+ || $this->applications()->exists()
+ || $this->githubApps()->exists()
+ || $this->gitlabApps()->exists();
+ }
+
+ public function safeDelete()
+ {
+ if (! $this->isInUse()) {
+ $this->delete();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public static function generateFingerprint($privateKey)
+ {
+ try {
+ $key = PublicKeyLoader::load($privateKey);
+ $publicKey = $key->getPublicKey();
+
+ return $publicKey->getFingerprint('sha256');
+ } catch (\Throwable $e) {
+ return null;
+ }
+ }
+
+ private static function fingerprintExists($fingerprint, $excludeId = null)
+ {
+ $query = self::where('fingerprint', $fingerprint);
+
+ if (! is_null($excludeId)) {
+ $query->where('id', '!=', $excludeId);
+ }
+
+ return $query->exists();
+ }
+
+ public static function cleanupUnusedKeys()
+ {
+ self::ownedByCurrentTeam()->each(function ($privateKey) {
+ $privateKey->safeDelete();
+ });
+ }
}
diff --git a/app/Models/Project.php b/app/Models/Project.php
index 18481751c..5a9dd964a 100644
--- a/app/Models/Project.php
+++ b/app/Models/Project.php
@@ -24,9 +24,11 @@ class Project extends BaseModel
{
protected $guarded = [];
+ protected $appends = ['default_environment'];
+
public static function ownedByCurrentTeam()
{
- return Project::whereTeamId(currentTeam()->id)->orderBy('name');
+ return Project::whereTeamId(currentTeam()->id)->orderByRaw('LOWER(name)');
}
protected static function booted()
@@ -131,7 +133,7 @@ class Project extends BaseModel
return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get());
}
- public function default_environment()
+ public function getDefaultEnvironmentAttribute()
{
$default = $this->environments()->where('name', 'production')->first();
if ($default) {
diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php
index 4c7faaa6f..a432a6e9c 100644
--- a/app/Models/S3Storage.php
+++ b/app/Models/S3Storage.php
@@ -40,6 +40,16 @@ class S3Storage extends BaseModel
return "{$this->endpoint}/{$this->bucket}";
}
+ public function isHetzner()
+ {
+ return str($this->endpoint)->contains('your-objectstorage.com');
+ }
+
+ public function isDigitalOcean()
+ {
+ return str($this->endpoint)->contains('digitaloceanspaces.com');
+ }
+
public function testConnection(bool $shouldSave = false)
{
try {
diff --git a/app/Models/ScheduledDatabaseBackup.php b/app/Models/ScheduledDatabaseBackup.php
index 50a0c8173..3921e32e4 100644
--- a/app/Models/ScheduledDatabaseBackup.php
+++ b/app/Models/ScheduledDatabaseBackup.php
@@ -35,14 +35,23 @@ class ScheduledDatabaseBackup extends BaseModel
{
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
}
+
public function server()
{
if ($this->database) {
- if ($this->database->destination && $this->database->destination->server) {
- $server = $this->database->destination->server;
+ if ($this->database instanceof ServiceDatabase) {
+ $destination = data_get($this->database->service, 'destination');
+ $server = data_get($destination, 'server');
+ } else {
+ $destination = data_get($this->database, 'destination');
+ $server = data_get($destination, 'server');
+ }
+ if ($server) {
return $server;
}
}
+
+
return null;
}
}
diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php
index 82f0036a5..3cee5a875 100644
--- a/app/Models/ScheduledTask.php
+++ b/app/Models/ScheduledTask.php
@@ -4,8 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
-use App\Models\Service;
-use App\Models\Application;
class ScheduledTask extends BaseModel
{
@@ -37,19 +35,23 @@ class ScheduledTask extends BaseModel
if ($this->application) {
if ($this->application->destination && $this->application->destination->server) {
$server = $this->application->destination->server;
+
return $server;
}
} elseif ($this->service) {
if ($this->service->destination && $this->service->destination->server) {
$server = $this->service->destination->server;
+
return $server;
}
} elseif ($this->database) {
if ($this->database->destination && $this->database->destination->server) {
$server = $this->database->destination->server;
+
return $server;
}
}
+
return null;
}
}
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 65d70083f..2468fc2b4 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -5,9 +5,10 @@ namespace App\Models;
use App\Actions\Server\InstallDocker;
use App\Enums\ProxyTypes;
use App\Jobs\PullSentinelImageJob;
-use App\Notifications\Server\Revived;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process;
@@ -37,12 +38,14 @@ use Symfony\Component\Yaml\Yaml;
'validation_logs' => ['type' => 'string'],
'log_drain_notification_sent' => ['type' => 'boolean'],
'swarm_cluster' => ['type' => 'string'],
+ 'delete_unused_volumes' => ['type' => 'boolean'],
+ 'delete_unused_networks' => ['type' => 'boolean'],
]
)]
class Server extends BaseModel
{
- use SchemalessAttributesTrait;
+ use SchemalessAttributesTrait,SoftDeletes;
public static $batch_counter = 0;
@@ -94,7 +97,8 @@ class Server extends BaseModel
}
}
});
- static::deleting(function ($server) {
+
+ static::forceDeleting(function ($server) {
$server->destinations()->each(function ($destination) {
$destination->delete();
});
@@ -106,6 +110,8 @@ class Server extends BaseModel
'proxy' => SchemalessAttributes::class,
'logdrain_axiom_api_key' => 'encrypted',
'logdrain_newrelic_license_key' => 'encrypted',
+ 'delete_unused_volumes' => 'boolean',
+ 'delete_unused_networks' => 'boolean',
];
protected $schemalessAttributes = [
@@ -156,24 +162,29 @@ class Server extends BaseModel
return $this->hasOne(ServerSetting::class);
}
+ public function proxySet()
+ {
+ return $this->proxyType() && $this->proxyType() !== 'NONE' && $this->isFunctional() && ! $this->isSwarmWorker() && ! $this->settings->is_build_server;
+ }
+
public function setupDefault404Redirect()
{
- $dynamic_conf_path = $this->proxyPath().'/dynamic';
+ $dynamic_conf_path = $this->proxyPath() . '/dynamic';
$proxy_type = $this->proxyType();
$redirect_url = $this->proxy->redirect_url;
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
- } elseif ($proxy_type === 'CADDY') {
+ } elseif ($proxy_type === ProxyTypes::CADDY->value) {
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
}
if (empty($redirect_url)) {
- if ($proxy_type === 'CADDY') {
+ if ($proxy_type === ProxyTypes::CADDY->value) {
$conf = ':80, :443 {
respond 404
}';
$conf =
- "# This file is automatically generated by Coolify.\n".
- "# Do not edit it manually (only if you know what are you doing).\n\n".
+ "# This file is automatically generated by Coolify.\n" .
+ "# Do not edit it manually (only if you know what are you doing).\n\n" .
$conf;
$base64 = base64_encode($conf);
instant_remote_process([
@@ -201,10 +212,13 @@ respond 404
1 => 'https',
],
'service' => 'noop',
- 'rule' => 'HostRegexp(`{catchall:.*}`)',
+ 'rule' => 'HostRegexp(`.+`)',
+ 'tls' => [
+ 'certResolver' => 'letsencrypt',
+ ],
'priority' => 1,
'middlewares' => [
- 0 => 'redirect-regexp@file',
+ 0 => 'redirect-regexp',
],
],
],
@@ -232,18 +246,18 @@ respond 404
];
$conf = Yaml::dump($dynamic_conf, 12, 2);
$conf =
- "# This file is automatically generated by Coolify.\n".
- "# Do not edit it manually (only if you know what are you doing).\n\n".
+ "# This file is automatically generated by Coolify.\n" .
+ "# Do not edit it manually (only if you know what are you doing).\n\n" .
$conf;
$base64 = base64_encode($conf);
- } elseif ($proxy_type === 'CADDY') {
+ } elseif ($proxy_type === ProxyTypes::CADDY->value) {
$conf = ":80, :443 {
redir $redirect_url
}";
$conf =
- "# This file is automatically generated by Coolify.\n".
- "# Do not edit it manually (only if you know what are you doing).\n\n".
+ "# This file is automatically generated by Coolify.\n" .
+ "# Do not edit it manually (only if you know what are you doing).\n\n" .
$conf;
$base64 = base64_encode($conf);
}
@@ -253,9 +267,6 @@ respond 404
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this);
- if (config('app.env') == 'local') {
- ray($conf);
- }
if ($proxy_type === 'CADDY') {
$this->reloadCaddy();
}
@@ -263,8 +274,8 @@ respond 404
public function setupDynamicProxyConfiguration()
{
- $settings = \App\Models\InstanceSettings::get();
- $dynamic_config_path = $this->proxyPath().'/dynamic';
+ $settings = instanceSettings();
+ $dynamic_config_path = $this->proxyPath() . '/dynamic';
if ($this->proxyType() === ProxyTypes::TRAEFIK->value) {
$file = "$dynamic_config_path/coolify.yaml";
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0) || ! $this->isLocalhost()) {
@@ -305,6 +316,13 @@ respond 404
'service' => 'coolify-realtime',
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
],
+ 'coolify-terminal-ws' => [
+ 'entryPoints' => [
+ 0 => 'http',
+ ],
+ 'service' => 'coolify-terminal',
+ 'rule' => "Host(`{$host}`) && PathPrefix(`/terminal/ws`)",
+ ],
],
'services' => [
'coolify' => [
@@ -325,6 +343,15 @@ respond 404
],
],
],
+ 'coolify-terminal' => [
+ 'loadBalancer' => [
+ 'servers' => [
+ 0 => [
+ 'url' => 'http://coolify-realtime:6002',
+ ],
+ ],
+ ],
+ ],
],
],
];
@@ -354,11 +381,21 @@ respond 404
'certresolver' => 'letsencrypt',
],
];
+ $traefik_dynamic_conf['http']['routers']['coolify-terminal-wss'] = [
+ 'entryPoints' => [
+ 0 => 'https',
+ ],
+ 'service' => 'coolify-terminal',
+ 'rule' => "Host(`{$host}`) && PathPrefix(`/terminal/ws`)",
+ 'tls' => [
+ 'certresolver' => 'letsencrypt',
+ ],
+ ];
}
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
$yaml =
- "# This file is automatically generated by Coolify.\n".
- "# Do not edit it manually (only if you know what are you doing).\n\n".
+ "# This file is automatically generated by Coolify.\n" .
+ "# Do not edit it manually (only if you know what are you doing).\n\n" .
$yaml;
$base64 = base64_encode($yaml);
@@ -387,6 +424,9 @@ $schema://$host {
handle /app/* {
reverse_proxy coolify-realtime:6001
}
+ handle /terminal/ws {
+ reverse_proxy coolify-realtime:6002
+ }
reverse_proxy coolify:80
}";
$base64 = base64_encode($caddy_file);
@@ -414,11 +454,19 @@ $schema://$host {
// Should move everything except /caddy and /nginx to /traefik
// The code needs to be modified as well, so maybe it does not worth it
if ($proxyType === ProxyTypes::TRAEFIK->value) {
- $proxy_path = $proxy_path;
+ // Do nothing
} elseif ($proxyType === ProxyTypes::CADDY->value) {
- $proxy_path = $proxy_path.'/caddy';
+ if (isDev()) {
+ $proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy';
+ } else {
+ $proxy_path = $proxy_path . '/caddy';
+ }
} elseif ($proxyType === ProxyTypes::NGINX->value) {
- $proxy_path = $proxy_path.'/nginx';
+ if (isDev()) {
+ $proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx';
+ } else {
+ $proxy_path = $proxy_path . '/nginx';
+ }
}
return $proxy_path;
@@ -426,15 +474,6 @@ $schema://$host {
public function proxyType()
{
- // $proxyType = $this->proxy->get('type');
- // if ($proxyType === ProxyTypes::NONE->value) {
- // return $proxyType;
- // }
- // if (is_null($proxyType)) {
- // $this->proxy->type = ProxyTypes::TRAEFIK->value;
- // $this->proxy->status = ProxyStatus::EXITED->value;
- // $this->save();
- // }
return data_get($this->proxy, 'type');
}
@@ -489,9 +528,20 @@ $schema://$host {
Storage::disk('ssh-mux')->delete($this->muxFilename());
}
+
+ public function sentinelHeartbeat(bool $isReset = false)
+ {
+ $this->sentinel_updated_at = $isReset ? now()->subMinutes(6000) : now();
+ $this->save();
+ }
+ public function isSentinelLive()
+ {
+ return Carbon::parse($this->sentinel_updated_at)->isAfter(now()->subMinutes(4));
+ }
+
public function isSentinelEnabled()
{
- return $this->isMetricsEnabled() || $this->isServerApiEnabled();
+ return ($this->isMetricsEnabled() || $this->isServerApiEnabled()) && !$this->isBuildServer();
}
public function isMetricsEnabled()
@@ -501,7 +551,7 @@ $schema://$host {
public function isServerApiEnabled()
{
- return $this->settings->is_server_api_enabled;
+ return $this->settings->is_sentinel_enabled;
}
public function checkServerApi()
@@ -519,7 +569,6 @@ $schema://$host {
ray($process->exitCode(), $process->output(), $process->errorOutput());
throw new \Exception("Server API is not reachable on http://{$server_ip}:12172");
}
-
}
}
@@ -543,7 +592,15 @@ $schema://$host {
{
if ($this->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
+ if (isDev() && $this->id === 0) {
+ $process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/cpu/history?from=$from");
+ if ($process->failed()) {
+ throw new \Exception($process->errorOutput());
+ }
+ $cpu = $process->output();
+ } else {
+ $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
+ }
if (str($cpu)->contains('error')) {
$error = json_decode($cpu, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -552,17 +609,12 @@ $schema://$host {
}
throw new \Exception($error);
}
- $cpu = str($cpu)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($cpu)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 0);
-
- return [(int) $time, (float) $cpu_usage_percent];
- });
+ $cpu = json_decode($cpu, true);
+ $parsedCollection = collect($cpu)->map(function ($metric) {
+ return [(int)$metric['time'], (float)$metric['percent']];
});
+ return $parsedCollection;
- return $parsedCollection->toArray();
}
}
@@ -570,7 +622,15 @@ $schema://$host {
{
if ($this->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
+ if (isDev() && $this->id === 0) {
+ $process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/memory/history?from=$from");
+ if ($process->failed()) {
+ throw new \Exception($process->errorOutput());
+ }
+ $memory = $process->output();
+ } else {
+ $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
+ }
if (str($memory)->contains('error')) {
$error = json_decode($memory, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -579,14 +639,9 @@ $schema://$host {
}
throw new \Exception($error);
}
- $memory = str($memory)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($memory)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $used, $free, $usedPercent] = explode(',', trim($line));
- $usedPercent = number_format($usedPercent, 0);
-
- return [(int) $time, (float) $usedPercent];
- });
+ $memory = json_decode($memory, true);
+ $parsedCollection = collect($memory)->map(function ($metric) {
+ return [(int)$metric['time'], (float)$metric['usedPercent']];
});
return $parsedCollection->toArray();
@@ -746,6 +801,18 @@ $schema://$host {
}
}
+ public function loadAllContainers(): Collection
+ {
+ if ($this->isFunctional()) {
+ $containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
+ $containers = format_docker_command_output_to_json($containers);
+
+ return collect($containers);
+ }
+
+ return collect([]);
+ }
+
public function loadUnmanagedContainers(): Collection
{
if ($this->isFunctional()) {
@@ -792,9 +859,9 @@ $schema://$host {
$clickhouses = data_get($standaloneDocker, 'clickhouses', collect([]));
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs)->concat($keydbs)->concat($dragonflies)->concat($clickhouses);
- })->filter(function ($item) {
+ })->flatten()->filter(function ($item) {
return data_get($item, 'name') !== 'coolify-db';
- })->flatten();
+ });
}
public function applications()
@@ -838,6 +905,35 @@ $schema://$host {
return $this->hasMany(Service::class);
}
+ public function port(): Attribute
+ {
+ return Attribute::make(
+ get: function ($value) {
+ return preg_replace('/[^0-9]/', '', $value);
+ }
+ );
+ }
+
+ public function user(): Attribute
+ {
+ return Attribute::make(
+ get: function ($value) {
+ $sanitizedValue = preg_replace('/[^A-Za-z0-9\-_]/', '', $value);
+
+ return $sanitizedValue;
+ }
+ );
+ }
+
+ public function ip(): Attribute
+ {
+ return Attribute::make(
+ get: function ($value) {
+ return preg_replace('/[^0-9a-zA-Z.:%-]/', '', $value);
+ }
+ );
+ }
+
public function getIp(): Attribute
{
return Attribute::make(
@@ -900,7 +996,8 @@ $schema://$host {
public function isProxyShouldRun()
{
- if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
+ // TODO: Do we need "|| $this->proxy->force_stop" here?
+ if ($this->proxyType() === ProxyTypes::NONE->value || $this->isBuildServer()) {
return false;
}
@@ -910,10 +1007,9 @@ $schema://$host {
public function isFunctional()
{
$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);
+ Storage::disk('ssh-mux')->delete($this->muxFilename());
}
return $isFunctional;
@@ -965,9 +1061,41 @@ $schema://$host {
return data_get($this, 'settings.is_swarm_worker');
}
- public function validateConnection()
+ public function status(): bool
{
- config()->set('constants.ssh.mux_enabled', false);
+ ['uptime' => $uptime] = $this->validateConnection(false);
+ if ($uptime) {
+ if ($this->unreachable_notification_sent === true) {
+ $this->update(['unreachable_notification_sent' => false]);
+ }
+ } else {
+ // $this->server->team?->notify(new Unreachable($this->server));
+ foreach ($this->applications as $application) {
+ $application->update(['status' => 'exited']);
+ }
+ foreach ($this->databases as $database) {
+ $database->update(['status' => 'exited']);
+ }
+ foreach ($this->services as $service) {
+ $apps = $service->applications()->get();
+ $dbs = $service->databases()->get();
+ foreach ($apps as $app) {
+ $app->update(['status' => 'exited']);
+ }
+ foreach ($dbs as $db) {
+ $db->update(['status' => 'exited']);
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+ public function validateConnection($isManualCheck = true)
+ {
+ config()->set('constants.ssh.mux_enabled', ! $isManualCheck);
+ // ray('Manual Check: ' . ($isManualCheck ? 'true' : 'false'));
$server = Server::find($this->id);
if (! $server) {
@@ -977,7 +1105,10 @@ $schema://$host {
return ['uptime' => false, 'error' => 'Server skipped.'];
}
try {
- // EC2 does not have `uptime` command, lol
+ // Make sure the private key is stored
+ if ($server->privateKey) {
+ $server->privateKey->storeInFileSystem();
+ }
instant_remote_process(['ls /'], $server);
$server->settings()->update([
'is_reachable' => true,
@@ -986,7 +1117,6 @@ $schema://$host {
'unreachable_count' => 0,
]);
if (data_get($server, 'unreachable_notification_sent') === true) {
- // $server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]);
}
@@ -1115,4 +1245,38 @@ $schema://$host {
{
return $this->settings->is_build_server;
}
+
+ public static function createWithPrivateKey(array $data, PrivateKey $privateKey)
+ {
+ $server = new self($data);
+ $server->privateKey()->associate($privateKey);
+ $server->save();
+
+ return $server;
+ }
+
+ public function updateWithPrivateKey(array $data, ?PrivateKey $privateKey = null)
+ {
+ $this->update($data);
+ if ($privateKey) {
+ $this->privateKey()->associate($privateKey);
+ $this->save();
+ }
+
+ return $this;
+ }
+
+ public function storageCheck(): ?string
+ {
+ $commands = [
+ 'df / --output=pcent | tr -cd 0-9',
+ ];
+
+ return instant_remote_process($commands, $this, false);
+ }
+
+ public function isIpv6(): bool
+ {
+ return str($this->ip)->contains(':');
+ }
}
diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php
index c44a393b4..8ef1420e0 100644
--- a/app/Models/ServerSetting.php
+++ b/app/Models/ServerSetting.php
@@ -24,7 +24,7 @@ use OpenApi\Attributes as OA;
'is_logdrain_newrelic_enabled' => ['type' => 'boolean'],
'is_metrics_enabled' => ['type' => 'boolean'],
'is_reachable' => ['type' => 'boolean'],
- 'is_server_api_enabled' => ['type' => 'boolean'],
+ 'is_sentinel_enabled' => ['type' => 'boolean'],
'is_swarm_manager' => ['type' => 'boolean'],
'is_swarm_worker' => ['type' => 'boolean'],
'is_usable' => ['type' => 'boolean'],
@@ -35,9 +35,9 @@ use OpenApi\Attributes as OA;
'logdrain_highlight_project_id' => ['type' => 'string'],
'logdrain_newrelic_base_uri' => ['type' => 'string'],
'logdrain_newrelic_license_key' => ['type' => 'string'],
- 'metrics_history_days' => ['type' => 'integer'],
- 'metrics_refresh_rate_seconds' => ['type' => 'integer'],
- 'metrics_token' => ['type' => 'string'],
+ 'sentinel_metrics_history_days' => ['type' => 'integer'],
+ 'sentinel_metrics_refresh_rate_seconds' => ['type' => 'integer'],
+ 'sentinel_token' => ['type' => 'string'],
'docker_cleanup_frequency' => ['type' => 'string'],
'docker_cleanup_threshold' => ['type' => 'integer'],
'server_id' => ['type' => 'integer'],
@@ -53,8 +53,65 @@ class ServerSetting extends Model
protected $casts = [
'force_docker_cleanup' => 'boolean',
'docker_cleanup_threshold' => 'integer',
+ 'sentinel_token' => 'encrypted',
];
+ protected static function booted()
+ {
+ static::creating(function ($setting) {
+ try {
+ if (str($setting->sentinel_token)->isEmpty()) {
+ $setting->generateSentinelToken(save: false);
+ }
+ if (str($setting->sentinel_custom_url)->isEmpty()) {
+ $url = $setting->generateSentinelUrl(save: false);
+ if (str($url)->isEmpty()) {
+ $setting->is_sentinel_enabled = false;
+ } else {
+ $setting->is_sentinel_enabled = true;
+ }
+ }
+ } catch (\Throwable $e) {
+ loggy('Error creating server setting: ' . $e->getMessage());
+ }
+ });
+ }
+
+ public function generateSentinelToken(bool $save = true)
+ {
+ $data = [
+ 'server_uuid' => $this->server->uuid,
+ ];
+ $token = json_encode($data);
+ $encrypted = encrypt($token);
+ $this->sentinel_token = $encrypted;
+ if ($save) {
+ $this->save();
+ }
+
+ return $encrypted;
+ }
+
+ public function generateSentinelUrl(bool $save = true)
+ {
+ $domain = null;
+ $settings = InstanceSettings::get();
+ if ($this->server->isLocalhost()) {
+ $domain = 'http://host.docker.internal:8000';
+ } else if ($settings->fqdn) {
+ $domain = $settings->fqdn;
+ } else if ($settings->ipv4) {
+ $domain = $settings->ipv4 . ':8000';
+ } else if ($settings->ipv6) {
+ $domain = $settings->ipv6 . ':8000';
+ }
+ $this->sentinel_custom_url = $domain;
+ if ($save) {
+ $this->save();
+ }
+ return $domain;
+ }
+
public function server()
{
return $this->belongsTo(Server::class);
diff --git a/app/Models/Service.php b/app/Models/Service.php
index d8def6663..16e11ecb6 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -6,7 +6,9 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Process\InvokedProcess;
use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
use OpenApi\Attributes as OA;
use Spatie\Url\Url;
@@ -40,7 +42,7 @@ class Service extends BaseModel
{
use HasFactory, SoftDeletes;
- private static $parserVersion = '3';
+ private static $parserVersion = '4';
protected $guarded = [];
@@ -131,15 +133,81 @@ class Service extends BaseModel
return $this->morphToMany(Tag::class, 'taggable');
}
+ public function getContainersToStop(): array
+ {
+ $containersToStop = [];
+ $applications = $this->applications()->get();
+ foreach ($applications as $application) {
+ $containersToStop[] = "{$application->name}-{$this->uuid}";
+ }
+ $dbs = $this->databases()->get();
+ foreach ($dbs as $db) {
+ $containersToStop[] = "{$db->name}-{$this->uuid}";
+ }
+
+ return $containersToStop;
+ }
+
+ public function stopContainers(array $containerNames, $server, int $timeout = 300)
+ {
+ $processes = [];
+ foreach ($containerNames as $containerName) {
+ $processes[$containerName] = $this->stopContainer($containerName, $timeout);
+ }
+
+ $startTime = time();
+ while (count($processes) > 0) {
+ $finishedProcesses = array_filter($processes, function ($process) {
+ return ! $process->running();
+ });
+ foreach (array_keys($finishedProcesses) as $containerName) {
+ unset($processes[$containerName]);
+ $this->removeContainer($containerName, $server);
+ }
+
+ if (time() - $startTime >= $timeout) {
+ $this->forceStopRemainingContainers(array_keys($processes), $server);
+ break;
+ }
+
+ usleep(100000);
+ }
+ }
+
+ public function stopContainer(string $containerName, int $timeout): InvokedProcess
+ {
+ return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
+ }
+
+ public function removeContainer(string $containerName, $server)
+ {
+ instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
+ }
+
+ public function forceStopRemainingContainers(array $containerNames, $server)
+ {
+ foreach ($containerNames as $containerName) {
+ instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
+ $this->removeContainer($containerName, $server);
+ }
+ }
+
public function delete_configurations()
{
- $server = data_get($this, 'server');
+ $server = data_get($this, 'destination.server');
$workdir = $this->workdir();
if (str($workdir)->endsWith($this->uuid)) {
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
}
}
+ public function delete_connected_networks($uuid)
+ {
+ $server = data_get($this, 'destination.server');
+ instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
+ instant_remote_process(["docker network rm {$uuid}"], $server, false);
+ }
+
public function status()
{
$applications = $this->applications;
@@ -215,9 +283,162 @@ class Service extends BaseModel
$fields = collect([]);
$applications = $this->applications()->get();
foreach ($applications as $application) {
- $image = str($application->image)->before(':')->value();
+ $image = str($application->image)->before(':');
+ if ($image->isEmpty()) {
+ continue;
+ }
switch ($image) {
- case str($image)?->contains('rabbitmq'):
+ case $image->contains('castopod'):
+ $data = collect([]);
+ $disable_https = $this->environment_variables()->where('key', 'CP_DISABLE_HTTPS')->first();
+ if ($disable_https) {
+ $data = $data->merge([
+ 'Disable HTTPS' => [
+ 'key' => 'CP_DISABLE_HTTPS',
+ 'value' => data_get($disable_https, 'value'),
+ 'rules' => 'required',
+ 'customHelper' => "If you want to use https, set this to 0. Variable name: CP_DISABLE_HTTPS",
+ ],
+ ]);
+ }
+ $fields->put('Castopod', $data->toArray());
+ break;
+ case $image->contains('label-studio'):
+ $data = collect([]);
+ $username = $this->environment_variables()->where('key', 'LABEL_STUDIO_USERNAME')->first();
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_LABELSTUDIO')->first();
+ if ($username) {
+ $data = $data->merge([
+ 'Username' => [
+ 'key' => 'LABEL_STUDIO_USERNAME',
+ 'value' => data_get($username, 'value'),
+ 'rules' => 'required',
+ ],
+ ]);
+ }
+ if ($password) {
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ $fields->put('Label Studio', $data->toArray());
+ break;
+ case $image->contains('litellm'):
+ $data = collect([]);
+ $username = $this->environment_variables()->where('key', 'SERVICE_USER_UI')->first();
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UI')->first();
+ if ($username) {
+ $data = $data->merge([
+ 'Username' => [
+ 'key' => data_get($username, 'key'),
+ 'value' => data_get($username, 'value'),
+ 'rules' => 'required',
+ ],
+ ]);
+ }
+ if ($password) {
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ $fields->put('Litellm', $data->toArray());
+ break;
+ case $image->contains('langfuse'):
+ $data = collect([]);
+ $email = $this->environment_variables()->where('key', 'LANGFUSE_INIT_USER_EMAIL')->first();
+ if ($email) {
+ $data = $data->merge([
+ 'Admin Email' => [
+ 'key' => data_get($email, 'key'),
+ 'value' => data_get($email, 'value'),
+ 'rules' => 'required|email',
+ ],
+ ]);
+ }
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_LANGFUSE')->first();
+ ray('password', $password);
+ if ($password) {
+ $data = $data->merge([
+ 'Admin Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ $fields->put('Langfuse', $data->toArray());
+ break;
+ case $image->contains('invoiceninja'):
+ $data = collect([]);
+ $email = $this->environment_variables()->where('key', 'IN_USER_EMAIL')->first();
+ $data = $data->merge([
+ 'Email' => [
+ 'key' => data_get($email, 'key'),
+ 'value' => data_get($email, 'value'),
+ 'rules' => 'required|email',
+ ],
+ ]);
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_INVOICENINJAUSER')->first();
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ $fields->put('Invoice Ninja', $data->toArray());
+ break;
+ case $image->contains('argilla'):
+ $data = collect([]);
+ $api_key = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_APIKEY')->first();
+ $data = $data->merge([
+ 'API Key' => [
+ 'key' => data_get($api_key, 'key'),
+ 'value' => data_get($api_key, 'value'),
+ 'isPassword' => true,
+ 'rules' => 'required',
+ ],
+ ]);
+ $data = $data->merge([
+ 'API Key' => [
+ 'key' => data_get($api_key, 'key'),
+ 'value' => data_get($api_key, 'value'),
+ 'isPassword' => true,
+ 'rules' => 'required',
+ ],
+ ]);
+ $username = $this->environment_variables()->where('key', 'ARGILLA_USERNAME')->first();
+ $data = $data->merge([
+ 'Username' => [
+ 'key' => data_get($username, 'key'),
+ 'value' => data_get($username, 'value'),
+ 'rules' => 'required',
+ ],
+ ]);
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ARGILLA')->first();
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ $fields->put('Argilla', $data->toArray());
+ break;
+ case $image->contains('rabbitmq'):
$data = collect([]);
$host_port = $this->environment_variables()->where('key', 'PORT')->first();
$username = $this->environment_variables()->where('key', 'SERVICE_USER_RABBITMQ')->first();
@@ -252,7 +473,7 @@ class Service extends BaseModel
}
$fields->put('RabbitMQ', $data->toArray());
break;
- case str($image)?->contains('tolgee'):
+ case $image->contains('tolgee'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
$data = $data->merge([
@@ -266,7 +487,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'SERVICE_PASSWORD_TOLGEE',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -275,7 +496,7 @@ class Service extends BaseModel
}
$fields->put('Tolgee', $data->toArray());
break;
- case str($image)?->contains('logto'):
+ case $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();
@@ -299,7 +520,7 @@ class Service extends BaseModel
}
$fields->put('Logto', $data->toArray());
break;
- case str($image)?->contains('unleash-server'):
+ case $image->contains('unleash-server'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UNLEASH')->first();
$data = $data->merge([
@@ -313,7 +534,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'SERVICE_PASSWORD_UNLEASH',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -322,7 +543,7 @@ class Service extends BaseModel
}
$fields->put('Unleash', $data->toArray());
break;
- case str($image)?->contains('grafana'):
+ case $image->contains('grafana'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
$data = $data->merge([
@@ -336,7 +557,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'GF_SECURITY_ADMIN_PASSWORD',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -345,7 +566,7 @@ class Service extends BaseModel
}
$fields->put('Grafana', $data->toArray());
break;
- case str($image)?->contains('directus'):
+ case $image->contains('directus'):
$data = collect([]);
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
@@ -371,7 +592,7 @@ class Service extends BaseModel
}
$fields->put('Directus', $data->toArray());
break;
- case str($image)?->contains('kong'):
+ case $image->contains('kong'):
$data = collect([]);
$dashboard_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
$dashboard_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
@@ -395,7 +616,7 @@ class Service extends BaseModel
]);
}
$fields->put('Supabase', $data->toArray());
- case str($image)?->contains('minio'):
+ case $image->contains('minio'):
$data = collect([]);
$console_url = $this->environment_variables()->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first();
$s3_api_url = $this->environment_variables()->where('key', 'MINIO_SERVER_URL')->first();
@@ -448,7 +669,7 @@ class Service extends BaseModel
$fields->put('MinIO', $data->toArray());
break;
- case str($image)?->contains('weblate'):
+ case $image->contains('weblate'):
$data = collect([]);
$admin_email = $this->environment_variables()->where('key', 'WEBLATE_ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_WEBLATE')->first();
@@ -474,7 +695,7 @@ class Service extends BaseModel
}
$fields->put('Weblate', $data->toArray());
break;
- case str($image)?->contains('meilisearch'):
+ case $image->contains('meilisearch'):
$data = collect([]);
$SERVICE_PASSWORD_MEILISEARCH = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_MEILISEARCH')->first();
if ($SERVICE_PASSWORD_MEILISEARCH) {
@@ -488,7 +709,7 @@ class Service extends BaseModel
}
$fields->put('Meilisearch', $data->toArray());
break;
- case str($image)?->contains('ghost'):
+ case $image->contains('ghost'):
$data = collect([]);
$MAIL_OPTIONS_AUTH_PASS = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_PASS')->first();
$MAIL_OPTIONS_AUTH_USER = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_USER')->first();
@@ -548,45 +769,8 @@ class Service extends BaseModel
$fields->put('Ghost', $data->toArray());
break;
- default:
- $data = collect([]);
- $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
- // Chaskiq
- $admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
- $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
- if ($admin_user) {
- $data = $data->merge([
- 'User' => [
- 'key' => 'SERVICE_USER_ADMIN',
- 'value' => data_get($admin_user, 'value', 'admin'),
- 'readonly' => true,
- 'rules' => 'required',
- ],
- ]);
- }
- if ($admin_password) {
- $data = $data->merge([
- 'Password' => [
- 'key' => 'SERVICE_PASSWORD_ADMIN',
- 'value' => data_get($admin_password, 'value'),
- 'rules' => 'required',
- 'isPassword' => true,
- ],
- ]);
- }
- if ($admin_email) {
- $data = $data->merge([
- 'Email' => [
- 'key' => 'ADMIN_EMAIL',
- 'value' => data_get($admin_email, 'value'),
- 'rules' => 'required|email',
- ],
- ]);
- }
- $fields->put('Admin', $data->toArray());
- break;
- case str($image)?->contains('vaultwarden'):
+ case $image->contains('vaultwarden'):
$data = collect([]);
$DATABASE_URL = $this->environment_variables()->where('key', 'DATABASE_URL')->first();
@@ -652,7 +836,7 @@ class Service extends BaseModel
$fields->put('Vaultwarden', $data);
break;
- case str($image)->contains('gitlab/gitlab'):
+ case $image->contains('gitlab/gitlab'):
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GITLAB')->first();
$data = collect([]);
if ($password) {
@@ -676,7 +860,7 @@ class Service extends BaseModel
$fields->put('GitLab', $data->toArray());
break;
- case str($image)->contains('code-server'):
+ case $image->contains('code-server'):
$data = collect([]);
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_PASSWORDCODESERVER')->first();
if ($password) {
@@ -702,14 +886,78 @@ class Service extends BaseModel
}
$fields->put('Code Server', $data->toArray());
break;
+ case $image->contains('elestio/strapi'):
+ $data = collect([]);
+ $license = $this->environment_variables()->where('key', 'STRAPI_LICENSE')->first();
+ if ($license) {
+ $data = $data->merge([
+ 'License' => [
+ 'key' => data_get($license, 'key'),
+ 'value' => data_get($license, 'value'),
+ ],
+ ]);
+ }
+ $nodeEnv = $this->environment_variables()->where('key', 'NODE_ENV')->first();
+ if ($nodeEnv) {
+ $data = $data->merge([
+ 'Node Environment' => [
+ 'key' => data_get($nodeEnv, 'key'),
+ 'value' => data_get($nodeEnv, 'value'),
+ ],
+ ]);
+ }
+
+ $fields->put('Strapi', $data->toArray());
+ break;
+ default:
+ $data = collect([]);
+ $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
+ // Chaskiq
+ $admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
+
+ $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
+ if ($admin_user) {
+ $data = $data->merge([
+ 'User' => [
+ 'key' => data_get($admin_user, 'key'),
+ 'value' => data_get($admin_user, 'value', 'admin'),
+ 'readonly' => true,
+ 'rules' => 'required',
+ ],
+ ]);
+ }
+ if ($admin_password) {
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($admin_password, 'key'),
+ 'value' => data_get($admin_password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ if ($admin_email) {
+ $data = $data->merge([
+ 'Email' => [
+ 'key' => data_get($admin_email, 'key'),
+ 'value' => data_get($admin_email, 'value'),
+ 'rules' => 'required|email',
+ ],
+ ]);
+ }
+ $fields->put('Admin', $data->toArray());
+ break;
}
}
$databases = $this->databases()->get();
foreach ($databases as $database) {
- $image = str($database->image)->before(':')->value();
+ $image = str($database->image)->before(':');
+ if ($image->isEmpty()) {
+ continue;
+ }
switch ($image) {
- case str($image)->contains('postgres'):
+ case $image->contains('postgres'):
$userVariables = ['SERVICE_USER_POSTGRES', 'SERVICE_USER_POSTGRESQL'];
$passwordVariables = ['SERVICE_PASSWORD_POSTGRES', 'SERVICE_PASSWORD_POSTGRESQL'];
$dbNameVariables = ['POSTGRESQL_DATABASE', 'POSTGRES_DB'];
@@ -747,10 +995,10 @@ class Service extends BaseModel
}
$fields->put('PostgreSQL', $data->toArray());
break;
- case str($image)->contains('mysql'):
+ case $image->contains('mysql'):
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
- $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD'];
- $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT'];
+ $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD','SERVICE_PASSWORD_64_MYSQL'];
+ $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT','SERVICE_PASSWORD_64_MYSQLROOT'];
$dbNameVariables = ['MYSQL_DATABASE'];
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
$mysql_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
@@ -797,7 +1045,7 @@ class Service extends BaseModel
}
$fields->put('MySQL', $data->toArray());
break;
- case str($image)->contains('mariadb'):
+ case $image->contains('mariadb'):
$userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER', 'SERVICE_USER_MYSQL', 'MYSQL_USER'];
$passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD'];
$rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD'];
@@ -860,6 +1108,7 @@ class Service extends BaseModel
foreach ($fields as $field) {
$key = data_get($field, 'key');
$value = data_get($field, 'value');
+ ray($key, $value);
$found = $this->environment_variables()->where('key', $key)->first();
if ($found) {
$found->value = $value;
@@ -983,13 +1232,12 @@ class Service extends BaseModel
public function environment_variables(): HasMany
{
-
- return $this->hasMany(EnvironmentVariable::class)->orderByRaw("key LIKE 'SERVICE%' DESC, value ASC");
+ return $this->hasMany(EnvironmentVariable::class)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC");
}
public function environment_variables_preview(): HasMany
{
- return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc');
+ return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC");
}
public function workdir()
@@ -1027,7 +1275,21 @@ class Service extends BaseModel
return 3;
});
foreach ($sorted as $env) {
- $commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
+ if (version_compare($env->version, '4.0.0-beta.347', '<=')) {
+ $commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
+ } else {
+ $real_value = $env->real_value;
+ if ($env->version === '4.0.0-beta.239') {
+ $real_value = $env->real_value;
+ } else {
+ if ($env->is_literal || $env->is_multiline) {
+ $real_value = '\''.$real_value.'\'';
+ } else {
+ $real_value = escapeEnvVariables($env->real_value);
+ }
+ }
+ $commands[] = "echo \"{$env->key}={$real_value}\" >> .env";
+ }
}
if ($sorted->count() === 0) {
$commands[] = 'touch .env';
@@ -1037,7 +1299,7 @@ class Service extends BaseModel
public function parse(bool $isNew = false): Collection
{
- if ($this->compose_parsing_version === '3') {
+ if ((int) $this->compose_parsing_version >= 3) {
return newParser($this);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile($this, $isNew);
@@ -1053,4 +1315,20 @@ class Service extends BaseModel
return $networks;
}
+
+ protected function isDeployable(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ $envs = $this->environment_variables()->where('is_required', true)->get();
+ foreach ($envs as $env) {
+ if ($env->is_really_required) {
+ return false;
+ }
+ }
+ return true;
+ }
+ );
+ }
+
}
diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php
index 6690f254e..0e79e1e2e 100644
--- a/app/Models/ServiceApplication.php
+++ b/app/Models/ServiceApplication.php
@@ -32,6 +32,16 @@ class ServiceApplication extends BaseModel
return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
}
+ public function isRunning()
+ {
+ return str($this->status)->contains('running');
+ }
+
+ public function isExited()
+ {
+ return str($this->status)->contains('exited');
+ }
+
public function isLogDrainEnabled()
{
return data_get($this, 'is_log_drain_enabled', false);
@@ -102,4 +112,9 @@ class ServiceApplication extends BaseModel
{
getFilesystemVolumesFromServer($this, $isInit);
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
+ }
}
diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php
index 4a749913e..927527118 100644
--- a/app/Models/ServiceDatabase.php
+++ b/app/Models/ServiceDatabase.php
@@ -25,6 +25,16 @@ class ServiceDatabase extends BaseModel
remote_process(["docker restart {$container_id}"], $this->service->server);
}
+ public function isRunning()
+ {
+ return str($this->status)->contains('running');
+ }
+
+ public function isExited()
+ {
+ return str($this->status)->contains('exited');
+ }
+
public function isLogDrainEnabled()
{
return data_get($this, 'is_log_drain_enabled', false);
@@ -105,4 +115,13 @@ class ServiceDatabase extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return str($this->databaseType())->contains('mysql') ||
+ str($this->databaseType())->contains('postgres') ||
+ str($this->databaseType())->contains('postgis') ||
+ str($this->databaseType())->contains('mariadb') ||
+ str($this->databaseType())->contains('mongodb');
+ }
}
diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php
index 4cd194cd8..6274f51b2 100644
--- a/app/Models/StandaloneClickhouse.php
+++ b/app/Models/StandaloneClickhouse.php
@@ -75,6 +75,11 @@ class StandaloneClickhouse extends BaseModel
}
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->contains('running');
+ }
+
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
@@ -267,7 +272,7 @@ class StandaloneClickhouse extends BaseModel
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -289,4 +294,9 @@ class StandaloneClickhouse extends BaseModel
return $parsedCollection->toArray();
}
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
+ }
}
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index 8726b2546..3555e7afd 100644
--- a/app/Models/StandaloneDragonfly.php
+++ b/app/Models/StandaloneDragonfly.php
@@ -75,6 +75,11 @@ class StandaloneDragonfly extends BaseModel
}
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->contains('running');
+ }
+
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
@@ -267,7 +272,7 @@ class StandaloneDragonfly extends BaseModel
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -289,4 +294,9 @@ class StandaloneDragonfly extends BaseModel
return $parsedCollection->toArray();
}
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
+ }
}
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index 7ecb00348..4725ca533 100644
--- a/app/Models/StandaloneKeydb.php
+++ b/app/Models/StandaloneKeydb.php
@@ -75,6 +75,11 @@ class StandaloneKeydb extends BaseModel
}
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->contains('running');
+ }
+
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
@@ -267,7 +272,7 @@ class StandaloneKeydb extends BaseModel
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -289,4 +294,9 @@ class StandaloneKeydb extends BaseModel
return $parsedCollection->toArray();
}
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
+ }
}
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index d88653e41..8f1a2c1ee 100644
--- a/app/Models/StandaloneMariadb.php
+++ b/app/Models/StandaloneMariadb.php
@@ -75,6 +75,11 @@ class StandaloneMariadb extends BaseModel
}
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->contains('running');
+ }
+
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
@@ -267,7 +272,7 @@ class StandaloneMariadb extends BaseModel
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -289,4 +294,9 @@ class StandaloneMariadb extends BaseModel
return $parsedCollection->toArray();
}
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return true;
+ }
}
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index f09e932bf..41b2ce9eb 100644
--- a/app/Models/StandaloneMongodb.php
+++ b/app/Models/StandaloneMongodb.php
@@ -79,6 +79,11 @@ class StandaloneMongodb extends BaseModel
}
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->contains('running');
+ }
+
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
@@ -287,7 +292,7 @@ class StandaloneMongodb extends BaseModel
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -309,4 +314,9 @@ class StandaloneMongodb extends BaseModel
return $parsedCollection->toArray();
}
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return true;
+ }
}
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index f4e56fab2..da2ac070f 100644
--- a/app/Models/StandaloneMysql.php
+++ b/app/Models/StandaloneMysql.php
@@ -76,6 +76,11 @@ class StandaloneMysql extends BaseModel
}
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->contains('running');
+ }
+
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
@@ -268,7 +273,7 @@ class StandaloneMysql extends BaseModel
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -290,4 +295,9 @@ class StandaloneMysql extends BaseModel
return $parsedCollection->toArray();
}
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return true;
+ }
}
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index 311c09c36..e0f42269d 100644
--- a/app/Models/StandalonePostgresql.php
+++ b/app/Models/StandalonePostgresql.php
@@ -102,6 +102,11 @@ class StandalonePostgresql extends BaseModel
}
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->contains('running');
+ }
+
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
@@ -269,7 +274,7 @@ class StandalonePostgresql extends BaseModel
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -291,4 +296,9 @@ class StandalonePostgresql extends BaseModel
return $parsedCollection->toArray();
}
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return true;
+ }
}
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index 8a202ea9e..fe9f6dfc7 100644
--- a/app/Models/StandaloneRedis.php
+++ b/app/Models/StandaloneRedis.php
@@ -71,6 +71,11 @@ class StandaloneRedis extends BaseModel
}
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->contains('running');
+ }
+
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
@@ -263,7 +268,7 @@ class StandaloneRedis extends BaseModel
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
@@ -285,4 +290,9 @@ class StandaloneRedis extends BaseModel
return $parsedCollection->toArray();
}
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
+ }
}
diff --git a/app/Notifications/Channels/TransactionalEmailChannel.php b/app/Notifications/Channels/TransactionalEmailChannel.php
index 549fc6cd3..cc7d76ebf 100644
--- a/app/Notifications/Channels/TransactionalEmailChannel.php
+++ b/app/Notifications/Channels/TransactionalEmailChannel.php
@@ -13,7 +13,7 @@ class TransactionalEmailChannel
{
public function send(User $notifiable, Notification $notification): void
{
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) {
Log::info('SMTP/Resend not enabled');
diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php
index c0e2a3c31..6377f2f15 100644
--- a/app/Notifications/Server/ForceDisabled.php
+++ b/app/Notifications/Server/ForceDisabled.php
@@ -52,7 +52,7 @@ class ForceDisabled extends Notification implements ShouldQueue
public function toDiscord(): string
{
- $message = "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions).";
+ $message = "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).";
return $message;
}
@@ -60,7 +60,7 @@ class ForceDisabled extends Notification implements ShouldQueue
public function toTelegram(): array
{
return [
- 'message' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions).",
+ 'message' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).",
];
}
}
diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php
index 8b1c02d39..3938a8da7 100644
--- a/app/Notifications/TransactionalEmails/ResetPassword.php
+++ b/app/Notifications/TransactionalEmails/ResetPassword.php
@@ -18,7 +18,7 @@ class ResetPassword extends Notification
public function __construct($token)
{
- $this->settings = \App\Models\InstanceSettings::get();
+ $this->settings = instanceSettings();
$this->token = $token;
}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index cd90918ad..8b4c2eef2 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -2,10 +2,8 @@
namespace App\Providers;
-use App\Models\InstanceSettings;
use App\Models\PersonalAccessToken;
use Illuminate\Support\Facades\Http;
-use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum;
@@ -30,9 +28,5 @@ class AppServiceProvider extends ServiceProvider
])->baseUrl($api_url);
}
});
- // if (! env('CI')) {
- // View::share('instanceSettings', InstanceSettings::get());
- // }
-
}
}
diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php
index 53a2e9281..b916b6234 100644
--- a/app/Providers/FortifyServiceProvider.php
+++ b/app/Providers/FortifyServiceProvider.php
@@ -46,7 +46,7 @@ class FortifyServiceProvider extends ServiceProvider
Fortify::registerView(function () {
$isFirstUser = User::count() === 0;
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if (! $settings->is_registration_enabled) {
return redirect()->route('login');
}
@@ -60,7 +60,7 @@ class FortifyServiceProvider extends ServiceProvider
});
Fortify::loginView(function () {
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
$enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
$users = User::count();
if ($users == 0) {
diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php
index 9b58882eb..f8ccee9db 100644
--- a/app/Traits/ExecuteRemoteCommand.php
+++ b/app/Traits/ExecuteRemoteCommand.php
@@ -3,6 +3,7 @@
namespace App\Traits;
use App\Enums\ApplicationDeploymentStatus;
+use App\Helpers\SshMultiplexingHelper;
use App\Models\Server;
use Carbon\Carbon;
use Illuminate\Support\Collection;
@@ -42,7 +43,7 @@ trait ExecuteRemoteCommand
$command = parseLineForSudo($command, $this->server);
}
}
- $remote_command = generateSshCommand($this->server, $command);
+ $remote_command = SshMultiplexingHelper::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($output)->trim();
if ($output->startsWith('╔')) {
diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php
index 6c9378cac..fbd7b0b15 100644
--- a/app/View/Components/Forms/Input.php
+++ b/app/View/Components/Forms/Input.php
@@ -22,6 +22,7 @@ class Input extends Component
public bool $allowToPeak = true,
public bool $isMultiline = false,
public string $defaultClass = 'input',
+ public string $autocomplete = 'off',
) {}
public function render(): View|Closure|string
diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php
index 8e14ef9ee..d7c16b607 100644
--- a/bootstrap/helpers/api.php
+++ b/bootstrap/helpers/api.php
@@ -2,6 +2,7 @@
use App\Enums\BuildPackTypes;
use App\Enums\RedirectTypes;
+use App\Enums\StaticImageTypes;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
@@ -89,6 +90,7 @@ function sharedDataApplications()
'git_branch' => 'string',
'build_pack' => Rule::enum(BuildPackTypes::class),
'is_static' => 'boolean',
+ 'static_image' => Rule::enum(StaticImageTypes::class),
'domains' => 'string',
'redirect' => Rule::enum(RedirectTypes::class),
'git_commit_sha' => 'string',
@@ -175,4 +177,6 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
$request->offsetUnset('instant_deploy');
$request->offsetUnset('github_app_uuid');
$request->offsetUnset('private_key_uuid');
+ $request->offsetUnset('use_build_server');
+ $request->offsetUnset('is_static');
}
diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php
index 1eeec8f94..303fcab8e 100644
--- a/bootstrap/helpers/constants.php
+++ b/bootstrap/helpers/constants.php
@@ -20,12 +20,16 @@ const RESTART_MODE = 'unless-stopped';
const DATABASE_DOCKER_IMAGES = [
'bitnami/mariadb',
'bitnami/mongodb',
- 'bitnami/mysql',
- 'bitnami/postgresql',
'bitnami/redis',
'mysql',
+ 'bitnami/mysql',
+ 'mysql/mysql-server',
'mariadb',
+ 'postgis/postgis',
'postgres',
+ 'bitnami/postgresql',
+ 'supabase/postgres',
+ 'elestio/postgres',
'mongo',
'redis',
'memcached',
@@ -33,10 +37,10 @@ const DATABASE_DOCKER_IMAGES = [
'neo4j',
'influxdb',
'clickhouse/clickhouse-server',
- 'supabase/postgres',
];
const SPECIFIC_SERVICES = [
'quay.io/minio/minio',
+ 'minio/minio',
'svhd/logto',
];
diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php
index 90093deb8..397bce029 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -40,6 +40,20 @@ function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pul
return $containers;
}
+function getCurrentServiceContainerStatus(Server $server, int $id): Collection
+{
+ $containers = collect([]);
+ if (! $server->isSwarm()) {
+ $containers = instant_remote_process(["docker ps -a --filter='label=coolify.serviceId={$id}' --format '{{json .}}' "], $server);
+ $containers = format_docker_command_output_to_json($containers);
+ $containers = $containers->filter();
+
+ return $containers;
+ }
+
+ return $containers;
+}
+
function format_docker_command_output_to_json($rawOutput): Collection
{
$outputLines = explode(PHP_EOL, $rawOutput);
@@ -120,6 +134,9 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
return 'exited';
}
$container = format_docker_command_output_to_json($container);
+ if ($container->isEmpty()) {
+ return 'exited';
+ }
if ($all_data) {
return $container[0];
}
@@ -215,12 +232,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
}
if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) {
$MINIO_BROWSER_REDIRECT_URL?->update([
- 'value' => generateFqdn($server, 'console-'.$uuid),
+ 'value' => generateFqdn($server, 'console-'.$uuid, true),
]);
}
if (is_null($MINIO_SERVER_URL?->value)) {
$MINIO_SERVER_URL?->update([
- 'value' => generateFqdn($server, 'minio-'.$uuid),
+ 'value' => generateFqdn($server, 'minio-'.$uuid, true),
]);
}
$payload = collect([
@@ -308,38 +325,20 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push('traefik.http.middlewares.gzip.compress=true');
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https');
- $basic_auth = false;
- $basic_auth_middleware = null;
- $redirect = false;
- $redirect_middleware = null;
+ $middlewares_from_labels = collect([]);
if ($serviceLabels) {
- $basic_auth = $serviceLabels->contains(function ($value) {
- return str_contains($value, 'basicauth');
- });
- if ($basic_auth) {
- $basic_auth_middleware = $serviceLabels
- ->map(function ($item) {
- if (preg_match('/traefik\.http\.middlewares\.(.*?)\.basicauth\.users/', $item, $matches)) {
- return $matches[1];
- }
- })
- ->filter()
- ->first();
- }
- $redirect = $serviceLabels->contains(function ($value) {
- return str_contains($value, 'redirectregex');
- });
- if ($redirect) {
- $redirect_middleware = $serviceLabels
- ->map(function ($item) {
- if (preg_match('/traefik\.http\.middlewares\.(.*?)\.redirectregex\.regex/', $item, $matches)) {
- return $matches[1];
- }
- })
- ->filter()
- ->first();
- }
+ $middlewares_from_labels = $serviceLabels->map(function ($item) {
+ if (preg_match('/traefik\.http\.middlewares\.(.*?)(\.|$)/', $item, $matches)) {
+ return $matches[1];
+ }
+ if (preg_match('/coolify\.traefik\.middlewares=(.*)/', $item, $matches)) {
+ return explode(',', $matches[1]);
+ }
+ return null;
+ })->flatten()
+ ->filter()
+ ->unique();
}
foreach ($domains as $loop => $domain) {
try {
@@ -387,20 +386,15 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
}
if ($path !== '/') {
+ // Middleware handling
$middlewares = collect([]);
- if ($is_stripprefix_enabled && ! str($image)->contains('ghost')) {
+ if ($is_stripprefix_enabled && !str($image)->contains('ghost')) {
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
$middlewares->push("{$https_label}-stripprefix");
}
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
- if ($basic_auth && $basic_auth_middleware) {
- $middlewares->push($basic_auth_middleware);
- }
- if ($redirect && $redirect_middleware) {
- $middlewares->push($redirect_middleware);
- }
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -408,10 +402,13 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_non_www);
$middlewares->push($to_non_www_name);
}
- if ($redirect_direction === 'www' && ! str($host)->startsWith('www.')) {
+ if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) {
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
+ $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
+ $middlewares->push($middleware_name);
+ });
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@@ -420,13 +417,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$middlewares = collect([]);
if ($is_gzip_enabled) {
$middlewares->push('gzip');
- }
- if ($basic_auth && $basic_auth_middleware) {
- $middlewares->push($basic_auth_middleware);
- }
- if ($redirect && $redirect_middleware) {
- $middlewares->push($redirect_middleware);
- }
+ }
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -438,6 +429,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
+ $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
+ $middlewares->push($middleware_name);
+ });
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@@ -473,12 +467,6 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
- if ($basic_auth && $basic_auth_middleware) {
- $middlewares->push($basic_auth_middleware);
- }
- if ($redirect && $redirect_middleware) {
- $middlewares->push($redirect_middleware);
- }
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -490,6 +478,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
+ $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
+ $middlewares->push($middleware_name);
+ });
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
@@ -499,12 +490,6 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
- if ($basic_auth && $basic_auth_middleware) {
- $middlewares->push($basic_auth_middleware);
- }
- if ($redirect && $redirect_middleware) {
- $middlewares->push($redirect_middleware);
- }
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -516,6 +501,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
+ $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
+ $middlewares->push($middleware_name);
+ });
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php
index c4c15b8fe..e2693a2cd 100644
--- a/bootstrap/helpers/proxy.php
+++ b/bootstrap/helpers/proxy.php
@@ -96,6 +96,8 @@ function connectProxyToNetworks(Server $server)
"echo 'Connecting coolify-proxy to $network network...'",
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --driver overlay --attachable $network >/dev/null",
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
+ "echo 'Successfully connected coolify-proxy to $network network.'",
+ "echo 'Proxy started and configured successfully!'",
];
});
} else {
@@ -104,6 +106,8 @@ function connectProxyToNetworks(Server $server)
"echo 'Connecting coolify-proxy to $network network...'",
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
+ "echo 'Successfully connected coolify-proxy to $network network.'",
+ "echo 'Proxy started and configured successfully!'",
];
});
}
@@ -144,6 +148,7 @@ function generate_default_proxy_configuration(Server $server)
'traefik.http.routers.traefik.service=api@internal',
'traefik.http.services.traefik.loadbalancer.server.port=8080',
'coolify.managed=true',
+ 'coolify.proxy=true',
];
$config = [
'networks' => $array_of_networks->toArray(),
@@ -159,6 +164,7 @@ function generate_default_proxy_configuration(Server $server)
'ports' => [
'80:80',
'443:443',
+ '443:443/udp',
'8080:8080',
],
'healthcheck' => [
@@ -182,6 +188,7 @@ function generate_default_proxy_configuration(Server $server)
'--entryPoints.http.http2.maxConcurrentStreams=50',
'--entrypoints.https.http.encodequerysemicolons=true',
'--entryPoints.https.http2.maxConcurrentStreams=50',
+ '--entrypoints.https.http3',
'--providers.docker.exposedbydefault=false',
'--providers.file.directory=/traefik/dynamic/',
'--providers.file.watch=true',
@@ -217,7 +224,6 @@ function generate_default_proxy_configuration(Server $server)
}
} elseif ($proxy_type === 'CADDY') {
$config = [
- 'version' => '3.8',
'networks' => $array_of_networks->toArray(),
'services' => [
'caddy' => [
@@ -235,13 +241,11 @@ function generate_default_proxy_configuration(Server $server)
'ports' => [
'80:80',
'443:443',
+ '443:443/udp',
+ ],
+ 'labels' => [
+ 'coolify.managed=true',
],
- // "healthcheck" => [
- // "test" => "wget -qO- http://localhost:80|| exit 1",
- // "interval" => "4s",
- // "timeout" => "2s",
- // "retries" => 5,
- // ],
'volumes' => [
'/var/run/docker.sock:/var/run/docker.sock:ro',
"{$proxy_path}/dynamic:/dynamic",
diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php
index 4ba378e67..67b60d6b7 100644
--- a/bootstrap/helpers/remoteProcess.php
+++ b/bootstrap/helpers/remoteProcess.php
@@ -3,6 +3,7 @@
use App\Actions\CoolifyTask\PrepareCoolifyTask;
use App\Data\CoolifyTaskArgs;
use App\Enums\ActivityTypes;
+use App\Helpers\SshMultiplexingHelper;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\PrivateKey;
@@ -10,9 +11,8 @@ use App\Models\Server;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
-use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Process;
-use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Spatie\Activitylog\Contracts\Activity;
@@ -26,29 +26,28 @@ function remote_process(
$callEventOnFinish = null,
$callEventData = null
): Activity {
- if (is_null($type)) {
- $type = ActivityTypes::INLINE->value;
- }
- if ($command instanceof Collection) {
- $command = $command->toArray();
- }
+ $type = $type ?? ActivityTypes::INLINE->value;
+ $command = $command instanceof Collection ? $command->toArray() : $command;
+
if ($server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $server);
}
+
$command_string = implode("\n", $command);
- if (auth()->user()) {
- $teams = auth()->user()->teams->pluck('id');
+
+ if (Auth::check()) {
+ $teams = Auth::user()->teams->pluck('id');
if (! $teams->contains($server->team_id) && ! $teams->contains(0)) {
throw new \Exception('User is not part of the team that owns this server');
}
}
+ SshMultiplexingHelper::ensureMultiplexedConnection($server);
+
return resolve(PrepareCoolifyTask::class, [
'remoteProcessArgs' => new CoolifyTaskArgs(
server_uuid: $server->uuid,
- command: <<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");
- }
- ['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($private_key_filename, $server->privateKey->private_key);
-
- return $location;
-}
-
-function generateScpCommand(Server $server, string $source, string $dest)
-{
- $user = $server->user;
- $port = $server->port;
- $privateKeyLocation = savePrivateKeyToFs($server);
- $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');
-
- $scp_command = "timeout $timeout scp ";
- $muxEnabled = config('constants.ssh.mux_enabled', true) && config('coolify.is_windows_docker_desktop') == false;
- // ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
-
- if ($muxEnabled) {
- $muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
- $scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
- ensureMultiplexedConnection($server);
- // ray('Using SSH Multiplexing')->green();
- } else {
- // ray('Not using SSH Multiplexing')->red();
- }
-
- if (data_get($server, 'settings.is_cloudflare_tunnel')) {
- $scp_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
- }
- $scp_command .= "-i {$privateKeyLocation} "
- .'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
- .'-o PasswordAuthentication=no '
- ."-o ConnectTimeout=$connectionTimeout "
- ."-o ServerAliveInterval=$serverInterval "
- .'-o RequestTTY=no '
- .'-o LogLevel=ERROR '
- ."-P {$port} "
- ."{$source} "
- ."{$user}@{$server->ip}:{$dest}";
-
- return $scp_command;
-}
function instant_scp(string $source, string $dest, Server $server, $throwError = true)
{
- $timeout = config('constants.ssh.command_timeout');
- $scp_command = generateScpCommand($server, $source, $dest);
- $process = Process::timeout($timeout)->run($scp_command);
+ $scp_command = SshMultiplexingHelper::generateScpCommand($server, $source, $dest);
+ $process = Process::timeout(config('constants.ssh.command_timeout'))->run($scp_command);
$output = trim($process->output());
$exitCode = $process->exitCode();
if ($exitCode !== 0) {
- if (! $throwError) {
- return null;
- }
-
- return excludeCertainErrors($process->errorOutput(), $exitCode);
- }
- if ($output === 'null') {
- $output = null;
+ return $throwError ? excludeCertainErrors($process->errorOutput(), $exitCode) : null;
}
- return $output;
-}
-function generateSshCommand(Server $server, string $command)
-{
- if ($server->settings->force_disabled) {
- throw new \RuntimeException('Server is disabled.');
- }
- $user = $server->user;
- $port = $server->port;
- $privateKeyLocation = savePrivateKeyToFs($server);
- $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 ";
-
- $muxEnabled = config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false;
- // ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
- if ($muxEnabled) {
- // Always use multiplexing when enabled
- $muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
- $ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
- ensureMultiplexedConnection($server);
- // ray('Using SSH Multiplexing')->green();
- } else {
- // ray('Not using SSH Multiplexing')->red();
- }
-
- 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} "
- .'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
- .'-o PasswordAuthentication=no '
- ."-o ConnectTimeout=$connectionTimeout "
- ."-o ServerAliveInterval=$serverInterval "
- .'-o RequestTTY=no '
- .'-o LogLevel=ERROR '
- ."-p {$port} "
- ."{$user}@{$server->ip} "
- ." 'bash -se' << \\$delimiter".PHP_EOL
- .$command.PHP_EOL
- .$delimiter;
-
- return $ssh_command;
-}
-
-function ensureMultiplexedConnection(Server $server)
-{
- if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) {
- return;
- }
-
- static $ensuredConnections = [];
-
- if (isset($ensuredConnections[$server->id])) {
- if (! shouldResetMultiplexedConnection($server)) {
- // ray('Using Existing Multiplexed Connection')->green();
-
- return;
- }
- }
-
- $muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
- $checkCommand = "ssh -O check -o ControlPath=$muxSocket ";
- if (data_get($server, 'settings.is_cloudflare_tunnel')) {
- $checkCommand .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
- }
- $checkCommand .= " {$server->user}@{$server->ip}";
-
- $process = Process::run($checkCommand);
-
- if ($process->exitCode() === 0) {
- // ray('Existing Multiplexed Connection is Valid')->green();
- $ensuredConnections[$server->id] = [
- 'timestamp' => now(),
- 'muxSocket' => $muxSocket,
- ];
-
- return;
- }
-
- // ray('Establishing New Multiplexed Connection')->orange();
-
- $privateKeyLocation = savePrivateKeyToFs($server);
- $connectionTimeout = config('constants.ssh.connection_timeout');
- $serverInterval = config('constants.ssh.server_interval');
- $muxPersistTime = config('constants.ssh.mux_persist_time');
-
- $establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
-
- if (data_get($server, 'settings.is_cloudflare_tunnel')) {
- $establishCommand .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
- }
- $establishCommand .= "-i {$privateKeyLocation} "
- .'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
- .'-o PasswordAuthentication=no '
- ."-o ConnectTimeout=$connectionTimeout "
- ."-o ServerAliveInterval=$serverInterval "
- .'-o RequestTTY=no '
- .'-o LogLevel=ERROR '
- ."-p {$server->port} "
- ."{$server->user}@{$server->ip}";
-
- $establishProcess = Process::run($establishCommand);
-
- if ($establishProcess->exitCode() !== 0) {
- throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
- }
-
- $ensuredConnections[$server->id] = [
- 'timestamp' => now(),
- 'muxSocket' => $muxSocket,
- ];
-
- // ray('Established New Multiplexed Connection')->green();
-}
-
-function shouldResetMultiplexedConnection(Server $server)
-{
- if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) {
- return false;
- }
-
- static $ensuredConnections = [];
-
- if (! isset($ensuredConnections[$server->id])) {
- return true;
- }
-
- $lastEnsured = $ensuredConnections[$server->id]['timestamp'];
- $muxPersistTime = config('constants.ssh.mux_persist_time');
- $resetInterval = strtotime($muxPersistTime) - time();
-
- return $lastEnsured->addSeconds($resetInterval)->isPast();
-}
-
-function resetMultiplexedConnection(Server $server)
-{
- if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) {
- return;
- }
-
- static $ensuredConnections = [];
-
- if (isset($ensuredConnections[$server->id])) {
- $muxSocket = $ensuredConnections[$server->id]['muxSocket'];
- $closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip}";
- Process::run($closeCommand);
- unset($ensuredConnections[$server->id]);
- }
+ return $output === 'null' ? null : $output;
}
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string
{
- static $processCount = 0;
- $processCount++;
-
- $timeout = config('constants.ssh.command_timeout');
- if ($command instanceof Collection) {
- $command = $command->toArray();
- }
+ $command = $command instanceof Collection ? $command->toArray() : $command;
if ($server->isNonRoot() && ! $no_sudo) {
$command = parseCommandsByLineForSudo(collect($command), $server);
}
$command_string = implode("\n", $command);
- $start_time = microtime(true);
- $sshCommand = generateSshCommand($server, $command_string);
- $process = Process::timeout($timeout)->run($sshCommand);
- $end_time = microtime(true);
+ // $start_time = microtime(true);
+ $sshCommand = SshMultiplexingHelper::generateSshCommand($server, $command_string);
+ $process = Process::timeout(config('constants.ssh.command_timeout'))->run($sshCommand);
+ // $end_time = microtime(true);
- $execution_time = ($end_time - $start_time) * 1000; // Convert to milliseconds
+ // $execution_time = ($end_time - $start_time) * 1000; // Convert to milliseconds
// ray('SSH command execution time:', $execution_time.' ms')->orange();
$output = trim($process->output());
$exitCode = $process->exitCode();
if ($exitCode !== 0) {
- if (! $throwError) {
- return null;
- }
-
- return excludeCertainErrors($process->errorOutput(), $exitCode);
- }
- if ($output === 'null') {
- $output = null;
+ return $throwError ? excludeCertainErrors($process->errorOutput(), $exitCode) : null;
}
- return $output;
+ return $output === 'null' ? null : $output;
}
+
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)
{
$ignoredErrors = collect([
'Permission denied (publickey',
'Could not resolve hostname',
]);
- $ignored = false;
- foreach ($ignoredErrors as $ignoredError) {
- if (Str::contains($errorOutput, $ignoredError)) {
- $ignored = true;
- break;
- }
- }
+ $ignored = $ignoredErrors->contains(fn ($error) => Str::contains($errorOutput, $error));
if ($ignored) {
// TODO: Create new exception and disable in sentry
throw new \RuntimeException($errorOutput, $exitCode);
}
throw new \RuntimeException($errorOutput, $exitCode);
}
+
function decode_remote_command_output(?ApplicationDeploymentQueue $application_deployment_queue = null): Collection
{
- $application = Application::find(data_get($application_deployment_queue, 'application_id'));
- $is_debug_enabled = data_get($application, 'settings.is_debug_enabled');
if (is_null($application_deployment_queue)) {
return collect([]);
}
+ $application = Application::find(data_get($application_deployment_queue, 'application_id'));
+ $is_debug_enabled = data_get($application, 'settings.is_debug_enabled');
try {
$decoded = json_decode(
data_get($application_deployment_queue, 'logs'),
@@ -379,7 +132,8 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
if (! $is_debug_enabled) {
$formatted = $formatted->filter(fn ($i) => $i['hidden'] === false ?? false);
}
- $formatted = $formatted
+
+ return $formatted
->sortBy(fn ($i) => data_get($i, 'order'))
->map(function ($i) {
data_set($i, 'timestamp', Carbon::parse(data_get($i, 'timestamp'))->format('Y-M-d H:i:s.u'));
@@ -421,36 +175,22 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
return $deploymentLogLines;
}, collect());
-
- return $formatted;
}
+
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);
- $closeCommand = "ssh -O exit -o ControlPath=/var/www/html/storage/app/ssh/mux/{$muxFilename} {$server->user}@{$server->ip}";
- Process::run($closeCommand);
-
- 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)) {
return;
}
foreach ($private_key->servers as $server) {
- $muxFilename = $server->muxFilename();
- $closeCommand = "ssh -O exit -o ControlPath=/var/www/html/storage/app/ssh/mux/{$muxFilename} {$server->user}@{$server->ip}";
- Process::run($closeCommand);
- Storage::disk('ssh-mux')->delete($muxFilename);
+ SshMultiplexingHelper::removeMuxFile($server);
}
}
@@ -468,9 +208,8 @@ function checkRequiredCommands(Server $server)
break;
}
$commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
- if ($commandFound) {
- continue;
+ if (! $commandFound) {
+ break;
}
- break;
}
}
diff --git a/bootstrap/helpers/s3.php b/bootstrap/helpers/s3.php
index 4a2252016..2ee7bf44a 100644
--- a/bootstrap/helpers/s3.php
+++ b/bootstrap/helpers/s3.php
@@ -1,14 +1,11 @@
endpoint) {
- $is_digital_ocean = Str::contains($s3->endpoint, 'digitaloceanspaces.com');
- }
+
config()->set('filesystems.disks.custom-s3', [
'driver' => 's3',
'region' => $s3['region'],
@@ -17,7 +14,7 @@ function set_s3_target(S3Storage $s3)
'bucket' => $s3['bucket'],
'endpoint' => $s3['endpoint'],
'use_path_style_endpoint' => true,
- 'bucket_endpoint' => $is_digital_ocean,
+ 'bucket_endpoint' => $s3->isHetzner() || $s3->isDigitalOcean(),
'aws_url' => $s3->awsUrl(),
]);
}
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index 028d20f33..14f44ed47 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -126,7 +126,7 @@ function refreshSession(?Team $team = null): void
}
function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null)
{
- ray($error);
+ loggy($error);
if ($error instanceof TooManyRequestsException) {
if (isset($livewire)) {
return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
@@ -164,10 +164,10 @@ function get_route_parameters(): array
function get_latest_sentinel_version(): string
{
try {
- $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json');
+ $response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
$versions = $response->json();
- return data_get($versions, 'sentinel.version');
+ return data_get($versions, 'coolify.sentinel.version');
} catch (\Throwable $e) {
//throw $e;
ray($e->getMessage());
@@ -247,7 +247,7 @@ function is_transactional_emails_active(): bool
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
{
if (! $settings) {
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
}
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
@@ -281,7 +281,7 @@ function base_ip(): string
if (isDev()) {
return 'localhost';
}
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if ($settings->public_ipv4) {
return "$settings->public_ipv4";
}
@@ -309,7 +309,7 @@ function getFqdnWithoutPort(string $fqdn)
*/
function base_url(bool $withPort = true): string
{
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if ($settings->fqdn) {
return $settings->fqdn;
}
@@ -343,6 +343,11 @@ function isSubscribed()
{
return isSubscriptionActive() || auth()->user()->isInstanceAdmin();
}
+
+function isProduction(): bool
+{
+ return ! isDev();
+}
function isDev(): bool
{
return config('app.env') === 'local';
@@ -384,7 +389,7 @@ function send_internal_notification(string $message): void
}
function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void
{
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
$type = set_transanctional_email_settings($settings);
if (! $type) {
throw new Exception('No email settings found.');
@@ -478,7 +483,7 @@ function data_get_str($data, $key, $default = null): Stringable
return str($str);
}
-function generateFqdn(Server $server, string $random): string
+function generateFqdn(Server $server, string $random, bool $forceHttps = false): string
{
$wildcard = data_get($server, 'settings.wildcard_domain');
if (is_null($wildcard) || $wildcard === '') {
@@ -488,6 +493,9 @@ function generateFqdn(Server $server, string $random): string
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
+ if ($forceHttps) {
+ $scheme = 'https';
+ }
$finalFqdn = "$scheme://{$random}.$host$path";
return $finalFqdn;
@@ -502,12 +510,23 @@ function sslip(Server $server)
return "http://$baseIp.sslip.io";
}
+ // ipv6
+ if (str($server->ip)->contains(':')) {
+ $ipv6 = str($server->ip)->replace(':', '-');
+
+ return "http://{$ipv6}.sslip.io";
+ }
return "http://{$server->ip}.sslip.io";
}
function get_service_templates(bool $force = false): Collection
{
+ if (isDev()) {
+ $services = File::get(base_path('templates/service-templates.json'));
+
+ return collect(json_decode($services))->sortKeys();
+ }
if ($force) {
try {
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
@@ -694,7 +713,9 @@ function getTopLevelNetworks(Service|Application $resource)
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
- $topLevelNetworks->put($networkDetails, null);
+ if (is_string($networkDetails) || is_int($networkDetails)) {
+ $topLevelNetworks->put($networkDetails, null);
+ }
}
}
}
@@ -744,7 +765,9 @@ function getTopLevelNetworks(Service|Application $resource)
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
- $topLevelNetworks->put($networkDetails, null);
+ if (is_string($networkDetails) || is_int($networkDetails)) {
+ $topLevelNetworks->put($networkDetails, null);
+ }
}
}
}
@@ -786,7 +809,7 @@ function replaceLocalSource(Stringable $source, Stringable $replacedWith)
if ($source->startsWith('..')) {
$source = $source->replaceFirst('..', $replacedWith->value());
}
- if ($source->endsWith('/')) {
+ if ($source->endsWith('/') && $source->value() !== '/') {
$source = $source->replaceLast('/', '');
}
@@ -810,6 +833,31 @@ function convertToArray($collection)
return $collection;
}
+function parseCommandFromMagicEnvVariable(Str|string $key): Stringable
+{
+ $value = str($key);
+ $count = substr_count($value->value(), '_');
+ if ($count === 2) {
+ if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
+ // SERVICE_FQDN_UMAMI
+ $command = $value->after('SERVICE_')->beforeLast('_');
+ } else {
+ // SERVICE_BASE64_UMAMI
+ $command = $value->after('SERVICE_')->beforeLast('_');
+ }
+ }
+ if ($count === 3) {
+ if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
+ // SERVICE_FQDN_UMAMI_1000
+ $command = $value->after('SERVICE_')->before('_');
+ } else {
+ // SERVICE_BASE64_64_UMAMI
+ $command = $value->after('SERVICE_')->beforeLast('_');
+ }
+ }
+
+ return str($command);
+}
function parseEnvVariable(Str|string $value)
{
$value = str($value);
@@ -841,6 +889,7 @@ function parseEnvVariable(Str|string $value)
} else {
// SERVICE_BASE64_64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
+ ray($command);
}
}
}
@@ -961,7 +1010,7 @@ function validate_dns_entry(string $fqdn, Server $server)
if (str($host)->contains('sslip.io')) {
return true;
}
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
$is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled');
if (! $is_dns_validation_enabled) {
return true;
@@ -1081,7 +1130,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
if ($domainFound) {
return true;
}
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
@@ -1125,10 +1174,10 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
if ($domains->contains($naked_domain)) {
if (data_get($resource, 'uuid')) {
if ($resource->uuid !== $app->uuid) {
- throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:
{$app->name}.");
+ throw new \RuntimeException("Domain $naked_domain is already in use by another resource:
Link: {$app->name}");
}
} elseif ($domain) {
- throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:
{$app->name}.");
+ throw new \RuntimeException("Domain $naked_domain is already in use by another resource:
Link: {$app->name}");
}
}
}
@@ -1144,16 +1193,16 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
if ($domains->contains($naked_domain)) {
if (data_get($resource, 'uuid')) {
if ($resource->uuid !== $app->uuid) {
- throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:
{$app->name}.");
+ throw new \RuntimeException("Domain $naked_domain is already in use by another resource:
Link: {$app->service->name}");
}
} elseif ($domain) {
- throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:
{$app->name}.");
+ throw new \RuntimeException("Domain $naked_domain is already in use by another resource:
Link: {$app->service->name}");
}
}
}
}
if ($resource) {
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
@@ -1170,12 +1219,26 @@ 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')) {
+ if (
+ ! str(trim($line))->startsWith([
+ 'cd',
+ 'command',
+ 'echo',
+ 'true',
+ 'if',
+ 'fi',
+ ])
+ ) {
return "sudo $line";
}
+ if (str(trim($line))->startsWith('if')) {
+ return str_replace('if', 'if 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');
@@ -1183,6 +1246,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
return $line;
});
+
$commands = $commands->map(function ($line) {
$line = str($line);
if (str($line)->contains('$(')) {
@@ -1227,8 +1291,6 @@ function parseLineForSudo(string $command, Server $server): string
function get_public_ips()
{
try {
- echo "Refreshing public ips!\n";
- $settings = \App\Models\InstanceSettings::get();
[$first, $second] = Process::concurrently(function (Pool $pool) {
$pool->path(__DIR__)->command('curl -4s https://ifconfig.io');
$pool->path(__DIR__)->command('curl -6s https://ifconfig.io');
@@ -1242,8 +1304,12 @@ function get_public_ips()
return;
}
- $settings->update(['public_ipv4' => $ipv4]);
+ InstanceSettings::get()->update(['public_ipv4' => $ipv4]);
}
+ } catch (\Exception $e) {
+ echo "Error: {$e->getMessage()}\n";
+ }
+ try {
$ipv6 = $second->output();
if ($ipv6) {
$ipv6 = trim($ipv6);
@@ -1253,7 +1319,7 @@ function get_public_ips()
return;
}
- $settings->update(['public_ipv6' => $ipv6]);
+ InstanceSettings::get()->update(['public_ipv6' => $ipv6]);
}
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
@@ -1272,13 +1338,6 @@ function isAnyDeploymentInprogress()
exit(0);
}
-function generateSentinelToken()
-{
- $token = Str::random(64);
-
- return $token;
-}
-
function isBase64Encoded($strValue)
{
return base64_encode(base64_decode($strValue, true)) === $strValue;
@@ -1577,7 +1636,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
- $topLevelNetworks->put($networkDetails, null);
+ if (is_string($networkDetails) || is_int($networkDetails)) {
+ $topLevelNetworks->put($networkDetails, null);
+ }
}
}
}
@@ -2100,16 +2161,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// TODO: move this in a shared function
if (! $parsedServiceVariables->has('COOLIFY_APP_NAME')) {
- $parsedServiceVariables->put('COOLIFY_APP_NAME', $resource->name);
+ $parsedServiceVariables->put('COOLIFY_APP_NAME', "\"{$resource->name}\"");
}
if (! $parsedServiceVariables->has('COOLIFY_SERVER_IP')) {
- $parsedServiceVariables->put('COOLIFY_SERVER_IP', $resource->destination->server->ip);
+ $parsedServiceVariables->put('COOLIFY_SERVER_IP', "\"{$resource->destination->server->ip}\"");
}
if (! $parsedServiceVariables->has('COOLIFY_ENVIRONMENT_NAME')) {
- $parsedServiceVariables->put('COOLIFY_ENVIRONMENT_NAME', $resource->environment->name);
+ $parsedServiceVariables->put('COOLIFY_ENVIRONMENT_NAME', "\"{$resource->environment->name}\"");
}
if (! $parsedServiceVariables->has('COOLIFY_PROJECT_NAME')) {
- $parsedServiceVariables->put('COOLIFY_PROJECT_NAME', $resource->project()->name);
+ $parsedServiceVariables->put('COOLIFY_PROJECT_NAME', "\"{$resource->project()->name}\"");
}
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
@@ -2492,7 +2553,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
- $topLevelNetworks->put($networkDetails, null);
+ if (is_string($networkDetails) || is_int($networkDetails)) {
+ $topLevelNetworks->put($networkDetails, null);
+ }
}
}
}
@@ -2921,10 +2984,11 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
}
$parsedServices = collect([]);
- ray()->clearAll();
+ // ray()->clearAll();
$allMagicEnvironments = collect([]);
foreach ($services as $serviceName => $service) {
+ $predefinedPort = null;
$magicEnvironments = collect([]);
$image = data_get_str($service, 'image');
$environment = collect(data_get($service, 'environment', []));
@@ -2933,12 +2997,41 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
if ($isService) {
+ $containerName = "$serviceName-{$resource->uuid}";
+
+ if ($serviceName === 'registry') {
+ $tempServiceName = 'docker-registry';
+ } else {
+ $tempServiceName = $serviceName;
+ }
+ 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') {
+ $predefinedPort = '8000';
+ }
if ($isDatabase) {
- $savedService = ServiceDatabase::firstOrCreate([
- 'name' => $serviceName,
- 'image' => $image,
- 'service_id' => $resource->id,
- ]);
+ $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
+ if ($applicationFound) {
+ $savedService = $applicationFound;
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $applicationFound->name,
+ 'image' => $applicationFound->image,
+ 'service_id' => $applicationFound->service_id,
+ ]);
+ $applicationFound->delete();
+ } else {
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $serviceName,
+ 'image' => $image,
+ 'service_id' => $resource->id,
+ ]);
+ }
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -2982,7 +3075,13 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
// Get magic environments where we need to preset the FQDN
if ($key->startsWith('SERVICE_FQDN_')) {
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
- $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
+ if (substr_count(str($key)->value(), '_') === 3) {
+ $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
+ $port = $key->afterLast('_')->value();
+ } else {
+ $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
+ $port = null;
+ }
if ($isApplication) {
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
} elseif ($isService) {
@@ -2992,19 +3091,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
}
}
+
if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
$path = $value->value();
if ($path !== '/') {
$fqdn = "$fqdn$path";
}
}
+ $fqdnWithPort = $fqdn;
+ if ($port) {
+ $fqdnWithPort = "$fqdn:$port";
+ }
if ($isApplication && is_null($resource->fqdn)) {
data_forget($resource, 'environment_variables');
data_forget($resource, 'environment_variables_preview');
- $resource->fqdn = $fqdn;
+ $resource->fqdn = $fqdnWithPort;
$resource->save();
} elseif ($isService && is_null($savedService->fqdn)) {
- $savedService->fqdn = $fqdn;
+ $savedService->fqdn = $fqdnWithPort;
$savedService->save();
}
@@ -3033,12 +3137,11 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
}
$allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
-
if ($magicEnvironments->count() > 0) {
foreach ($magicEnvironments as $key => $value) {
$key = str($key);
$value = replaceVariables($value);
- $command = $key->after('SERVICE_')->before('_');
+ $command = parseCommandFromMagicEnvVariable($key);
$found = $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->first();
if ($found) {
continue;
@@ -3071,6 +3174,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} elseif ($isService) {
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
}
+ $fqdn = str($fqdn)->replace('http://', '')->replace('https://', '');
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([
'key' => $key->value(),
$nameOfId => $resource->id,
@@ -3149,12 +3253,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($serviceName === 'plausible') {
$predefinedPort = '8000';
}
+
if ($isDatabase) {
- $savedService = ServiceDatabase::firstOrCreate([
- 'name' => $serviceName,
- 'image' => $image,
- 'service_id' => $resource->id,
- ]);
+ $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
+ if ($applicationFound) {
+ $savedService = $applicationFound;
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $applicationFound->name,
+ 'image' => $applicationFound->image,
+ 'service_id' => $applicationFound->service_id,
+ ]);
+ $applicationFound->delete();
+ } else {
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $serviceName,
+ 'image' => $image,
+ 'service_id' => $resource->id,
+ ]);
+ }
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -3224,12 +3340,19 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') {
$volume = $source->value().':'.$target->value();
} else {
- $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
+ if ((int) $resource->compose_parsing_version >= 4) {
+ if ($isApplication) {
+ $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
+ } elseif ($isService) {
+ $mainDirectory = str(base_configuration_dir().'/services/'.$uuid);
+ }
+ } else {
+ $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
+ }
$source = replaceLocalSource($source, $mainDirectory);
if ($isApplication && $isPullRequest) {
$source = $source."-pr-$pullRequestId";
}
-
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
@@ -3245,6 +3368,17 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
'resource_type' => get_class($originalResource),
]
);
+ if (isDev()) {
+ if ((int) $resource->compose_parsing_version >= 4) {
+ if ($isApplication) {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
+ } elseif ($isService) {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid);
+ }
+ } else {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
+ }
+ }
$volume = "$source:$target";
}
} elseif ($type->value() === 'volume') {
@@ -3428,6 +3562,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
]);
} else {
if ($value->startsWith('$')) {
+ $isRequired = false;
if ($value->contains(':-')) {
$value = replaceVariables($value);
$key = $value->before(':');
@@ -3442,13 +3577,28 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$key = $value->before(':');
$value = $value->after(':?');
+ $isRequired = true;
} elseif ($value->contains('?')) {
$value = replaceVariables($value);
$key = $value->before('?');
$value = $value->after('?');
+ $isRequired = true;
}
if ($originalValue->value() === $value->value()) {
+ // This means the variable does not have a default value, so it needs to be created in Coolify
+ $parsedKeyValue = replaceVariables($value);
+ $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->firstOrCreate([
+ 'key' => $parsedKeyValue,
+ $nameOfId => $resource->id,
+ ], [
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ 'is_required' => $isRequired,
+ ]);
+ // Add the variable to the environment so it will be shown in the deployable compose file
+ $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->first()->value;
+
continue;
}
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([
@@ -3458,6 +3608,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
'value' => $value,
'is_build_time' => false,
'is_preview' => false,
+ 'is_required' => $isRequired,
]);
}
@@ -3469,13 +3620,13 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$branch = "pull/{$pullRequestId}/head";
}
if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
- $coolifyEnvironments->put('COOLIFY_BRANCH', $branch);
+ $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\"");
}
}
// Add COOLIFY_CONTAINER_NAME to environment
if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
- $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', $containerName);
+ $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "\"{$containerName}\"");
}
if ($isApplication) {
@@ -3541,14 +3692,37 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($environment->count() > 0) {
$environment = $environment->filter(function ($value, $key) {
return ! str($key)->startsWith('SERVICE_FQDN_');
+ })->map(function ($value, $key) use ($resource) {
+ // if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used
+ if (str($value)->isEmpty()) {
+ if ($resource->environment_variables()->where('key', $key)->exists()) {
+ $value = $resource->environment_variables()->where('key', $key)->first()->value;
+ } else {
+ $value = null;
+ }
+ }
+
+ return $value;
});
}
$serviceLabels = $labels->merge($defaultLabels);
+ if ($serviceLabels->count() > 0) {
+ if ($isApplication) {
+ $isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled');
+ } else {
+ $isContainerLabelEscapeEnabled = data_get($resource, 'is_container_label_escape_enabled');
+ }
+ if ($isContainerLabelEscapeEnabled) {
+ $serviceLabels = $serviceLabels->map(function ($value, $key) {
+ return escapeDollarSign($value);
+ });
+ }
+ }
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
if ($isApplication) {
$shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
$uuid = $resource->uuid;
- $network = $resource->destination->network;
+ $network = data_get($resource, 'destination.network');
if ($isPullRequest) {
$uuid = "{$resource->uuid}-{$pullRequestId}";
}
@@ -3558,7 +3732,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} else {
$shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels;
$uuid = $resource->uuid;
- $network = $resource->destination->network;
+ $network = data_get($resource, 'destination.network');
}
if ($shouldGenerateLabelsExactly) {
switch ($server->proxyType()) {
@@ -3625,6 +3799,14 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
data_forget($service, 'volumes.*.is_directory');
data_forget($service, 'exclude_from_hc');
+ $volumesParsed = $volumesParsed->map(function ($volume) {
+ data_forget($volume, 'content');
+ data_forget($volume, 'is_directory');
+ data_forget($volume, 'isDirectory');
+
+ return $volume;
+ });
+
$payload = collect($service)->merge([
'container_name' => $containerName,
'restart' => $restart->value(),
@@ -3655,6 +3837,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$parsedServices->put($serviceName, $payload);
}
$topLevel->put('services', $parsedServices);
+
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
@@ -3711,6 +3894,8 @@ function isAssociativeArray($array)
*/
function add_coolify_default_environment_variables(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|Application|Service $resource, Collection &$where_to_add, ?Collection $where_to_check = null)
{
+ // Currently disabled
+ return;
if ($resource instanceof Service) {
$ip = $resource->server->ip;
} else {
@@ -3723,30 +3908,30 @@ function add_coolify_default_environment_variables(StandaloneRedis|StandalonePos
}
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_APP_NAME')->isEmpty()) {
if ($isAssociativeArray) {
- $where_to_add->put('COOLIFY_APP_NAME', $resource->name);
+ $where_to_add->put('COOLIFY_APP_NAME', "\"{$resource->name}\"");
} else {
- $where_to_add->push("COOLIFY_APP_NAME={$resource->name}");
+ $where_to_add->push("COOLIFY_APP_NAME=\"{$resource->name}\"");
}
}
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_SERVER_IP')->isEmpty()) {
if ($isAssociativeArray) {
- $where_to_add->put('COOLIFY_SERVER_IP', $ip);
+ $where_to_add->put('COOLIFY_SERVER_IP', "\"{$ip}\"");
} else {
- $where_to_add->push("COOLIFY_SERVER_IP={$ip}");
+ $where_to_add->push("COOLIFY_SERVER_IP=\"{$ip}\"");
}
}
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_ENVIRONMENT_NAME')->isEmpty()) {
if ($isAssociativeArray) {
- $where_to_add->put('COOLIFY_ENVIRONMENT_NAME', $resource->environment->name);
+ $where_to_add->put('COOLIFY_ENVIRONMENT_NAME', "\"{$resource->environment->name}\"");
} else {
- $where_to_add->push("COOLIFY_ENVIRONMENT_NAME={$resource->environment->name}");
+ $where_to_add->push("COOLIFY_ENVIRONMENT_NAME=\"{$resource->environment->name}\"");
}
}
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_PROJECT_NAME')->isEmpty()) {
if ($isAssociativeArray) {
- $where_to_add->put('COOLIFY_PROJECT_NAME', $resource->project()->name);
+ $where_to_add->put('COOLIFY_PROJECT_NAME', "\"{$resource->project()->name}\"");
} else {
- $where_to_add->push("COOLIFY_PROJECT_NAME={$resource->project()->name}");
+ $where_to_add->push("COOLIFY_PROJECT_NAME=\"{$resource->project()->name}\"");
}
}
}
@@ -3755,14 +3940,37 @@ function convertComposeEnvironmentToArray($environment)
{
$convertedServiceVariables = collect([]);
if (isAssociativeArray($environment)) {
+ // Example: $environment = ['FOO' => 'bar', 'BAZ' => 'qux'];
+ if ($environment instanceof Collection) {
+ $changedEnvironment = collect([]);
+ $environment->each(function ($value, $key) use ($changedEnvironment) {
+ if (is_numeric($key)) {
+ $parts = explode('=', $value, 2);
+ if (count($parts) === 2) {
+ $key = $parts[0];
+ $realValue = $parts[1] ?? '';
+ $changedEnvironment->put($key, $realValue);
+ } else {
+ $changedEnvironment->put($key, $value);
+ }
+ } else {
+ $changedEnvironment->put($key, $value);
+ }
+ });
+
+ return $changedEnvironment;
+ }
$convertedServiceVariables = $environment;
} else {
+ // Example: $environment = ['FOO=bar', 'BAZ=qux'];
foreach ($environment as $value) {
- $parts = explode('=', $value, 2);
- $key = $parts[0];
- $realValue = $parts[1] ?? '';
- if ($key) {
- $convertedServiceVariables->put($key, $realValue);
+ if (is_string($value)) {
+ $parts = explode('=', $value, 2);
+ $key = $parts[0];
+ $realValue = $parts[1] ?? '';
+ if ($key) {
+ $convertedServiceVariables->put($key, $realValue);
+ }
}
}
}
@@ -3770,3 +3978,50 @@ function convertComposeEnvironmentToArray($environment)
return $convertedServiceVariables;
}
+function instanceSettings()
+{
+ return InstanceSettings::get();
+}
+
+function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id) {
+
+ $server = Server::find($server_id)->where('team_id', $team_id)->first();
+ if (!$server) {
+ return;
+ }
+ $uuid = new Cuid2();
+ $cloneCommand = "git clone --no-checkout -b $branch $repository .";
+ $workdir = rtrim($base_directory, '/');
+ $fileList = collect([".$workdir/coolify.json"]);
+ $commands = collect([
+ "rm -rf /tmp/{$uuid}",
+ "mkdir -p /tmp/{$uuid}",
+ "cd /tmp/{$uuid}",
+ $cloneCommand,
+ 'git sparse-checkout init --cone',
+ "git sparse-checkout set {$fileList->implode(' ')}",
+ 'git read-tree -mu HEAD',
+ "cat .$workdir/coolify.json",
+ 'rm -rf /tmp/{$uuid}',
+ ]);
+ try {
+ return instant_remote_process($commands, $server);
+ } catch (\Exception $e) {
+ // continue
+ }
+}
+
+function loggy($message = null, array $context = [])
+{
+ if (!isDev()) {
+ return;
+ }
+ if (function_exists('ray') && config('app.debug')) {
+ ray($message, $context);
+ }
+ if (is_null($message)) {
+ return app('log');
+ }
+
+ return app('log')->debug($message, $context);
+}
diff --git a/composer.json b/composer.json
index e8b46105d..b17c3bf4e 100644
--- a/composer.json
+++ b/composer.json
@@ -14,7 +14,8 @@
"guzzlehttp/guzzle": "^7.5.0",
"laravel/fortify": "^v1.16.0",
"laravel/framework": "^v11",
- "laravel/horizon": "^5.27.1",
+ "laravel/horizon": "^5.29.1",
+ "laravel/pail": "^1.1",
"laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v4.0",
"laravel/socialite": "^v5.14.0",
@@ -48,6 +49,7 @@
"zircote/swagger-php": "^4.10"
},
"require-dev": {
+ "barryvdh/laravel-debugbar": "^3.13",
"fakerphp/faker": "^v1.21.0",
"laravel/dusk": "^v8.0",
"laravel/pint": "^1.16",
@@ -84,7 +86,11 @@
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
"Illuminate\\Foundation\\ComposerScripts::postUpdate"
],
- "post-install-cmd": [],
+ "post-install-cmd": [
+ "cp -r 'hooks/' '.git/hooks/'",
+ "php -r \"copy('hooks/pre-commit', '.git/hooks/pre-commit');\"",
+ "php -r \"chmod('.git/hooks/pre-commit', 0777);\""
+ ],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
diff --git a/composer.lock b/composer.lock
index fffb320d3..981e723d4 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "96f8146407d0e6e897ff097c5eccd3a4",
+ "content-hash": "943975ec232403b96a40d215253492d8",
"packages": [
{
"name": "amphp/amp",
@@ -317,16 +317,16 @@
},
{
"name": "amphp/parallel",
- "version": "v2.2.9",
+ "version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/parallel.git",
- "reference": "73d293f1fc4df1bebc3c4fce1432e82dd7032238"
+ "reference": "9777db1460d1535bc2a843840684fb1205225b87"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/amphp/parallel/zipball/73d293f1fc4df1bebc3c4fce1432e82dd7032238",
- "reference": "73d293f1fc4df1bebc3c4fce1432e82dd7032238",
+ "url": "https://api.github.com/repos/amphp/parallel/zipball/9777db1460d1535bc2a843840684fb1205225b87",
+ "reference": "9777db1460d1535bc2a843840684fb1205225b87",
"shasum": ""
},
"require": {
@@ -389,7 +389,7 @@
],
"support": {
"issues": "https://github.com/amphp/parallel/issues",
- "source": "https://github.com/amphp/parallel/tree/v2.2.9"
+ "source": "https://github.com/amphp/parallel/tree/v2.3.0"
},
"funding": [
{
@@ -397,7 +397,7 @@
"type": "github"
}
],
- "time": "2024-03-24T18:27:44+00:00"
+ "time": "2024-09-14T19:16:14+00:00"
},
{
"name": "amphp/parser",
@@ -921,16 +921,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.321.9",
+ "version": "3.324.0",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "5de5099cfe0e17cb3eb2fe51de0101c99bc9442a"
+ "reference": "b258712f0d986e00e1143d55246b6f9e344c7184"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5de5099cfe0e17cb3eb2fe51de0101c99bc9442a",
- "reference": "5de5099cfe0e17cb3eb2fe51de0101c99bc9442a",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b258712f0d986e00e1143d55246b6f9e344c7184",
+ "reference": "b258712f0d986e00e1143d55246b6f9e344c7184",
"shasum": ""
},
"require": {
@@ -1013,22 +1013,22 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.321.9"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.324.0"
},
- "time": "2024-09-11T18:15:49+00:00"
+ "time": "2024-10-10T18:06:36+00:00"
},
{
"name": "bacon/bacon-qr-code",
- "version": "v3.0.0",
+ "version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/Bacon/BaconQrCode.git",
- "reference": "510de6eca6248d77d31b339d62437cc995e2fb41"
+ "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/510de6eca6248d77d31b339d62437cc995e2fb41",
- "reference": "510de6eca6248d77d31b339d62437cc995e2fb41",
+ "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/f9cc1f52b5a463062251d666761178dbdb6b544f",
+ "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f",
"shasum": ""
},
"require": {
@@ -1067,9 +1067,9 @@
"homepage": "https://github.com/Bacon/BaconQrCode",
"support": {
"issues": "https://github.com/Bacon/BaconQrCode/issues",
- "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.0"
+ "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.1"
},
- "time": "2024-04-18T11:16:25+00:00"
+ "time": "2024-10-01T13:55:55+00:00"
},
{
"name": "brick/math",
@@ -1518,16 +1518,16 @@
},
{
"name": "doctrine/dbal",
- "version": "3.9.1",
+ "version": "3.9.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7"
+ "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/d7dc08f98cba352b2bab5d32c5e58f7e745c11a7",
- "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba",
+ "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba",
"shasum": ""
},
"require": {
@@ -1543,7 +1543,7 @@
"doctrine/coding-standard": "12.0.0",
"fig/log-test": "^1",
"jetbrains/phpstorm-stubs": "2023.1",
- "phpstan/phpstan": "1.12.0",
+ "phpstan/phpstan": "1.12.6",
"phpstan/phpstan-strict-rules": "^1.6",
"phpunit/phpunit": "9.6.20",
"psalm/plugin-phpunit": "0.18.4",
@@ -1611,7 +1611,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
- "source": "https://github.com/doctrine/dbal/tree/3.9.1"
+ "source": "https://github.com/doctrine/dbal/tree/3.9.3"
},
"funding": [
{
@@ -1627,7 +1627,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-01T13:49:23+00:00"
+ "time": "2024-10-10T17:56:43+00:00"
},
{
"name": "doctrine/deprecations",
@@ -1937,16 +1937,16 @@
},
{
"name": "dragonmantank/cron-expression",
- "version": "v3.3.3",
+ "version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/dragonmantank/cron-expression.git",
- "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a"
+ "reference": "8c784d071debd117328803d86b2097615b457500"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a",
- "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a",
+ "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500",
+ "reference": "8c784d071debd117328803d86b2097615b457500",
"shasum": ""
},
"require": {
@@ -1959,10 +1959,14 @@
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.0",
- "phpstan/phpstan-webmozart-assert": "^1.0",
"phpunit/phpunit": "^7.0|^8.0|^9.0"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
"autoload": {
"psr-4": {
"Cron\\": "src/Cron/"
@@ -1986,7 +1990,7 @@
],
"support": {
"issues": "https://github.com/dragonmantank/cron-expression/issues",
- "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3"
+ "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0"
},
"funding": [
{
@@ -1994,7 +1998,7 @@
"type": "github"
}
],
- "time": "2023-08-10T19:36:49+00:00"
+ "time": "2024-10-09T13:47:03+00:00"
},
{
"name": "egulias/email-validator",
@@ -2789,16 +2793,16 @@
},
{
"name": "laravel/fortify",
- "version": "v1.24.1",
+ "version": "v1.24.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/fortify.git",
- "reference": "8158ba0960bb5f4aae509d01d74a95e16e30de20"
+ "reference": "42695c45087e5abb3e173725b4f1ef4956a7b47d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/fortify/zipball/8158ba0960bb5f4aae509d01d74a95e16e30de20",
- "reference": "8158ba0960bb5f4aae509d01d74a95e16e30de20",
+ "url": "https://api.github.com/repos/laravel/fortify/zipball/42695c45087e5abb3e173725b4f1ef4956a7b47d",
+ "reference": "42695c45087e5abb3e173725b4f1ef4956a7b47d",
"shasum": ""
},
"require": {
@@ -2850,20 +2854,20 @@
"issues": "https://github.com/laravel/fortify/issues",
"source": "https://github.com/laravel/fortify"
},
- "time": "2024-09-03T10:02:14+00:00"
+ "time": "2024-09-16T19:20:52+00:00"
},
{
"name": "laravel/framework",
- "version": "v11.23.2",
+ "version": "v11.27.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3"
+ "reference": "a51d1f2b771c542324a3d9b76a98b1bbc75c0ee9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3",
- "reference": "d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/a51d1f2b771c542324a3d9b76a98b1bbc75c0ee9",
+ "reference": "a51d1f2b771c542324a3d9b76a98b1bbc75c0ee9",
"shasum": ""
},
"require": {
@@ -2882,7 +2886,7 @@
"fruitcake/php-cors": "^1.3",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/uri-template": "^1.0",
- "laravel/prompts": "^0.1.18",
+ "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
"laravel/serializable-closure": "^1.3",
"league/commonmark": "^2.2.1",
"league/flysystem": "^3.8.0",
@@ -2968,7 +2972,7 @@
"league/flysystem-sftp-v3": "^3.0",
"mockery/mockery": "^1.6",
"nyholm/psr7": "^1.2",
- "orchestra/testbench-core": "^9.4.0",
+ "orchestra/testbench-core": "^9.5",
"pda/pheanstalk": "^5.0",
"phpstan/phpstan": "^1.11.5",
"phpunit/phpunit": "^10.5|^11.0",
@@ -3027,6 +3031,7 @@
"src/Illuminate/Filesystem/functions.php",
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Log/functions.php",
+ "src/Illuminate/Support/functions.php",
"src/Illuminate/Support/helpers.php"
],
"psr-4": {
@@ -3058,20 +3063,20 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2024-09-11T21:59:23+00:00"
+ "time": "2024-10-09T04:17:35+00:00"
},
{
"name": "laravel/horizon",
- "version": "v5.28.1",
+ "version": "v5.29.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/horizon.git",
- "reference": "9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f"
+ "reference": "9f482f21c23ed01c2366d1157843165165579c23"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/horizon/zipball/9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f",
- "reference": "9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f",
+ "url": "https://api.github.com/repos/laravel/horizon/zipball/9f482f21c23ed01c2366d1157843165165579c23",
+ "reference": "9f482f21c23ed01c2366d1157843165165579c23",
"shasum": ""
},
"require": {
@@ -3135,9 +3140,86 @@
],
"support": {
"issues": "https://github.com/laravel/horizon/issues",
- "source": "https://github.com/laravel/horizon/tree/v5.28.1"
+ "source": "https://github.com/laravel/horizon/tree/v5.29.1"
},
- "time": "2024-09-04T14:06:50+00:00"
+ "time": "2024-10-08T18:23:02+00:00"
+ },
+ {
+ "name": "laravel/pail",
+ "version": "v1.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/pail.git",
+ "reference": "b33ad8321416fe86efed7bf398f3306c47b4871b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/pail/zipball/b33ad8321416fe86efed7bf398f3306c47b4871b",
+ "reference": "b33ad8321416fe86efed7bf398f3306c47b4871b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "illuminate/console": "^10.24|^11.0",
+ "illuminate/contracts": "^10.24|^11.0",
+ "illuminate/log": "^10.24|^11.0",
+ "illuminate/process": "^10.24|^11.0",
+ "illuminate/support": "^10.24|^11.0",
+ "nunomaduro/termwind": "^1.15|^2.0",
+ "php": "^8.2",
+ "symfony/console": "^6.0|^7.0"
+ },
+ "require-dev": {
+ "laravel/pint": "^1.13",
+ "orchestra/testbench": "^8.12|^9.0",
+ "pestphp/pest": "^2.20",
+ "pestphp/pest-plugin-type-coverage": "^2.3",
+ "phpstan/phpstan": "^1.10",
+ "symfony/var-dumper": "^6.3|^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Laravel\\Pail\\PailServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Pail\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Nuno Maduro",
+ "email": "enunomaduro@gmail.com"
+ }
+ ],
+ "description": "Easily delve into your Laravel application's log files directly from the command line.",
+ "homepage": "https://github.com/laravel/pail",
+ "keywords": [
+ "laravel",
+ "logs",
+ "php",
+ "tail"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/pail/issues",
+ "source": "https://github.com/laravel/pail"
+ },
+ "time": "2024-10-15T20:06:24+00:00"
},
{
"name": "laravel/prompts",
@@ -3199,16 +3281,16 @@
},
{
"name": "laravel/sanctum",
- "version": "v4.0.2",
+ "version": "v4.0.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
- "reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1"
+ "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/sanctum/zipball/9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
- "reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
+ "url": "https://api.github.com/repos/laravel/sanctum/zipball/54aea9d13743ae8a6cdd3c28dbef128a17adecab",
+ "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab",
"shasum": ""
},
"require": {
@@ -3259,20 +3341,20 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
- "time": "2024-04-10T19:39:58+00:00"
+ "time": "2024-09-27T14:55:41+00:00"
},
{
"name": "laravel/serializable-closure",
- "version": "v1.3.4",
+ "version": "v1.3.5",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
- "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81"
+ "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
- "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c",
+ "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c",
"shasum": ""
},
"require": {
@@ -3320,7 +3402,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
- "time": "2024-08-02T07:48:17+00:00"
+ "time": "2024-09-23T13:33:08+00:00"
},
{
"name": "laravel/socialite",
@@ -3465,16 +3547,16 @@
},
{
"name": "laravel/tinker",
- "version": "v2.9.0",
+ "version": "v2.10.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/tinker.git",
- "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe"
+ "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe",
- "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe",
+ "url": "https://api.github.com/repos/laravel/tinker/zipball/ba4d51eb56de7711b3a37d63aa0643e99a339ae5",
+ "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5",
"shasum": ""
},
"require": {
@@ -3525,9 +3607,9 @@
],
"support": {
"issues": "https://github.com/laravel/tinker/issues",
- "source": "https://github.com/laravel/tinker/tree/v2.9.0"
+ "source": "https://github.com/laravel/tinker/tree/v2.10.0"
},
- "time": "2024-01-04T16:10:04+00:00"
+ "time": "2024-09-23T13:32:56+00:00"
},
{
"name": "laravel/ui",
@@ -3594,38 +3676,38 @@
},
{
"name": "lcobucci/jwt",
- "version": "5.3.0",
+ "version": "5.4.0",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
- "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83"
+ "reference": "aac4fd512681fd5cb4b77d2105ab7ec700c72051"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/lcobucci/jwt/zipball/08071d8d2c7f4b00222cc4b1fb6aa46990a80f83",
- "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83",
+ "url": "https://api.github.com/repos/lcobucci/jwt/zipball/aac4fd512681fd5cb4b77d2105ab7ec700c72051",
+ "reference": "aac4fd512681fd5cb4b77d2105ab7ec700c72051",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-sodium": "*",
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
+ "php": "~8.2.0 || ~8.3.0 || ~8.4.0",
"psr/clock": "^1.0"
},
"require-dev": {
- "infection/infection": "^0.27.0",
- "lcobucci/clock": "^3.0",
+ "infection/infection": "^0.29",
+ "lcobucci/clock": "^3.2",
"lcobucci/coding-standard": "^11.0",
- "phpbench/phpbench": "^1.2.9",
+ "phpbench/phpbench": "^1.2",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.10.7",
"phpstan/phpstan-deprecation-rules": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.3.10",
"phpstan/phpstan-strict-rules": "^1.5.0",
- "phpunit/phpunit": "^10.2.6"
+ "phpunit/phpunit": "^11.1"
},
"suggest": {
- "lcobucci/clock": ">= 3.0"
+ "lcobucci/clock": ">= 3.2"
},
"type": "library",
"autoload": {
@@ -3651,7 +3733,7 @@
],
"support": {
"issues": "https://github.com/lcobucci/jwt/issues",
- "source": "https://github.com/lcobucci/jwt/tree/5.3.0"
+ "source": "https://github.com/lcobucci/jwt/tree/5.4.0"
},
"funding": [
{
@@ -3663,7 +3745,7 @@
"type": "patreon"
}
],
- "time": "2024-04-11T23:07:54+00:00"
+ "time": "2024-10-08T22:06:45+00:00"
},
{
"name": "league/commonmark",
@@ -3855,16 +3937,16 @@
},
{
"name": "league/flysystem",
- "version": "3.28.0",
+ "version": "3.29.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c"
+ "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c",
- "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319",
+ "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319",
"shasum": ""
},
"require": {
@@ -3932,22 +4014,22 @@
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
- "source": "https://github.com/thephpleague/flysystem/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem/tree/3.29.1"
},
- "time": "2024-05-22T10:09:12+00:00"
+ "time": "2024-10-08T08:58:34+00:00"
},
{
"name": "league/flysystem-aws-s3-v3",
- "version": "3.28.0",
+ "version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
- "reference": "22071ef1604bc776f5ff2468ac27a752514665c8"
+ "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/22071ef1604bc776f5ff2468ac27a752514665c8",
- "reference": "22071ef1604bc776f5ff2468ac27a752514665c8",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c6ff6d4606e48249b63f269eba7fabdb584e76a9",
+ "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9",
"shasum": ""
},
"require": {
@@ -3987,22 +4069,22 @@
"storage"
],
"support": {
- "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.29.0"
},
- "time": "2024-05-06T20:05:52+00:00"
+ "time": "2024-08-17T13:10:48+00:00"
},
{
"name": "league/flysystem-local",
- "version": "3.28.0",
+ "version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-local.git",
- "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40"
+ "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40",
- "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27",
+ "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27",
"shasum": ""
},
"require": {
@@ -4036,22 +4118,22 @@
"local"
],
"support": {
- "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0"
},
- "time": "2024-05-06T20:05:52+00:00"
+ "time": "2024-08-09T21:24:39+00:00"
},
{
"name": "league/flysystem-sftp-v3",
- "version": "3.28.0",
+ "version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-sftp-v3.git",
- "reference": "abedadd3c64d4f0e276d6ecc796ec8194d136b41"
+ "reference": "ce9b209e2fbe33122c755ffc18eb4d5bd256f252"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/abedadd3c64d4f0e276d6ecc796ec8194d136b41",
- "reference": "abedadd3c64d4f0e276d6ecc796ec8194d136b41",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/ce9b209e2fbe33122c755ffc18eb4d5bd256f252",
+ "reference": "ce9b209e2fbe33122c755ffc18eb4d5bd256f252",
"shasum": ""
},
"require": {
@@ -4085,22 +4167,22 @@
"sftp"
],
"support": {
- "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.29.0"
},
- "time": "2024-05-06T20:05:52+00:00"
+ "time": "2024-08-14T19:35:54+00:00"
},
{
"name": "league/mime-type-detection",
- "version": "1.15.0",
+ "version": "1.16.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/mime-type-detection.git",
- "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301"
+ "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301",
- "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301",
+ "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9",
+ "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9",
"shasum": ""
},
"require": {
@@ -4131,7 +4213,7 @@
"description": "Mime-type detection for Flysystem",
"support": {
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
- "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0"
+ "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0"
},
"funding": [
{
@@ -4143,7 +4225,7 @@
"type": "tidelift"
}
],
- "time": "2024-01-28T23:22:08+00:00"
+ "time": "2024-09-21T08:32:55+00:00"
},
{
"name": "league/oauth1-client",
@@ -4955,24 +5037,24 @@
},
{
"name": "nette/schema",
- "version": "v1.3.0",
+ "version": "v1.3.2",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
- "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188"
+ "reference": "da801d52f0354f70a638673c4a0f04e16529431d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188",
- "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188",
+ "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
+ "reference": "da801d52f0354f70a638673c4a0f04e16529431d",
"shasum": ""
},
"require": {
"nette/utils": "^4.0",
- "php": "8.1 - 8.3"
+ "php": "8.1 - 8.4"
},
"require-dev": {
- "nette/tester": "^2.4",
+ "nette/tester": "^2.5.2",
"phpstan/phpstan-nette": "^1.0",
"tracy/tracy": "^2.8"
},
@@ -5011,9 +5093,9 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
- "source": "https://github.com/nette/schema/tree/v1.3.0"
+ "source": "https://github.com/nette/schema/tree/v1.3.2"
},
- "time": "2023-12-11T11:54:22+00:00"
+ "time": "2024-10-06T23:10:23+00:00"
},
{
"name": "nette/utils",
@@ -5103,16 +5185,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.1.0",
+ "version": "v5.3.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
+ "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
- "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
+ "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
"shasum": ""
},
"require": {
@@ -5155,9 +5237,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
},
- "time": "2024-07-01T20:03:41+00:00"
+ "time": "2024-10-08T18:51:32+00:00"
},
{
"name": "nubs/random-name-generator",
@@ -5897,16 +5979,16 @@
},
{
"name": "phpseclib/phpseclib",
- "version": "3.0.41",
+ "version": "3.0.42",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb"
+ "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
- "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98",
+ "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98",
"shasum": ""
},
"require": {
@@ -5987,7 +6069,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
- "source": "https://github.com/phpseclib/phpseclib/tree/3.0.41"
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.42"
},
"funding": [
{
@@ -6003,20 +6085,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T00:13:54+00:00"
+ "time": "2024-09-16T03:06:04+00:00"
},
{
"name": "phpstan/phpdoc-parser",
- "version": "1.30.1",
+ "version": "1.32.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "51b95ec8670af41009e2b2b56873bad96682413e"
+ "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e",
- "reference": "51b95ec8670af41009e2b2b56873bad96682413e",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4",
+ "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4",
"shasum": ""
},
"require": {
@@ -6048,22 +6130,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0"
},
- "time": "2024-09-07T20:13:05+00:00"
+ "time": "2024-09-26T07:23:32+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.12.3",
+ "version": "1.12.6",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009"
+ "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009",
- "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae",
+ "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae",
"shasum": ""
},
"require": {
@@ -6108,7 +6190,7 @@
"type": "github"
}
],
- "time": "2024-09-09T08:10:35+00:00"
+ "time": "2024-10-06T15:03:59+00:00"
},
{
"name": "pion/laravel-chunk-upload",
@@ -6814,16 +6896,16 @@
},
{
"name": "purplepixie/phpdns",
- "version": "2.1.1",
+ "version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/purplepixie/phpdns.git",
- "reference": "18cd3a43fadcfd16e2789e3c78a264945f6cbfad"
+ "reference": "2b77de5bb218bc4e5d9c4a4a12bd18fe80a6ab4d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/purplepixie/phpdns/zipball/18cd3a43fadcfd16e2789e3c78a264945f6cbfad",
- "reference": "18cd3a43fadcfd16e2789e3c78a264945f6cbfad",
+ "url": "https://api.github.com/repos/purplepixie/phpdns/zipball/2b77de5bb218bc4e5d9c4a4a12bd18fe80a6ab4d",
+ "reference": "2b77de5bb218bc4e5d9c4a4a12bd18fe80a6ab4d",
"shasum": ""
},
"require": {
@@ -6856,9 +6938,9 @@
"description": "PHP DNS Direct Query Module",
"support": {
"issues": "https://github.com/purplepixie/phpdns/issues",
- "source": "https://github.com/purplepixie/phpdns/tree/2.1.1"
+ "source": "https://github.com/purplepixie/phpdns/tree/2.2.0"
},
- "time": "2024-05-27T13:27:50+00:00"
+ "time": "2024-09-26T14:39:58+00:00"
},
{
"name": "pusher/pusher-php-server",
@@ -7148,21 +7230,21 @@
},
{
"name": "rector/rector",
- "version": "1.2.5",
+ "version": "1.2.6",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "e98aa793ca3fcd17e893cfaf9103ac049775d339"
+ "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/e98aa793ca3fcd17e893cfaf9103ac049775d339",
- "reference": "e98aa793ca3fcd17e893cfaf9103ac049775d339",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/6ca85da28159dbd3bb36211c5104b7bc91278e99",
+ "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99",
"shasum": ""
},
"require": {
"php": "^7.2|^8.0",
- "phpstan/phpstan": "^1.12.2"
+ "phpstan/phpstan": "^1.12.5"
},
"conflict": {
"rector/rector-doctrine": "*",
@@ -7195,7 +7277,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/1.2.5"
+ "source": "https://github.com/rectorphp/rector/tree/1.2.6"
},
"funding": [
{
@@ -7203,7 +7285,7 @@
"type": "github"
}
],
- "time": "2024-09-08T17:43:24+00:00"
+ "time": "2024-10-03T08:56:44+00:00"
},
{
"name": "resend/resend-laravel",
@@ -7494,16 +7576,16 @@
},
{
"name": "sentry/sentry-laravel",
- "version": "4.8.0",
+ "version": "4.9.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-laravel.git",
- "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7"
+ "reference": "73078e1f26d57f7a10e3bee2a2f543a02f6493c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/2bbcb7e81097993cf64d5b296eaa6d396cddd5a7",
- "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7",
+ "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/73078e1f26d57f7a10e3bee2a2f543a02f6493c3",
+ "reference": "73078e1f26d57f7a10e3bee2a2f543a02f6493c3",
"shasum": ""
},
"require": {
@@ -7567,7 +7649,7 @@
],
"support": {
"issues": "https://github.com/getsentry/sentry-laravel/issues",
- "source": "https://github.com/getsentry/sentry-laravel/tree/4.8.0"
+ "source": "https://github.com/getsentry/sentry-laravel/tree/4.9.0"
},
"funding": [
{
@@ -7579,7 +7661,7 @@
"type": "custom"
}
],
- "time": "2024-08-15T19:03:01+00:00"
+ "time": "2024-09-19T12:58:53+00:00"
},
{
"name": "socialiteproviders/manager",
@@ -8580,16 +8662,16 @@
},
{
"name": "symfony/console",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111"
+ "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111",
- "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111",
+ "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee",
+ "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee",
"shasum": ""
},
"require": {
@@ -8653,7 +8735,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.1.4"
+ "source": "https://github.com/symfony/console/tree/v7.1.5"
},
"funding": [
{
@@ -8669,7 +8751,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-15T22:48:53+00:00"
+ "time": "2024-09-20T08:28:38+00:00"
},
{
"name": "symfony/css-selector",
@@ -9100,16 +9182,16 @@
},
{
"name": "symfony/http-foundation",
- "version": "v7.1.3",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a"
+ "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
- "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e30ef73b1e44eea7eb37ba69600a354e553f694b",
+ "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b",
"shasum": ""
},
"require": {
@@ -9157,7 +9239,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v7.1.3"
+ "source": "https://github.com/symfony/http-foundation/tree/v7.1.5"
},
"funding": [
{
@@ -9173,20 +9255,20 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:41:01+00:00"
+ "time": "2024-09-20T08:28:38+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "6efcbd1b3f444f631c386504fc83eeca25963747"
+ "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6efcbd1b3f444f631c386504fc83eeca25963747",
- "reference": "6efcbd1b3f444f631c386504fc83eeca25963747",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/44204d96150a9df1fc57601ec933d23fefc2d65b",
+ "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b",
"shasum": ""
},
"require": {
@@ -9271,7 +9353,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v7.1.4"
+ "source": "https://github.com/symfony/http-kernel/tree/v7.1.5"
},
"funding": [
{
@@ -9287,20 +9369,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-30T17:02:28+00:00"
+ "time": "2024-09-21T06:09:21+00:00"
},
{
"name": "symfony/mailer",
- "version": "v7.1.2",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
- "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee"
+ "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee",
- "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/bbf21460c56f29810da3df3e206e38dfbb01e80b",
+ "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b",
"shasum": ""
},
"require": {
@@ -9351,7 +9433,7 @@
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/mailer/tree/v7.1.2"
+ "source": "https://github.com/symfony/mailer/tree/v7.1.5"
},
"funding": [
{
@@ -9367,20 +9449,20 @@
"type": "tidelift"
}
],
- "time": "2024-06-28T08:00:31+00:00"
+ "time": "2024-09-08T12:32:26+00:00"
},
{
"name": "symfony/mime",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
- "reference": "ccaa6c2503db867f472a587291e764d6a1e58758"
+ "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mime/zipball/ccaa6c2503db867f472a587291e764d6a1e58758",
- "reference": "ccaa6c2503db867f472a587291e764d6a1e58758",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/711d2e167e8ce65b05aea6b258c449671cdd38ff",
+ "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff",
"shasum": ""
},
"require": {
@@ -9435,7 +9517,7 @@
"mime-type"
],
"support": {
- "source": "https://github.com/symfony/mime/tree/v7.1.4"
+ "source": "https://github.com/symfony/mime/tree/v7.1.5"
},
"funding": [
{
@@ -9451,7 +9533,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-13T14:28:19+00:00"
+ "time": "2024-09-20T08:28:38+00:00"
},
{
"name": "symfony/options-resolver",
@@ -10238,16 +10320,16 @@
},
{
"name": "symfony/process",
- "version": "v7.1.3",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca"
+ "reference": "5c03ee6369281177f07f7c68252a280beccba847"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca",
- "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca",
+ "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847",
+ "reference": "5c03ee6369281177f07f7c68252a280beccba847",
"shasum": ""
},
"require": {
@@ -10279,7 +10361,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.1.3"
+ "source": "https://github.com/symfony/process/tree/v7.1.5"
},
"funding": [
{
@@ -10295,7 +10377,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:44:47+00:00"
+ "time": "2024-09-19T21:48:23+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
@@ -10608,16 +10690,16 @@
},
{
"name": "symfony/string",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b"
+ "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b",
- "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b",
+ "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306",
+ "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306",
"shasum": ""
},
"require": {
@@ -10675,7 +10757,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.1.4"
+ "source": "https://github.com/symfony/string/tree/v7.1.5"
},
"funding": [
{
@@ -10691,20 +10773,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T09:59:40+00:00"
+ "time": "2024-09-20T08:28:38+00:00"
},
{
"name": "symfony/translation",
- "version": "v7.1.3",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1"
+ "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1",
- "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/235535e3f84f3dfbdbde0208ede6ca75c3a489ea",
+ "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea",
"shasum": ""
},
"require": {
@@ -10769,7 +10851,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v7.1.3"
+ "source": "https://github.com/symfony/translation/tree/v7.1.5"
},
"funding": [
{
@@ -10785,7 +10867,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:41:01+00:00"
+ "time": "2024-09-16T06:30:38+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -10867,16 +10949,16 @@
},
{
"name": "symfony/uid",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
- "reference": "82177535395109075cdb45a70533aa3d7a521cdf"
+ "reference": "8c7bb8acb933964055215d89f9a9871df0239317"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/uid/zipball/82177535395109075cdb45a70533aa3d7a521cdf",
- "reference": "82177535395109075cdb45a70533aa3d7a521cdf",
+ "url": "https://api.github.com/repos/symfony/uid/zipball/8c7bb8acb933964055215d89f9a9871df0239317",
+ "reference": "8c7bb8acb933964055215d89f9a9871df0239317",
"shasum": ""
},
"require": {
@@ -10921,7 +11003,7 @@
"uuid"
],
"support": {
- "source": "https://github.com/symfony/uid/tree/v7.1.4"
+ "source": "https://github.com/symfony/uid/tree/v7.1.5"
},
"funding": [
{
@@ -10937,20 +11019,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T09:59:40+00:00"
+ "time": "2024-09-17T09:16:35+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v7.1.4",
+ "version": "v7.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa"
+ "reference": "e20e03889539fd4e4211e14d2179226c513c010d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5fa7481b199090964d6fd5dab6294d5a870c7aa",
- "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e20e03889539fd4e4211e14d2179226c513c010d",
+ "reference": "e20e03889539fd4e4211e14d2179226c513c010d",
"shasum": ""
},
"require": {
@@ -11004,7 +11086,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.1.4"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.1.5"
},
"funding": [
{
@@ -11020,20 +11102,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-30T16:12:47+00:00"
+ "time": "2024-09-16T10:07:02+00:00"
},
{
"name": "symfony/yaml",
- "version": "v6.4.11",
+ "version": "v6.4.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "be37e7f13195e05ab84ca5269365591edd240335"
+ "reference": "762ee56b2649659380e0ef4d592d807bc17b7971"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/be37e7f13195e05ab84ca5269365591edd240335",
- "reference": "be37e7f13195e05ab84ca5269365591edd240335",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971",
+ "reference": "762ee56b2649659380e0ef4d592d807bc17b7971",
"shasum": ""
},
"require": {
@@ -11076,7 +11158,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.4.11"
+ "source": "https://github.com/symfony/yaml/tree/v6.4.12"
},
"funding": [
{
@@ -11092,7 +11174,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T09:55:28+00:00"
+ "time": "2024-09-17T12:47:12+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -11742,16 +11824,16 @@
},
{
"name": "zircote/swagger-php",
- "version": "4.10.6",
+ "version": "4.11.0",
"source": {
"type": "git",
"url": "https://github.com/zircote/swagger-php.git",
- "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6"
+ "reference": "3b6f3800f4fd6544ada4dce180c6b69eaead7c7c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zircote/swagger-php/zipball/e462ff5269ea0ec91070edd5d51dc7215bdea3b6",
- "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6",
+ "url": "https://api.github.com/repos/zircote/swagger-php/zipball/3b6f3800f4fd6544ada4dce180c6b69eaead7c7c",
+ "reference": "3b6f3800f4fd6544ada4dce180c6b69eaead7c7c",
"shasum": ""
},
"require": {
@@ -11765,7 +11847,7 @@
"require-dev": {
"composer/package-versions-deprecated": "^1.11",
"doctrine/annotations": "^1.7 || ^2.0",
- "friendsofphp/php-cs-fixer": "^2.17 || ^3.47.1",
+ "friendsofphp/php-cs-fixer": "^2.17 || 3.62.0",
"phpstan/phpstan": "^1.6",
"phpunit/phpunit": ">=8",
"vimeo/psalm": "^4.23"
@@ -11817,12 +11899,96 @@
],
"support": {
"issues": "https://github.com/zircote/swagger-php/issues",
- "source": "https://github.com/zircote/swagger-php/tree/4.10.6"
+ "source": "https://github.com/zircote/swagger-php/tree/4.11.0"
},
- "time": "2024-07-26T03:04:43+00:00"
+ "time": "2024-10-09T03:11:12+00:00"
}
],
"packages-dev": [
+ {
+ "name": "barryvdh/laravel-debugbar",
+ "version": "v3.14.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/barryvdh/laravel-debugbar.git",
+ "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd",
+ "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/routing": "^9|^10|^11",
+ "illuminate/session": "^9|^10|^11",
+ "illuminate/support": "^9|^10|^11",
+ "maximebf/debugbar": "~1.23.0",
+ "php": "^8.0",
+ "symfony/finder": "^6|^7"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.3.3",
+ "orchestra/testbench-dusk": "^5|^6|^7|^8|^9",
+ "phpunit/phpunit": "^9.6|^10.5",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.14-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Barryvdh\\Debugbar\\ServiceProvider"
+ ],
+ "aliases": {
+ "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar"
+ }
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Barryvdh\\Debugbar\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "PHP Debugbar integration for Laravel",
+ "keywords": [
+ "debug",
+ "debugbar",
+ "laravel",
+ "profiler",
+ "webprofiler"
+ ],
+ "support": {
+ "issues": "https://github.com/barryvdh/laravel-debugbar/issues",
+ "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.3"
+ },
+ "funding": [
+ {
+ "url": "https://fruitcake.nl",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/barryvdh",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-02T09:17:49+00:00"
+ },
{
"name": "brianium/paratest",
"version": "v7.4.3",
@@ -12043,26 +12209,26 @@
},
{
"name": "filp/whoops",
- "version": "2.15.4",
+ "version": "2.16.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
- "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546"
+ "reference": "befcdc0e5dce67252aa6322d82424be928214fa2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546",
- "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2",
+ "reference": "befcdc0e5dce67252aa6322d82424be928214fa2",
"shasum": ""
},
"require": {
- "php": "^5.5.9 || ^7.0 || ^8.0",
+ "php": "^7.1 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
- "mockery/mockery": "^0.9 || ^1.0",
- "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
- "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
+ "symfony/var-dumper": "^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
@@ -12102,7 +12268,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
- "source": "https://github.com/filp/whoops/tree/2.15.4"
+ "source": "https://github.com/filp/whoops/tree/2.16.0"
},
"funding": [
{
@@ -12110,7 +12276,7 @@
"type": "github"
}
],
- "time": "2023-11-03T12:00:00+00:00"
+ "time": "2024-09-25T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@@ -12165,16 +12331,16 @@
},
{
"name": "laravel/dusk",
- "version": "v8.2.5",
+ "version": "v8.2.8",
"source": {
"type": "git",
"url": "https://github.com/laravel/dusk.git",
- "reference": "e641800393ce4ad39f0a47133f51aae67ceb01ad"
+ "reference": "5bff1e8dd87ec653a2202475377152e5d14fde40"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/dusk/zipball/e641800393ce4ad39f0a47133f51aae67ceb01ad",
- "reference": "e641800393ce4ad39f0a47133f51aae67ceb01ad",
+ "url": "https://api.github.com/repos/laravel/dusk/zipball/5bff1e8dd87ec653a2202475377152e5d14fde40",
+ "reference": "5bff1e8dd87ec653a2202475377152e5d14fde40",
"shasum": ""
},
"require": {
@@ -12231,22 +12397,22 @@
],
"support": {
"issues": "https://github.com/laravel/dusk/issues",
- "source": "https://github.com/laravel/dusk/tree/v8.2.5"
+ "source": "https://github.com/laravel/dusk/tree/v8.2.8"
},
- "time": "2024-08-26T12:34:33+00:00"
+ "time": "2024-10-04T14:02:20+00:00"
},
{
"name": "laravel/pint",
- "version": "v1.17.3",
+ "version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
- "reference": "9d77be916e145864f10788bb94531d03e1f7b482"
+ "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/pint/zipball/9d77be916e145864f10788bb94531d03e1f7b482",
- "reference": "9d77be916e145864f10788bb94531d03e1f7b482",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9",
+ "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9",
"shasum": ""
},
"require": {
@@ -12299,7 +12465,75 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
- "time": "2024-09-03T15:00:28+00:00"
+ "time": "2024-09-24T17:22:50+00:00"
+ },
+ {
+ "name": "maximebf/debugbar",
+ "version": "v1.23.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/maximebf/php-debugbar.git",
+ "reference": "689720d724c771ac4add859056744b7b3f2406da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/689720d724c771ac4add859056744b7b3f2406da",
+ "reference": "689720d724c771ac4add859056744b7b3f2406da",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2|^8",
+ "psr/log": "^1|^2|^3",
+ "symfony/var-dumper": "^4|^5|^6|^7"
+ },
+ "require-dev": {
+ "dbrekelmans/bdi": "^1",
+ "phpunit/phpunit": "^8|^9",
+ "symfony/panther": "^1|^2.1",
+ "twig/twig": "^1.38|^2.7|^3.0"
+ },
+ "suggest": {
+ "kriswallsmith/assetic": "The best way to manage assets",
+ "monolog/monolog": "Log using Monolog",
+ "predis/predis": "Redis storage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.23-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "DebugBar\\": "src/DebugBar/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Maxime Bouroumeau-Fuseau",
+ "email": "maxime.bouroumeau@gmail.com",
+ "homepage": "http://maximebf.com"
+ },
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "Debug bar in the browser for php application",
+ "homepage": "https://github.com/maximebf/php-debugbar",
+ "keywords": [
+ "debug",
+ "debugbar"
+ ],
+ "support": {
+ "issues": "https://github.com/maximebf/php-debugbar/issues",
+ "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.2"
+ },
+ "time": "2024-09-16T11:23:09+00:00"
},
{
"name": "mockery/mockery",
@@ -14740,16 +14974,16 @@
},
{
"name": "symfony/http-client",
- "version": "v6.4.11",
+ "version": "v6.4.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65"
+ "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/4c92046bb788648ff1098cc66da69aa7eac8cb65",
- "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56",
+ "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56",
"shasum": ""
},
"require": {
@@ -14813,7 +15047,7 @@
"http"
],
"support": {
- "source": "https://github.com/symfony/http-client/tree/v6.4.11"
+ "source": "https://github.com/symfony/http-client/tree/v6.4.12"
},
"funding": [
{
@@ -14829,7 +15063,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-26T06:30:21+00:00"
+ "time": "2024-09-20T08:21:33+00:00"
},
{
"name": "symfony/http-client-contracts",
diff --git a/config/clockwork.php b/config/clockwork.php
deleted file mode 100644
index ce880464a..000000000
--- a/config/clockwork.php
+++ /dev/null
@@ -1,424 +0,0 @@
- env('CLOCKWORK_ENABLE', null),
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Features
- |------------------------------------------------------------------------------------------------------------------
- |
- | You can enable or disable various Clockwork features here. Some features have additional settings (eg. slow query
- | threshold for database queries).
- |
- */
-
- 'features' => [
-
- // Cache usage stats and cache queries including results
- 'cache' => [
- 'enabled' => env('CLOCKWORK_CACHE_ENABLED', true),
-
- // Collect cache queries
- 'collect_queries' => env('CLOCKWORK_CACHE_QUERIES', true),
-
- // Collect values from cache queries (high performance impact with a very high number of queries)
- 'collect_values' => env('CLOCKWORK_CACHE_COLLECT_VALUES', false)
- ],
-
- // Database usage stats and queries
- 'database' => [
- 'enabled' => env('CLOCKWORK_DATABASE_ENABLED', true),
-
- // Collect database queries (high performance impact with a very high number of queries)
- 'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', true),
-
- // Collect details of models updates (high performance impact with a lot of model updates)
- 'collect_models_actions' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_ACTIONS', true),
-
- // Collect details of retrieved models (very high performance impact with a lot of models retrieved)
- 'collect_models_retrieved' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_RETRIEVED', false),
-
- // Query execution time threshold in milliseconds after which the query will be marked as slow
- 'slow_threshold' => env('CLOCKWORK_DATABASE_SLOW_THRESHOLD'),
-
- // Collect only slow database queries
- 'slow_only' => env('CLOCKWORK_DATABASE_SLOW_ONLY', false),
-
- // Detect and report duplicate queries
- 'detect_duplicate_queries' => env('CLOCKWORK_DATABASE_DETECT_DUPLICATE_QUERIES', false)
- ],
-
- // Dispatched events
- 'events' => [
- 'enabled' => env('CLOCKWORK_EVENTS_ENABLED', true),
-
- // Ignored events (framework events are ignored by default)
- 'ignored_events' => [
- // App\Events\UserRegistered::class,
- // 'user.registered'
- ],
- ],
-
- // Laravel log (you can still log directly to Clockwork with laravel log disabled)
- 'log' => [
- 'enabled' => env('CLOCKWORK_LOG_ENABLED', true)
- ],
-
- // Sent notifications
- 'notifications' => [
- 'enabled' => env('CLOCKWORK_NOTIFICATIONS_ENABLED', true),
- ],
-
- // Performance metrics
- 'performance' => [
- // Allow collecting of client metrics. Requires separate clockwork-browser npm package.
- 'client_metrics' => env('CLOCKWORK_PERFORMANCE_CLIENT_METRICS', true)
- ],
-
- // Dispatched queue jobs
- 'queue' => [
- 'enabled' => env('CLOCKWORK_QUEUE_ENABLED', true)
- ],
-
- // Redis commands
- 'redis' => [
- 'enabled' => env('CLOCKWORK_REDIS_ENABLED', true)
- ],
-
- // Routes list
- 'routes' => [
- 'enabled' => env('CLOCKWORK_ROUTES_ENABLED', false),
-
- // Collect only routes from particular namespaces (only application routes by default)
- 'only_namespaces' => [ 'App' ]
- ],
-
- // Rendered views
- 'views' => [
- 'enabled' => env('CLOCKWORK_VIEWS_ENABLED', true),
-
- // Collect views including view data (high performance impact with a high number of views)
- 'collect_data' => env('CLOCKWORK_VIEWS_COLLECT_DATA', false),
-
- // Use Twig profiler instead of Laravel events for apps using laravel-twigbridge (more precise, but does
- // not support collecting view data)
- 'use_twig_profiler' => env('CLOCKWORK_VIEWS_USE_TWIG_PROFILER', false)
- ]
-
- ],
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Enable web UI
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork comes with a web UI accessible via http://your.app/clockwork. Here you can enable or disable this
- | feature. You can also set a custom path for the web UI.
- |
- */
-
- 'web' => env('CLOCKWORK_WEB', true),
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Enable toolbar
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork can show a toolbar with basic metrics on all responses. Here you can enable or disable this feature.
- | Requires a separate clockwork-browser npm library.
- | For installation instructions see https://underground.works/clockwork/#docs-viewing-data
- |
- */
-
- 'toolbar' => env('CLOCKWORK_TOOLBAR', true),
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | HTTP requests collection
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork collects data about HTTP requests to your app. Here you can choose which requests should be collected.
- |
- */
-
- 'requests' => [
- // With on-demand mode enabled, Clockwork will only profile requests when the browser extension is open or you
- // manually pass a "clockwork-profile" cookie or get/post data key.
- // Optionally you can specify a "secret" that has to be passed as the value to enable profiling.
- 'on_demand' => env('CLOCKWORK_REQUESTS_ON_DEMAND', false),
-
- // Collect only errors (requests with HTTP 4xx and 5xx responses)
- 'errors_only' => env('CLOCKWORK_REQUESTS_ERRORS_ONLY', false),
-
- // Response time threshold in milliseconds after which the request will be marked as slow
- 'slow_threshold' => env('CLOCKWORK_REQUESTS_SLOW_THRESHOLD'),
-
- // Collect only slow requests
- 'slow_only' => env('CLOCKWORK_REQUESTS_SLOW_ONLY', false),
-
- // Sample the collected requests (e.g. set to 100 to collect only 1 in 100 requests)
- 'sample' => env('CLOCKWORK_REQUESTS_SAMPLE', false),
-
- // List of URIs that should not be collected
- 'except' => [
- '/horizon/.*', // Laravel Horizon requests
- '/telescope/.*', // Laravel Telescope requests
- '/_tt/.*', // Laravel Telescope toolbar
- '/_debugbar/.*', // Laravel DebugBar requests
- ],
-
- // List of URIs that should be collected, any other URI will not be collected if not empty
- 'only' => [
- // '/api/.*'
- ],
-
- // Don't collect OPTIONS requests, mostly used in the CSRF pre-flight requests and are rarely of interest
- 'except_preflight' => env('CLOCKWORK_REQUESTS_EXCEPT_PREFLIGHT', true)
- ],
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Artisan commands collection
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork can collect data about executed artisan commands. Here you can enable and configure which commands
- | should be collected.
- |
- */
-
- 'artisan' => [
- // Enable or disable collection of executed Artisan commands
- 'collect' => env('CLOCKWORK_ARTISAN_COLLECT', false),
-
- // List of commands that should not be collected (built-in commands are not collected by default)
- 'except' => [
- // 'inspire'
- ],
-
- // List of commands that should be collected, any other command will not be collected if not empty
- 'only' => [
- // 'inspire'
- ],
-
- // Enable or disable collection of command output
- 'collect_output' => env('CLOCKWORK_ARTISAN_COLLECT_OUTPUT', false),
-
- // Enable or disable collection of built-in Laravel commands
- 'except_laravel_commands' => env('CLOCKWORK_ARTISAN_EXCEPT_LARAVEL_COMMANDS', true)
- ],
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Queue jobs collection
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork can collect data about executed queue jobs. Here you can enable and configure which queue jobs should
- | be collected.
- |
- */
-
- 'queue' => [
- // Enable or disable collection of executed queue jobs
- 'collect' => env('CLOCKWORK_QUEUE_COLLECT', false),
-
- // List of queue jobs that should not be collected
- 'except' => [
- // App\Jobs\ExpensiveJob::class
- ],
-
- // List of queue jobs that should be collected, any other queue job will not be collected if not empty
- 'only' => [
- // App\Jobs\BuggyJob::class
- ]
- ],
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Tests collection
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork can collect data about executed tests. Here you can enable and configure which tests should be
- | collected.
- |
- */
-
- 'tests' => [
- // Enable or disable collection of ran tests
- 'collect' => env('CLOCKWORK_TESTS_COLLECT', false),
-
- // List of tests that should not be collected
- 'except' => [
- // Tests\Unit\ExampleTest::class
- ]
- ],
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Enable data collection when Clockwork is disabled
- |------------------------------------------------------------------------------------------------------------------
- |
- | You can enable this setting to collect data even when Clockwork is disabled, e.g. for future analysis.
- |
- */
-
- 'collect_data_always' => env('CLOCKWORK_COLLECT_DATA_ALWAYS', false),
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Metadata storage
- |------------------------------------------------------------------------------------------------------------------
- |
- | Configure how is the metadata collected by Clockwork stored. Three options are available:
- | - files - A simple fast storage implementation storing data in one-per-request files.
- | - sql - Stores requests in a sql database. Supports MySQL, PostgreSQL and SQLite. Requires PDO.
- | - redis - Stores requests in redis. Requires phpredis.
- */
-
- 'storage' => env('CLOCKWORK_STORAGE', 'files'),
-
- // Path where the Clockwork metadata is stored
- 'storage_files_path' => env('CLOCKWORK_STORAGE_FILES_PATH', storage_path('clockwork')),
-
- // Compress the metadata files using gzip, trading a little bit of performance for lower disk usage
- 'storage_files_compress' => env('CLOCKWORK_STORAGE_FILES_COMPRESS', false),
-
- // SQL database to use, can be a name of database configured in database.php or a path to a SQLite file
- 'storage_sql_database' => env('CLOCKWORK_STORAGE_SQL_DATABASE', storage_path('clockwork.sqlite')),
-
- // SQL table name to use, the table is automatically created and updated when needed
- 'storage_sql_table' => env('CLOCKWORK_STORAGE_SQL_TABLE', 'clockwork'),
-
- // Redis connection, name of redis connection or cluster configured in database.php
- 'storage_redis' => env('CLOCKWORK_STORAGE_REDIS', 'default'),
-
- // Redis prefix for Clockwork keys ("clockwork" if not set)
- 'storage_redis_prefix' => env('CLOCKWORK_STORAGE_REDIS_PREFIX', 'clockwork'),
-
- // Maximum lifetime of collected metadata in minutes, older requests will automatically be deleted, false to disable
- 'storage_expiration' => env('CLOCKWORK_STORAGE_EXPIRATION', 60 * 24 * 7),
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Authentication
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork can be configured to require authentication before allowing access to the collected data. This might be
- | useful when the application is publicly accessible. Setting to true will enable a simple authentication with a
- | pre-configured password. You can also pass a class name of a custom implementation.
- |
- */
-
- 'authentication' => env('CLOCKWORK_AUTHENTICATION', false),
-
- // Password for the simple authentication
- 'authentication_password' => env('CLOCKWORK_AUTHENTICATION_PASSWORD', 'VerySecretPassword'),
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Stack traces collection
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork can collect stack traces for log messages and certain data like database queries. Here you can set
- | whether to collect stack traces, limit the number of collected frames and set further configuration. Collecting
- | long stack traces considerably increases metadata size.
- |
- */
-
- 'stack_traces' => [
- // Enable or disable collecting of stack traces
- 'enabled' => env('CLOCKWORK_STACK_TRACES_ENABLED', true),
-
- // Limit the number of frames to be collected
- 'limit' => env('CLOCKWORK_STACK_TRACES_LIMIT', 10),
-
- // List of vendor names to skip when determining caller, common vendors are automatically added
- 'skip_vendors' => [
- // 'phpunit'
- ],
-
- // List of namespaces to skip when determining caller
- 'skip_namespaces' => [
- // 'Laravel'
- ],
-
- // List of class names to skip when determining caller
- 'skip_classes' => [
- // App\CustomLog::class
- ]
-
- ],
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Serialization
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork serializes the collected data to json for storage and transfer. Here you can configure certain aspects
- | of serialization. Serialization has a large effect on the cpu time and memory usage.
- |
- */
-
- // Maximum depth of serialized multi-level arrays and objects
- 'serialization_depth' => env('CLOCKWORK_SERIALIZATION_DEPTH', 10),
-
- // A list of classes that will never be serialized (e.g. a common service container class)
- 'serialization_blackbox' => [
- \Illuminate\Container\Container::class,
- \Illuminate\Foundation\Application::class,
- \Laravel\Lumen\Application::class
- ],
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Register helpers
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork comes with a "clock" global helper function. You can use this helper to quickly log something and to
- | access the Clockwork instance.
- |
- */
-
- 'register_helpers' => env('CLOCKWORK_REGISTER_HELPERS', true),
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Send headers for AJAX request
- |------------------------------------------------------------------------------------------------------------------
- |
- | When trying to collect data, the AJAX method can sometimes fail if it is missing required headers. For example, an
- | API might require a version number using Accept headers to route the HTTP request to the correct codebase.
- |
- */
-
- 'headers' => [
- // 'Accept' => 'application/vnd.com.whatever.v1+json',
- ],
-
- /*
- |------------------------------------------------------------------------------------------------------------------
- | Server timing
- |------------------------------------------------------------------------------------------------------------------
- |
- | Clockwork supports the W3C Server Timing specification, which allows for collecting a simple performance metrics
- | in a cross-browser way. E.g. in Chrome, your app, database and timeline event timings will be shown in the Dev
- | Tools network tab. This setting specifies the max number of timeline events that will be sent. Setting to false
- | will disable the feature.
- |
- */
-
- 'server_timing' => env('CLOCKWORK_SERVER_TIMING', 10)
-
-];
diff --git a/config/constants.php b/config/constants.php
index 906ef3ba2..5792b358c 100644
--- a/config/constants.php
+++ b/config/constants.php
@@ -6,9 +6,8 @@ return [
'contact' => 'https://coolify.io/docs/contact',
],
'ssh' => [
- // Using MUX
- 'mux_enabled' => env('MUX_ENABLED', env('SSH_MUX_ENABLED', true), true),
- 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1h'),
+ 'mux_enabled' => env('MUX_ENABLED', env('SSH_MUX_ENABLED', true)),
+ 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', 3600),
'connection_timeout' => 10,
'server_interval' => 20,
'command_timeout' => 7200,
diff --git a/config/debugbar.php b/config/debugbar.php
new file mode 100644
index 000000000..eae406ba7
--- /dev/null
+++ b/config/debugbar.php
@@ -0,0 +1,325 @@
+ env('DEBUGBAR_ENABLED', null),
+ 'except' => [
+ 'telescope*',
+ 'horizon*',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Storage settings
+ |--------------------------------------------------------------------------
+ |
+ | DebugBar stores data for session/ajax requests.
+ | You can disable this, so the debugbar stores data in headers/session,
+ | but this can cause problems with large data collectors.
+ | By default, file storage (in the storage folder) is used. Redis and PDO
+ | can also be used. For PDO, run the package migrations first.
+ |
+ | Warning: Enabling storage.open will allow everyone to access previous
+ | request, do not enable open storage in publicly available environments!
+ | Specify a callback if you want to limit based on IP or authentication.
+ | Leaving it to null will allow localhost only.
+ */
+ 'storage' => [
+ 'enabled' => true,
+ 'open' => env('DEBUGBAR_OPEN_STORAGE'), // bool/callback.
+ 'driver' => 'file', // redis, file, pdo, socket, custom
+ 'path' => storage_path('debugbar'), // For file driver
+ 'connection' => null, // Leave null for default connection (Redis/PDO)
+ 'provider' => '', // Instance of StorageInterface for custom driver
+ 'hostname' => '127.0.0.1', // Hostname to use with the "socket" driver
+ 'port' => 2304, // Port to use with the "socket" driver
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Editor
+ |--------------------------------------------------------------------------
+ |
+ | Choose your preferred editor to use when clicking file name.
+ |
+ | Supported: "phpstorm", "vscode", "vscode-insiders", "vscode-remote",
+ | "vscode-insiders-remote", "vscodium", "textmate", "emacs",
+ | "sublime", "atom", "nova", "macvim", "idea", "netbeans",
+ | "xdebug", "espresso"
+ |
+ */
+
+ 'editor' => env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'phpstorm'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Remote Path Mapping
+ |--------------------------------------------------------------------------
+ |
+ | If you are using a remote dev server, like Laravel Homestead, Docker, or
+ | even a remote VPS, it will be necessary to specify your path mapping.
+ |
+ | Leaving one, or both of these, empty or null will not trigger the remote
+ | URL changes and Debugbar will treat your editor links as local files.
+ |
+ | "remote_sites_path" is an absolute base path for your sites or projects
+ | in Homestead, Vagrant, Docker, or another remote development server.
+ |
+ | Example value: "/home/vagrant/Code"
+ |
+ | "local_sites_path" is an absolute base path for your sites or projects
+ | on your local computer where your IDE or code editor is running on.
+ |
+ | Example values: "/Users//Code", "C:\Users\\Documents\Code"
+ |
+ */
+
+ 'remote_sites_path' => env('DEBUGBAR_REMOTE_SITES_PATH'),
+ 'local_sites_path' => env('DEBUGBAR_LOCAL_SITES_PATH', env('IGNITION_LOCAL_SITES_PATH')),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Vendors
+ |--------------------------------------------------------------------------
+ |
+ | Vendor files are included by default, but can be set to false.
+ | This can also be set to 'js' or 'css', to only include javascript or css vendor files.
+ | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
+ | and for js: jquery and highlight.js
+ | So if you want syntax highlighting, set it to true.
+ | jQuery is set to not conflict with existing jQuery scripts.
+ |
+ */
+
+ 'include_vendors' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Capture Ajax Requests
+ |--------------------------------------------------------------------------
+ |
+ | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors),
+ | you can use this option to disable sending the data through the headers.
+ |
+ | Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
+ |
+ | Note for your request to be identified as ajax requests they must either send the header
+ | X-Requested-With with the value XMLHttpRequest (most JS libraries send this), or have application/json as a Accept header.
+ |
+ | By default `ajax_handler_auto_show` is set to true allowing ajax requests to be shown automatically in the Debugbar.
+ | Changing `ajax_handler_auto_show` to false will prevent the Debugbar from reloading.
+ */
+
+ 'capture_ajax' => true,
+ 'add_ajax_timing' => false,
+ 'ajax_handler_auto_show' => true,
+ 'ajax_handler_enable_tab' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Custom Error Handler for Deprecated warnings
+ |--------------------------------------------------------------------------
+ |
+ | When enabled, the Debugbar shows deprecated warnings for Symfony components
+ | in the Messages tab.
+ |
+ */
+ 'error_handler' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Clockwork integration
+ |--------------------------------------------------------------------------
+ |
+ | The Debugbar can emulate the Clockwork headers, so you can use the Chrome
+ | Extension, without the server-side code. It uses Debugbar collectors instead.
+ |
+ */
+ 'clockwork' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | DataCollectors
+ |--------------------------------------------------------------------------
+ |
+ | Enable/disable DataCollectors
+ |
+ */
+
+ 'collectors' => [
+ 'phpinfo' => true, // Php version
+ 'messages' => true, // Messages
+ 'time' => true, // Time Datalogger
+ 'memory' => true, // Memory usage
+ 'exceptions' => true, // Exception displayer
+ 'log' => true, // Logs from Monolog (merged in messages if enabled)
+ 'db' => true, // Show database (PDO) queries and bindings
+ 'views' => true, // Views with their data
+ 'route' => true, // Current route information
+ 'auth' => false, // Display Laravel authentication status
+ 'gate' => true, // Display Laravel Gate checks
+ 'session' => true, // Display session data
+ 'symfony_request' => true, // Only one can be enabled..
+ 'mail' => true, // Catch mail messages
+ 'laravel' => false, // Laravel version and environment
+ 'events' => false, // All events fired
+ 'default_request' => false, // Regular or special Symfony request logger
+ 'logs' => false, // Add the latest log messages
+ 'files' => false, // Show the included files
+ 'config' => false, // Display config settings
+ 'cache' => false, // Display cache events
+ 'models' => true, // Display models
+ 'livewire' => true, // Display Livewire (when available)
+ 'jobs' => false, // Display dispatched jobs
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Extra options
+ |--------------------------------------------------------------------------
+ |
+ | Configure some DataCollectors
+ |
+ */
+
+ 'options' => [
+ 'time' => [
+ 'memory_usage' => false, // Calculated by subtracting memory start and end, it may be inaccurate
+ ],
+ 'messages' => [
+ 'trace' => true, // Trace the origin of the debug message
+ ],
+ 'memory' => [
+ 'reset_peak' => false, // run memory_reset_peak_usage before collecting
+ 'with_baseline' => false, // Set boot memory usage as memory peak baseline
+ 'precision' => 0, // Memory rounding precision
+ ],
+ 'auth' => [
+ 'show_name' => true, // Also show the users name/email in the debugbar
+ 'show_guards' => true, // Show the guards that are used
+ ],
+ 'db' => [
+ 'with_params' => true, // Render SQL with the parameters substituted
+ 'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
+ 'backtrace_exclude_paths' => [], // Paths to exclude from backtrace. (in addition to defaults)
+ 'timeline' => false, // Add the queries to the timeline
+ 'duration_background' => true, // Show shaded background on each query relative to how long it took to execute.
+ 'explain' => [ // Show EXPLAIN output on queries
+ 'enabled' => false,
+ 'types' => ['SELECT'], // Deprecated setting, is always only SELECT
+ ],
+ 'hints' => false, // Show hints for common mistakes
+ 'show_copy' => false, // Show copy button next to the query,
+ 'slow_threshold' => false, // Only track queries that last longer than this time in ms
+ 'memory_usage' => false, // Show queries memory usage
+ 'soft_limit' => 100, // After the soft limit, no parameters/backtrace are captured
+ 'hard_limit' => 500, // After the hard limit, queries are ignored
+ ],
+ 'mail' => [
+ 'timeline' => false, // Add mails to the timeline
+ 'show_body' => true,
+ ],
+ 'views' => [
+ 'timeline' => false, // Add the views to the timeline (Experimental)
+ 'data' => false, //true for all data, 'keys' for only names, false for no parameters.
+ 'group' => 50, // Group duplicate views. Pass value to auto-group, or true/false to force
+ 'exclude_paths' => [ // Add the paths which you don't want to appear in the views
+ 'vendor/filament', // Exclude Filament components by default
+ ],
+ ],
+ 'route' => [
+ 'label' => true, // show complete route on bar
+ ],
+ 'session' => [
+ 'hiddens' => [], // hides sensitive values using array paths
+ ],
+ 'symfony_request' => [
+ 'hiddens' => [], // hides sensitive values using array paths, example: request_request.password
+ ],
+ 'events' => [
+ 'data' => false, // collect events data, listeners
+ ],
+ 'logs' => [
+ 'file' => null,
+ ],
+ 'cache' => [
+ 'values' => true, // collect cache values
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Inject Debugbar in Response
+ |--------------------------------------------------------------------------
+ |
+ | Usually, the debugbar is added just before