From 2214099c90f03f4681cae3f2b012a4a2b00c0de9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:10:43 +0200 Subject: [PATCH 001/202] feat(scheduling): add frequency filter option for manual execution of scheduled jobs --- .../Commands/RunScheduledJobsManually.php | 60 ++++++++++++++----- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/app/Console/Commands/RunScheduledJobsManually.php b/app/Console/Commands/RunScheduledJobsManually.php index 1685d47cc..238bcbce3 100644 --- a/app/Console/Commands/RunScheduledJobsManually.php +++ b/app/Console/Commands/RunScheduledJobsManually.php @@ -13,6 +13,7 @@ class RunScheduledJobsManually extends Command { protected $signature = 'schedule:run-manual {--type=all : Type of jobs to run (all, backups, tasks)} + {--frequency= : Filter by frequency (daily, hourly, weekly, monthly, yearly, or cron expression)} {--chunk=5 : Number of jobs to process in each batch} {--delay=30 : Delay in seconds between batches} {--max= : Maximum number of jobs to process (useful for testing)} @@ -23,37 +24,52 @@ class RunScheduledJobsManually extends Command public function handle() { $type = $this->option('type'); + $frequency = $this->option('frequency'); $chunkSize = (int) $this->option('chunk'); $delay = (int) $this->option('delay'); $maxJobs = $this->option('max') ? (int) $this->option('max') : null; $dryRun = $this->option('dry-run'); $this->info('Starting manual execution of scheduled jobs...'.($dryRun ? ' (DRY RUN)' : '')); - $this->info("Type: {$type}, Chunk size: {$chunkSize}, Delay: {$delay}s".($maxJobs ? ", Max jobs: {$maxJobs}" : '').($dryRun ? ', Dry run: enabled' : '')); + $this->info("Type: {$type}".($frequency ? ", Frequency: {$frequency}" : '').", Chunk size: {$chunkSize}, Delay: {$delay}s".($maxJobs ? ", Max jobs: {$maxJobs}" : '').($dryRun ? ', Dry run: enabled' : '')); if ($dryRun) { $this->warn('DRY RUN MODE: No jobs will actually be dispatched'); } if ($type === 'all' || $type === 'backups') { - $this->runScheduledBackups($chunkSize, $delay, $maxJobs, $dryRun); + $this->runScheduledBackups($chunkSize, $delay, $maxJobs, $dryRun, $frequency); } if ($type === 'all' || $type === 'tasks') { - $this->runScheduledTasks($chunkSize, $delay, $maxJobs, $dryRun); + $this->runScheduledTasks($chunkSize, $delay, $maxJobs, $dryRun, $frequency); } $this->info('Completed manual execution of scheduled jobs.'.($dryRun ? ' (DRY RUN)' : '')); } - private function runScheduledBackups(int $chunkSize, int $delay, ?int $maxJobs = null, bool $dryRun = false): void + private function runScheduledBackups(int $chunkSize, int $delay, ?int $maxJobs = null, bool $dryRun = false, ?string $frequency = null): void { $this->info('Processing scheduled database backups...'); - $scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get(); + $query = ScheduledDatabaseBackup::where('enabled', true); + + if ($frequency) { + $query->where(function ($q) use ($frequency) { + // Handle human-readable frequency strings + if (in_array($frequency, ['daily', 'hourly', 'weekly', 'monthly', 'yearly', 'every_minute'])) { + $q->where('frequency', $frequency); + } else { + // Handle cron expressions + $q->where('frequency', $frequency); + } + }); + } + + $scheduled_backups = $query->get(); if ($scheduled_backups->isEmpty()) { - $this->info('No enabled scheduled backups found.'); + $this->info('No enabled scheduled backups found'.($frequency ? " with frequency '{$frequency}'" : '').'.'); return; } @@ -96,7 +112,7 @@ class RunScheduledJobsManually extends Command $this->info("Limited to {$maxJobs} scheduled backups for testing"); } - $this->info("Found {$finalScheduledBackups->count()} valid scheduled backups to process"); + $this->info("Found {$finalScheduledBackups->count()} valid scheduled backups to process".($frequency ? " with frequency '{$frequency}'" : '')); $chunks = $finalScheduledBackups->chunk($chunkSize); foreach ($chunks as $index => $chunk) { @@ -105,10 +121,10 @@ class RunScheduledJobsManually extends Command foreach ($chunk as $scheduled_backup) { try { if ($dryRun) { - $this->info("🔍 Would dispatch backup job for: {$scheduled_backup->name} (ID: {$scheduled_backup->id})"); + $this->info("🔍 Would dispatch backup job for: {$scheduled_backup->name} (ID: {$scheduled_backup->id}, Frequency: {$scheduled_backup->frequency})"); } else { DatabaseBackupJob::dispatch($scheduled_backup); - $this->info("✓ Dispatched backup job for: {$scheduled_backup->name} (ID: {$scheduled_backup->id})"); + $this->info("✓ Dispatched backup job for: {$scheduled_backup->name} (ID: {$scheduled_backup->id}, Frequency: {$scheduled_backup->frequency})"); } } catch (\Exception $e) { $this->error("✗ Failed to dispatch backup job for {$scheduled_backup->id}: ".$e->getMessage()); @@ -123,14 +139,28 @@ class RunScheduledJobsManually extends Command } } - private function runScheduledTasks(int $chunkSize, int $delay, ?int $maxJobs = null, bool $dryRun = false): void + private function runScheduledTasks(int $chunkSize, int $delay, ?int $maxJobs = null, bool $dryRun = false, ?string $frequency = null): void { $this->info('Processing scheduled tasks...'); - $scheduled_tasks = ScheduledTask::where('enabled', true)->get(); + $query = ScheduledTask::where('enabled', true); + + if ($frequency) { + $query->where(function ($q) use ($frequency) { + // Handle human-readable frequency strings + if (in_array($frequency, ['daily', 'hourly', 'weekly', 'monthly', 'yearly', 'every_minute'])) { + $q->where('frequency', $frequency); + } else { + // Handle cron expressions + $q->where('frequency', $frequency); + } + }); + } + + $scheduled_tasks = $query->get(); if ($scheduled_tasks->isEmpty()) { - $this->info('No enabled scheduled tasks found.'); + $this->info('No enabled scheduled tasks found'.($frequency ? " with frequency '{$frequency}'" : '').'.'); return; } @@ -188,7 +218,7 @@ class RunScheduledJobsManually extends Command $this->info("Limited to {$maxJobs} scheduled tasks for testing"); } - $this->info("Found {$finalScheduledTasks->count()} valid scheduled tasks to process"); + $this->info("Found {$finalScheduledTasks->count()} valid scheduled tasks to process".($frequency ? " with frequency '{$frequency}'" : '')); $chunks = $finalScheduledTasks->chunk($chunkSize); foreach ($chunks as $index => $chunk) { @@ -197,10 +227,10 @@ class RunScheduledJobsManually extends Command foreach ($chunk as $scheduled_task) { try { if ($dryRun) { - $this->info("🔍 Would dispatch task job for: {$scheduled_task->name} (ID: {$scheduled_task->id})"); + $this->info("🔍 Would dispatch task job for: {$scheduled_task->name} (ID: {$scheduled_task->id}, Frequency: {$scheduled_task->frequency})"); } else { ScheduledTaskJob::dispatch($scheduled_task); - $this->info("✓ Dispatched task job for: {$scheduled_task->name} (ID: {$scheduled_task->id})"); + $this->info("✓ Dispatched task job for: {$scheduled_task->name} (ID: {$scheduled_task->id}, Frequency: {$scheduled_task->frequency})"); } } catch (\Exception $e) { $this->error("✗ Failed to dispatch task job for {$scheduled_task->id}: ".$e->getMessage()); From 36fe235bea66e64b93d97e69e586340ae293d885 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:10:55 +0200 Subject: [PATCH 002/202] feat(logging): implement scheduled logs command and enhance backup/task scheduling with cron checks --- app/Console/Commands/ViewScheduledLogs.php | 203 +++++++++++++++++++++ app/Console/Kernel.php | 50 +++++ config/logging.php | 14 ++ 3 files changed, 267 insertions(+) create mode 100644 app/Console/Commands/ViewScheduledLogs.php diff --git a/app/Console/Commands/ViewScheduledLogs.php b/app/Console/Commands/ViewScheduledLogs.php new file mode 100644 index 000000000..157afc7c1 --- /dev/null +++ b/app/Console/Commands/ViewScheduledLogs.php @@ -0,0 +1,203 @@ +option('date') ?: now()->format('Y-m-d'); + $logPaths = $this->getLogPaths($date); + + if (empty($logPaths)) { + $this->showAvailableLogFiles($date); + + return; + } + + $lines = $this->option('lines'); + $follow = $this->option('follow'); + + // Build grep filters + $filters = $this->buildFilters(); + $filterDescription = $this->getFilterDescription(); + $logTypeDescription = $this->getLogTypeDescription(); + + if ($follow) { + $this->info("Following {$logTypeDescription} logs for {$date}{$filterDescription} (Press Ctrl+C to stop)..."); + $this->line(''); + + if (count($logPaths) === 1) { + $logPath = $logPaths[0]; + if ($filters) { + passthru("tail -f {$logPath} | grep -E '{$filters}'"); + } else { + passthru("tail -f {$logPath}"); + } + } else { + // Multiple files - use multitail or tail with process substitution + $logPathsStr = implode(' ', $logPaths); + if ($filters) { + passthru("tail -f {$logPathsStr} | grep -E '{$filters}'"); + } else { + passthru("tail -f {$logPathsStr}"); + } + } + } else { + $this->info("Showing last {$lines} lines of {$logTypeDescription} logs for {$date}{$filterDescription}:"); + $this->line(''); + + if (count($logPaths) === 1) { + $logPath = $logPaths[0]; + if ($filters) { + passthru("tail -n {$lines} {$logPath} | grep -E '{$filters}'"); + } else { + passthru("tail -n {$lines} {$logPath}"); + } + } else { + // Multiple files - concatenate and sort by timestamp + $logPathsStr = implode(' ', $logPaths); + if ($filters) { + passthru("tail -n {$lines} {$logPathsStr} | sort | grep -E '{$filters}'"); + } else { + passthru("tail -n {$lines} {$logPathsStr} | sort"); + } + } + } + } + + private function getLogPaths(string $date): array + { + $paths = []; + + if ($this->option('errors')) { + // Error logs only + $errorPath = storage_path("logs/scheduled-errors-{$date}.log"); + if (File::exists($errorPath)) { + $paths[] = $errorPath; + } + } elseif ($this->option('all')) { + // Both normal and error logs + $normalPath = storage_path("logs/scheduled-{$date}.log"); + $errorPath = storage_path("logs/scheduled-errors-{$date}.log"); + + if (File::exists($normalPath)) { + $paths[] = $normalPath; + } + if (File::exists($errorPath)) { + $paths[] = $errorPath; + } + } else { + // Normal logs only (default) + $normalPath = storage_path("logs/scheduled-{$date}.log"); + if (File::exists($normalPath)) { + $paths[] = $normalPath; + } + } + + return $paths; + } + + private function showAvailableLogFiles(string $date): void + { + $logType = $this->getLogTypeDescription(); + $this->warn("No {$logType} logs found for date {$date}"); + + // Show available log files + $normalFiles = File::glob(storage_path('logs/scheduled-*.log')); + $errorFiles = File::glob(storage_path('logs/scheduled-errors-*.log')); + + if (! empty($normalFiles) || ! empty($errorFiles)) { + $this->info('Available scheduled log files:'); + + if (! empty($normalFiles)) { + $this->line(' Normal logs:'); + foreach ($normalFiles as $file) { + $basename = basename($file); + $this->line(" - {$basename}"); + } + } + + if (! empty($errorFiles)) { + $this->line(' Error logs:'); + foreach ($errorFiles as $file) { + $basename = basename($file); + $this->line(" - {$basename}"); + } + } + } + } + + private function getLogTypeDescription(): string + { + if ($this->option('errors')) { + return 'error'; + } elseif ($this->option('all')) { + return 'all'; + } else { + return 'normal'; + } + } + + private function buildFilters(): ?string + { + $filters = []; + + if ($taskName = $this->option('task-name')) { + $filters[] = '"task_name":"[^"]*'.preg_quote($taskName, '/').'[^"]*"'; + } + + if ($taskId = $this->option('task-id')) { + $filters[] = '"task_id":'.preg_quote($taskId, '/'); + } + + if ($backupName = $this->option('backup-name')) { + $filters[] = '"backup_name":"[^"]*'.preg_quote($backupName, '/').'[^"]*"'; + } + + if ($backupId = $this->option('backup-id')) { + $filters[] = '"backup_id":'.preg_quote($backupId, '/'); + } + + return empty($filters) ? null : implode('|', $filters); + } + + private function getFilterDescription(): string + { + $descriptions = []; + + if ($taskName = $this->option('task-name')) { + $descriptions[] = "task name: {$taskName}"; + } + + if ($taskId = $this->option('task-id')) { + $descriptions[] = "task ID: {$taskId}"; + } + + if ($backupName = $this->option('backup-name')) { + $descriptions[] = "backup name: {$backupName}"; + } + + if ($backupId = $this->option('backup-id')) { + $descriptions[] = "backup ID: {$backupId}"; + } + + return empty($descriptions) ? '' : ' (filtered by '.implode(', ', $descriptions).')'; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 395c58dee..190e46961 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -20,6 +20,7 @@ use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledTask; use App\Models\Server; use App\Models\Team; +use Cron\CronExpression; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Support\Carbon; @@ -197,6 +198,7 @@ class Kernel extends ConsoleKernel private function checkScheduledBackups(): void { $scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get(); + if ($scheduled_backups->isEmpty()) { return; } @@ -238,10 +240,33 @@ class Kernel extends ConsoleKernel $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; } $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); + + // Check if the backup should run now + $cron = new CronExpression($scheduled_backup->frequency); + $now = Carbon::now($serverTimezone); + + if ($cron->isDue($now)) { + Log::channel('scheduled')->info('Backup job running now', [ + 'backup_id' => $scheduled_backup->id, + 'backup_name' => $scheduled_backup->name ?? 'unnamed', + 'server_name' => $server->name, + 'frequency' => $scheduled_backup->frequency, + 'timezone' => $serverTimezone, + 'current_time' => $now->format('Y-m-d H:i:s T'), + ]); + } + $this->scheduleInstance->job(new DatabaseBackupJob( backup: $scheduled_backup ))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer(); + } catch (\Exception $e) { + Log::channel('scheduled-errors')->error('Error scheduling backup', [ + 'backup_id' => $scheduled_backup->id, + 'backup_name' => $scheduled_backup->name ?? 'unnamed', + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); Log::error('Error scheduling backup: '.$e->getMessage()); Log::error($e->getTraceAsString()); } @@ -251,6 +276,7 @@ class Kernel extends ConsoleKernel private function checkScheduledTasks(): void { $scheduled_tasks = ScheduledTask::where('enabled', true)->get(); + if ($scheduled_tasks->isEmpty()) { return; } @@ -301,10 +327,34 @@ class Kernel extends ConsoleKernel if (validate_timezone($serverTimezone) === false) { $serverTimezone = config('app.timezone'); } + + // Check if the task should run now + $cron = new CronExpression($scheduled_task->frequency); + $now = Carbon::now($serverTimezone); + + if ($cron->isDue($now)) { + Log::channel('scheduled')->info('Task job running now', [ + 'task_id' => $scheduled_task->id, + 'task_name' => $scheduled_task->name ?? 'unnamed', + 'server_name' => $server->name, + 'frequency' => $scheduled_task->frequency, + 'timezone' => $serverTimezone, + 'type' => $scheduled_task->service ? 'service' : 'application', + 'current_time' => $now->format('Y-m-d H:i:s T'), + ]); + } + $this->scheduleInstance->job(new ScheduledTaskJob( task: $scheduled_task ))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer(); + } catch (\Exception $e) { + Log::channel('scheduled-errors')->error('Error scheduling task', [ + 'task_id' => $scheduled_task->id, + 'task_name' => $scheduled_task->name ?? 'unnamed', + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); Log::error('Error scheduling task: '.$e->getMessage()); Log::error($e->getTraceAsString()); } diff --git a/config/logging.php b/config/logging.php index 4c3df4ce1..a804295fa 100644 --- a/config/logging.php +++ b/config/logging.php @@ -118,6 +118,20 @@ return [ 'emergency' => [ 'path' => storage_path('logs/laravel.log'), ], + + 'scheduled' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/scheduled.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 1, // Keep logs for 1 day only (truncated daily) + ], + + 'scheduled-errors' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/scheduled-errors.log'), + 'level' => 'error', + 'days' => 7, // Keep error logs for 7 days + ], ], ]; From e6720113126f291077fdb7b46f13a0fda653e6e0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:11:00 +0200 Subject: [PATCH 003/202] fix(versions): update coolify version numbers in versions.json and constants.php to 4.0.0-beta.420.6 and 4.0.0-beta.420.7 --- config/constants.php | 2 +- versions.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/constants.php b/config/constants.php index dde69362b..ae984aa18 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.420.5', + 'version' => '4.0.0-beta.420.6', 'helper_version' => '1.0.8', 'realtime_version' => '1.0.9', 'self_hosted' => env('SELF_HOSTED', true), diff --git a/versions.json b/versions.json index 8a962b51d..7b12bd23e 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.420.5" + "version": "4.0.0-beta.420.6" }, "nightly": { - "version": "4.0.0-beta.420.6" + "version": "4.0.0-beta.420.7" }, "helper": { "version": "1.0.8" From 316f233eb8b550d4e59e3fed68857f946077b3da Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 11 Jul 2025 19:26:42 +0200 Subject: [PATCH 004/202] feat(logging): add frequency filters for scheduled logs command to support hourly, daily, weekly, and monthly job views --- app/Console/Commands/ViewScheduledLogs.php | 77 +++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/ViewScheduledLogs.php b/app/Console/Commands/ViewScheduledLogs.php index 157afc7c1..9ecf90716 100644 --- a/app/Console/Commands/ViewScheduledLogs.php +++ b/app/Console/Commands/ViewScheduledLogs.php @@ -16,7 +16,12 @@ class ViewScheduledLogs extends Command {--backup-name= : Filter by backup name (partial match)} {--backup-id= : Filter by backup ID} {--errors : View error logs only} - {--all : View both normal and error logs}'; + {--all : View both normal and error logs} + {--hourly : Filter hourly jobs} + {--daily : Filter daily jobs} + {--weekly : Filter weekly jobs} + {--monthly : Filter monthly jobs} + {--frequency= : Filter by specific cron expression}'; protected $description = 'View scheduled backups and tasks logs with optional filtering'; @@ -175,9 +180,58 @@ class ViewScheduledLogs extends Command $filters[] = '"backup_id":'.preg_quote($backupId, '/'); } + // Frequency filters + if ($this->option('hourly')) { + $filters[] = $this->getFrequencyPattern('hourly'); + } + + if ($this->option('daily')) { + $filters[] = $this->getFrequencyPattern('daily'); + } + + if ($this->option('weekly')) { + $filters[] = $this->getFrequencyPattern('weekly'); + } + + if ($this->option('monthly')) { + $filters[] = $this->getFrequencyPattern('monthly'); + } + + if ($frequency = $this->option('frequency')) { + $filters[] = '"frequency":"'.preg_quote($frequency, '/').'"'; + } + return empty($filters) ? null : implode('|', $filters); } + private function getFrequencyPattern(string $type): string + { + $patterns = [ + 'hourly' => [ + '0 \* \* \* \*', // 0 * * * * + '@hourly', // @hourly + ], + 'daily' => [ + '0 0 \* \* \*', // 0 0 * * * + '@daily', // @daily + '@midnight', // @midnight + ], + 'weekly' => [ + '0 0 \* \* [0-6]', // 0 0 * * 0-6 (any day of week) + '@weekly', // @weekly + ], + 'monthly' => [ + '0 0 1 \* \*', // 0 0 1 * * (first of month) + '@monthly', // @monthly + ], + ]; + + $typePatterns = $patterns[$type] ?? []; + + // For grep, we need to match the frequency field in JSON + return '"frequency":"('.implode('|', $typePatterns).')"'; + } + private function getFilterDescription(): string { $descriptions = []; @@ -198,6 +252,27 @@ class ViewScheduledLogs extends Command $descriptions[] = "backup ID: {$backupId}"; } + // Frequency filters + if ($this->option('hourly')) { + $descriptions[] = 'hourly jobs'; + } + + if ($this->option('daily')) { + $descriptions[] = 'daily jobs'; + } + + if ($this->option('weekly')) { + $descriptions[] = 'weekly jobs'; + } + + if ($this->option('monthly')) { + $descriptions[] = 'monthly jobs'; + } + + if ($frequency = $this->option('frequency')) { + $descriptions[] = "frequency: {$frequency}"; + } + return empty($descriptions) ? '' : ' (filtered by '.implode(', ', $descriptions).')'; } } From 18f2e5ea32e62b3348d06071e795fecd83a04b40 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 12 Jul 2025 12:26:19 +0200 Subject: [PATCH 005/202] fix(scheduling): remove unnecessary padding from scheduled task form layout for improved UI consistency --- .../views/livewire/project/shared/scheduled-task/show.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/project/shared/scheduled-task/show.blade.php b/resources/views/livewire/project/shared/scheduled-task/show.blade.php index c32659ad8..1ede7775a 100644 --- a/resources/views/livewire/project/shared/scheduled-task/show.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/show.blade.php @@ -11,7 +11,7 @@
-
+

Scheduled Task

Save From b08c6b5b333da390b77c5924cb752153ef6a8c33 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 12 Jul 2025 14:04:01 +0200 Subject: [PATCH 006/202] fix(horizon): update queue configuration to use environment variable for dynamic queue management --- config/horizon.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/horizon.php b/config/horizon.php index 6086b30da..9dd80b8e4 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -182,7 +182,7 @@ return [ 'defaults' => [ 's6' => [ 'connection' => 'redis', - 'queue' => ['high', 'default'], + 'queue' => explode(',', env('HORIZON_QUEUES', 'high,default')), 'balance' => env('HORIZON_BALANCE', 'auto'), 'maxTime' => 0, 'maxJobs' => 0, From 0f5c98865868ba797dd008c86ea34d89a9a2b3ce Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 12 Jul 2025 14:44:32 +0200 Subject: [PATCH 007/202] fix(horizon): add silenced jobs --- app/Events/BackupCreated.php | 3 ++- app/Events/ServiceChecked.php | 3 ++- app/Jobs/PushServerUpdateJob.php | 3 ++- app/Jobs/ServerStorageCheckJob.php | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Events/BackupCreated.php b/app/Events/BackupCreated.php index bc1ecee0d..9670f5c3c 100644 --- a/app/Events/BackupCreated.php +++ b/app/Events/BackupCreated.php @@ -7,8 +7,9 @@ use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; +use Laravel\Horizon\Contracts\Silenced; -class BackupCreated implements ShouldBroadcast +class BackupCreated implements ShouldBroadcast, Silenced { use Dispatchable, InteractsWithSockets, SerializesModels; diff --git a/app/Events/ServiceChecked.php b/app/Events/ServiceChecked.php index 3f130a0fb..86a27a892 100644 --- a/app/Events/ServiceChecked.php +++ b/app/Events/ServiceChecked.php @@ -7,8 +7,9 @@ use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; +use Laravel\Horizon\Contracts\Silenced; -class ServiceChecked implements ShouldBroadcast +class ServiceChecked implements ShouldBroadcast, Silenced { use Dispatchable, InteractsWithSockets, SerializesModels; diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index 61206da6f..3e3aa1eb7 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -21,8 +21,9 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; +use Laravel\Horizon\Contracts\Silenced; -class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue +class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue, Silenced { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; diff --git a/app/Jobs/ServerStorageCheckJob.php b/app/Jobs/ServerStorageCheckJob.php index 9a8d86be1..9d45491c6 100644 --- a/app/Jobs/ServerStorageCheckJob.php +++ b/app/Jobs/ServerStorageCheckJob.php @@ -11,8 +11,9 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\RateLimiter; +use Laravel\Horizon\Contracts\Silenced; -class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue +class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue, Silenced { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; From 80fae306e625d1274119b5ae7905c3a097257a41 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 12 Jul 2025 14:44:44 +0200 Subject: [PATCH 008/202] feat(scheduling): introduce ScheduledJobManager and ServerResourceManager for enhanced job scheduling and resource management --- app/Jobs/ScheduledJobManager.php | 255 +++++++++++++++++++++++++++++ app/Jobs/ServerResourceManager.php | 168 +++++++++++++++++++ config/logging.php | 8 +- 3 files changed, 427 insertions(+), 4 deletions(-) create mode 100644 app/Jobs/ScheduledJobManager.php create mode 100644 app/Jobs/ServerResourceManager.php diff --git a/app/Jobs/ScheduledJobManager.php b/app/Jobs/ScheduledJobManager.php new file mode 100644 index 000000000..572491c0d --- /dev/null +++ b/app/Jobs/ScheduledJobManager.php @@ -0,0 +1,255 @@ +onQueue($this->determineQueue()); + } + + private function determineQueue(): string + { + $preferredQueue = 'crons'; + $fallbackQueue = 'high'; + + $configuredQueues = explode(',', env('HORIZON_QUEUES', 'high,default')); + + return in_array($preferredQueue, $configuredQueues) ? $preferredQueue : $fallbackQueue; + } + + /** + * Get the middleware the job should pass through. + */ + public function middleware(): array + { + return [ + (new WithoutOverlapping('scheduled-job-manager')) + ->releaseAfter(60), // Release the lock after 60 seconds if job fails + ]; + } + + public function handle(): void + { + // Freeze the execution time at the start of the job + $this->executionTime = Carbon::now(); + + Log::channel('scheduled')->info('ScheduledJobManager started', [ + 'execution_time' => $this->executionTime->format('Y-m-d H:i:s T'), + ]); + + // Process backups - don't let failures stop task processing + try { + $this->processScheduledBackups(); + } catch (\Exception $e) { + Log::channel('scheduled-errors')->error('Failed to process scheduled backups', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + } + + // Process tasks - don't let failures stop the job manager + try { + $this->processScheduledTasks(); + } catch (\Exception $e) { + Log::channel('scheduled-errors')->error('Failed to process scheduled tasks', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + } + + Log::channel('scheduled')->info('ScheduledJobManager completed'); + } + + private function processScheduledBackups(): void + { + $backups = ScheduledDatabaseBackup::with(['database']) + ->where('enabled', true) + ->get(); + + foreach ($backups as $backup) { + try { + // Apply the same filtering logic as the original + if (! $this->shouldProcessBackup($backup)) { + continue; + } + + $server = $backup->server(); + $serverTimezone = data_get($server->settings, 'server_timezone', config('app.timezone')); + + if (validate_timezone($serverTimezone) === false) { + $serverTimezone = config('app.timezone'); + } + + $frequency = $backup->frequency; + if (isset(VALID_CRON_STRINGS[$frequency])) { + $frequency = VALID_CRON_STRINGS[$frequency]; + } + + if ($this->shouldRunNow($frequency, $serverTimezone)) { + Log::channel('scheduled')->info('Dispatching backup job', [ + 'backup_id' => $backup->id, + 'backup_name' => $backup->name ?? 'unnamed', + 'server_name' => $server->name, + 'frequency' => $frequency, + 'timezone' => $serverTimezone, + 'execution_time' => $this->executionTime?->format('Y-m-d H:i:s T'), + 'current_time' => Carbon::now()->format('Y-m-d H:i:s T'), + ]); + + DatabaseBackupJob::dispatch($backup); + } + } catch (\Exception $e) { + Log::channel('scheduled-errors')->error('Error processing backup', [ + 'backup_id' => $backup->id, + 'error' => $e->getMessage(), + ]); + } + } + } + + private function processScheduledTasks(): void + { + $tasks = ScheduledTask::with(['service', 'application']) + ->where('enabled', true) + ->get(); + + foreach ($tasks as $task) { + try { + if (! $this->shouldProcessTask($task)) { + continue; + } + + $server = $task->server(); + $serverTimezone = data_get($server->settings, 'server_timezone', config('app.timezone')); + + if (validate_timezone($serverTimezone) === false) { + $serverTimezone = config('app.timezone'); + } + + $frequency = $task->frequency; + if (isset(VALID_CRON_STRINGS[$frequency])) { + $frequency = VALID_CRON_STRINGS[$frequency]; + } + + if ($this->shouldRunNow($frequency, $serverTimezone)) { + Log::channel('scheduled')->info('Dispatching task job', [ + 'task_id' => $task->id, + 'task_name' => $task->name ?? 'unnamed', + 'server_name' => $server->name, + 'frequency' => $frequency, + 'timezone' => $serverTimezone, + 'execution_time' => $this->executionTime?->format('Y-m-d H:i:s T'), + 'current_time' => Carbon::now()->format('Y-m-d H:i:s T'), + ]); + + ScheduledTaskJob::dispatch($task); + } + } catch (\Exception $e) { + Log::channel('scheduled-errors')->error('Error processing task', [ + 'task_id' => $task->id, + 'error' => $e->getMessage(), + ]); + } + } + } + + private function shouldProcessBackup(ScheduledDatabaseBackup $backup): bool + { + if (blank(data_get($backup, 'database'))) { + $backup->delete(); + + return false; + } + + $server = $backup->server(); + if (blank($server)) { + $backup->delete(); + + return false; + } + + if ($server->isFunctional() === false) { + return false; + } + + if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false && $server->team->id !== 0) { + return false; + } + + return true; + } + + private function shouldProcessTask(ScheduledTask $task): bool + { + $service = $task->service; + $application = $task->application; + + $server = $task->server(); + if (blank($server)) { + $task->delete(); + + return false; + } + + if ($server->isFunctional() === false) { + return false; + } + + if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false && $server->team->id !== 0) { + return false; + } + + if (! $service && ! $application) { + $task->delete(); + + return false; + } + + if ($application && str($application->status)->contains('running') === false) { + return false; + } + + if ($service && str($service->status)->contains('running') === false) { + return false; + } + + return true; + } + + private function shouldRunNow(string $frequency, string $timezone): bool + { + $cron = new CronExpression($frequency); + + // Use the frozen execution time, not the current time + // Fallback to current time if execution time is not set (shouldn't happen) + $baseTime = $this->executionTime ?? Carbon::now(); + $executionTime = $baseTime->copy()->setTimezone($timezone); + + return $cron->isDue($executionTime); + } +} diff --git a/app/Jobs/ServerResourceManager.php b/app/Jobs/ServerResourceManager.php new file mode 100644 index 000000000..9059bc6cb --- /dev/null +++ b/app/Jobs/ServerResourceManager.php @@ -0,0 +1,168 @@ +onQueue('high'); + } + + /** + * Get the middleware the job should pass through. + */ + public function middleware(): array + { + return [ + (new WithoutOverlapping('server-resource-manager')) + ->releaseAfter(60), + ]; + } + + public function handle(): void + { + // Freeze the execution time at the start of the job + $this->executionTime = Carbon::now(); + + $this->settings = instanceSettings(); + $this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone'); + + if (validate_timezone($this->instanceTimezone) === false) { + $this->instanceTimezone = config('app.timezone'); + } + + Log::channel('scheduled')->info('ServerResourceManager started', [ + 'execution_time' => $this->executionTime->format('Y-m-d H:i:s T'), + ]); + + // Process server checks - don't let failures stop the job + try { + $this->processServerChecks(); + } catch (\Exception $e) { + Log::channel('scheduled-errors')->error('Failed to process server checks', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + } + + Log::channel('scheduled')->info('ServerResourceManager completed'); + } + + private function processServerChecks(): void + { + $servers = $this->getServers(); + + foreach ($servers as $server) { + try { + $this->processServer($server); + } catch (\Exception $e) { + Log::channel('scheduled-errors')->error('Error processing server', [ + 'server_id' => $server->id, + 'server_name' => $server->name, + 'error' => $e->getMessage(), + ]); + } + } + } + + private function getServers() + { + $allServers = Server::where('ip', '!=', '1.2.3.4'); + + if (isCloud()) { + $servers = $allServers->whereRelation('team.subscription', 'stripe_invoice_paid', true)->get(); + $own = Team::find(0)->servers; + + return $servers->merge($own); + } else { + return $allServers->get(); + } + } + + private function processServer(Server $server): void + { + $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); + if (validate_timezone($serverTimezone) === false) { + $serverTimezone = config('app.timezone'); + } + + // Sentinel check + $lastSentinelUpdate = $server->sentinel_updated_at; + if (Carbon::parse($lastSentinelUpdate)->isBefore($this->executionTime->subSeconds($server->waitBeforeDoingSshCheck()))) { + // Dispatch ServerCheckJob if due + $checkFrequency = isCloud() ? '*/5 * * * *' : '* * * * *'; // Every 5 min for cloud, every minute for self-hosted + if ($this->shouldRunNow($checkFrequency, $serverTimezone)) { + ServerCheckJob::dispatch($server); + } + + // Dispatch ServerStorageCheckJob if due + $serverDiskUsageCheckFrequency = data_get($server->settings, 'server_disk_usage_check_frequency', '0 * * * *'); + if (isset(VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency])) { + $serverDiskUsageCheckFrequency = VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency]; + } + if ($this->shouldRunNow($serverDiskUsageCheckFrequency, $serverTimezone)) { + ServerStorageCheckJob::dispatch($server); + } + } + + // Dispatch DockerCleanupJob if due + $dockerCleanupFrequency = data_get($server->settings, 'docker_cleanup_frequency', '0 * * * *'); + if (isset(VALID_CRON_STRINGS[$dockerCleanupFrequency])) { + $dockerCleanupFrequency = VALID_CRON_STRINGS[$dockerCleanupFrequency]; + } + if ($this->shouldRunNow($dockerCleanupFrequency, $serverTimezone)) { + DockerCleanupJob::dispatch($server); + } + + // Dispatch ServerPatchCheckJob if due (weekly) + if ($this->shouldRunNow('0 0 * * 0', $serverTimezone)) { // Weekly on Sunday at midnight + ServerPatchCheckJob::dispatch($server); + } + + // Dispatch Sentinel restart if due (daily for Sentinel-enabled servers) + if ($server->isSentinelEnabled() && $this->shouldRunNow('0 0 * * *', $serverTimezone)) { + dispatch(function () use ($server) { + $server->restartContainer('coolify-sentinel'); + }); + } + } + + private function shouldRunNow(string $frequency, string $timezone): bool + { + $cron = new CronExpression($frequency); + + // Use the frozen execution time, not the current time + $baseTime = $this->executionTime ?? Carbon::now(); + $executionTime = $baseTime->copy()->setTimezone($timezone); + + return $cron->isDue($executionTime); + } +} diff --git a/config/logging.php b/config/logging.php index a804295fa..488327414 100644 --- a/config/logging.php +++ b/config/logging.php @@ -122,15 +122,15 @@ return [ 'scheduled' => [ 'driver' => 'daily', 'path' => storage_path('logs/scheduled.log'), - 'level' => env('LOG_LEVEL', 'debug'), - 'days' => 1, // Keep logs for 1 day only (truncated daily) + 'level' => 'debug', + 'days' => 1, ], 'scheduled-errors' => [ 'driver' => 'daily', 'path' => storage_path('logs/scheduled-errors.log'), - 'level' => 'error', - 'days' => 7, // Keep error logs for 7 days + 'level' => 'debug', + 'days' => 7, ], ], From fbe98cfd1189df3ec91710fa04de4db190d2ef60 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 12 Jul 2025 14:44:52 +0200 Subject: [PATCH 009/202] refactor(scheduling): replace deprecated job checks with ScheduledJobManager and ServerResourceManager for improved scheduling efficiency --- app/Console/Kernel.php | 249 ++--------------------------------------- 1 file changed, 8 insertions(+), 241 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 190e46961..eda2fca74 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -6,24 +6,16 @@ use App\Jobs\CheckAndStartSentinelJob; use App\Jobs\CheckForUpdatesJob; use App\Jobs\CheckHelperImageJob; use App\Jobs\CleanupInstanceStuffsJob; -use App\Jobs\DatabaseBackupJob; -use App\Jobs\DockerCleanupJob; use App\Jobs\PullTemplatesFromCDN; use App\Jobs\RegenerateSslCertJob; -use App\Jobs\ScheduledTaskJob; -use App\Jobs\ServerCheckJob; -use App\Jobs\ServerPatchCheckJob; -use App\Jobs\ServerStorageCheckJob; +use App\Jobs\ScheduledJobManager; +use App\Jobs\ServerResourceManager; use App\Jobs\UpdateCoolifyJob; use App\Models\InstanceSettings; -use App\Models\ScheduledDatabaseBackup; -use App\Models\ScheduledTask; use App\Models\Server; use App\Models\Team; -use Cron\CronExpression; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; -use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Log; class Kernel extends ConsoleKernel @@ -62,10 +54,10 @@ class Kernel extends ConsoleKernel $this->scheduleInstance->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer(); // Server Jobs - $this->checkResources(); + $this->scheduleInstance->job(new ServerResourceManager)->everyMinute()->onOneServer(); - $this->checkScheduledBackups(); - $this->checkScheduledTasks(); + // Scheduled Jobs (Backups & Tasks) + $this->scheduleInstance->job(new ScheduledJobManager)->everyMinute()->onOneServer(); $this->scheduleInstance->command('uploads:clear')->everyTwoMinutes(); @@ -80,12 +72,12 @@ class Kernel extends ConsoleKernel $this->scheduleUpdates(); // Server Jobs - $this->checkResources(); + $this->scheduleInstance->job(new ServerResourceManager)->everyMinute()->onOneServer(); $this->pullImages(); - $this->checkScheduledBackups(); - $this->checkScheduledTasks(); + // Scheduled Jobs (Backups & Tasks) + $this->scheduleInstance->job(new ScheduledJobManager)->everyMinute()->onOneServer(); $this->scheduleInstance->job(new RegenerateSslCertJob)->twiceDaily(); @@ -136,231 +128,6 @@ class Kernel extends ConsoleKernel } } - private function checkResources(): void - { - if (isCloud()) { - $servers = $this->allServers->whereRelation('team.subscription', 'stripe_invoice_paid', true)->get(); - $own = Team::find(0)->servers; - $servers = $servers->merge($own); - } else { - $servers = $this->allServers->get(); - } - - foreach ($servers as $server) { - try { - $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); - if (validate_timezone($serverTimezone) === false) { - $serverTimezone = config('app.timezone'); - } - - // Sentinel check - $lastSentinelUpdate = $server->sentinel_updated_at; - if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) { - // Check container status every minute if Sentinel does not activated - if (isCloud()) { - $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer(); - } else { - $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer(); - } - // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer(); - - $serverDiskUsageCheckFrequency = data_get($server->settings, 'server_disk_usage_check_frequency', '0 * * * *'); - if (isset(VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency])) { - $serverDiskUsageCheckFrequency = VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency]; - } - $this->scheduleInstance->job(new ServerStorageCheckJob($server))->cron($serverDiskUsageCheckFrequency)->timezone($serverTimezone)->onOneServer(); - } - - $dockerCleanupFrequency = data_get($server->settings, 'docker_cleanup_frequency', '0 * * * *'); - if (isset(VALID_CRON_STRINGS[$dockerCleanupFrequency])) { - $dockerCleanupFrequency = VALID_CRON_STRINGS[$dockerCleanupFrequency]; - } - $this->scheduleInstance->job(new DockerCleanupJob($server))->cron($dockerCleanupFrequency)->timezone($serverTimezone)->onOneServer(); - - // Server patch check - weekly - $this->scheduleInstance->job(new ServerPatchCheckJob($server))->weekly()->timezone($serverTimezone)->onOneServer(); - - // Cleanup multiplexed connections every hour - // $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer(); - - // Temporary solution until we have better memory management for Sentinel - if ($server->isSentinelEnabled()) { - $this->scheduleInstance->job(function () use ($server) { - $server->restartContainer('coolify-sentinel'); - })->daily()->onOneServer(); - } - } catch (\Exception $e) { - Log::error('Error checking resources: '.$e->getMessage()); - } - } - } - - private function checkScheduledBackups(): void - { - $scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get(); - - if ($scheduled_backups->isEmpty()) { - return; - } - $finalScheduledBackups = collect(); - foreach ($scheduled_backups as $scheduled_backup) { - if (blank(data_get($scheduled_backup, 'database'))) { - $scheduled_backup->delete(); - - continue; - } - $server = $scheduled_backup->server(); - if (blank($server)) { - $scheduled_backup->delete(); - - continue; - } - if ($server->isFunctional() === false) { - continue; - } - if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false && $server->team->id !== 0) { - continue; - } - $finalScheduledBackups->push($scheduled_backup); - } - - foreach ($finalScheduledBackups as $scheduled_backup) { - try { - if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { - $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; - } - $server = $scheduled_backup->server(); - $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); - - if (validate_timezone($serverTimezone) === false) { - $serverTimezone = config('app.timezone'); - } - - if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { - $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; - } - $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); - - // Check if the backup should run now - $cron = new CronExpression($scheduled_backup->frequency); - $now = Carbon::now($serverTimezone); - - if ($cron->isDue($now)) { - Log::channel('scheduled')->info('Backup job running now', [ - 'backup_id' => $scheduled_backup->id, - 'backup_name' => $scheduled_backup->name ?? 'unnamed', - 'server_name' => $server->name, - 'frequency' => $scheduled_backup->frequency, - 'timezone' => $serverTimezone, - 'current_time' => $now->format('Y-m-d H:i:s T'), - ]); - } - - $this->scheduleInstance->job(new DatabaseBackupJob( - backup: $scheduled_backup - ))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer(); - - } catch (\Exception $e) { - Log::channel('scheduled-errors')->error('Error scheduling backup', [ - 'backup_id' => $scheduled_backup->id, - 'backup_name' => $scheduled_backup->name ?? 'unnamed', - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString(), - ]); - Log::error('Error scheduling backup: '.$e->getMessage()); - Log::error($e->getTraceAsString()); - } - } - } - - private function checkScheduledTasks(): void - { - $scheduled_tasks = ScheduledTask::where('enabled', true)->get(); - - if ($scheduled_tasks->isEmpty()) { - return; - } - $finalScheduledTasks = collect(); - foreach ($scheduled_tasks as $scheduled_task) { - $service = $scheduled_task->service; - $application = $scheduled_task->application; - - $server = $scheduled_task->server(); - if (blank($server)) { - $scheduled_task->delete(); - - continue; - } - - if ($server->isFunctional() === false) { - continue; - } - - if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false && $server->team->id !== 0) { - continue; - } - - if (! $service && ! $application) { - $scheduled_task->delete(); - - continue; - } - - if ($application && str($application->status)->contains('running') === false) { - continue; - } - if ($service && str($service->status)->contains('running') === false) { - continue; - } - - $finalScheduledTasks->push($scheduled_task); - } - - foreach ($finalScheduledTasks as $scheduled_task) { - try { - $server = $scheduled_task->server(); - if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { - $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; - } - $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); - - if (validate_timezone($serverTimezone) === false) { - $serverTimezone = config('app.timezone'); - } - - // Check if the task should run now - $cron = new CronExpression($scheduled_task->frequency); - $now = Carbon::now($serverTimezone); - - if ($cron->isDue($now)) { - Log::channel('scheduled')->info('Task job running now', [ - 'task_id' => $scheduled_task->id, - 'task_name' => $scheduled_task->name ?? 'unnamed', - 'server_name' => $server->name, - 'frequency' => $scheduled_task->frequency, - 'timezone' => $serverTimezone, - 'type' => $scheduled_task->service ? 'service' : 'application', - 'current_time' => $now->format('Y-m-d H:i:s T'), - ]); - } - - $this->scheduleInstance->job(new ScheduledTaskJob( - task: $scheduled_task - ))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer(); - - } catch (\Exception $e) { - Log::channel('scheduled-errors')->error('Error scheduling task', [ - 'task_id' => $scheduled_task->id, - 'task_name' => $scheduled_task->name ?? 'unnamed', - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString(), - ]); - Log::error('Error scheduling task: '.$e->getMessage()); - Log::error($e->getTraceAsString()); - } - } - } - protected function commands(): void { $this->load(__DIR__.'/Commands'); From 0b847928717f8c19f2d084852757e0a5f5afe188 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Jul 2025 12:36:53 +0200 Subject: [PATCH 010/202] feat(deployment): add pull request filtering and pagination to deployment and backup execution components fix(ui): make them more stylish yeah --- CLAUDE.md | 87 ++++++++ .../Project/Application/Deployment/Index.php | 105 +++++++--- .../Project/Database/BackupExecutions.php | 95 ++++++++- app/Models/Application.php | 7 +- app/Models/ScheduledDatabaseBackup.php | 12 ++ .../application/deployment/index.blade.php | 45 +++-- .../database/backup-executions.blade.php | 30 ++- .../database/scheduled-backups.blade.php | 185 ++++++++++++++++-- 8 files changed, 493 insertions(+), 73 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..a3bb31cee --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,87 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Coolify is an open-source, self-hostable platform for deploying applications and managing servers - an alternative to Heroku/Netlify/Vercel. It's built with Laravel (PHP) and uses Docker for containerization. + +## Development Commands + +### Frontend Development +- `npm run dev` - Start Vite development server for frontend assets +- `npm run build` - Build frontend assets for production + +### Backend Development +- `php artisan serve` - Start Laravel development server +- `php artisan migrate` - Run database migrations +- `php artisan queue:work` - Start queue worker for background jobs +- `php artisan horizon` - Start Laravel Horizon for queue monitoring +- `php artisan tinker` - Start interactive PHP REPL + +### Code Quality +- `./vendor/bin/pint` - Run Laravel Pint for code formatting +- `./vendor/bin/phpstan` - Run PHPStan for static analysis +- `./vendor/bin/pest` - Run Pest tests + +## Architecture Overview + +### Technology Stack +- **Backend**: Laravel 12 (PHP 8.4) +- **Frontend**: Livewire + Alpine.js + Tailwind CSS +- **Database**: PostgreSQL 15 +- **Cache/Queue**: Redis 7 +- **Real-time**: Soketi (WebSocket server) +- **Containerization**: Docker & Docker Compose + +### Key Components + +#### Core Models +- `Application` - Deployed applications with Git integration +- `Server` - Remote servers managed by Coolify +- `Service` - Docker Compose services +- `Database` - Standalone database instances (PostgreSQL, MySQL, MongoDB, Redis, etc.) +- `Team` - Multi-tenancy support +- `Project` - Grouping of environments and resources + +#### Job System +- Uses Laravel Horizon for queue management +- Key jobs: `ApplicationDeploymentJob`, `ServerCheckJob`, `DatabaseBackupJob` +- `ScheduledJobManager` and `ServerResourceManager` handle job scheduling + +#### Deployment Flow +1. Git webhook triggers deployment +2. `ApplicationDeploymentJob` handles build and deployment +3. Docker containers are managed on target servers +4. Proxy configuration (Nginx/Traefik) is updated + +#### Server Management +- SSH-based server communication via `ExecuteRemoteCommand` trait +- Docker installation and management +- Proxy configuration generation +- Resource monitoring and cleanup + +### Directory Structure +- `app/Actions/` - Domain-specific actions (Application, Database, Server, etc.) +- `app/Jobs/` - Background queue jobs +- `app/Livewire/` - Frontend components (full-stack with Livewire) +- `app/Models/` - Eloquent models +- `bootstrap/helpers/` - Helper functions for various domains +- `database/migrations/` - Database schema evolution + +## Development Guidelines + +### Code Organization +- Use Actions pattern for complex business logic +- Livewire components handle UI and user interactions +- Jobs handle asynchronous operations +- Traits provide shared functionality (e.g., `ExecuteRemoteCommand`) + +### Testing +- Uses Pest for testing framework +- Tests located in `tests/` directory + +### Deployment and Docker +- Applications are deployed using Docker containers +- Configuration generated dynamically based on application settings +- Supports multiple deployment targets and proxy configurations \ No newline at end of file diff --git a/app/Livewire/Project/Application/Deployment/Index.php b/app/Livewire/Project/Application/Deployment/Index.php index c957615ac..5b621cb95 100644 --- a/app/Livewire/Project/Application/Deployment/Index.php +++ b/app/Livewire/Project/Application/Deployment/Index.php @@ -18,11 +18,13 @@ class Index extends Component public int $skip = 0; - public int $default_take = 10; + public int $defaultTake = 10; - public bool $show_next = false; + public bool $showNext = false; - public bool $show_prev = false; + public bool $showPrev = false; + + public int $currentPage = 1; public ?string $pull_request_id = null; @@ -51,68 +53,111 @@ class Index extends Component if (! $application) { return redirect()->route('dashboard'); } - ['deployments' => $deployments, 'count' => $count] = $application->deployments(0, $this->default_take); + // Validate pull request ID from URL parameters + if ($this->pull_request_id !== null && $this->pull_request_id !== '') { + if (! is_numeric($this->pull_request_id) || (float) $this->pull_request_id <= 0 || (float) $this->pull_request_id != (int) $this->pull_request_id) { + $this->pull_request_id = null; + $this->dispatch('error', 'Invalid Pull Request ID in URL. Filter cleared.'); + } else { + // Ensure it's stored as a string representation of a positive integer + $this->pull_request_id = (string) (int) $this->pull_request_id; + } + } + + ['deployments' => $deployments, 'count' => $count] = $application->deployments(0, $this->defaultTake, $this->pull_request_id); $this->application = $application; $this->deployments = $deployments; $this->deployments_count = $count; $this->current_url = url()->current(); - $this->show_pull_request_only(); - $this->show_more(); + $this->updateCurrentPage(); + $this->showMore(); } - private function show_pull_request_only() - { - if ($this->pull_request_id) { - $this->deployments = $this->deployments->where('pull_request_id', $this->pull_request_id); - } - } - - private function show_more() + private function showMore() { if ($this->deployments->count() !== 0) { - $this->show_next = true; - if ($this->deployments->count() < $this->default_take) { - $this->show_next = false; + $this->showNext = true; + if ($this->deployments->count() < $this->defaultTake) { + $this->showNext = false; } return; } } - public function reload_deployments() + public function reloadDeployments() { - $this->load_deployments(); + $this->loadDeployments(); } - public function previous_page(?int $take = null) + public function previousPage(?int $take = null) { if ($take) { $this->skip = $this->skip - $take; } - $this->skip = $this->skip - $this->default_take; + $this->skip = $this->skip - $this->defaultTake; if ($this->skip < 0) { - $this->show_prev = false; + $this->showPrev = false; $this->skip = 0; } - $this->load_deployments(); + $this->updateCurrentPage(); + $this->loadDeployments(); } - public function next_page(?int $take = null) + public function nextPage(?int $take = null) { if ($take) { $this->skip = $this->skip + $take; } - $this->show_prev = true; - $this->load_deployments(); + $this->showPrev = true; + $this->updateCurrentPage(); + $this->loadDeployments(); } - public function load_deployments() + public function loadDeployments() { - ['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $this->default_take); + ['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $this->defaultTake, $this->pull_request_id); $this->deployments = $deployments; $this->deployments_count = $count; - $this->show_pull_request_only(); - $this->show_more(); + $this->showMore(); + } + + public function updatedPullRequestId($value) + { + // Sanitize and validate the pull request ID + if ($value !== null && $value !== '') { + // Check if it's numeric and positive + if (! is_numeric($value) || (float) $value <= 0 || (float) $value != (int) $value) { + $this->pull_request_id = null; + $this->dispatch('error', 'Invalid Pull Request ID. Please enter a valid positive number.'); + + return; + } + // Ensure it's stored as a string representation of a positive integer + $this->pull_request_id = (string) (int) $value; + } else { + $this->pull_request_id = null; + } + + // Reset pagination when filter changes + $this->skip = 0; + $this->showPrev = false; + $this->updateCurrentPage(); + $this->loadDeployments(); + } + + public function clearFilter() + { + $this->pull_request_id = null; + $this->skip = 0; + $this->showPrev = false; + $this->updateCurrentPage(); + $this->loadDeployments(); + } + + private function updateCurrentPage() + { + $this->currentPage = intval($this->skip / $this->defaultTake) + 1; } public function render() diff --git a/app/Livewire/Project/Database/BackupExecutions.php b/app/Livewire/Project/Database/BackupExecutions.php index f96ca9a6a..2f3aae8cf 100644 --- a/app/Livewire/Project/Database/BackupExecutions.php +++ b/app/Livewire/Project/Database/BackupExecutions.php @@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database; use App\Models\InstanceSettings; use App\Models\ScheduledDatabaseBackup; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Livewire\Component; @@ -14,7 +15,19 @@ class BackupExecutions extends Component public $database; - public $executions = []; + public ?Collection $executions; + + public int $executions_count = 0; + + public int $skip = 0; + + public int $defaultTake = 10; + + public bool $showNext = false; + + public bool $showPrev = false; + + public int $currentPage = 1; public $setDeletableBackup; @@ -40,6 +53,20 @@ class BackupExecutions extends Component } } + public function cleanupDeleted() + { + if ($this->backup) { + $deletedCount = $this->backup->executions()->where('local_storage_deleted', true)->count(); + if ($deletedCount > 0) { + $this->backup->executions()->where('local_storage_deleted', true)->delete(); + $this->refreshBackupExecutions(); + $this->dispatch('success', "Cleaned up {$deletedCount} backup entries deleted from local storage."); + } else { + $this->dispatch('info', 'No backup entries found that are deleted from local storage.'); + } + } + } + public function deleteBackup($executionId, $password) { if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) { @@ -85,18 +112,74 @@ class BackupExecutions extends Component public function refreshBackupExecutions(): void { - if ($this->backup && $this->backup->exists) { - $this->executions = $this->backup->executions()->get()->toArray(); - } else { - $this->executions = []; + $this->loadExecutions(); + } + + public function reloadExecutions() + { + $this->loadExecutions(); + } + + public function previousPage(?int $take = null) + { + if ($take) { + $this->skip = $this->skip - $take; } + $this->skip = $this->skip - $this->defaultTake; + if ($this->skip < 0) { + $this->showPrev = false; + $this->skip = 0; + } + $this->updateCurrentPage(); + $this->loadExecutions(); + } + + public function nextPage(?int $take = null) + { + if ($take) { + $this->skip = $this->skip + $take; + } + $this->showPrev = true; + $this->updateCurrentPage(); + $this->loadExecutions(); + } + + private function loadExecutions() + { + if ($this->backup && $this->backup->exists) { + ['executions' => $executions, 'count' => $count] = $this->backup->executionsPaginated($this->skip, $this->defaultTake); + $this->executions = $executions; + $this->executions_count = $count; + } else { + $this->executions = collect([]); + $this->executions_count = 0; + } + $this->showMore(); + } + + private function showMore() + { + if ($this->executions->count() !== 0) { + $this->showNext = true; + if ($this->executions->count() < $this->defaultTake) { + $this->showNext = false; + } + + return; + } + } + + private function updateCurrentPage() + { + $this->currentPage = intval($this->skip / $this->defaultTake) + 1; } public function mount(ScheduledDatabaseBackup $backup) { $this->backup = $backup; $this->database = $backup->database; - $this->refreshBackupExecutions(); + $this->updateCurrentPage(); + $this->loadExecutions(); } public function server() diff --git a/app/Models/Application.php b/app/Models/Application.php index f3f063d19..2464429e2 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -836,9 +836,14 @@ class Application extends BaseModel return ApplicationDeploymentQueue::where('application_id', $this->id)->where('created_at', '>=', now()->subDays(7))->orderBy('created_at', 'desc')->get(); } - public function deployments(int $skip = 0, int $take = 10) + public function deployments(int $skip = 0, int $take = 10, ?string $pullRequestId = null) { $deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->orderBy('created_at', 'desc'); + + if ($pullRequestId) { + $deployments = $deployments->where('pull_request_id', $pullRequestId); + } + $count = $deployments->count(); $deployments = $deployments->skip($skip)->take($take)->get(); diff --git a/app/Models/ScheduledDatabaseBackup.php b/app/Models/ScheduledDatabaseBackup.php index 473fc7b4b..90204d8df 100644 --- a/app/Models/ScheduledDatabaseBackup.php +++ b/app/Models/ScheduledDatabaseBackup.php @@ -36,6 +36,18 @@ class ScheduledDatabaseBackup extends BaseModel return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get(); } + public function executionsPaginated(int $skip = 0, int $take = 10) + { + $executions = $this->hasMany(ScheduledDatabaseBackupExecution::class)->orderBy('created_at', 'desc'); + $count = $executions->count(); + $executions = $executions->skip($skip)->take($take)->get(); + + return [ + 'count' => $count, + 'executions' => $executions, + ]; + } + public function server() { if ($this->database) { diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index 096af2878..f8881b736 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -3,31 +3,36 @@

Deployments

-
+

Deployments ({{ $deployments_count }})

@if ($deployments_count > 0) - - - - - - - - - - +
+ + + + + + + Page {{ $currentPage }} of {{ ceil($deployments_count / $defaultTake) }} + + + + + + +
@endif
- @if ($deployments_count > 0) - - - Filter - - @endif +
+ + Filter + @if ($pull_request_id) + Clear + @endif +
@forelse ($deployments as $deployment)
@isset($backup)
-

Executions

+

Executions ({{ $executions_count }})

+ @if ($executions_count > 0) +
+ + + + + + + Page {{ $currentPage }} of {{ ceil($executions_count / $defaultTake) }} + + + + + + +
+ @endif Cleanup Failed Backups +
-
+
@forelse($executions as $execution)
scheduledBackups as $backup) @if ($type == 'database') - + $backup->latest_log && + data_get($backup->latest_log, 'status') === 'running', + 'border-error' => + $backup->latest_log && + data_get($backup->latest_log, 'status') === 'failed', + 'border-success' => + $backup->latest_log && + data_get($backup->latest_log, 'status') === 'success', + 'border-gray-200 dark:border-coolgray-300' => !$backup->latest_log, + ]) href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}"> -
-
Frequency: {{ $backup->frequency }} - ({{ data_get($backup->server(), 'settings.server_timezone', 'Instance timezone') }}) + @if ($backup->latest_log && data_get($backup->latest_log, 'status') === 'running') +
+
-
Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}
+ @endif +
+ @if ($backup->latest_log) + + data_get($backup->latest_log, 'status') === 'running', + 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-200 dark:shadow-red-900/5' => + data_get($backup->latest_log, 'status') === 'failed', + 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200 dark:shadow-green-900/5' => + data_get($backup->latest_log, 'status') === 'success', + ])> + @php + $statusText = match (data_get($backup->latest_log, 'status')) { + 'success' => 'Success', + 'running' => 'In Progress', + 'failed' => 'Failed', + default => ucfirst(data_get($backup->latest_log, 'status')), + }; + @endphp + {{ $statusText }} + + @else + + No executions yet + + @endif +

{{ $backup->frequency }}

+
+
+ @if ($backup->latest_log) + Started: + {{ formatDateInServerTimezone(data_get($backup->latest_log, 'created_at'), $backup->server()) }} + @if (data_get($backup->latest_log, 'status') !== 'running') +
Ended: + {{ formatDateInServerTimezone(data_get($backup->latest_log, 'finished_at'), $backup->server()) }} +
Duration: + {{ calculateDuration(data_get($backup->latest_log, 'created_at'), data_get($backup->latest_log, 'finished_at')) }} +
Finished + {{ \Carbon\Carbon::parse(data_get($backup->latest_log, 'finished_at'))->diffForHumans() }} + @endif + @if ($backup->save_s3) +
S3 Storage: Enabled + @endif + @if (data_get($backup->latest_log, 'status') === 'success') + @php + $size = data_get($backup->latest_log, 'size', 0); + $sizeFormatted = + $size > 0 ? number_format($size / 1024 / 1024, 2) . ' MB' : 'Unknown'; + @endphp +
Last Backup Size: {{ $sizeFormatted }} + @endif + @else + Last Run: Never +
Total Executions: 0 + @if ($backup->save_s3) +
S3 Storage: Enabled + @endif + @endif
@else -
-
- data_get($backup, 'id') === data_get($selectedBackup, 'id'), - 'flex flex-col border-l-2 border-transparent', - ])> -
Frequency: {{ $backup->frequency }} - ({{ data_get($backup->server(), 'settings.server_timezone', 'Instance timezone') }}) +
+ data_get($backup, 'id') === data_get($selectedBackup, 'id'), + 'border-blue-500/50 border-dashed' => + $backup->latest_log && + data_get($backup->latest_log, 'status') === 'running', + 'border-error' => + $backup->latest_log && + data_get($backup->latest_log, 'status') === 'failed', + 'border-success' => + $backup->latest_log && + data_get($backup->latest_log, 'status') === 'success', + 'border-gray-200 dark:border-coolgray-300' => !$backup->latest_log, + 'border-coollabs' => + data_get($backup, 'id') === data_get($selectedBackup, 'id'), + ]) wire:click="setSelectedBackup('{{ data_get($backup, 'id') }}')"> + @if ($backup->latest_log && data_get($backup->latest_log, 'status') === 'running') +
+
-
Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}
+ @endif +
+ @if ($backup->latest_log) + + data_get($backup->latest_log, 'status') === 'running', + 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-200 dark:shadow-red-900/5' => + data_get($backup->latest_log, 'status') === 'failed', + 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200 dark:shadow-green-900/5' => + data_get($backup->latest_log, 'status') === 'success', + ])> + @php + $statusText = match (data_get($backup->latest_log, 'status')) { + 'success' => 'Success', + 'running' => 'In Progress', + 'failed' => 'Failed', + default => ucfirst(data_get($backup->latest_log, 'status')), + }; + @endphp + {{ $statusText }} + + @else + + No executions yet + + @endif +

{{ $backup->frequency }} Backup

+
+
+ @if ($backup->latest_log) + Started: + {{ formatDateInServerTimezone(data_get($backup->latest_log, 'created_at'), $backup->server()) }} + @if (data_get($backup->latest_log, 'status') !== 'running') +
Ended: + {{ formatDateInServerTimezone(data_get($backup->latest_log, 'finished_at'), $backup->server()) }} +
Duration: + {{ calculateDuration(data_get($backup->latest_log, 'created_at'), data_get($backup->latest_log, 'finished_at')) }} +
Finished + {{ \Carbon\Carbon::parse(data_get($backup->latest_log, 'finished_at'))->diffForHumans() }} + @endif +

Total Executions: {{ $backup->executions()->count() }} + @if ($backup->save_s3) +
S3 Storage: Enabled + @endif + @php + $successCount = $backup->executions()->where('status', 'success')->count(); + $totalCount = $backup->executions()->count(); + $successRate = $totalCount > 0 ? round(($successCount / $totalCount) * 100) : 0; + @endphp + @if ($totalCount > 0) +
Success Rate: $successRate >= 80, + 'text-yellow-600' => $successRate >= 50 && $successRate < 80, + 'text-red-600' => $successRate < 50, + ])>{{ $successRate }}% + ({{ $successCount }}/{{ $totalCount }}) + @endif + @if (data_get($backup->latest_log, 'status') === 'success') + @php + $size = data_get($backup->latest_log, 'size', 0); + $sizeFormatted = + $size > 0 ? number_format($size / 1024 / 1024, 2) . ' MB' : 'Unknown'; + @endphp +
Last Backup Size: {{ $sizeFormatted }} + @endif + @else + Last Run: Never +
Total Executions: 0 + @if ($backup->save_s3) +
S3 Storage: Enabled + @endif + @endif
@endif From 3bb28c29be58cec16ba166073fe161ab14e48178 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:45:01 +0200 Subject: [PATCH 011/202] fix(application): sanitize service names for HTML form binding and ensure original names are stored in docker compose domains --- app/Livewire/Project/Application/General.php | 50 +++++++++++++++++-- .../project/application/general.blade.php | 2 +- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 74f47232c..3e89ad663 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -156,6 +156,14 @@ class General extends Component $this->application->settings->save(); } $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; + // Convert service names with dots to use underscores for HTML form binding + $sanitizedDomains = []; + foreach ($this->parsedServiceDomains as $serviceName => $domain) { + $sanitizedKey = str($serviceName)->slug('_')->toString(); + $sanitizedDomains[$sanitizedKey] = $domain; + } + $this->parsedServiceDomains = $sanitizedDomains; + $this->ports_exposes = $this->application->ports_exposes; $this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled; $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; @@ -242,8 +250,26 @@ class General extends Component { $uuid = new Cuid2; $domain = generateFqdn($this->application->destination->server, $uuid); - $this->parsedServiceDomains[$serviceName]['domain'] = $domain; - $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); + $sanitizedKey = str($serviceName)->slug('_')->toString(); + $this->parsedServiceDomains[$sanitizedKey]['domain'] = $domain; + + // Convert back to original service names for storage + $originalDomains = []; + foreach ($this->parsedServiceDomains as $key => $value) { + // Find the original service name by checking parsed services + $originalServiceName = $key; + if (isset($this->parsedServices['services'])) { + foreach ($this->parsedServices['services'] as $originalName => $service) { + if (str($originalName)->slug('_')->toString() === $key) { + $originalServiceName = $originalName; + break; + } + } + } + $originalDomains[$originalServiceName] = $value; + } + + $this->application->docker_compose_domains = json_encode($originalDomains); $this->application->save(); $this->dispatch('success', 'Domain generated.'); if ($this->application->build_pack === 'dockercompose') { @@ -429,9 +455,25 @@ class General extends Component $this->application->publish_directory = rtrim($this->application->publish_directory, '/'); } if ($this->application->build_pack === 'dockercompose') { - $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); + // Convert sanitized service names back to original names for storage + $originalDomains = []; + foreach ($this->parsedServiceDomains as $key => $value) { + // Find the original service name by checking parsed services + $originalServiceName = $key; + if (isset($this->parsedServices['services'])) { + foreach ($this->parsedServices['services'] as $originalName => $service) { + if (str($originalName)->slug('_')->toString() === $key) { + $originalServiceName = $originalName; + break; + } + } + } + $originalDomains[$originalServiceName] = $value; + } - foreach ($this->parsedServiceDomains as $serviceName => $service) { + $this->application->docker_compose_domains = json_encode($originalDomains); + + foreach ($originalDomains as $serviceName => $service) { $domain = data_get($service, 'domain'); if ($domain) { if (! validate_dns_entry($domain, $this->application->destination->server)) { diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 39102c39b..03f26fd04 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -51,7 +51,7 @@ + id="parsedServiceDomains.{{ str($serviceName)->slug('_') }}.domain"> Generate Domain
From 76766017b04fa2a3b41db873fb1391d4efd4611a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:53:34 +0200 Subject: [PATCH 012/202] fix(previews): adjust padding for rate limit message in application previews --- resources/views/livewire/project/application/previews.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 37f7e35b5..8bca6b4be 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -13,7 +13,7 @@
@endif @isset($rate_limit_remaining) -
Requests remaining till rate limited by Git: {{ $rate_limit_remaining }}
+
Requests remaining till rate limited by Git: {{ $rate_limit_remaining }}
@endisset
@if ($pull_requests->count() > 0) From e5a0cdf3b71a8112c5cd099aed18ea101a8161be Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:22:02 +0200 Subject: [PATCH 013/202] fix(previews): order application previews by pull request ID in descending order --- app/Models/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index 2464429e2..f304debad 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -798,7 +798,7 @@ class Application extends BaseModel public function previews() { - return $this->hasMany(ApplicationPreview::class); + return $this->hasMany(ApplicationPreview::class)->orderBy('pull_request_id', 'desc'); } public function deployment_queue() From 98ceec3b7e6faecef6adc0d8ad2ee2fea8f31596 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:22:12 +0200 Subject: [PATCH 014/202] fix(previews): add unique wire keys for preview containers and services based on pull request ID --- .../views/livewire/project/application/previews.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 8bca6b4be..4fec0f48f 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -66,7 +66,7 @@

Deployments

@foreach (data_get($application, 'previews') as $previewName => $preview) -
+
PR #{{ data_get($preview, 'pull_request_id') }} | @if (str(data_get($preview, 'status'))->startsWith('running')) @@ -100,7 +100,7 @@ @else @foreach (collect(json_decode($preview->docker_compose_domains)) as $serviceName => $service) - @endforeach @endif From a9e542bc4beb5763c32ba86d2bb0eb455820c86d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:42:01 +0200 Subject: [PATCH 015/202] fix(previews): enhance domain generation logic for application previews, ensuring unique domains are created when none are set --- .../Project/Application/PreviewsCompose.php | 35 ++++++++++++++----- app/Models/ApplicationPreview.php | 29 +++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/app/Livewire/Project/Application/PreviewsCompose.php b/app/Livewire/Project/Application/PreviewsCompose.php index b3e838bb3..334d96cad 100644 --- a/app/Livewire/Project/Application/PreviewsCompose.php +++ b/app/Livewire/Project/Application/PreviewsCompose.php @@ -38,9 +38,25 @@ class PreviewsCompose extends Component $domain = $domains->first(function ($_, $key) { return $key === $this->serviceName; }); - if ($domain) { - $domain = data_get($domain, 'domain'); - $url = Url::fromString($domain); + + $domain_string = data_get($domain, 'domain'); + + // If no domain is set in the main application, generate a default domain + if (empty($domain_string)) { + $server = $this->preview->application->destination->server; + $template = $this->preview->application->preview_url_template; + $random = new Cuid2; + + // Generate a unique domain like main app services do + $generated_fqdn = generateFqdn($server, $random); + + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', str($generated_fqdn)->after('://'), $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn); + $preview_fqdn = str($generated_fqdn)->before('://').'://'.$preview_fqdn; + } else { + // Use the existing domain from the main application + $url = Url::fromString($domain_string); $template = $this->preview->application->preview_url_template; $host = $url->getHost(); $schema = $url->getScheme(); @@ -49,12 +65,15 @@ class PreviewsCompose extends Component $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); $preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn); $preview_fqdn = "$schema://$preview_fqdn"; - $docker_compose_domains = data_get($this->preview, 'docker_compose_domains'); - $docker_compose_domains = json_decode($docker_compose_domains, true); - $docker_compose_domains[$this->serviceName]['domain'] = $this->service->domain = $preview_fqdn; - $this->preview->docker_compose_domains = json_encode($docker_compose_domains); - $this->preview->save(); } + + // Save the generated domain + $docker_compose_domains = data_get($this->preview, 'docker_compose_domains'); + $docker_compose_domains = json_decode($docker_compose_domains, true); + $docker_compose_domains[$this->serviceName]['domain'] = $this->service->domain = $preview_fqdn; + $this->preview->docker_compose_domains = json_encode($docker_compose_domains); + $this->preview->save(); + $this->dispatch('update_links'); $this->dispatch('success', 'Domain generated.'); } diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index c635f146a..7bf45de85 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -56,8 +56,34 @@ class ApplicationPreview extends BaseModel $docker_compose_domains = data_get($this, 'docker_compose_domains'); $docker_compose_domains = json_decode($docker_compose_domains, true) ?? []; + // Get all services from the parsed compose file to ensure all services have entries + $parsedServices = $this->application->parse(pull_request_id: $this->pull_request_id); + if (isset($parsedServices['services'])) { + foreach ($parsedServices['services'] as $serviceName => $service) { + if (! isDatabaseImage(data_get($service, 'image'))) { + // Remove PR suffix from service name to get original service name + $originalServiceName = str($serviceName)->replaceLast('-pr-'.$this->pull_request_id, '')->toString(); + + // Ensure all services have an entry, even if empty + if (! $services->has($originalServiceName)) { + $services->put($originalServiceName, ['domain' => '']); + } + } + } + } + foreach ($services as $service_name => $service_config) { $domain_string = data_get($service_config, 'domain'); + + // If domain string is empty or null, don't auto-generate domain + // Only generate domains when main app already has domains set + if (empty($domain_string)) { + // Ensure service has an empty domain entry for form binding + $docker_compose_domains[$service_name]['domain'] = ''; + + continue; + } + $service_domains = str($domain_string)->explode(',')->map(fn ($d) => trim($d)); $preview_domains = []; @@ -80,6 +106,9 @@ class ApplicationPreview extends BaseModel if (! empty($preview_domains)) { $docker_compose_domains[$service_name]['domain'] = implode(',', $preview_domains); + } else { + // Ensure service has an empty domain entry for form binding + $docker_compose_domains[$service_name]['domain'] = ''; } } From 8a107b3c4bcd37ea3aa21eec2de2db8e2efa086f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:47:16 +0200 Subject: [PATCH 016/202] fix(previews): refine preview domain generation for Docker Compose applications, ensuring correct method usage based on build pack type --- app/Jobs/ApplicationDeploymentJob.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index bc5fab30c..708d2f15d 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -229,7 +229,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue // Set preview fqdn if ($this->pull_request_id !== 0) { - $this->preview = $this->application->generate_preview_fqdn($this->pull_request_id); + if ($this->application->build_pack === 'dockercompose') { + // For Docker Compose apps, use the preview model's compose-specific method + $this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); + if ($this->preview) { + $this->preview->generate_preview_fqdn_compose(); + } + } else { + // For non-Docker Compose apps, use the application model's method + $this->preview = $this->application->generate_preview_fqdn($this->pull_request_id); + } if ($this->application->is_github_based()) { ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS); } From 5bdf2e84816f5bfa195b1e1c06db6a27e3d60df9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Jul 2025 19:12:57 +0200 Subject: [PATCH 017/202] refactor(previews): move preview domain generation logic to ApplicationPreview model for better encapsulation and consistency across webhook handlers --- app/Http/Controllers/Webhook/Bitbucket.php | 3 +- app/Http/Controllers/Webhook/Gitea.php | 3 +- app/Http/Controllers/Webhook/Github.php | 3 +- app/Http/Controllers/Webhook/Gitlab.php | 3 +- app/Jobs/ApplicationDeploymentJob.php | 7 +++-- app/Livewire/Project/Application/Previews.php | 4 +-- app/Models/Application.php | 28 ------------------- app/Models/ApplicationPreview.php | 27 ++++++++++++++++++ 8 files changed, 42 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php index 490b66e58..078494f82 100644 --- a/app/Http/Controllers/Webhook/Bitbucket.php +++ b/app/Http/Controllers/Webhook/Bitbucket.php @@ -143,12 +143,13 @@ class Bitbucket extends Controller ]); $pr_app->generate_preview_fqdn_compose(); } else { - ApplicationPreview::create([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'bitbucket', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url, ]); + $pr_app->generate_preview_fqdn(); } } $result = queue_application_deployment( diff --git a/app/Http/Controllers/Webhook/Gitea.php b/app/Http/Controllers/Webhook/Gitea.php index 3c3d6e0b6..3e0c5a0b6 100644 --- a/app/Http/Controllers/Webhook/Gitea.php +++ b/app/Http/Controllers/Webhook/Gitea.php @@ -175,12 +175,13 @@ class Gitea extends Controller ]); $pr_app->generate_preview_fqdn_compose(); } else { - ApplicationPreview::create([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'gitea', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url, ]); + $pr_app->generate_preview_fqdn(); } } $result = queue_application_deployment( diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 597ec023f..8872754e5 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -183,12 +183,13 @@ class Github extends Controller ]); $pr_app->generate_preview_fqdn_compose(); } else { - ApplicationPreview::create([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'github', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url, ]); + $pr_app->generate_preview_fqdn(); } } diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index d6d12a05f..3187663d4 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -202,12 +202,13 @@ class Gitlab extends Controller ]); $pr_app->generate_preview_fqdn_compose(); } else { - ApplicationPreview::create([ + $pr_app = ApplicationPreview::create([ 'git_type' => 'gitlab', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url, ]); + $pr_app->generate_preview_fqdn(); } } $result = queue_application_deployment( diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 708d2f15d..67043555d 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -236,8 +236,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->preview->generate_preview_fqdn_compose(); } } else { - // For non-Docker Compose apps, use the application model's method - $this->preview = $this->application->generate_preview_fqdn($this->pull_request_id); + // For non-Docker Compose apps, use the preview model's method + $this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); + if ($this->preview) { + $this->preview->generate_preview_fqdn(); + } } if ($this->application->is_github_based()) { ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS); diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index c781c9d8a..64e920cd7 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -86,7 +86,7 @@ class Previews extends Component return; } - $this->application->generate_preview_fqdn($preview->pull_request_id); + $preview->generate_preview_fqdn(); $this->application->refresh(); $this->dispatch('update_links'); $this->dispatch('success', 'Domain generated.'); @@ -118,7 +118,7 @@ class Previews extends Component 'pull_request_html_url' => $pull_request_html_url, ]); } - $this->application->generate_preview_fqdn($pull_request_id); + $found->generate_preview_fqdn(); $this->application->refresh(); $this->dispatch('update_links'); $this->dispatch('success', 'Preview added.'); diff --git a/app/Models/Application.php b/app/Models/Application.php index f304debad..86eea1de8 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1583,34 +1583,6 @@ class Application extends BaseModel } } - public function generate_preview_fqdn(int $pull_request_id) - { - $preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pull_request_id); - if (is_null(data_get($preview, 'fqdn')) && $this->fqdn) { - if (str($this->fqdn)->contains(',')) { - $url = Url::fromString(str($this->fqdn)->explode(',')[0]); - $preview_fqdn = getFqdnWithoutPort(str($this->fqdn)->explode(',')[0]); - } else { - $url = Url::fromString($this->fqdn); - if (data_get($preview, 'fqdn')) { - $preview_fqdn = getFqdnWithoutPort(data_get($preview, 'fqdn')); - } - } - $template = $this->preview_url_template; - $host = $url->getHost(); - $schema = $url->getScheme(); - $random = new Cuid2; - $preview_fqdn = str_replace('{{random}}', $random, $template); - $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); - $preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn); - $preview_fqdn = "$schema://$preview_fqdn"; - $preview->fqdn = $preview_fqdn; - $preview->save(); - } - - return $preview; - } - public static function getDomainsByUuid(string $uuid): array { $application = self::where('uuid', $uuid)->first(); diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index 7bf45de85..a92eb320b 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -50,6 +50,33 @@ class ApplicationPreview extends BaseModel return $this->belongsTo(Application::class); } + public function generate_preview_fqdn() + { + if (is_null($this->fqdn) && $this->application->fqdn) { + if (str($this->application->fqdn)->contains(',')) { + $url = Url::fromString(str($this->application->fqdn)->explode(',')[0]); + $preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]); + } else { + $url = Url::fromString($this->application->fqdn); + if ($this->fqdn) { + $preview_fqdn = getFqdnWithoutPort($this->fqdn); + } + } + $template = $this->application->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2; + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn"; + $this->fqdn = $preview_fqdn; + $this->save(); + } + + return $this; + } + public function generate_preview_fqdn_compose() { $services = collect(json_decode($this->application->docker_compose_domains)) ?? collect(); From 13f10028db3c749387e3b3c617dab8d6aced15b9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:17:40 +0200 Subject: [PATCH 018/202] feat(previews): implement soft delete and cleanup for ApplicationPreview, enhancing resource management in DeleteResourceJob --- app/Jobs/DeleteResourceJob.php | 61 ++++++++++++++++++- app/Livewire/Project/Application/Previews.php | 51 +++++----------- app/Models/ApplicationPreview.php | 28 ++++++++- ...leted_at_to_application_previews_table.php | 28 +++++++++ 4 files changed, 129 insertions(+), 39 deletions(-) create mode 100644 database/migrations/2025_07_14_191016_add_deleted_at_to_application_previews_table.php diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 408bb2a7a..2750110f2 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -8,6 +8,7 @@ use App\Actions\Server\CleanupDocker; use App\Actions\Service\DeleteService; use App\Actions\Service\StopService; use App\Models\Application; +use App\Models\ApplicationPreview; use App\Models\Service; use App\Models\StandaloneClickhouse; use App\Models\StandaloneDragonfly; @@ -30,7 +31,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function __construct( - public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, + public Application|ApplicationPreview|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = true, public bool $deleteVolumes = true, public bool $dockerCleanup = true, @@ -42,6 +43,13 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue public function handle() { try { + // Handle ApplicationPreview instances separately + if ($this->resource instanceof ApplicationPreview) { + $this->deleteApplicationPreview(); + + return; + } + switch ($this->resource->type()) { case 'application': StopApplication::run($this->resource, previewDeployments: true); @@ -104,4 +112,55 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue Artisan::queue('cleanup:stucked-resources'); } } + + private function deleteApplicationPreview() + { + $application = $this->resource->application; + $server = $application->destination->server; + $pull_request_id = $this->resource->pull_request_id; + + // Ensure the preview is soft deleted (may already be done in Livewire component) + if (! $this->resource->trashed()) { + $this->resource->delete(); + } + + try { + if ($server->isSwarm()) { + instant_remote_process(["docker stack rm {$application->uuid}-{$pull_request_id}"], $server); + } else { + $containers = getCurrentApplicationContainerStatus($server, $application->id, $pull_request_id)->toArray(); + $this->stopPreviewContainers($containers, $server); + } + } catch (\Throwable $e) { + // Log the error but don't fail the job + ray('Error stopping preview containers: '.$e->getMessage()); + } + + // Finally, force delete to trigger resource cleanup + $this->resource->forceDelete(); + } + + private function stopPreviewContainers(array $containers, $server, int $timeout = 30) + { + if (empty($containers)) { + return; + } + + $containerNames = []; + foreach ($containers as $container) { + $containerNames[] = str_replace('/', '', $container['Names']); + } + + $containerList = implode(' ', array_map('escapeshellarg', $containerNames)); + $commands = [ + "docker stop --time=$timeout $containerList", + "docker rm -f $containerList", + ]; + + instant_remote_process( + command: $commands, + server: $server, + throwError: false + ); + } } diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index 64e920cd7..62b1f1929 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -3,6 +3,7 @@ namespace App\Livewire\Project\Application; use App\Actions\Docker\GetContainersStatus; +use App\Jobs\DeleteResourceJob; use App\Models\Application; use App\Models\ApplicationPreview; use Illuminate\Support\Collection; @@ -205,48 +206,28 @@ class Previews extends Component public function delete(int $pull_request_id) { try { - $server = $this->application->destination->server; + $preview = ApplicationPreview::where('application_id', $this->application->id) + ->where('pull_request_id', $pull_request_id) + ->first(); - if ($this->application->destination->server->isSwarm()) { - instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server); - } else { - $containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray(); - $this->stopContainers($containers, $server); + if (! $preview) { + $this->dispatch('error', 'Preview not found.'); + + return; } - ApplicationPreview::where('application_id', $this->application->id) - ->where('pull_request_id', $pull_request_id) - ->first() - ->delete(); + // Soft delete immediately for instant UI feedback + $preview->delete(); - $this->application->refresh(); + // Dispatch the job for async cleanup (container stopping + force delete) + DeleteResourceJob::dispatch($preview); + + // Refresh the application and its previews relationship to reflect the soft delete + $this->application->load('previews'); $this->dispatch('update_links'); - $this->dispatch('success', 'Preview deleted.'); + $this->dispatch('success', 'Preview deletion started. It may take a few moments to complete.'); } catch (\Throwable $e) { return handleError($e, $this); } } - - private function stopContainers(array $containers, $server, int $timeout = 30) - { - if (empty($containers)) { - return; - } - $containerNames = []; - foreach ($containers as $container) { - $containerNames[] = str_replace('/', '', $container['Names']); - } - - $containerList = implode(' ', array_map('escapeshellarg', $containerNames)); - $commands = [ - "docker stop --time=$timeout $containerList", - "docker rm -f $containerList", - ]; - - instant_remote_process( - command: $commands, - server: $server, - throwError: false - ); - } } diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index a92eb320b..f45f9da40 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -2,19 +2,25 @@ namespace App\Models; +use Illuminate\Database\Eloquent\SoftDeletes; use Spatie\Url\Url; use Visus\Cuid2\Cuid2; class ApplicationPreview extends BaseModel { + use SoftDeletes; + protected $guarded = []; protected static function booted() { - static::deleting(function ($preview) { + static::forceDeleting(function ($preview) { + $server = $preview->application->destination->server; + $application = $preview->application; + if (data_get($preview, 'application.build_pack') === 'dockercompose') { - $server = $preview->application->destination->server; - $composeFile = $preview->application->parse(pull_request_id: $preview->pull_request_id); + // Docker Compose volume and network cleanup + $composeFile = $application->parse(pull_request_id: $preview->pull_request_id); $volumes = data_get($composeFile, 'volumes'); $networks = data_get($composeFile, 'networks'); $networkKeys = collect($networks)->keys(); @@ -26,7 +32,18 @@ class ApplicationPreview extends BaseModel instant_remote_process(["docker network disconnect $key coolify-proxy"], $server, false); instant_remote_process(["docker network rm $key"], $server, false); }); + } else { + // Regular application volume cleanup + $persistentStorages = $preview->persistentStorages()->get() ?? collect(); + if ($persistentStorages->count() > 0) { + foreach ($persistentStorages as $storage) { + instant_remote_process(["docker volume rm -f $storage->name"], $server, false); + } + } } + + // Clean up persistent storage records + $preview->persistentStorages()->delete(); }); static::saving(function ($preview) { if ($preview->isDirty('status')) { @@ -50,6 +67,11 @@ class ApplicationPreview extends BaseModel return $this->belongsTo(Application::class); } + public function persistentStorages() + { + return $this->morphMany(\App\Models\LocalPersistentVolume::class, 'resource'); + } + public function generate_preview_fqdn() { if (is_null($this->fqdn) && $this->application->fqdn) { diff --git a/database/migrations/2025_07_14_191016_add_deleted_at_to_application_previews_table.php b/database/migrations/2025_07_14_191016_add_deleted_at_to_application_previews_table.php new file mode 100644 index 000000000..25aa0f5f0 --- /dev/null +++ b/database/migrations/2025_07_14_191016_add_deleted_at_to_application_previews_table.php @@ -0,0 +1,28 @@ +softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_previews', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + } +}; From ed6e58f7ee817eed369c27ffc842e5088cce64d6 Mon Sep 17 00:00:00 2001 From: ari Date: Fri, 18 Jul 2025 15:00:49 +0200 Subject: [PATCH 019/202] fix(ui): typo on proxy request handler tooltip (#6192) --- resources/views/livewire/server/proxy.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/server/proxy.blade.php b/resources/views/livewire/server/proxy.blade.php index 39bab8185..76fbd09b7 100644 --- a/resources/views/livewire/server/proxy.blade.php +++ b/resources/views/livewire/server/proxy.blade.php @@ -29,7 +29,7 @@ label="Generate labels only for {{ str($server->proxyType())->title() }}" instantSave /> + helper="Requests to unknown hosts or stopped services will receive a 503 response or be redirected to the URL you set below (need to enable this first)." /> @if ($redirect_enabled) From e5e1bdcd4ddbc80ecd24a2ea6b56fd3d0ace5218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?= <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 15:47:14 +0200 Subject: [PATCH 020/202] fix(backups): large database backups are not working (#6217) --- app/Jobs/DatabaseBackupJob.php | 33 ++++++++++++++++--- app/Livewire/Project/Database/BackupEdit.php | 5 +++ ...ut_to_scheduled_database_backups_table.php | 18 ++++++++++ .../project/database/backup-edit.blade.php | 1 + 4 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 database/migrations/2025_07_16_202201_add_timeout_to_scheduled_database_backups_table.php diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index a6c423cac..adb5473b5 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -23,6 +23,8 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Str; +use Throwable; +use Visus\Cuid2\Cuid2; class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue { @@ -60,9 +62,16 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue public ?S3Storage $s3 = null; + public $timeout = 3600; + + public string $backup_log_uuid; + public function __construct(public ScheduledDatabaseBackup $backup) { $this->onQueue('high'); + $this->timeout = $backup->timeout; + + $this->backup_log_uuid = (string) new Cuid2; } public function handle(): void @@ -219,12 +228,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue $this->mongo_root_username = str($rootUsername)->after('MONGO_INITDB_ROOT_USERNAME=')->value(); } } - \Log::info('MongoDB credentials extracted from environment', [ - 'has_username' => filled($this->mongo_root_username), - 'has_password' => filled($this->mongo_root_password), - ]); + } catch (\Throwable $e) { - \Log::warning('Failed to extract MongoDB environment variables', ['error' => $e->getMessage()]); // Continue without env vars - will be handled in backup_standalone_mongodb method } } @@ -288,6 +293,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue } $this->backup_location = $this->backup_dir.$this->backup_file; $this->backup_log = ScheduledDatabaseBackupExecution::create([ + 'uuid' => $this->backup_log_uuid, 'database_name' => $database, 'filename' => $this->backup_location, 'scheduled_database_backup_id' => $this->backup->id, @@ -307,6 +313,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue $this->backup_file = "/mongo-dump-$databaseName-".Carbon::now()->timestamp.'.tar.gz'; $this->backup_location = $this->backup_dir.$this->backup_file; $this->backup_log = ScheduledDatabaseBackupExecution::create([ + 'uuid' => $this->backup_log_uuid, 'database_name' => $databaseName, 'filename' => $this->backup_location, 'scheduled_database_backup_id' => $this->backup->id, @@ -319,6 +326,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue } $this->backup_location = $this->backup_dir.$this->backup_file; $this->backup_log = ScheduledDatabaseBackupExecution::create([ + 'uuid' => $this->backup_log_uuid, 'database_name' => $database, 'filename' => $this->backup_location, 'scheduled_database_backup_id' => $this->backup->id, @@ -331,6 +339,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue } $this->backup_location = $this->backup_dir.$this->backup_file; $this->backup_log = ScheduledDatabaseBackupExecution::create([ + 'uuid' => $this->backup_log_uuid, 'database_name' => $database, 'filename' => $this->backup_location, 'scheduled_database_backup_id' => $this->backup->id, @@ -574,4 +583,18 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue return "{$helperImage}:{$latestVersion}"; } + + public function failed(?Throwable $exception): void + { + $log = ScheduledDatabaseBackupExecution::where('uuid', $this->backup_log_uuid)->first(); + + if ($log) { + $log->update([ + 'status' => 'failed', + 'message' => 'Job failed: '.$exception->getMessage(), + 'size' => 0, + 'filename' => null, + ]); + } + } } diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php index 0d363e983..abc88d736 100644 --- a/app/Livewire/Project/Database/BackupEdit.php +++ b/app/Livewire/Project/Database/BackupEdit.php @@ -73,6 +73,9 @@ class BackupEdit extends Component #[Validate(['required', 'boolean'])] public bool $dumpAll = false; + #[Validate(['required', 'int', 'min:1', 'max:36000'])] + public int $timeout = 3600; + public function mount() { try { @@ -98,6 +101,7 @@ class BackupEdit extends Component $this->backup->s3_storage_id = $this->s3StorageId; $this->backup->databases_to_backup = $this->databasesToBackup; $this->backup->dump_all = $this->dumpAll; + $this->backup->timeout = $this->timeout; $this->customValidate(); $this->backup->save(); } else { @@ -114,6 +118,7 @@ class BackupEdit extends Component $this->s3StorageId = $this->backup->s3_storage_id; $this->databasesToBackup = $this->backup->databases_to_backup; $this->dumpAll = $this->backup->dump_all; + $this->timeout = $this->backup->timeout; } } diff --git a/database/migrations/2025_07_16_202201_add_timeout_to_scheduled_database_backups_table.php b/database/migrations/2025_07_16_202201_add_timeout_to_scheduled_database_backups_table.php new file mode 100644 index 000000000..f8f8cb8ad --- /dev/null +++ b/database/migrations/2025_07_16_202201_add_timeout_to_scheduled_database_backups_table.php @@ -0,0 +1,18 @@ +integer('timeout')->default(3600); + }); + } +}; diff --git a/resources/views/livewire/project/database/backup-edit.blade.php b/resources/views/livewire/project/database/backup-edit.blade.php index f83af91a0..c070edba2 100644 --- a/resources/views/livewire/project/database/backup-edit.blade.php +++ b/resources/views/livewire/project/database/backup-edit.blade.php @@ -77,6 +77,7 @@ +

Backup Retention Settings

From a0db5b342f36dbcffe121794fcbe6cdefd45be08 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 15:56:01 +0200 Subject: [PATCH 021/202] fix(backups): error message if there is no exception --- app/Jobs/DatabaseBackupJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index adb5473b5..428cdfda2 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -591,7 +591,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue if ($log) { $log->update([ 'status' => 'failed', - 'message' => 'Job failed: '.$exception->getMessage(), + 'message' => 'Job failed: '.($exception?->getMessage() ?? 'Unknown error'), 'size' => 0, 'filename' => null, ]); From 889712b53a7b4366f7c7bcc40ac470cbbfbec7ec Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:24:13 +0200 Subject: [PATCH 022/202] chore(deps): update npm and composer deps --- composer.json | 68 +-- composer.lock | 426 +++++++++---------- docker/coolify-realtime/package-lock.json | 13 +- package-lock.json | 477 +++++++++++----------- 4 files changed, 500 insertions(+), 484 deletions(-) diff --git a/composer.json b/composer.json index 854ba1dab..7e8e768c0 100644 --- a/composer.json +++ b/composer.json @@ -13,65 +13,65 @@ "require": { "php": "^8.4", "danharrin/livewire-rate-limiting": "^2.1.0", - "doctrine/dbal": "^4.2.2", - "guzzlehttp/guzzle": "^7.9.2", - "laravel/fortify": "^1.25.4", - "laravel/framework": "^12.4.1", - "laravel/horizon": "^5.30.3", - "laravel/pail": "^1.2.2", - "laravel/prompts": "^0.3.5|^0.3.5|^0.3.5", - "laravel/sanctum": "^4.0.8", - "laravel/socialite": "^5.18.0", + "doctrine/dbal": "^4.3.0", + "guzzlehttp/guzzle": "^7.9.3", + "laravel/fortify": "^1.27.0", + "laravel/framework": "^12.20.0", + "laravel/horizon": "^5.33.1", + "laravel/pail": "^1.2.3", + "laravel/prompts": "^0.3.6|^0.3.6|^0.3.6", + "laravel/sanctum": "^4.1.2", + "laravel/socialite": "^5.21.0", "laravel/tinker": "^2.10.1", "laravel/ui": "^4.6.1", "lcobucci/jwt": "^5.5.0", "league/flysystem-aws-s3-v3": "^3.29", - "league/flysystem-sftp-v3": "^3.29", - "livewire/livewire": "^3.5.20", + "league/flysystem-sftp-v3": "^3.30", + "livewire/livewire": "^3.6.4", "log1x/laravel-webfonts": "^2.0.1", - "lorisleiva/laravel-actions": "^2.8.6", + "lorisleiva/laravel-actions": "^2.9.0", "nubs/random-name-generator": "^2.2", - "phpseclib/phpseclib": "^3.0.43", - "pion/laravel-chunk-upload": "^1.5.4", + "phpseclib/phpseclib": "^3.0.46", + "pion/laravel-chunk-upload": "^1.5.6", "poliander/cron": "^3.2.1", "purplepixie/phpdns": "^2.2", "pusher/pusher-php-server": "^7.2.7", "resend/resend-laravel": "^0.19.0", - "sentry/sentry-laravel": "^4.13", + "sentry/sentry-laravel": "^4.15.1", "socialiteproviders/authentik": "^5.2", "socialiteproviders/clerk": "^5.0", "socialiteproviders/discord": "^4.2", "socialiteproviders/google": "^4.1", "socialiteproviders/infomaniak": "^4.0", "socialiteproviders/microsoft-azure": "^5.2", - "socialiteproviders/zitadel": "^4.1", - "spatie/laravel-activitylog": "^4.10.1", - "spatie/laravel-data": "^4.13.1", - "spatie/laravel-ray": "^1.39.1", + "socialiteproviders/zitadel": "^4.2", + "spatie/laravel-activitylog": "^4.10.2", + "spatie/laravel-data": "^4.17.0", + "spatie/laravel-ray": "^1.40.2", "spatie/laravel-schemaless-attributes": "^2.5.1", "spatie/url": "^2.4", - "stevebauman/purify": "^6.3", - "stripe/stripe-php": "^16.5.1", - "symfony/yaml": "^7.2.3", + "stevebauman/purify": "^6.3.1", + "stripe/stripe-php": "^16.6.0", + "symfony/yaml": "^7.3.1", "visus/cuid2": "^4.1.0", "yosymfony/toml": "^1.0.4", - "zircote/swagger-php": "^5.0.5" + "zircote/swagger-php": "^5.1.4" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.15.1", - "driftingly/rector-laravel": "^2.0.2", + "barryvdh/laravel-debugbar": "^3.15.4", + "driftingly/rector-laravel": "^2.0.5", "fakerphp/faker": "^1.24.1", - "laravel/dusk": "^8.3.1", - "laravel/pint": "^1.21", - "laravel/telescope": "^5.5", + "laravel/dusk": "^8.3.3", + "laravel/pint": "^1.24", + "laravel/telescope": "^5.10", "mockery/mockery": "^1.6.12", - "nunomaduro/collision": "^8.6.1", - "pestphp/pest": "^3.8.0", - "phpstan/phpstan": "^2.1.6", - "rector/rector": "^2.0.9", + "nunomaduro/collision": "^8.8.2", + "pestphp/pest": "^3.8.2", + "phpstan/phpstan": "^2.1.18", + "rector/rector": "^2.1.2", "serversideup/spin": "^3.0.2", "spatie/laravel-ignition": "^2.9.1", - "symfony/http-client": "^7.2.3" + "symfony/http-client": "^7.3.1" }, "minimum-stability": "stable", "prefer-stable": true, @@ -127,4 +127,4 @@ "@php artisan key:generate --ansi" ] } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index d655ad48d..8d170cdc1 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": "f1d647186c558d85c525f8a6314474d4", + "content-hash": "52a680a0eb446dcaa74bc35e158aca57", "packages": [ { "name": "amphp/amp", @@ -870,16 +870,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.347.0", + "version": "3.351.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "c66a35e650f077caddd7db8d3a1f58b2c2b8c78b" + "reference": "f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c66a35e650f077caddd7db8d3a1f58b2c2b8c78b", - "reference": "c66a35e650f077caddd7db8d3a1f58b2c2b8c78b", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805", + "reference": "f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805", "shasum": "" }, "require": { @@ -961,9 +961,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.347.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.351.1" }, - "time": "2025-06-23T18:12:15+00:00" + "time": "2025-07-17T18:07:08+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1373,21 +1373,21 @@ }, { "name": "doctrine/dbal", - "version": "4.2.4", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "b37d160498ea91a2382a2ebe825c4ea6254fc0ec" + "reference": "5fe09532be619202d59c70956c6fb20e97933ee3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/b37d160498ea91a2382a2ebe825c4ea6254fc0ec", - "reference": "b37d160498ea91a2382a2ebe825c4ea6254fc0ec", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/5fe09532be619202d59c70956c6fb20e97933ee3", + "reference": "5fe09532be619202d59c70956c6fb20e97933ee3", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3|^1", - "php": "^8.1", + "doctrine/deprecations": "^1.1.5", + "php": "^8.2", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, @@ -1398,7 +1398,7 @@ "phpstan/phpstan": "2.1.17", "phpstan/phpstan-phpunit": "2.0.6", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "10.5.46", + "phpunit/phpunit": "11.5.23", "slevomat/coding-standard": "8.16.2", "squizlabs/php_codesniffer": "3.13.1", "symfony/cache": "^6.3.8|^7.0", @@ -1459,7 +1459,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.2.4" + "source": "https://github.com/doctrine/dbal/tree/4.3.0" }, "funding": [ { @@ -1475,7 +1475,7 @@ "type": "tidelift" } ], - "time": "2025-06-15T23:15:01+00:00" + "time": "2025-06-16T19:31:04+00:00" }, { "name": "doctrine/deprecations", @@ -2678,16 +2678,16 @@ }, { "name": "laravel/framework", - "version": "v12.19.3", + "version": "v12.20.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262" + "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/4e6ec689ef704bb4bd282f29d9dd658dfb4fb262", - "reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262", + "url": "https://api.github.com/repos/laravel/framework/zipball/1b9a00f8caf5503c92aa436279172beae1a484ff", + "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff", "shasum": "" }, "require": { @@ -2889,7 +2889,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-06-18T12:56:23+00:00" + "time": "2025-07-08T15:02:21+00:00" }, { "name": "laravel/horizon", @@ -3052,16 +3052,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.5", + "version": "v0.3.6", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1" + "reference": "86a8b692e8661d0fb308cec64f3d176821323077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/57b8f7efe40333cdb925700891c7d7465325d3b1", - "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1", + "url": "https://api.github.com/repos/laravel/prompts/zipball/86a8b692e8661d0fb308cec64f3d176821323077", + "reference": "86a8b692e8661d0fb308cec64f3d176821323077", "shasum": "" }, "require": { @@ -3105,22 +3105,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.5" + "source": "https://github.com/laravel/prompts/tree/v0.3.6" }, - "time": "2025-02-11T13:34:40+00:00" + "time": "2025-07-07T14:17:42+00:00" }, { "name": "laravel/sanctum", - "version": "v4.1.1", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5" + "reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/a360a6a1fd2400ead4eb9b6a9c1bb272939194f5", - "reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/e4c09e69aecd5a383e0c1b85a6bb501c997d7491", + "reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491", "shasum": "" }, "require": { @@ -3171,7 +3171,7 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2025-04-23T13:03:38+00:00" + "time": "2025-07-01T15:49:32+00:00" }, { "name": "laravel/serializable-closure", @@ -3699,16 +3699,16 @@ }, { "name": "league/flysystem", - "version": "3.29.1", + "version": "3.30.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e", + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e", "shasum": "" }, "require": { @@ -3732,13 +3732,13 @@ "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", - "ext-mongodb": "^1.3", + "ext-mongodb": "^1.3|^2", "ext-zip": "*", "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "guzzlehttp/psr7": "^2.6", "microsoft/azure-storage-blob": "^1.1", - "mongodb/mongodb": "^1.2", + "mongodb/mongodb": "^1.2|^2", "phpseclib/phpseclib": "^3.0.36", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", @@ -3776,9 +3776,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.30.0" }, - "time": "2024-10-08T08:58:34+00:00" + "time": "2025-06-25T13:29:59+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -3837,16 +3837,16 @@ }, { "name": "league/flysystem-local", - "version": "3.29.0", + "version": "3.30.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/6691915f77c7fb69adfb87dcd550052dc184ee10", + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10", "shasum": "" }, "require": { @@ -3880,22 +3880,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.0" }, - "time": "2024-08-09T21:24:39+00:00" + "time": "2025-05-21T10:34:19+00:00" }, { "name": "league/flysystem-sftp-v3", - "version": "3.29.0", + "version": "3.30.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-sftp-v3.git", - "reference": "ce9b209e2fbe33122c755ffc18eb4d5bd256f252" + "reference": "93f297837b5052f4cfee601b2e0352addd956448" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/ce9b209e2fbe33122c755ffc18eb4d5bd256f252", - "reference": "ce9b209e2fbe33122c755ffc18eb4d5bd256f252", + "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/93f297837b5052f4cfee601b2e0352addd956448", + "reference": "93f297837b5052f4cfee601b2e0352addd956448", "shasum": "" }, "require": { @@ -3929,9 +3929,9 @@ "sftp" ], "support": { - "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.30.0" }, - "time": "2024-08-14T19:35:54+00:00" + "time": "2025-04-17T15:49:35+00:00" }, { "name": "league/mime-type-detection", @@ -4241,16 +4241,16 @@ }, { "name": "livewire/livewire", - "version": "v3.6.3", + "version": "v3.6.4", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "56aa1bb63a46e06181c56fa64717a7287e19115e" + "reference": "ef04be759da41b14d2d129e670533180a44987dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/56aa1bb63a46e06181c56fa64717a7287e19115e", - "reference": "56aa1bb63a46e06181c56fa64717a7287e19115e", + "url": "https://api.github.com/repos/livewire/livewire/zipball/ef04be759da41b14d2d129e670533180a44987dc", + "reference": "ef04be759da41b14d2d129e670533180a44987dc", "shasum": "" }, "require": { @@ -4305,7 +4305,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.6.3" + "source": "https://github.com/livewire/livewire/tree/v3.6.4" }, "funding": [ { @@ -4313,7 +4313,7 @@ "type": "github" } ], - "time": "2025-04-12T22:26:52+00:00" + "time": "2025-07-17T05:12:15+00:00" }, { "name": "log1x/laravel-webfonts", @@ -5883,16 +5883,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.45", + "version": "3.0.46", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "bd81b90d5963c6b9d87de50357585375223f4dd8" + "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/bd81b90d5963c6b9d87de50357585375223f4dd8", - "reference": "bd81b90d5963c6b9d87de50357585375223f4dd8", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", + "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", "shasum": "" }, "require": { @@ -5973,7 +5973,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.45" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46" }, "funding": [ { @@ -5989,20 +5989,20 @@ "type": "tidelift" } ], - "time": "2025-06-22T22:54:43+00:00" + "time": "2025-06-26T16:29:55+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "2.1.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", "shasum": "" }, "require": { @@ -6034,9 +6034,9 @@ "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/2.1.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" }, - "time": "2025-02-19T13:28:12+00:00" + "time": "2025-07-13T07:04:09+00:00" }, { "name": "pion/laravel-chunk-upload", @@ -6971,21 +6971,20 @@ }, { "name": "ramsey/uuid", - "version": "4.8.1", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", "shasum": "" }, "require": { "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", - "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -7044,9 +7043,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.8.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.0" }, - "time": "2025-06-01T06:28:46+00:00" + "time": "2025-06-25T14:20:11+00:00" }, { "name": "resend/resend-laravel", @@ -7119,16 +7118,16 @@ }, { "name": "resend/resend-php", - "version": "v0.18.0", + "version": "v0.18.1", "source": { "type": "git", "url": "https://github.com/resend/resend-php.git", - "reference": "d6194782ff1952627bcdd52e5958572c4bd98043" + "reference": "f20a9a50a7f705294af1662995c93bfd806fed3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/resend/resend-php/zipball/d6194782ff1952627bcdd52e5958572c4bd98043", - "reference": "d6194782ff1952627bcdd52e5958572c4bd98043", + "url": "https://api.github.com/repos/resend/resend-php/zipball/f20a9a50a7f705294af1662995c93bfd806fed3e", + "reference": "f20a9a50a7f705294af1662995c93bfd806fed3e", "shasum": "" }, "require": { @@ -7170,9 +7169,9 @@ ], "support": { "issues": "https://github.com/resend/resend-php/issues", - "source": "https://github.com/resend/resend-php/tree/v0.18.0" + "source": "https://github.com/resend/resend-php/tree/v0.18.1" }, - "time": "2025-05-06T21:18:26+00:00" + "time": "2025-07-04T00:12:53+00:00" }, { "name": "revolt/event-loop", @@ -7793,27 +7792,27 @@ }, { "name": "socialiteproviders/zitadel", - "version": "4.1.0", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Zitadel.git", - "reference": "2e1c0843a9531eb0e31a04b31683a63f1a7d1865" + "reference": "852ceebd71503ac116c42b8aa352e22b4fd396d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Zitadel/zipball/2e1c0843a9531eb0e31a04b31683a63f1a7d1865", - "reference": "2e1c0843a9531eb0e31a04b31683a63f1a7d1865", + "url": "https://api.github.com/repos/SocialiteProviders/Zitadel/zipball/852ceebd71503ac116c42b8aa352e22b4fd396d9", + "reference": "852ceebd71503ac116c42b8aa352e22b4fd396d9", "shasum": "" }, "require": { "ext-json": "*", - "php": "^8.1", + "php": "^8.0", "socialiteproviders/manager": "^4.4" }, "type": "library", "autoload": { "psr-4": { - "Socialiteproviders\\Zitadel\\": "" + "SocialiteProviders\\Zitadel\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -7827,12 +7826,19 @@ } ], "description": "Zitadel OAuth2 Provider for Laravel Socialite", + "keywords": [ + "laravel", + "oauth", + "provider", + "socialite", + "zitadel" + ], "support": { - "docs": "https://socialiteproviders.com/zoho", + "docs": "https://socialiteproviders.com/zitadel", "issues": "https://github.com/socialiteproviders/providers/issues", "source": "https://github.com/socialiteproviders/providers" }, - "time": "2024-08-26T06:14:57+00:00" + "time": "2024-11-07T21:57:40+00:00" }, { "name": "spatie/backtrace", @@ -7990,16 +7996,16 @@ }, { "name": "spatie/laravel-data", - "version": "4.16.1", + "version": "4.17.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-data.git", - "reference": "e652b52bdaca4774abb4a6024736850a1c4ab50b" + "reference": "6b110d25ad4219774241b083d09695b20a7fb472" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-data/zipball/e652b52bdaca4774abb4a6024736850a1c4ab50b", - "reference": "e652b52bdaca4774abb4a6024736850a1c4ab50b", + "url": "https://api.github.com/repos/spatie/laravel-data/zipball/6b110d25ad4219774241b083d09695b20a7fb472", + "reference": "6b110d25ad4219774241b083d09695b20a7fb472", "shasum": "" }, "require": { @@ -8061,7 +8067,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-data/issues", - "source": "https://github.com/spatie/laravel-data/tree/4.16.1" + "source": "https://github.com/spatie/laravel-data/tree/4.17.0" }, "funding": [ { @@ -8069,20 +8075,20 @@ "type": "github" } ], - "time": "2025-06-24T08:19:42+00:00" + "time": "2025-06-25T11:36:37+00:00" }, { "name": "spatie/laravel-package-tools", - "version": "1.92.4", + "version": "1.92.7", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c" + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d20b1969f836d210459b78683d85c9cd5c5f508c", - "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f09a799850b1ed765103a4f0b4355006360c49a5", + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5", "shasum": "" }, "require": { @@ -8122,7 +8128,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.4" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.7" }, "funding": [ { @@ -8130,7 +8136,7 @@ "type": "github" } ], - "time": "2025-04-11T15:27:14+00:00" + "time": "2025-07-17T15:46:43+00:00" }, { "name": "spatie/laravel-ray", @@ -8773,16 +8779,16 @@ }, { "name": "symfony/console", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44" + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44", + "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", "shasum": "" }, "require": { @@ -8847,7 +8853,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.0" + "source": "https://github.com/symfony/console/tree/v7.3.1" }, "funding": [ { @@ -8863,7 +8869,7 @@ "type": "tidelift" } ], - "time": "2025-05-24T10:34:04+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/css-selector", @@ -8999,16 +9005,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83" + "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/cf68d225bc43629de4ff54778029aee6dc191b83", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/35b55b166f6752d6aaf21aa042fc5ed280fce235", + "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235", "shasum": "" }, "require": { @@ -9056,7 +9062,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.0" + "source": "https://github.com/symfony/error-handler/tree/v7.3.1" }, "funding": [ { @@ -9072,7 +9078,7 @@ "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2025-06-13T07:48:40+00:00" }, { "name": "symfony/event-dispatcher", @@ -9296,16 +9302,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "4236baf01609667d53b20371486228231eb135fd" + "reference": "23dd60256610c86a3414575b70c596e5deff6ed9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4236baf01609667d53b20371486228231eb135fd", - "reference": "4236baf01609667d53b20371486228231eb135fd", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/23dd60256610c86a3414575b70c596e5deff6ed9", + "reference": "23dd60256610c86a3414575b70c596e5deff6ed9", "shasum": "" }, "require": { @@ -9355,7 +9361,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.3.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.1" }, "funding": [ { @@ -9371,20 +9377,20 @@ "type": "tidelift" } ], - "time": "2025-05-12T14:48:23+00:00" + "time": "2025-06-23T15:07:14+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f" + "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ac7b8e163e8c83dce3abcc055a502d4486051a9f", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1644879a66e4aa29c36fe33dfa6c54b450ce1831", + "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831", "shasum": "" }, "require": { @@ -9469,7 +9475,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.3.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.1" }, "funding": [ { @@ -9485,20 +9491,20 @@ "type": "tidelift" } ], - "time": "2025-05-29T07:47:32+00:00" + "time": "2025-06-28T08:24:55+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c" + "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/0f375bbbde96ae8c78e4aa3e63aabd486e33364c", - "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b5db5105b290bdbea5ab27b89c69effcf1cb3368", + "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368", "shasum": "" }, "require": { @@ -9549,7 +9555,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.0" + "source": "https://github.com/symfony/mailer/tree/v7.3.1" }, "funding": [ { @@ -9565,7 +9571,7 @@ "type": "tidelift" } ], - "time": "2025-04-04T09:51:09+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/mime", @@ -10894,16 +10900,16 @@ }, { "name": "symfony/translation", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667" + "reference": "241d5ac4910d256660238a7ecf250deba4c73063" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/4aba29076a29a3aa667e09b791e5f868973a8667", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667", + "url": "https://api.github.com/repos/symfony/translation/zipball/241d5ac4910d256660238a7ecf250deba4c73063", + "reference": "241d5ac4910d256660238a7ecf250deba4c73063", "shasum": "" }, "require": { @@ -10970,7 +10976,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.0" + "source": "https://github.com/symfony/translation/tree/v7.3.1" }, "funding": [ { @@ -10986,7 +10992,7 @@ "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/translation-contracts", @@ -11068,16 +11074,16 @@ }, { "name": "symfony/uid", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3" + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/7beeb2b885cd584cd01e126c5777206ae4c3c6a3", - "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3", + "url": "https://api.github.com/repos/symfony/uid/zipball/a69f69f3159b852651a6bf45a9fdd149520525bb", + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb", "shasum": "" }, "require": { @@ -11122,7 +11128,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.3.0" + "source": "https://github.com/symfony/uid/tree/v7.3.1" }, "funding": [ { @@ -11138,20 +11144,20 @@ "type": "tidelift" } ], - "time": "2025-05-24T14:28:13+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e" + "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/548f6760c54197b1084e1e5c71f6d9d523f2f78e", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", + "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", "shasum": "" }, "require": { @@ -11206,7 +11212,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.1" }, "funding": [ { @@ -11222,20 +11228,20 @@ "type": "tidelift" } ], - "time": "2025-04-27T18:39:23+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "cea40a48279d58dc3efee8112634cb90141156c2" + "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/cea40a48279d58dc3efee8112634cb90141156c2", - "reference": "cea40a48279d58dc3efee8112634cb90141156c2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", + "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", "shasum": "" }, "require": { @@ -11278,7 +11284,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.0" + "source": "https://github.com/symfony/yaml/tree/v7.3.1" }, "funding": [ { @@ -11294,7 +11300,7 @@ "type": "tidelift" } ], - "time": "2025-04-04T10:10:33+00:00" + "time": "2025-06-03T06:57:57+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11945,16 +11951,16 @@ }, { "name": "zircote/swagger-php", - "version": "5.1.3", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "b8ba6bd99805c0ae09a38d1b26c1c92820509bd0" + "reference": "471f2e7c24c9508a2ee08df245cab64b62dbf721" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/b8ba6bd99805c0ae09a38d1b26c1c92820509bd0", - "reference": "b8ba6bd99805c0ae09a38d1b26c1c92820509bd0", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/471f2e7c24c9508a2ee08df245cab64b62dbf721", + "reference": "471f2e7c24c9508a2ee08df245cab64b62dbf721", "shasum": "" }, "require": { @@ -12025,9 +12031,9 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/5.1.3" + "source": "https://github.com/zircote/swagger-php/tree/5.1.4" }, - "time": "2025-05-20T03:35:10+00:00" + "time": "2025-07-15T23:54:13+00:00" } ], "packages-dev": [ @@ -12566,16 +12572,16 @@ }, { "name": "laravel/pint", - "version": "v1.22.1", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "941d1927c5ca420c22710e98420287169c7bcaf7" + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/941d1927c5ca420c22710e98420287169c7bcaf7", - "reference": "941d1927c5ca420c22710e98420287169c7bcaf7", + "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", "shasum": "" }, "require": { @@ -12586,10 +12592,10 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.75.0", - "illuminate/view": "^11.44.7", - "larastan/larastan": "^3.4.0", - "laravel-zero/framework": "^11.36.1", + "friendsofphp/php-cs-fixer": "^3.82.2", + "illuminate/view": "^11.45.1", + "larastan/larastan": "^3.5.0", + "laravel-zero/framework": "^11.45.0", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3.1", "pestphp/pest": "^2.36.0" @@ -12599,6 +12605,9 @@ ], "type": "project", "autoload": { + "files": [ + "overrides/Runner/Parallel/ProcessFactory.php" + ], "psr-4": { "App\\": "app/", "Database\\Seeders\\": "database/seeders/", @@ -12628,20 +12637,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-05-08T08:38:12+00:00" + "time": "2025-07-10T18:09:32+00:00" }, { "name": "laravel/telescope", - "version": "v5.9.1", + "version": "v5.10.0", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "403d4ad1ecfe126139f5cf29cabd6b1c816c46a2" + "reference": "fc0a8662682c0375b534033873debb780c003486" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/403d4ad1ecfe126139f5cf29cabd6b1c816c46a2", - "reference": "403d4ad1ecfe126139f5cf29cabd6b1c816c46a2", + "url": "https://api.github.com/repos/laravel/telescope/zipball/fc0a8662682c0375b534033873debb780c003486", + "reference": "fc0a8662682c0375b534033873debb780c003486", "shasum": "" }, "require": { @@ -12695,9 +12704,9 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v5.9.1" + "source": "https://github.com/laravel/telescope/tree/v5.10.0" }, - "time": "2025-06-10T21:42:27+00:00" + "time": "2025-07-07T14:47:19+00:00" }, { "name": "mockery/mockery", @@ -12784,16 +12793,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", "shasum": "" }, "require": { @@ -12832,7 +12841,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" }, "funding": [ { @@ -12840,20 +12849,20 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-07-05T12:25:42+00:00" }, { "name": "nunomaduro/collision", - "version": "v8.8.1", + "version": "v8.8.2", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5" + "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/44ccb82e3e21efb5446748d2a3c81a030ac22bd5", - "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", + "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", "shasum": "" }, "require": { @@ -12939,7 +12948,7 @@ "type": "patreon" } ], - "time": "2025-06-11T01:04:21+00:00" + "time": "2025-06-25T02:12:12+00:00" }, { "name": "pestphp/pest", @@ -13521,16 +13530,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.17", + "version": "2.1.18", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053" + "reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053", - "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ee1f390b7a70cdf74a2b737e554f68afea885db7", + "reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7", "shasum": "" }, "require": { @@ -13575,7 +13584,7 @@ "type": "github" } ], - "time": "2025-05-21T20:55:28+00:00" + "time": "2025-07-17T17:22:31+00:00" }, { "name": "phpunit/php-code-coverage", @@ -14015,21 +14024,21 @@ }, { "name": "rector/rector", - "version": "2.0.18", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "be3a452085b524a04056e3dfe72d861948711062" + "reference": "40a71441dd73fa150a66102f5ca1364c44fc8fff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/be3a452085b524a04056e3dfe72d861948711062", - "reference": "be3a452085b524a04056e3dfe72d861948711062", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/40a71441dd73fa150a66102f5ca1364c44fc8fff", + "reference": "40a71441dd73fa150a66102f5ca1364c44fc8fff", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.17" + "phpstan/phpstan": "^2.1.18" }, "conflict": { "rector/rector-doctrine": "*", @@ -14054,6 +14063,7 @@ "MIT" ], "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", "keywords": [ "automation", "dev", @@ -14062,7 +14072,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.0.18" + "source": "https://github.com/rectorphp/rector/tree/2.1.2" }, "funding": [ { @@ -14070,7 +14080,7 @@ "type": "github" } ], - "time": "2025-06-11T11:19:37+00:00" + "time": "2025-07-17T19:30:06+00:00" }, { "name": "sebastian/cli-parser", @@ -15426,16 +15436,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "57e4fb86314015a695a750ace358d07a7e37b8a9" + "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/57e4fb86314015a695a750ace358d07a7e37b8a9", - "reference": "57e4fb86314015a695a750ace358d07a7e37b8a9", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64", + "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64", "shasum": "" }, "require": { @@ -15447,6 +15457,7 @@ }, "conflict": { "amphp/amp": "<2.5", + "amphp/socket": "<1.1", "php-http/discovery": "<1.15", "symfony/http-foundation": "<6.4" }, @@ -15459,7 +15470,6 @@ "require-dev": { "amphp/http-client": "^4.2.1|^5.0", "amphp/http-tunnel": "^1.0|^2.0", - "amphp/socket": "^1.1", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", @@ -15501,7 +15511,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.0" + "source": "https://github.com/symfony/http-client/tree/v7.3.1" }, "funding": [ { @@ -15517,7 +15527,7 @@ "type": "tidelift" } ], - "time": "2025-05-02T08:23:16+00:00" + "time": "2025-06-28T07:58:39+00:00" }, { "name": "symfony/http-client-contracts", diff --git a/docker/coolify-realtime/package-lock.json b/docker/coolify-realtime/package-lock.json index 1c329e47f..49907cbd4 100644 --- a/docker/coolify-realtime/package-lock.json +++ b/docker/coolify-realtime/package-lock.json @@ -181,14 +181,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -323,9 +324,9 @@ } }, "node_modules/nan": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.1.tgz", - "integrity": "sha512-pfRR4ZcNTSm2ZFHaztuvbICf+hyiG6ecA06SfAxoPmuHjvMu0KUIae7Y8GyVkbBqeEIidsmXeYooWIX9+qjfRQ==", + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", "license": "MIT" }, "node_modules/node-pty": { diff --git a/package-lock.json b/package-lock.json index d86caea87..10489a7d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,13 +74,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -90,9 +90,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", + "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -104,9 +104,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", + "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", "cpu": [ "ppc64" ], @@ -121,9 +121,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", + "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", "cpu": [ "arm" ], @@ -138,9 +138,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", + "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", "cpu": [ "arm64" ], @@ -155,9 +155,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", + "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", "cpu": [ "x64" ], @@ -172,9 +172,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", + "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", "cpu": [ "arm64" ], @@ -189,9 +189,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", + "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", "cpu": [ "x64" ], @@ -206,9 +206,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", + "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", "cpu": [ "arm64" ], @@ -223,9 +223,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", + "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", "cpu": [ "x64" ], @@ -240,9 +240,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", + "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", "cpu": [ "arm" ], @@ -257,9 +257,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", + "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", "cpu": [ "arm64" ], @@ -274,9 +274,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", + "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", "cpu": [ "ia32" ], @@ -291,9 +291,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", + "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", "cpu": [ "loong64" ], @@ -308,9 +308,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", + "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", "cpu": [ "mips64el" ], @@ -325,9 +325,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", + "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", "cpu": [ "ppc64" ], @@ -342,9 +342,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", + "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", "cpu": [ "riscv64" ], @@ -359,9 +359,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", + "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", "cpu": [ "s390x" ], @@ -376,9 +376,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", + "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", "cpu": [ "x64" ], @@ -393,9 +393,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", + "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", "cpu": [ "arm64" ], @@ -410,9 +410,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", + "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", "cpu": [ "x64" ], @@ -427,9 +427,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", + "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", "cpu": [ "arm64" ], @@ -444,9 +444,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", + "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", "cpu": [ "x64" ], @@ -460,10 +460,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", + "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", + "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", "cpu": [ "x64" ], @@ -478,9 +495,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", + "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", "cpu": [ "arm64" ], @@ -495,9 +512,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", + "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", "cpu": [ "ia32" ], @@ -512,9 +529,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", + "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", "cpu": [ "x64" ], @@ -548,18 +565,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -572,27 +585,17 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -601,9 +604,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz", - "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", "cpu": [ "arm" ], @@ -615,9 +618,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz", - "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", "cpu": [ "arm64" ], @@ -629,9 +632,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz", - "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", "cpu": [ "arm64" ], @@ -643,9 +646,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz", - "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", "cpu": [ "x64" ], @@ -657,9 +660,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz", - "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", "cpu": [ "arm64" ], @@ -671,9 +674,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz", - "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", "cpu": [ "x64" ], @@ -685,9 +688,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz", - "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", "cpu": [ "arm" ], @@ -699,9 +702,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz", - "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", "cpu": [ "arm" ], @@ -713,9 +716,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz", - "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", "cpu": [ "arm64" ], @@ -727,9 +730,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz", - "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", "cpu": [ "arm64" ], @@ -741,9 +744,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz", - "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", "cpu": [ "loong64" ], @@ -755,9 +758,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz", - "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", "cpu": [ "ppc64" ], @@ -769,9 +772,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz", - "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", "cpu": [ "riscv64" ], @@ -783,9 +786,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz", - "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", "cpu": [ "riscv64" ], @@ -797,9 +800,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz", - "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", "cpu": [ "s390x" ], @@ -811,9 +814,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz", - "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", "cpu": [ "x64" ], @@ -825,9 +828,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz", - "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", "cpu": [ "x64" ], @@ -839,9 +842,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz", - "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", "cpu": [ "arm64" ], @@ -853,9 +856,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz", - "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", "cpu": [ "ia32" ], @@ -867,9 +870,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz", - "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", "cpu": [ "x64" ], @@ -1192,9 +1195,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -1544,9 +1547,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1620,9 +1623,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", - "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", + "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1633,31 +1636,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.4", - "@esbuild/android-arm": "0.25.4", - "@esbuild/android-arm64": "0.25.4", - "@esbuild/android-x64": "0.25.4", - "@esbuild/darwin-arm64": "0.25.4", - "@esbuild/darwin-x64": "0.25.4", - "@esbuild/freebsd-arm64": "0.25.4", - "@esbuild/freebsd-x64": "0.25.4", - "@esbuild/linux-arm": "0.25.4", - "@esbuild/linux-arm64": "0.25.4", - "@esbuild/linux-ia32": "0.25.4", - "@esbuild/linux-loong64": "0.25.4", - "@esbuild/linux-mips64el": "0.25.4", - "@esbuild/linux-ppc64": "0.25.4", - "@esbuild/linux-riscv64": "0.25.4", - "@esbuild/linux-s390x": "0.25.4", - "@esbuild/linux-x64": "0.25.4", - "@esbuild/netbsd-arm64": "0.25.4", - "@esbuild/netbsd-x64": "0.25.4", - "@esbuild/openbsd-arm64": "0.25.4", - "@esbuild/openbsd-x64": "0.25.4", - "@esbuild/sunos-x64": "0.25.4", - "@esbuild/win32-arm64": "0.25.4", - "@esbuild/win32-ia32": "0.25.4", - "@esbuild/win32-x64": "0.25.4" + "@esbuild/aix-ppc64": "0.25.6", + "@esbuild/android-arm": "0.25.6", + "@esbuild/android-arm64": "0.25.6", + "@esbuild/android-x64": "0.25.6", + "@esbuild/darwin-arm64": "0.25.6", + "@esbuild/darwin-x64": "0.25.6", + "@esbuild/freebsd-arm64": "0.25.6", + "@esbuild/freebsd-x64": "0.25.6", + "@esbuild/linux-arm": "0.25.6", + "@esbuild/linux-arm64": "0.25.6", + "@esbuild/linux-ia32": "0.25.6", + "@esbuild/linux-loong64": "0.25.6", + "@esbuild/linux-mips64el": "0.25.6", + "@esbuild/linux-ppc64": "0.25.6", + "@esbuild/linux-riscv64": "0.25.6", + "@esbuild/linux-s390x": "0.25.6", + "@esbuild/linux-x64": "0.25.6", + "@esbuild/netbsd-arm64": "0.25.6", + "@esbuild/netbsd-x64": "0.25.6", + "@esbuild/openbsd-arm64": "0.25.6", + "@esbuild/openbsd-x64": "0.25.6", + "@esbuild/openharmony-arm64": "0.25.6", + "@esbuild/sunos-x64": "0.25.6", + "@esbuild/win32-arm64": "0.25.6", + "@esbuild/win32-ia32": "0.25.6", + "@esbuild/win32-x64": "0.25.6" } }, "node_modules/estree-walker": { @@ -1668,9 +1672,9 @@ "license": "MIT" }, "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1704,15 +1708,16 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -2306,9 +2311,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -2424,13 +2429,13 @@ } }, "node_modules/rollup": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz", - "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -2440,26 +2445,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.2", - "@rollup/rollup-android-arm64": "4.40.2", - "@rollup/rollup-darwin-arm64": "4.40.2", - "@rollup/rollup-darwin-x64": "4.40.2", - "@rollup/rollup-freebsd-arm64": "4.40.2", - "@rollup/rollup-freebsd-x64": "4.40.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", - "@rollup/rollup-linux-arm-musleabihf": "4.40.2", - "@rollup/rollup-linux-arm64-gnu": "4.40.2", - "@rollup/rollup-linux-arm64-musl": "4.40.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", - "@rollup/rollup-linux-riscv64-gnu": "4.40.2", - "@rollup/rollup-linux-riscv64-musl": "4.40.2", - "@rollup/rollup-linux-s390x-gnu": "4.40.2", - "@rollup/rollup-linux-x64-gnu": "4.40.2", - "@rollup/rollup-linux-x64-musl": "4.40.2", - "@rollup/rollup-win32-arm64-msvc": "4.40.2", - "@rollup/rollup-win32-ia32-msvc": "4.40.2", - "@rollup/rollup-win32-x64-msvc": "4.40.2", + "@rollup/rollup-android-arm-eabi": "4.45.1", + "@rollup/rollup-android-arm64": "4.45.1", + "@rollup/rollup-darwin-arm64": "4.45.1", + "@rollup/rollup-darwin-x64": "4.45.1", + "@rollup/rollup-freebsd-arm64": "4.45.1", + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" } }, @@ -2600,9 +2605,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, "license": "MIT", "dependencies": { From 6ddca603e2ad2d7865ecb8ce8ed6c75a95382f13 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:25:01 +0200 Subject: [PATCH 023/202] chore(deps): update docker deps - update Nixpacks to v1.39.0 --- docker/coolify-helper/Dockerfile | 10 +++++----- docker/coolify-realtime/Dockerfile | 2 +- docker/development/Dockerfile | 4 ++-- docker/production/Dockerfile | 4 ++-- docker/testing-host/Dockerfile | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docker/coolify-helper/Dockerfile b/docker/coolify-helper/Dockerfile index b62469cef..8c7073519 100644 --- a/docker/coolify-helper/Dockerfile +++ b/docker/coolify-helper/Dockerfile @@ -4,15 +4,15 @@ ARG BASE_IMAGE=alpine:3.21 # https://download.docker.com/linux/static/stable/ ARG DOCKER_VERSION=28.0.0 # https://github.com/docker/compose/releases -ARG DOCKER_COMPOSE_VERSION=2.34.0 +ARG DOCKER_COMPOSE_VERSION=2.38.2 # https://github.com/docker/buildx/releases -ARG DOCKER_BUILDX_VERSION=0.22.0 +ARG DOCKER_BUILDX_VERSION=0.25.0 # https://github.com/buildpacks/pack/releases -ARG PACK_VERSION=0.37.0 +ARG PACK_VERSION=0.38.2 # https://github.com/railwayapp/nixpacks/releases -ARG NIXPACKS_VERSION=1.34.1 +ARG NIXPACKS_VERSION=1.39.0 # https://github.com/minio/mc/releases -ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z +ARG MINIO_VERSION=RELEASE.2025-05-21T01-59-54Z FROM minio/mc:${MINIO_VERSION} AS minio-client diff --git a/docker/coolify-realtime/Dockerfile b/docker/coolify-realtime/Dockerfile index 7a24200d6..18c2f9301 100644 --- a/docker/coolify-realtime/Dockerfile +++ b/docker/coolify-realtime/Dockerfile @@ -2,7 +2,7 @@ # https://github.com/soketi/soketi/releases ARG SOKETI_VERSION=1.6-16-alpine # https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2025.5.0 +ARG CLOUDFLARED_VERSION=2025.7.0 FROM quay.io/soketi/soketi:${SOKETI_VERSION} diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile index 8c5beec07..85cce14d7 100644 --- a/docker/development/Dockerfile +++ b/docker/development/Dockerfile @@ -2,9 +2,9 @@ # https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine # https://github.com/minio/mc/releases -ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z +ARG MINIO_VERSION=RELEASE.2025-05-21T01-59-54Z # https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2025.2.0 +ARG CLOUDFLARED_VERSION=2025.7.0 # https://www.postgresql.org/support/versioning/ ARG POSTGRES_VERSION=15 diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 5633170e3..a2a4b5fa3 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -2,9 +2,9 @@ # https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine # https://github.com/minio/mc/releases -ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z +ARG MINIO_VERSION=RELEASE.2025-05-21T01-59-54Z # https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2025.2.0 +ARG CLOUDFLARED_VERSION=2025.7.0 # https://www.postgresql.org/support/versioning/ ARG POSTGRES_VERSION=15 diff --git a/docker/testing-host/Dockerfile b/docker/testing-host/Dockerfile index b19d0875c..fdad3cc41 100644 --- a/docker/testing-host/Dockerfile +++ b/docker/testing-host/Dockerfile @@ -2,9 +2,9 @@ # https://download.docker.com/linux/static/stable/ ARG DOCKER_VERSION=28.0.0 # https://github.com/docker/compose/releases -ARG DOCKER_COMPOSE_VERSION=2.34.0 +ARG DOCKER_COMPOSE_VERSION=2.38.2 # https://github.com/docker/buildx/releases -ARG DOCKER_BUILDX_VERSION=0.22.0 +ARG DOCKER_BUILDX_VERSION=0.25.0 FROM debian:12-slim From d4fcafe4d3b0f2123d107826bedb9eaf25d9e2f6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:37:58 +0200 Subject: [PATCH 024/202] fix(installer): public IPv4 link does not work --- other/nightly/install.sh | 7 ++++++- scripts/install.sh | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/other/nightly/install.sh b/other/nightly/install.sh index e9f54952a..92ad12302 100755 --- a/other/nightly/install.sh +++ b/other/nightly/install.sh @@ -253,6 +253,11 @@ if [ "$OS_TYPE" = "endeavouros" ]; then OS_TYPE="arch" fi +# Check if the OS is Cachy OS, if so, change it to arch +if [ "$OS_TYPE" = "cachyos" ]; then + OS_TYPE="arch" +fi + # Check if the OS is Asahi Linux, if so, change it to fedora if [ "$OS_TYPE" = "fedora-asahi-remix" ]; then OS_TYPE="fedora" @@ -844,7 +849,7 @@ IPV6_PUBLIC_IP=$(curl -6s https://ifconfig.io || true) echo -e "\nYour instance is ready to use!\n" if [ -n "$IPV4_PUBLIC_IP" ]; then - echo -e "You can access Coolify through your Public IPV4: http://$(curl -4s https://ifconfig.io):8000" + echo -e "You can access Coolify through your Public IPV4: http://$IPV4_PUBLIC_IP:8000" fi if [ -n "$IPV6_PUBLIC_IP" ]; then echo -e "You can access Coolify through your Public IPv6: http://[$IPV6_PUBLIC_IP]:8000" diff --git a/scripts/install.sh b/scripts/install.sh index 48bbbb9f9..064fc7e4d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -849,7 +849,7 @@ IPV6_PUBLIC_IP=$(curl -6s https://ifconfig.io || true) echo -e "\nYour instance is ready to use!\n" if [ -n "$IPV4_PUBLIC_IP" ]; then - echo -e "You can access Coolify through your Public IPV4: http://[$IPV4_PUBLIC_IP]:8000" + echo -e "You can access Coolify through your Public IPV4: http://$IPV4_PUBLIC_IP:8000" fi if [ -n "$IPV6_PUBLIC_IP" ]; then echo -e "You can access Coolify through your Public IPv6: http://[$IPV6_PUBLIC_IP]:8000" From d75eb49f0135a28d3f11f2c17b7c050c0535b12f Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:39:50 +0200 Subject: [PATCH 025/202] fix(composer): version constraint of prompts --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7e8e768c0..4557f1d80 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "laravel/framework": "^12.20.0", "laravel/horizon": "^5.33.1", "laravel/pail": "^1.2.3", - "laravel/prompts": "^0.3.6|^0.3.6|^0.3.6", + "laravel/prompts": "^0.3.6", "laravel/sanctum": "^4.1.2", "laravel/socialite": "^5.21.0", "laravel/tinker": "^2.10.1", From 8a0d5ab8c8fc24501fdf5d122651ece851392f9f Mon Sep 17 00:00:00 2001 From: Alfred Nutile <365385+alnutile@users.noreply.github.com> Date: Fri, 18 Jul 2025 10:44:40 -0400 Subject: [PATCH 026/202] fix(service): Budibase secret keys (#6205) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 🏔️ Peak <122374094+peaklabs-dev@users.noreply.github.com> --- templates/compose/budibase.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/compose/budibase.yaml b/templates/compose/budibase.yaml index a5ecb8fbb..1364e7137 100644 --- a/templates/compose/budibase.yaml +++ b/templates/compose/budibase.yaml @@ -17,8 +17,9 @@ services: - INTERNAL_API_KEY=$SERVICE_BASE64_128_BUDIBASE - BUDIBASE_ENVIRONMENT=${BUDIBASE_ENVIRONMENT:-PRODUCTION} - PORT=4002 - - API_ENCRYPTION_KEY=$SERVICE_BASE64_64_BUDIBASE - - JWT_SECRET=$SERVICE_BASE64_64_BUDIBASE + - API_ENCRYPTION_KEY=$SERVICE_BASE64_64_BUDIBASEAPI + - ENCRYPTION_KEY=$SERVICE_BASE64_64_BUDIBASE + - JWT_SECRET=$SERVICE_BASE64_64_BUDIBASEJWT - LOG_LEVEL=info - ENABLE_ANALYTICS=${ENABLE_ANALYTICS:-true} - REDIS_URL=redis-service:6379 From 40407bae6889d67b06b4cb4a60e79431bbba63fc Mon Sep 17 00:00:00 2001 From: Katja Lutz Date: Fri, 18 Jul 2025 16:55:26 +0200 Subject: [PATCH 027/202] chore(service): update Nitropage template (#6181) - Use new registry - Updated the DATABASE_URL --- templates/compose/nitropage-with-postgresql.yaml | 2 +- templates/compose/nitropage.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/compose/nitropage-with-postgresql.yaml b/templates/compose/nitropage-with-postgresql.yaml index 4cea4d875..da515755c 100644 --- a/templates/compose/nitropage-with-postgresql.yaml +++ b/templates/compose/nitropage-with-postgresql.yaml @@ -6,7 +6,7 @@ services: nitropage: - image: codeberg.org/nitropage/nitropage + image: nitropage/nitropage environment: - SERVICE_FQDN_NITROPAGE_3000 - NP_AUTH_SALT=${SERVICE_BASE64_SALT} diff --git a/templates/compose/nitropage.yaml b/templates/compose/nitropage.yaml index bcc58de5f..2025c0b6e 100644 --- a/templates/compose/nitropage.yaml +++ b/templates/compose/nitropage.yaml @@ -6,12 +6,12 @@ services: nitropage: - image: codeberg.org/nitropage/nitropage:sqlite + image: nitropage/nitropage:sqlite environment: - SERVICE_FQDN_NITROPAGE_3000 - NP_AUTH_SALT=${SERVICE_BASE64_SALT} - NP_AUTH_PASSWORD=${SERVICE_PASSWORD_64_SESSION} - - DATABASE_URL=file:../../.data/dev.db + - DATABASE_URL=file:../.data/dev.db volumes: - nitropage-data:/app/.data healthcheck: From 7c85b80703e64a69b04f09eacce8638f16153e12 Mon Sep 17 00:00:00 2001 From: Nicanor Alexander de la Cruz Caba Date: Fri, 18 Jul 2025 11:03:40 -0400 Subject: [PATCH 028/202] feat(service): enable password protection for the Wireguard Ul --- templates/compose/wireguard-easy.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/templates/compose/wireguard-easy.yaml b/templates/compose/wireguard-easy.yaml index 7ccf60554..311ca88d1 100644 --- a/templates/compose/wireguard-easy.yaml +++ b/templates/compose/wireguard-easy.yaml @@ -8,11 +8,11 @@ services: wg-easy: image: ghcr.io/wg-easy/wg-easy:latest environment: - - SERVICE_FQDN_WIREGUARDEASY_8000 + - SERVICE_FQDN_WIREGUARDEASY_51821 - WG_HOST=${SERVICE_FQDN_WIREGUARDEASY} - LANG=${LANG:-en} - - PORT=8000 - WG_PORT=51820 + - _PASSWORD=${SERVICE_PASSWORD_ADMIN} volumes: - wg-easy:/etc/wireguard ports: @@ -23,3 +23,9 @@ services: sysctls: - net.ipv4.conf.all.src_valid_mark=1 - net.ipv4.ip_forward=1 + entrypoint: + - /bin/bash + - -c + - | + eval "wgpw '${SERVICE_PASSWORD_ADMIN}' > /pass-hash" + eval "$(cat /pass-hash) dumb-init node server.js" From 675a711664e47c2fecdde4e54abec813491cc403 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 17:10:01 +0200 Subject: [PATCH 029/202] fix(service): wg-easy host should be just the FQDN --- templates/compose/wireguard-easy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/wireguard-easy.yaml b/templates/compose/wireguard-easy.yaml index 311ca88d1..6d5451c2d 100644 --- a/templates/compose/wireguard-easy.yaml +++ b/templates/compose/wireguard-easy.yaml @@ -9,7 +9,7 @@ services: image: ghcr.io/wg-easy/wg-easy:latest environment: - SERVICE_FQDN_WIREGUARDEASY_51821 - - WG_HOST=${SERVICE_FQDN_WIREGUARDEASY} + - WG_HOST=${SERVICE_URL_WIREGUARDEASY} - LANG=${LANG:-en} - WG_PORT=51820 - _PASSWORD=${SERVICE_PASSWORD_ADMIN} From e287b86759666af37a92f8da491e691668f5b308 Mon Sep 17 00:00:00 2001 From: Rachit Khurana <70265590+notnotrachit@users.noreply.github.com> Date: Fri, 18 Jul 2025 20:45:18 +0530 Subject: [PATCH 030/202] fix(ui): search box overlaps the sidebar navigation (#6176) --- resources/views/livewire/project/new/select.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index 5c1e3c6f7..eae754e56 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -12,7 +12,7 @@
Deploy resources, like Applications, Databases, Services...
@if ($current_step === 'type') -
+
From 5fd8fec172f119396d8fc61f210b1bdd1c7fd1e6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:57:43 +0200 Subject: [PATCH 031/202] chore(versions): update all version --- config/constants.php | 6 +++--- versions.json | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/constants.php b/config/constants.php index ae984aa18..c7a36d311 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,9 +2,9 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.420.6', - 'helper_version' => '1.0.8', - 'realtime_version' => '1.0.9', + 'version' => '4.0.0-beta.420.7', + 'helper_version' => '1.0.9', + 'realtime_version' => '1.0.10', 'self_hosted' => env('SELF_HOSTED', true), 'autoupdate' => env('AUTOUPDATE'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), diff --git a/versions.json b/versions.json index 7b12bd23e..03f9a97e2 100644 --- a/versions.json +++ b/versions.json @@ -1,16 +1,16 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.420.6" - }, - "nightly": { "version": "4.0.0-beta.420.7" }, + "nightly": { + "version": "4.0.0-beta.420.8" + }, "helper": { - "version": "1.0.8" + "version": "1.0.9" }, "realtime": { - "version": "1.0.9" + "version": "1.0.10" }, "sentinel": { "version": "0.0.15" From 00225a9eff75505700384d76104d4b56bf3b128f Mon Sep 17 00:00:00 2001 From: Rado Date: Fri, 18 Jul 2025 19:04:08 +0200 Subject: [PATCH 032/202] fix(webhooks): exclude webhook routes from CSRF protection (#6200) --- app/Http/Middleware/VerifyCsrfToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 9e8652172..f07050d5e 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware * @var array */ protected $except = [ - // + 'webhooks/*', ]; } From d736b3e8b79a6c6ee4fede5a2639dec5f10e1a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?= <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 19:17:28 +0200 Subject: [PATCH 033/202] feat(queues): improve Horizon config to reduce CPU and RAM usage (#6212) --- config/horizon.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/config/horizon.php b/config/horizon.php index 9dd80b8e4..cdabcb1e8 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -182,14 +182,15 @@ return [ 'defaults' => [ 's6' => [ 'connection' => 'redis', - 'queue' => explode(',', env('HORIZON_QUEUES', 'high,default')), - 'balance' => env('HORIZON_BALANCE', 'auto'), - 'maxTime' => 0, - 'maxJobs' => 0, + 'balance' => env('HORIZON_BALANCE', 'false'), + 'queue' => env('HORIZON_QUEUES', 'high,default'), + 'maxTime' => 3600, + 'maxJobs' => 400, 'memory' => 128, 'tries' => 1, - 'timeout' => 3560, 'nice' => 0, + 'sleep' => 3, + 'timeout' => 3600, ], ], @@ -198,7 +199,7 @@ return [ 's6' => [ 'autoScalingStrategy' => 'size', 'minProcesses' => env('HORIZON_MIN_PROCESSES', 1), - 'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6), + 'maxProcesses' => env('HORIZON_MAX_PROCESSES', 4), 'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1), 'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1), ], @@ -208,7 +209,7 @@ return [ 's6' => [ 'autoScalingStrategy' => 'size', 'minProcesses' => env('HORIZON_MIN_PROCESSES', 1), - 'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6), + 'maxProcesses' => env('HORIZON_MAX_PROCESSES', 4), 'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1), 'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1), ], From 19a56170882fbcb75575c672b7c3c7497da3dd30 Mon Sep 17 00:00:00 2001 From: Ficky Irwanto Date: Sat, 19 Jul 2025 00:49:27 +0700 Subject: [PATCH 034/202] feat(service): add Gowa service (#6164) --- public/svgs/gowa.svg | 1 + templates/compose/gowa.yaml | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 public/svgs/gowa.svg create mode 100644 templates/compose/gowa.yaml diff --git a/public/svgs/gowa.svg b/public/svgs/gowa.svg new file mode 100644 index 000000000..1121b05bc --- /dev/null +++ b/public/svgs/gowa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/compose/gowa.yaml b/templates/compose/gowa.yaml new file mode 100644 index 000000000..832c368ad --- /dev/null +++ b/templates/compose/gowa.yaml @@ -0,0 +1,25 @@ +# documentation: https://github.com/aldinokemal/go-whatsapp-web-multidevice +# slogan: Golang WhatsApp - Built with Go for efficient memory use +# tags: whatsapp,golang,multidevice,api,go-whatsapp +# logo: svgs/gowa.svg +# port: 3000 + +services: + gowa: + image: aldinokemal2104/go-whatsapp-web-multidevice + container_name: gowa + restart: always + volumes: + - 'gowa:/app/storages' + environment: + - SERVICE_FQDN_GOWA_3000 + - SERVICE_USER_GOWA + - SERVICE_PASSWORD_GOWA + - 'APP_BASIC_AUTH=${SERVICE_USER_GOWA}:${SERVICE_PASSWORD_GOWA}' + - APP_PORT=${APP_PORT:-3000} + - APP_DEBUG=${APP_DEBUG:-false} + - APP_ACCOUNT_VALIDATION=${APP_ACCOUNT_VALIDATION:-false} + - 'WHATSAPP_WEBHOOK=${WHATSAPP_WEBHOOK}' + - 'WHATSAPP_WEBHOOK_SECRET=${WHATSAPP_WEBHOOK_SECRET}' +volumes: + gowa: null From cacb7700de6a59480938c825f42a9cb5442f9d63 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 19:54:05 +0200 Subject: [PATCH 035/202] refactor(service): improve gowa - remove name and restart - hardcode port - formatting --- templates/compose/gowa.yaml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/templates/compose/gowa.yaml b/templates/compose/gowa.yaml index 832c368ad..9de7b49fe 100644 --- a/templates/compose/gowa.yaml +++ b/templates/compose/gowa.yaml @@ -6,20 +6,16 @@ services: gowa: - image: aldinokemal2104/go-whatsapp-web-multidevice - container_name: gowa - restart: always - volumes: - - 'gowa:/app/storages' + image: aldinokemal2104/go-whatsapp-web-multidevice:latest environment: - SERVICE_FQDN_GOWA_3000 - SERVICE_USER_GOWA - SERVICE_PASSWORD_GOWA - - 'APP_BASIC_AUTH=${SERVICE_USER_GOWA}:${SERVICE_PASSWORD_GOWA}' - - APP_PORT=${APP_PORT:-3000} + - APP_BASIC_AUTH=${SERVICE_USER_GOWA}:${SERVICE_PASSWORD_GOWA} + - APP_PORT=3000 - APP_DEBUG=${APP_DEBUG:-false} - APP_ACCOUNT_VALIDATION=${APP_ACCOUNT_VALIDATION:-false} - - 'WHATSAPP_WEBHOOK=${WHATSAPP_WEBHOOK}' - - 'WHATSAPP_WEBHOOK_SECRET=${WHATSAPP_WEBHOOK_SECRET}' -volumes: - gowa: null + - WHATSAPP_WEBHOOK=${WHATSAPP_WEBHOOK} + - WHATSAPP_WEBHOOK_SECRET=${WHATSAPP_WEBHOOK_SECRET} + volumes: + - gowa_data:/app/storages From 4df349f74d43ccccddc09db60c784f5289890ff2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 17 Jul 2025 09:31:55 +0200 Subject: [PATCH 036/202] refactor(previews): streamline preview domain generation logic in ApplicationDeploymentJob for improved clarity and maintainability --- app/Jobs/ApplicationDeploymentJob.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 67043555d..07d4ea9a0 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -229,16 +229,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue // Set preview fqdn if ($this->pull_request_id !== 0) { - if ($this->application->build_pack === 'dockercompose') { - // For Docker Compose apps, use the preview model's compose-specific method - $this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); - if ($this->preview) { + $this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); + if ($this->preview) { + if ($this->application->build_pack === 'dockercompose') { $this->preview->generate_preview_fqdn_compose(); - } - } else { - // For non-Docker Compose apps, use the preview model's method - $this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); - if ($this->preview) { + } else { $this->preview->generate_preview_fqdn(); } } From 9ed77e5eefbd5bfc77058e2de9e16a177b962d97 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 18 Jul 2025 20:18:12 +0200 Subject: [PATCH 037/202] fix(services): update environment variable naming convention to use underscores instead of dashes for SERVICE_FQDN and SERVICE_URL --- bootstrap/helpers/services.php | 8 ++++---- bootstrap/helpers/shared.php | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index cd99713a2..1bddfede7 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -119,7 +119,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $resourceFqdns = str($resource->fqdn)->explode(','); if ($resourceFqdns->count() === 1) { $resourceFqdns = $resourceFqdns->first(); - $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', ''); + $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_'); $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) ->where('resourceable_id', $resource->service_id) ->where('key', $variableName) @@ -151,7 +151,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $generatedEnv->save(); } } - $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', ''); + $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) ->where('resourceable_id', $resource->service_id) ->where('key', $variableName) @@ -243,7 +243,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $port_env_url->save(); } } else { - $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', ''); + $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_'); $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) ->where('resourceable_id', $resource->service_id) ->where('key', $variableName) @@ -254,7 +254,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $generatedEnv->value = $fqdn; $generatedEnv->save(); } - $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', ''); + $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) ->where('resourceable_id', $resource->service_id) ->where('key', $variableName) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 00a674eeb..de6c954e0 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -3080,11 +3080,8 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } } elseif ($isService) { if (blank($savedService->fqdn)) { - if ($fqdnFor) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - } else { - $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); - } + // For services, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty + $fqdn = ''; } else { $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); } @@ -3150,6 +3147,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int if ($isApplication && $resource->build_pack === 'dockercompose') { continue; } + // For services, only generate FQDN if explicit FQDN is set + if ($isService && blank($savedService->fqdn)) { + continue; + } $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); if (str($fqdnFor)->contains('_')) { $fqdnFor = str($fqdnFor)->before('_'); @@ -3168,6 +3169,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int if ($isApplication && $resource->build_pack === 'dockercompose') { continue; } + // For services, only generate URL if explicit FQDN is set + if ($isService && blank($savedService->fqdn)) { + continue; + } $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); if (str($fqdnFor)->contains('_')) { $fqdnFor = str($fqdnFor)->before('_'); @@ -3674,8 +3679,8 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $coolifyScheme = $coolifyUrl->getScheme(); $coolifyFqdn = $coolifyUrl->getHost(); $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); - $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper(), $coolifyUrl->__toString()); - $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper(), $coolifyFqdn); + $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyUrl->__toString()); + $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyFqdn); } } } From 5e693eb4b51a9f5d9e4e17afe8a11868eedb0a81 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 18 Jul 2025 20:24:53 +0200 Subject: [PATCH 038/202] refactor(services): simplify environment variable updates by using updateOrCreate and add cleanup for removed FQDNs --- bootstrap/helpers/services.php | 91 ++++++++++++++++------------------ 1 file changed, 43 insertions(+), 48 deletions(-) diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 1bddfede7..0adccd023 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -120,69 +120,53 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) if ($resourceFqdns->count() === 1) { $resourceFqdns = $resourceFqdns->first(); $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_'); - $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', $variableName) - ->first(); $fqdn = Url::fromString($resourceFqdns); $port = $fqdn->getPort(); $path = $fqdn->getPath(); $fqdn = $fqdn->getScheme().'://'.$fqdn->getHost(); - if ($generatedEnv) { - if ($path === '/') { - $generatedEnv->value = $fqdn; - } else { - $generatedEnv->value = $fqdn.$path; - } - $generatedEnv->save(); - } + $fqdnValue = ($path === '/') ? $fqdn : $fqdn.$path; + EnvironmentVariable::updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $fqdnValue, + ]); if ($port) { $variableName = $variableName."_$port"; - $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', $variableName) - ->first(); - if ($generatedEnv) { - if ($path === '/') { - $generatedEnv->value = $fqdn; - } else { - $generatedEnv->value = $fqdn.$path; - } - $generatedEnv->save(); - } + EnvironmentVariable::updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $fqdnValue, + ]); } $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); - $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', $variableName) - ->first(); $url = Url::fromString($fqdn); $port = $url->getPort(); $path = $url->getPath(); $url = $url->getHost(); - if ($generatedEnv) { - $url = str($fqdn)->after('://'); - if ($path === '/') { - $generatedEnv->value = $url; - } else { - $generatedEnv->value = $url.$path; - } - $generatedEnv->save(); + $urlValue = str($fqdn)->after('://'); + if ($path !== '/') { + $urlValue = $urlValue.$path; } + EnvironmentVariable::updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $urlValue, + ]); if ($port) { $variableName = $variableName."_$port"; - $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', $variableName) - ->first(); - if ($generatedEnv) { - if ($path === '/') { - $generatedEnv->value = $url; - } else { - $generatedEnv->value = $url.$path; - } - $generatedEnv->save(); - } + EnvironmentVariable::updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $urlValue, + ]); } } elseif ($resourceFqdns->count() > 1) { foreach ($resourceFqdns as $fqdn) { @@ -269,6 +253,17 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) } } } + } else { + // If FQDN is removed, delete the corresponding environment variables + $serviceName = str($resource->name)->upper()->replace('-', '_'); + EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceName}%") + ->delete(); + EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', 'LIKE', "SERVICE_URL_{$serviceName}%") + ->delete(); } } catch (\Throwable $e) { return handleError($e); From ef91e43384f5a05616b55e370c6ef710df467054 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 18 Jul 2025 20:44:07 +0200 Subject: [PATCH 039/202] feat(container): add updatedSelectedContainer method to connect to non-default containers and update wire:model for improved reactivity --- app/Livewire/Project/Shared/ExecuteContainerCommand.php | 7 +++++++ .../project/shared/execute-container-command.blade.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php index ca1597d4f..9003e093a 100644 --- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php +++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php @@ -137,6 +137,13 @@ class ExecuteContainerCommand extends Component } } + public function updatedSelectedContainer() + { + if ($this->selected_container !== 'default') { + $this->connectToContainer(); + } + } + #[On('connectToServer')] public function connectToServer() { diff --git a/resources/views/livewire/project/shared/execute-container-command.blade.php b/resources/views/livewire/project/shared/execute-container-command.blade.php index c04af1dd5..71d253103 100644 --- a/resources/views/livewire/project/shared/execute-container-command.blade.php +++ b/resources/views/livewire/project/shared/execute-container-command.blade.php @@ -21,7 +21,7 @@
No containers are running or terminal access is disabled on this server.
@else
- + @foreach ($containers as $container) @if ($loop->first) From 90ccaeba51f6bb1b260baa83dd67184205ba72c3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 18 Jul 2025 20:48:51 +0200 Subject: [PATCH 040/202] feat(application): implement environment variable updates for Docker Compose applications, including creation, updating, and deletion of SERVICE_FQDN and SERVICE_URL variables --- app/Livewire/Project/Application/General.php | 88 ++++++++++++++++++++ bootstrap/helpers/services.php | 8 ++ 2 files changed, 96 insertions(+) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 3e89ad663..ac2e6794d 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application; use App\Actions\Application\GenerateConfig; use App\Models\Application; +use App\Models\EnvironmentVariable; use Illuminate\Support\Collection; use Livewire\Component; use Spatie\Url\Url; @@ -488,6 +489,12 @@ class General extends Component } $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); + + // Update SERVICE_FQDN_ and SERVICE_URL_ environment variables for Docker Compose applications + if ($this->application->build_pack === 'dockercompose') { + $this->updateServiceEnvironmentVariables(); + } + $showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!'); } catch (\Throwable $e) { $originalFqdn = $this->application->getOriginal('fqdn'); @@ -513,4 +520,85 @@ class General extends Component 'Content-Disposition' => 'attachment; filename='.$fileName, ]); } + + private function updateServiceEnvironmentVariables() + { + $domains = collect(json_decode($this->application->docker_compose_domains, true)) ?? collect([]); + + foreach ($domains as $serviceName => $service) { + $serviceNameFormatted = str($serviceName)->upper()->replace('-', '_'); + $domain = data_get($service, 'domain'); + + if ($domain) { + // Create or update SERVICE_FQDN_ and SERVICE_URL_ variables + $fqdn = Url::fromString($domain); + $port = $fqdn->getPort(); + $path = $fqdn->getPath(); + $fqdnValue = $fqdn->getScheme().'://'.$fqdn->getHost(); + if ($path !== '/') { + $fqdnValue = $fqdnValue.$path; + } + $urlValue = str($domain)->after('://'); + if ($path !== '/') { + $urlValue = $urlValue.$path; + } + + // Create/update SERVICE_FQDN_ + EnvironmentVariable::updateOrCreate([ + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + 'key' => "SERVICE_FQDN_{$serviceNameFormatted}", + ], [ + 'value' => $fqdnValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + + // Create/update SERVICE_URL_ + EnvironmentVariable::updateOrCreate([ + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + 'key' => "SERVICE_URL_{$serviceNameFormatted}", + ], [ + 'value' => $urlValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + + // Create/update port-specific variables if port exists + if ($port) { + EnvironmentVariable::updateOrCreate([ + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + 'key' => "SERVICE_FQDN_{$serviceNameFormatted}_{$port}", + ], [ + 'value' => $fqdnValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + + EnvironmentVariable::updateOrCreate([ + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + 'key' => "SERVICE_URL_{$serviceNameFormatted}_{$port}", + ], [ + 'value' => $urlValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } else { + // Delete SERVICE_FQDN_ and SERVICE_URL_ variables if domain is removed + EnvironmentVariable::where('resourceable_type', Application::class) + ->where('resourceable_id', $this->application->id) + ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%") + ->delete(); + + EnvironmentVariable::where('resourceable_type', Application::class) + ->where('resourceable_id', $this->application->id) + ->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%") + ->delete(); + } + } + } } diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 0adccd023..1e1d2a073 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -131,6 +131,8 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) 'key' => $variableName, ], [ 'value' => $fqdnValue, + 'is_build_time' => false, + 'is_preview' => false, ]); if ($port) { $variableName = $variableName."_$port"; @@ -140,6 +142,8 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) 'key' => $variableName, ], [ 'value' => $fqdnValue, + 'is_build_time' => false, + 'is_preview' => false, ]); } $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); @@ -157,6 +161,8 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) 'key' => $variableName, ], [ 'value' => $urlValue, + 'is_build_time' => false, + 'is_preview' => false, ]); if ($port) { $variableName = $variableName."_$port"; @@ -166,6 +172,8 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) 'key' => $variableName, ], [ 'value' => $urlValue, + 'is_build_time' => false, + 'is_preview' => false, ]); } } elseif ($resourceFqdns->count() > 1) { From 8f920ab4e15769c2d08e25e8bb8389c3b9186d8c Mon Sep 17 00:00:00 2001 From: CrazyTim71 <118295691+CrazyTim71@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:26:23 +0200 Subject: [PATCH 041/202] feat(service): add TriliumNext service (#5970) --- public/svgs/triliumnext.svg | 28 ++++++++++++++++++++++++++++ templates/compose/triliumnext.yaml | 23 +++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 public/svgs/triliumnext.svg create mode 100644 templates/compose/triliumnext.yaml diff --git a/public/svgs/triliumnext.svg b/public/svgs/triliumnext.svg new file mode 100644 index 000000000..173712891 --- /dev/null +++ b/public/svgs/triliumnext.svg @@ -0,0 +1,28 @@ + + + TriliumNext Notes + + + + + + + + + + + + + + + diff --git a/templates/compose/triliumnext.yaml b/templates/compose/triliumnext.yaml new file mode 100644 index 000000000..a87a766b5 --- /dev/null +++ b/templates/compose/triliumnext.yaml @@ -0,0 +1,23 @@ +# documentation: https://github.com/TriliumNext/Notes +# slogan: Build your personal knowledge base with TriliumNext Notes. +# tags: self-hosted, notes, todo, organize, markdown, wiki +# logo: svgs/triliumnext.svg +# port: 8080 + +services: + triliumnext: + image: triliumnext/notes:latest + environment: + - SERVICE_FQDN_TRILIUMNEXT_8080 + - TZ=${TZ:-Europe/Berlin} + volumes: + - triliumnext_data:/home/node/trilium-data + healthcheck: + test: + [ + "CMD-SHELL", + "wget --quiet --tries=1 --spider http://127.0.0.1:8080/api/health-check || exit 1", + ] + interval: 5s + timeout: 20s + retries: 10 From f14858ebeea344748e01406e8bcf4dc4ff2482d4 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:31:36 +0200 Subject: [PATCH 042/202] fix(service): triliumnext platform and link --- templates/compose/triliumnext.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/compose/triliumnext.yaml b/templates/compose/triliumnext.yaml index a87a766b5..1dd230473 100644 --- a/templates/compose/triliumnext.yaml +++ b/templates/compose/triliumnext.yaml @@ -1,4 +1,4 @@ -# documentation: https://github.com/TriliumNext/Notes +# documentation: https://github.com/TriliumNext/Trilium # slogan: Build your personal knowledge base with TriliumNext Notes. # tags: self-hosted, notes, todo, organize, markdown, wiki # logo: svgs/triliumnext.svg @@ -6,7 +6,8 @@ services: triliumnext: - image: triliumnext/notes:latest + image: ghcr.io/triliumnext/trilium:stable + platform: linux/amd64 # https://github.com/TriliumNext/Trilium/issues/6390 environment: - SERVICE_FQDN_TRILIUMNEXT_8080 - TZ=${TZ:-Europe/Berlin} From fea75443000db8f5d778759cec3c6836742e68a7 Mon Sep 17 00:00:00 2001 From: Nicanor Alexander de la Cruz Caba Date: Fri, 18 Jul 2025 15:39:11 -0400 Subject: [PATCH 043/202] feat(service): add Matrix service (#6029) --- public/svgs/matrix.svg | 7 ++ templates/compose/matrix.yaml | 130 ++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 public/svgs/matrix.svg create mode 100644 templates/compose/matrix.yaml diff --git a/public/svgs/matrix.svg b/public/svgs/matrix.svg new file mode 100644 index 000000000..bc41720a2 --- /dev/null +++ b/public/svgs/matrix.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/templates/compose/matrix.yaml b/templates/compose/matrix.yaml new file mode 100644 index 000000000..372ba57df --- /dev/null +++ b/templates/compose/matrix.yaml @@ -0,0 +1,130 @@ +# documentation: https://matrix.org/docs/chat_basics/matrix-for-im/ +# slogan: Chat securely with your family, friends, community, or build great apps with Matrix! +# tags: chat,slack,discord,voip,video,call +# logo: svgs/matrix.svg + +services: + matrix: + image: matrixdotorg/synapse:latest + volumes: + - ./data:/data + environment: + - SERVICE_FQDN_MATRIX_8008 + - SYNAPSE_SERVER_NAME=${SERVICE_URL_MATRIX} + - SYNAPSE_REPORT_STATS=no + - ENABLE_REGISTRATION=${ENABLE_REGISTRATION} + - RECAPTCHA_PUBLIC_KEY=${RECAPTCHA_PUBLIC_KEY} + - RECAPTCHA_PRIVATE_KEY=${RECAPTCHA_PRIVATE_KEY} + - _SERVER_NAME=${SERVICE_URL_MATRIX} + - _ADMIN_NAME=${SERVICE_USER_ADMIN} + - _ADMIN_PASS=${SERVICE_PASSWORD_ADMIN} + entrypoint: + - /bin/bash + - -c + - | + ! test -f /data/homeserver.yaml && /start.py generate + + # registration_shared_secret + grep "registration_shared_secret" /data/homeserver.yaml \ + | awk '{print $2}' > ./registration_shared_secret + + # macaroon_secret_key + grep "macaroon_secret_key" /data/homeserver.yaml \ + | awk '{print $2}' > ./macaroon_secret_key + + # form_secret + grep "form_secret" /data/homeserver.yaml \ + | awk '{print $2}' > ./form_secret + + ########################## + # # + # homeserver.yaml: start # + # # + ########################## + cat < /data/homeserver.yaml + server_name: "${SERVICE_URL_MATRIX}" + pid_file: /data/homeserver.pid + + # server + listeners: + - port: 8008 + tls: false + type: http + x_forwarded: true + resources: + - names: [client, federation] + compress: false + + # database + database: + name: sqlite3 + args: + database: /data/homeserver.db + + # general + log_config: "/data/${SERVICE_URL_MATRIX}.log.config" + media_store_path: /data/media_store + report_stats: false + + # secrets + registration_shared_secret: $(<./registration_shared_secret) + macaroon_secret_key: $(<./macaroon_secret_key) + form_secret: $(<./form_secret) + signing_key_path: "/data/${SERVICE_URL_MATRIX}.signing.key" + + #rooms + auto_join_rooms: + - "#general:${SERVICE_URL_MATRIX}" + + # federation + trusted_key_servers: + - server_name: "matrix.org" + autocreate_auto_join_rooms_federated: false + allow_public_rooms_over_federation: false + EOF + ######################## + # # + # homeserver.yaml: end # + # # + ######################## + + test -n "${ENABLE_REGISTRATION}" && ! grep "#registration" /data/homeserver.yaml &>/dev/null \ + && echo >> /data/homeserver.yaml \ + && cat <> /data/homeserver.yaml + #registration + enable_registration: true # Allows users to register on your server. + EOF + + ! grep ${RECAPTCHA_PUBLIC_KEY} /data/homeserver.yaml &>/dev/null \ + && echo >> /data/homeserver.yaml \ + && cat <> /data/homeserver.yaml + # reCAPTCHA settings + enable_registration_captcha: true # Enables CAPTCHA for registrations. + recaptcha_public_key: "${RECAPTCHA_PUBLIC_KEY}" + recaptcha_private_key: "${RECAPTCHA_PRIVATE_KEY}" + recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify" + EOF + + register_admin(){ + while ! curl -I localhost:8008 &>/dev/null; do + sleep 1 + done + register_new_matrix_user \ + -a \ + -u ${SERVICE_USER_ADMIN} \ + -p ${SERVICE_PASSWORD_ADMIN} \ + -c /data/homeserver.yaml \ + http://localhost:8008 &>/dev/null + } + register_admin & + + /start.py + healthcheck: + test: + - CMD + - curl + - -I + - localhost:8008 + interval: 5s + timeout: 3s + retries: 5 From 9292d3c223e7feaae6bf3c19cbf13ffb9f9c28a6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:41:42 +0200 Subject: [PATCH 044/202] chore(service): improve matrix service - formatting and ENVs adjustment --- templates/compose/matrix.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/templates/compose/matrix.yaml b/templates/compose/matrix.yaml index 372ba57df..99232967e 100644 --- a/templates/compose/matrix.yaml +++ b/templates/compose/matrix.yaml @@ -2,22 +2,23 @@ # slogan: Chat securely with your family, friends, community, or build great apps with Matrix! # tags: chat,slack,discord,voip,video,call # logo: svgs/matrix.svg +# port: 8008 services: matrix: image: matrixdotorg/synapse:latest - volumes: - - ./data:/data environment: - SERVICE_FQDN_MATRIX_8008 - SYNAPSE_SERVER_NAME=${SERVICE_URL_MATRIX} - - SYNAPSE_REPORT_STATS=no - - ENABLE_REGISTRATION=${ENABLE_REGISTRATION} + - SYNAPSE_REPORT_STATS=${SYNAPSE_REPORT_STATS:-no} + - ENABLE_REGISTRATION=${ENABLE_REGISTRATION:-false} - RECAPTCHA_PUBLIC_KEY=${RECAPTCHA_PUBLIC_KEY} - RECAPTCHA_PRIVATE_KEY=${RECAPTCHA_PRIVATE_KEY} - _SERVER_NAME=${SERVICE_URL_MATRIX} - _ADMIN_NAME=${SERVICE_USER_ADMIN} - _ADMIN_PASS=${SERVICE_PASSWORD_ADMIN} + volumes: + - matrix-data:/data entrypoint: - /bin/bash - -c From 451aadbd7f7fc484722b7e2d68c362ad4ce20ba1 Mon Sep 17 00:00:00 2001 From: Eric Dahl Date: Fri, 18 Jul 2025 15:54:39 -0400 Subject: [PATCH 045/202] feat(service): add GitHub Action runner service (#6209) --- public/svgs/github-runner.png | Bin 0 -> 13322 bytes templates/compose/github-runner.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 public/svgs/github-runner.png create mode 100644 templates/compose/github-runner.yml diff --git a/public/svgs/github-runner.png b/public/svgs/github-runner.png new file mode 100644 index 0000000000000000000000000000000000000000..fb5b5c1b769af43cbab8638df0f2fd73f5af5e1d GIT binary patch literal 13322 zcmb7rc{r5c`@dx{_A%Dbm>EN5AK4AY7<+`uAjH_RhS1oG4932cqAZD0mdTcFknE&T zku9=Cj4UOh^nJWP*Y*4V_lN5;=bY!vdG7UfzwR?hR+h%x9HJZy3=G_+CU_eL2FA?e zKQJpW67x!Jh=Jkks3~6mA}OC1fYkkqiI+NZcG3uXpqbNX=6{Iy!l)$o3yi#N+IaxiAiV%Dw#x7QD0=%)789s^>c& zXvmoLo5wXzu*C2C)*dIKdz{M#3m3^@+h)~sN;bUAK^bUV^Fj}D%qg0H5f8YGuscEF z?y2Eyulu8474i(hPhS!E1;vrE@0s31(n_#xsnmvTML^x+p4l*E0u z-dxkt8|t{_S&{41H%T~(E=U#*CRkXd%kB@&r_JmiZ>+&?gSEgQi18L|yYjd`@uk9STa$S5w^HS;0xVz(igQI5_f7)CD}O$R$jA0X`X4>_${;&9gK7W+BBnoS8Eob1fGJupPQCI7EU;WK$mR!C0<1) znW5U71X9)D57+b|{kFbM^ec*}B{z1hVC|0(n3F z0^>lR$8u8wdTg~+0S(Seoh6t`_Fw~o2u zD)Yo}=_Cq-x%X+zjOJkY%wyGXWzF>VFX?YzN^ai+7F$(7C$Uh9SZg$tL-;2)UG+&}D5ow`Pa$R)WE}QVT^! z{d1pJngsHGIl)he|%C2)HLg0@&he|Q!ZtLYyH;EHn>M?;#MS6@@+LCoJl`4 zr@Zah{8dFxee3bc%y@GOY_kJb47GF~?}-AOk$FmbR4?;mxJWr9fj}KHb*B)O-y|#Z=NuYqPF2*Xg zWpsAx#Cbh>U6LX;lA`tt*v0&Jt$3@UXHC~@tIil)>5vg)&6j99UZ?rj?vbV6m5sNI z9RE3cJ<9)`(6kh};D7`3wk{oSblLcZOqA-@wElE@8QJX*;dG2kV0P^%>23(g6l*uz zH-l3hJ{vgPwP)bXJKNv)F`K>x%bP2asU=f#UozcJM6WZFmRF(9CcLHQU{jl#n-@K5 zFasqY@wOC1%>c7&xCD!*r5%f_W~kgK-AZYg>8Y*!JS zVl&@(SA6;ReajjB z=jVz{*U92H1#p$FIcz)W^$q5mlD2E8LsTzH`+>Z&Ol=tJd#Dkm)J_VXnua+gZSRCO zbe!uO%HSuzBt}tcTOs4G4Z-siJrG%>y(KMZA3B(JLnmpD=fKA>$KBZNoKZ=SNY4d| zejPow+A_BC!t?7g^Rh;h=&w2ZB2gh$bMAXvG}=ylbwZ~d4&PfFqXccK6Oev4Pwmoe zj&-^GpsRxTOuI~e!Qr>tuZZ`SbX;1i971hr7anvhaH z>+KE*smWK+aqzdlV1$}gBs|sP@)yq?m#e5rULC1PUM4Z;9u2GsmP{c#V4eBLMN{`` z&Y=gNn$(^(ZVxbk0ADPb68(x7f!EH3_21|ewFaVlhDq%q<7Y;$1WKL&ONVAni?M%6Ff8Xoz7+t8~XugqJYv~iE^X>Kp2DbcYo z3WRUVY&KCR(_L?Og0K#DmA{Ydgk(ZCo56^Cq#jY2p`E0Cru*EkiloQz6H2B+wi$3# zhI?!~imqu(+qv#9+82EjU%l8nLX4nVMW$l`=&S3QdoDP8lInxP+YU{)?(e3=C@J%JF9$t^c(9E}Hy$5%5w zLAMSX-5mHe#O)+4Kx8n%GCdt7r0BE`u5~qwcyRb~pcznbJ{MO~j^!SXGHZfG7H7HZ z{rc-bhIE=>7ntOyPPJc2gy1gzSw3W;+}EGnP%U0+G1BBf;IE)#$0qMmBOog zPjFl@uS^6rn|r4Mc(a%8o7|;6I-u%lA2>yiQWy|SD}vtp8RY>Dpbv~i;>pH6UdDxw zH=HlyC=4z++h$mY+o<1UQdx=o*&+8EjFN1RXYwJjKavGxNEUXI;X6UU`J5Adw{x`v zV&7}CJx)l)k$Gj=9+!S&4J=!n`mk~_`c)z|S=^+nd?^~t$M_=I+9>aH47vKMRkZZ) zrhE}Mm`+k3*TVcx>=3DFN0&^AX~{z_hR7(EGKxwZDfQ-)BG}bo-CdcTd1%KF0piw2 z)1N00d^aDxH-BNe^I(mJQ8dbXv3YyD{Ss6G5BkWoQxJFM1x1VvJ)Z4 zb{H#iK@);t#mU1jbjA&TN}ATb2h0{999c52vSMo(d0o{Jc(wIy@r{c_iCm=E*`Y?QuMcaq@kpn}_a^FLlKaxoo_g?YB@{27Jp_R) z4(E?HdnQs2d%B|Y2`tL_fw9peA@GuwD=bPO6;g-+{JdC`zaVJmjj8a!ZRQ(VRGrT# zb(8EO;SxVRm2HO26Dg!h7(dR>^a9qd7%~`#LSa;O{cNJuX+5WD+dmJdJHX4P7&3)& zh3Pg1yy(6zdkT9>;(m^Cs8?$9kBhFCz9@eQ>TfHyitpWzYKM+Pa2g9^@Bew`GhEM~ zDCr#y>9BA_n8LcA=P!r$ZaK$8T$&3Gg>7=b>Y!0y1%}gjxnYl2V!vt@#@cuq(e5Gd z?a7h%2P9x%HKmgLJ+QnE@v&w0p-+^CacEA)(WPjY-Gk(v4n%jRJ&)^&1tVR_s>NiT z4AY&T=;o zI7+|WDR?FK#(Pfow#4^D1cwJruYo8gTKBiC#>6I>1i=!?y$A045 z@%7`LJah@C;lO>BKd@YAg|mNG>HYUt)*M0>8U@Hu*=rl{}M)(zDb!gF#StHFK&9AqLA*yxotUOQe`j z|G))iEPwpEX{S&5!1x^d$A0d!cu^h%Nvu`-cOLM0{mPc(QmCkY++in36Y?$vwZ~Scrk!WyXPRD_1p>Rz)B`2esLDl$0O+sk;cbN}9YunGT`M3z^Sd-?fK9 zKk?&8p0bSSS6=aTcfK^5L~stnXT02G76Ur>+{hv*|BC9Hu}f&$&MK%Ag&|sFs3;h& zv~Qe=$&bYO$z+P-){H$QBky|@I7_+!1iB&NH&c^qg8vhfa$ilx`9hHHqeEpJS2`6@Df&yQ?LksWZ%W+LtRK|~*oCA-i1!C` zY^KOd!N*N_J3~8$CoO8wuTNofzOGIPJoYDt>)4~loGO{}GYXHmzw0K*2bykWMGcn> z%x4DBInfsn5~_s+O6W5S@G?VA!D;_5iA>1?6Zb(6^UX125mo_4#=h?P3vcN9_>0A3 zSy7JP;i#);XXlHp4L=-I&7eURmjofNGC%3HalRp&Bha)q8V1B-tIAK|+ zfKyuakaA`i^J^Ha@)fc`%TpD}U`RQejaxDIzE;49w83mYG`*E)u~z;S1StoUJwdX_ z-4Lu3=1+9vBJ#P()8L5&tAn^sla8mnpfJ zwfIc;&Dp;HO-a84b?*!E#hIsH37nK~*%BJOH)nu}QZg{lTh-M@kWc1wP=anuZb^AU z?i^-HQ1&ZjMm2W6_^af-{$bTz4_Od?$I{)@Rx*;XHoq^p(br}hh`?M57v0?YP zRp&McQOX(A{pY5gWnc?Wk{P~T-_BD_M*!@-yP^t`1nK)fj|WJk-4b2BVzHE#d6_jW|oux`9LP(%@tni za+=0`xrm2E^ef8!7>oQzm73(WdXKNYxjuEbtiPXzxfA|^ZLmH7{{cdOXcoM}yW z*?gTlrxxg`N$z-ha89oTq&2l4HrP+q0LjC9qa$|^R`5@J^}h7y!@>{4OHN%L!RZLN z6C7^~XNopGxDUG=-u+ITRXC(cCX2hBgIn(hg+9mwFdN)Zr%zeUg}QlN8*S{r=i_a@ zo8A^{H5HgMH0!6*p#?D?K_iYzA&*IRuj}%;=vXWGKLJM@6+w4G0L2#TBU)S~nRBE7 zRv2wak8&Jut}4EHZEs>r|JL~$1TEN$Fv@9?wxW3g0eL%fC$^C16S~in_b8I{^ zb~P3H>?Zu(<`3V#$N9W)9!72JE|n|p=Wj9{Mj3dezbR~Nzhd&NpQ@$bSn-;pU%CGw zM(gNhXv)g9wDETX`55LCSH#bjL9onG(oe1YFnRoS`S`i>XHLwr`t2ASmpyYih|(;SMK?jOOkSD@ z+Z}wjegd-@{fdG2oVef4;K++qGyp~pS0nEbfJoLZ;*mJU8imLjblFzwF$g`~Pi1P5 zWv85ERZUh0Xq1z!D-Sc(_LX~1KO}Y)sg-Z}RWBCL%y8CLY;Bk!=IYX$&|y1iZ)!*_4|cyN;^|q*m=x-Pm(lTZ$?yuCyv4B!lW2$-aOe9 zcHzvw?bluA^^9na{6k2s4Mz|DlFj(oxuZ>*kdC95pDdop0d*^wMj$i_CP;x}{$(Y( z{}}@%dsgo!W|xZGQq3a#*?=KOrI>4(QMdrB1nE6(EWTj z@N~dris}$H01~d<-LO>AVVals2a`n41gs}U7I=%M6;wl%iJK*3T&Kz zo^4ufGJQ}%;rNJ&KqX^TGr;)cWg)MoZdDJ=Z7r< z0&D3JA}$#5AJ}OiGC*r&*-e_nUyE%k-{fEDA?@SC%}{j0RuHImhU|iBy>6Z zPV2y7r-ldHa%?~K{qX|FrK{0@C4y0EcQ#V-D}+wbcVNFeVm6Z=FswpV5`9ol1<-o; z8@}ufsqQ6aP1iv?*$1ey9*fx(*6q}feWy$DE%amqNZ6BytKNYZ2_8#}?_J`?{2?u( z=GWpfo|yISu-9yQDM>lDOmnUl%hDDgWFS^GIi7GkVt-T+1`gUhop7K5R{Yu4x?4~^ z`r=2>iFNS@9_|D^_Se)f6x==8I*dcHqmiS!ugv7}udvrA{dUA~wKvRQ zzeTlcD+1|jFG1Z*&@7gJzkc_Lw1hT1e%mE=bm(7n0>YyfzNSsJ&YGxX8 z*`+$V|Ff0|;u_?0EJ z?uIwa(z61B`OOzJT-*ukpElw_-HcIG<`|;j>1T}W8LuscoSro?7qR$P=pW|4^IDQt zOBp2T#Om+u1-{VRzp2AG5+*Zk{_$ZO)%L_6X$g&5*_3|fZE&#Uib-wV!B4i>71o=f zV`*yP#C~cgjtR;D_AO8&qc|4BWBn_fCvuFmr7MFs}xfyRL&!Dx9jJaF&_R-x=E4B z2qFG`45GH2Vxu3{6YENdA0$b#7~ROw-GCe5-=XD*|g4uqmP$`jc zHom%BOCyE2Q5CBM0}$<+hg&zWQeHoN}kBosTg#o`~-Pn=tglW`MEznkdV!6A%V zxWwCZ83f=Slu)I$mB^xCjNdP~nx+f+1kQKn3~h(^Ef7zMCsWzZ&wA8OEAyyFnmla1 z9c05baHv#U0e>`^1HxCjPd9Ulk%sNMUwiWU+$pgvvOe=8t(uj4GUv(!;N^S|UHrlv zdQy7c!aho~-DPu+LC8tcb~wh4x8}dX?n_@c5Q!Id6IQ=cN)@arnO;F2`k{ci6Yh_Ir+ z@$|tn<8&EGc~Kq86Ww2qUyl*3sp1(Cs|{s@yw~p8PVbNxqrVQd%Z+Xiv3>kPoZ!bg2STnsow}<0fp{0aX-zT?8%wu3wgD|>$vVMEy(G<_F-<=~ zRVY79bvC<`yd=%x{MBPai`Hy_1V5WJSVfex=3H&vb4NU%TvDkS8B?yUkUBY^VE`vqQj&Wo$>z%iP^y!YXU1I{*MCB zW`A?s&(9{u`(L~@8y;~bB`PL`&Az{%y85$0)&eD1?qHkj*0{7(P%!g;>gkc=^y3=8 zhOz}(f5z-10$HR=Y}lAsIpCAzOF*_Of7`A)_H{OgR0$}6{n%B>q1BUj{AjMRk@*n+ z&z+X6|J5RZSOb~Xe(%XQl$pd<5bvHe$4p7meBxCBw*z~1gwDR4IIU>A)>O}-qy?ur zq=X)im%-U1DBrGYsUC63(|dKTN7QR8yfgsBLD^z*2;1b{|7fSPlrC7ifTRWc0`@VV zM!YMip*od|6INE&I4S+v`jc?2?2Thp&FUjVgzv1{%x+!q+qxcp@)uBu7&9SnGoa}i zV7*e?IXy$=AA!%_LZT70+7yR8hL_6hob{FaE~%PzDlH#a_Ih)N%`y|?=*cM{kO{UE zBz6QA(@i)sHgt=7GYuA)6l7xuXU33uJ*D$q7pKKHn&q$ieScw=+LuT#*M>sij7gmJ z15~dw`yz%~23!Z1pFO zg~e@WloH)?hn*!|4~7$zdK!D@%F;r6ut05X#DvU#GTNY(^j4-58@4OU3NId&!uk&^ z=7{;(SaJT^hicihdKOV{F?CTL8Na~oFXL3*wzN0YrC7@8*c;aV^c(x$cTWCEkL%qm znpgQpP5XS1<&)rnq@(CEWPB?K^d)xp%CN^%pfp3ZmFnMC8he8N z+P&wCP>#Q`7Tv`wNPeDOFI$8|GI|5e+1eH858~aw77?0pNWP3ksq(a&fljQ_`>l0N0HUs3`rSkxa0=Dy@pXc$} zFnAJC{dswDhi6_}|I31Y*rmy_9)s}2t%6G^_^sodd=emf^RabtXkDGAN zQDUVdHxJ;wo57%K-N%l5M7DnWi0!p#VC0`f-4d^hysdpvj%z&DY=M!EVY~V9PEMGK zI$5bvu}rgY*Szk9w63ZZdvy4@#HEJ_c4Q?>+nYcq#{z$vRP=ilJEI zxB*gkRQlRmzE-|?SLfpczGMN;@CCSW(z<+G$8Crocaz8h;hh`{ zlcRGW^f)+N<@LE<(s_P&idc?hl$)&^F7rvqh#!dMKk^{_wVN0#Yy1loWwjs&?>}(4 zQ0LTIu}-Z~zD$xpZr#?&TQ69m8}i+*Ueq6pyM6Tf{M@EKl8FhL@iOe?f1@nkglI!N zQ!Uf`w4aXw5LkL&KoF4sPQ5+{8U9K+QCBtLQrnN8?I@(4M6dpYpe)~K5!>Z4>H&oU za%TZDb#K+Xc5$An*uup8zShy*@dIWd*c#uCqk{Th@v-PQu9S%W@F6j$YgN;N4|F2x zDyY%*D7Y*(B}u!}CT{KBx*ew6;xEvKYfQg6clJwO>y1B!yot68z_?J@?zbvE+HJ3e z8EOm37MiIFG;;Lbr;Y2QKAfEW0j8?9EDHUM)4HqCx-#FUdZ3(sdE~ra+viyzv-rXb zpI!rq6f?V1c2)rg?!P|?oIVE*t}pv7kK$+BiwOW?binT$B(~+JmUNkqSv6l9GXWCn z{Iy1a(difM=k0O7_OxVSEphqCz&ee?2>f$uCu(Ccv!`D1SyOEQp=h?(jj&ql`xpWYZ~x^%w1J${_U+j#yVpR9T(y!VV)B|Q;EUhz1^2>*5zoT#d z8UPHR{@IbDU#!1Y|yk50UDmz<>JVQ(BU6EzhUH{6zD>*Kz~FJ%>p=$T+5uKOV9 zkUEPKm%~&Awi(_5_WPb`2IbXzKEvv@fFR{%QP%yF4@fFq_CtFGlJz1dDK9^4__!>L za(P*Aa1{6Cm|&;6e&S@PLGyCb3IJUFP@H7UjbF-jOg`b8#rm0=APrN6@Oc3q=FQ#; zX`q}V=4mC)u>BxMK6H3F*3j`ZJ;~f?B_6(~|FYs^c~t{TLFn_am+v^UV*L8Jx5HXq zKVL^f`CJob%0Ia7wB=95bgTbxfj$%l&;|o^ZhXV+lHFyT-|j4G6<-@aSm7QS7aETt z%O>mE3(36rl_=Wcz!nbvRHtjdgKYqb-D%S_*u$q&9{w>~sDXO+d4}Ppgf7yuNtOQv zo)*^G;h>AE%=#Dd0Qmf~{Pyiyf+7l?m(&2YKDyH0SH`5UvT2hv7lsAVUHhx>S z)ja^!>(^CeChEsCm_=3rc)3(!<(_EXiJxyHxZnb>TPivgVl8vT-g~2YRm2@6X~CRt zsUs|;SWqcjn$^9kc3U>EcX)Fkg$-iR;fdb!4rZU^WZ!u#1H%f?IJ^_W`++9a6+r6W z*t3h&yb3o(+`a<=eC4&6Yp9XOiSJe}1wugIwK|qQ$QuIWBnZ}TE)2N|o~69;;qPU> zs{2!HWg3@nP?xBUDb}y4D4x@p6Cxo5Xq_iv>|oAw@pb+q+9L3uUDi*87E7O5%%%Z# z3NBv{sHWJCt0}w^^K`uH%CnEP(x~p&1i84A0{th@Q(s#VmHLZ7++F5hNG$#0KQ`)vW1;Ly8mpY>L0fDFBx)p(}Ltw zp82g$y;HO}zS^B|R1D;yZYH6XD~}PyVYw^|~x-}Q7omh9MlFkC*BdS94#SwQlVJ+aqrMWR#m zR?PI$eOLf3aly2Qr-yo%IS8ci;iQ9KBvf?nM9Rfej$A$7XPdtp?EbraC78&>Nb^P1 zr~Eqm_NKm$t6#|4JOgavPMPf)0UGoemuP@A6aU8MKejuyMU!9r8c9#eBDn;djR-|g zdxVGKgmLSLgyGJzQyZvrd0Le)A{5dGl%uDOCXX0eD8FV`Np_)0A7EeRez77)p1eG= z#3Ibi&M3CZ%x#)7L3!Aqkn=9ebH!@iMz%aVH@2=l%-=ORf@R|Di%E#$p=W&J5kQ62 zEt!@n`ap#|%!&blI~^p_&@^TtsF6lSjnUk&uHZGBX0~lEc9V{eIGfbk zS8kmCd|#7>B6M4UlR01EBZla5$HGBmjq`X&W(*#O9t=paZb*4QH~p~<+PVKW(gs{U zJhO3IC2TD6h_T8(`^*l6^Y}ec%D@}+jFdZ(q--B7@wuQL4Qq2O=c|^$)Wc{udn?-= zqxH=1?YTsLza@5YqNC)vC4pp_uNsuqRO|iYpQ@SQ1vSg9*!#u`>u&hlCG;;LpJ7wk zV}c8S@b9vkK+TH&UH<@a>$?WI+^2%1fP{cc+XY77wtVoEV*X)7eRZk zdQIOM$=>L~oWViDM(5W2dZxy1{W~|PUKz*MOpV6U_S!K(Y4T_v=)wd`ijy`&Vlt ztzH9?{2=upc)O!UHXNXUezco5L^;C98QT8jq)8P`;C4!u47N& zTX(^PT&$a8CZ9ZU$yk48o}pHD_|d@WW0nPAkvIc4!MJI8RX;dydN8||S(8r z-+F7SL(Hvr>l(9f;Xd>t4fo$b$<$amdi0nx>i(D)wCh_hE1{Lq4T(!S9rcm{9a`>G z?DTsihjH+wze5?nOcryC3m_Q0OLW9+ZTNi7^#rJ+G!{kF3rjNt z0pJF}O}JXaS&DTL|EKE-EQ;=~1Yk6Utr5sfeAB^Z&M&mie9rpBCAxHLf_-i{YgzYR zmeVfiw?vCm)nN2T6KuCF93O1p{0q=s9N0@AGa)?Z7j?=~pF$2G0HGS*X%FFDIo`~v z38~uU8+SC6+;8+u+mxml3rqh{e~>^U!-5)0IQ+U6yO6>I}Rs0|G@ri`^LN5Kmh zqAh7jB#Bo|q-tNnn+**j2$pk+76H(tJ6QaAOL?+5ml<02{8CB$%Ys<3uX=L+JINnD zb~D?(tVl_0+Tc(hs+zhawtc+%iqflv(S;Ye^ZF+iu8!w)tT&+=vg~>+(1VZVru~_} z66_i)B2;|2I@YU?k$6KRpzKv{49j#}bO3N?zqqJLOsYlgPD35QhhBEn1AjRKK>LPM z0asMaLaL^KdTIKYwY93w<@HuJHjjTeLIxF1NCkcG)XE-;tn4D{mJTS=I4n%QBx%-F z_|LsMUYjrHEG>f2FI*gY&8kkCOGL;2I3Q};x1LkN-NqzdpshjQ-H8E(Z6~3>wsr0KWO61yzNF1hp$OB0?Q7zd-4uc1#$O%q2e8a)^_%aYsRX8n zM-S5?`AApTCT4V7+IVa*J|nK5Dx6+63)je#t~hU$SC9a0RmU3hdD|3-znW*P9-|OQ zBARNyeE2boPuuTnPbM|d=QgkzlQP_T!A~X z!jH7p-&vay>0H!{jk3?)Wjnnrx&HjSGz#8%0w@FZ@vV^uvCgs6&_46v}Yo|in zyZ+XSN!*v{2%(4osT+#vozOg`EP<#*^LC zxsQPIGk#N8GMg{^%5lwkNZZkQQ3^=(amON8dIKrX z0=SPF#A-lz>c3IwdHSQkYUjZv!Ask5(rNmWId7876-==BkB^4O zW6@u^)!N=>;@fIo5%k7h$T*F7y-9JZIlMH92Usg+m|1$-zw?qRqhAq91`{sn+&X># t{GUekR>vmd8Y`@JPB`uV1Af-??;pSeYphm0odZ5Fm>OE*sW_M0{|C|>j-~(r literal 0 HcmV?d00001 diff --git a/templates/compose/github-runner.yml b/templates/compose/github-runner.yml new file mode 100644 index 000000000..1f175280b --- /dev/null +++ b/templates/compose/github-runner.yml @@ -0,0 +1,27 @@ +# documentation: https://github.com/myoung34/docker-github-actions-runner/wiki/Usage +# slogan: A GitHub Actions runner for Docker +# tags: github,actions,runner,docker +# logo: svgs/github-runner.png + +services: + runner: + image: 'myoung34/github-runner:latest' + environment: + - REPO_URL=${REPO_URL} + - RUNNER_NAME_PREFIX=${RUNNER_NAME_PREFIX:-coolify-runner} + - RUNNER_NAME_SUFFIX=${RUNNER_NAME_SUFFIX:-true} + - ACCESS_TOKEN=${ACCESS_TOKEN} + - RUNNER_WORKDIR=/tmp/runner/work + - RUNNER_SCOPE=${RUNNER_SCOPE:-repo} + - LABELS=${LABELS:-default} + - ORG_NAME=${ORG_NAME} + security_opt: + - 'label:disable' + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' + - runner:/tmp/runner + healthcheck: + test: ["CMD-SHELL", "ps aux | grep '[R]unner' > /dev/null || exit 1"] + interval: 5s + timeout: 10s + retries: 15 From 948c50167d1f4682bdb210a934539690feb2c06e Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:57:34 +0200 Subject: [PATCH 046/202] chore(service): format runner service --- .../{github-runner.yml => github-runner.yaml} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename templates/compose/{github-runner.yml => github-runner.yaml} (73%) diff --git a/templates/compose/github-runner.yml b/templates/compose/github-runner.yaml similarity index 73% rename from templates/compose/github-runner.yml rename to templates/compose/github-runner.yaml index 1f175280b..75a144830 100644 --- a/templates/compose/github-runner.yml +++ b/templates/compose/github-runner.yaml @@ -5,21 +5,21 @@ services: runner: - image: 'myoung34/github-runner:latest' + image: myoung34/github-runner:latest environment: - REPO_URL=${REPO_URL} - RUNNER_NAME_PREFIX=${RUNNER_NAME_PREFIX:-coolify-runner} - - RUNNER_NAME_SUFFIX=${RUNNER_NAME_SUFFIX:-true} + - RUNNER_NAME_SUFFIX=${RUNNER_NAME_SUFFIX:-false} - ACCESS_TOKEN=${ACCESS_TOKEN} - - RUNNER_WORKDIR=/tmp/runner/work + - RUNNER_WORKDIR=/runner/work - RUNNER_SCOPE=${RUNNER_SCOPE:-repo} - LABELS=${LABELS:-default} - ORG_NAME=${ORG_NAME} security_opt: - - 'label:disable' + - label:disable volumes: - - '/var/run/docker.sock:/var/run/docker.sock' - - runner:/tmp/runner + - /var/run/docker.sock:/var/run/docker.sock + - runner-data:/runner healthcheck: test: ["CMD-SHELL", "ps aux | grep '[R]unner' > /dev/null || exit 1"] interval: 5s From fe900d3cd782237fba0a76f591c3cbd738243308 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 18 Jul 2025 22:36:38 +0200 Subject: [PATCH 047/202] Update service-templates.json --- templates/service-templates.json | 68 ++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/templates/service-templates.json b/templates/service-templates.json index d308acb7c..6aa4e1eb8 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -250,7 +250,7 @@ "budibase": { "documentation": "https://docs.budibase.com/docs/docker-compose?utm_source=coolify.io", "slogan": "Low code platform for building business apps and workflows in minutes. Supports PostgreSQL, MySQL, MSSQL, MongoDB, Rest API, Docker, K8s, and more.", - "compose": "c2VydmljZXM6CiAgYXBwLXNlcnZpY2U6CiAgICBpbWFnZTogYnVkaWJhc2UuZG9ja2VyLnNjYXJmLnNoL2J1ZGliYXNlL2FwcHMKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFTEZfSE9TVEVEPTEKICAgICAgLSAnQ09VQ0hfREJfVVJMPWh0dHA6Ly8kU0VSVklDRV9VU0VSX0NPVUNIREI6JFNFUlZJQ0VfUEFTU1dPUkRfQ09VQ0hEQkBjb3VjaGRiLXNlcnZpY2U6NTk4NCcKICAgICAgLSAnV09SS0VSX1VSTD1odHRwOi8vd29ya2VyLXNlcnZpY2U6NDAwMycKICAgICAgLSAnTUlOSU9fVVJMPWh0dHA6Ly9taW5pby1zZXJ2aWNlOjkwMDAnCiAgICAgIC0gTUlOSU9fQUNDRVNTX0tFWT0kU0VSVklDRV9VU0VSX01JTklPCiAgICAgIC0gTUlOSU9fU0VDUkVUX0tFWT0kU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICAtIElOVEVSTkFMX0FQSV9LRVk9JFNFUlZJQ0VfQkFTRTY0XzEyOF9CVURJQkFTRQogICAgICAtICdCVURJQkFTRV9FTlZJUk9OTUVOVD0ke0JVRElCQVNFX0VOVklST05NRU5UOi1QUk9EVUNUSU9OfScKICAgICAgLSBQT1JUPTQwMDIKICAgICAgLSBBUElfRU5DUllQVElPTl9LRVk9JFNFUlZJQ0VfQkFTRTY0XzY0X0JVRElCQVNFCiAgICAgIC0gSldUX1NFQ1JFVD0kU0VSVklDRV9CQVNFNjRfNjRfQlVESUJBU0UKICAgICAgLSBMT0dfTEVWRUw9aW5mbwogICAgICAtICdFTkFCTEVfQU5BTFlUSUNTPSR7RU5BQkxFX0FOQUxZVElDUzotdHJ1ZX0nCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpcy1zZXJ2aWNlOjYzNzknCiAgICAgIC0gUkVESVNfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUkVESVMKICAgICAgLSBCQl9BRE1JTl9VU0VSX0VNQUlMPQogICAgICAtIEJCX0FETUlOX1VTRVJfUEFTU1dPUkQ9CiAgICBkZXBlbmRzX29uOgogICAgICAtIHdvcmtlci1zZXJ2aWNlCiAgICAgIC0gcmVkaXMtc2VydmljZQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHdnZXQKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICctcU8tJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6NDAwMi9oZWFsdGgnCiAgICAgIGludGVydmFsOiAxNXMKICAgICAgdGltZW91dDogMTVzCiAgICAgIHJldHJpZXM6IDUKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICB3b3JrZXItc2VydmljZToKICAgIGltYWdlOiBidWRpYmFzZS5kb2NrZXIuc2NhcmYuc2gvYnVkaWJhc2Uvd29ya2VyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRUxGX0hPU1RFRD0xCiAgICAgIC0gUE9SVD00MDAzCiAgICAgIC0gQ0xVU1RFUl9QT1JUPTEwMDAwCiAgICAgIC0gQVBJX0VOQ1JZUFRJT05fS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9CVURJQkFTRQogICAgICAtIEpXVF9TRUNSRVQ9JFNFUlZJQ0VfQkFTRTY0XzY0X0JVRElCQVNFCiAgICAgIC0gTUlOSU9fQUNDRVNTX0tFWT0kU0VSVklDRV9VU0VSX01JTklPCiAgICAgIC0gTUlOSU9fU0VDUkVUX0tFWT0kU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICAtICdNSU5JT19VUkw9aHR0cDovL21pbmlvLXNlcnZpY2U6OTAwMCcKICAgICAgLSAnQVBQU19VUkw9aHR0cDovL2FwcC1zZXJ2aWNlOjQwMDInCiAgICAgIC0gQ09VQ0hfREJfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9DT1VDSERCCiAgICAgIC0gQ09VQ0hfREJfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfQ09VQ0hEQgogICAgICAtICdDT1VDSF9EQl9VUkw9aHR0cDovLyRTRVJWSUNFX1VTRVJfQ09VQ0hEQjokU0VSVklDRV9QQVNTV09SRF9DT1VDSERCQGNvdWNoZGItc2VydmljZTo1OTg0JwogICAgICAtIElOVEVSTkFMX0FQSV9LRVk9JFNFUlZJQ0VfQkFTRTY0XzEyOF9CVURJQkFTRQogICAgICAtICdSRURJU19VUkw9cmVkaXMtc2VydmljZTo2Mzc5JwogICAgICAtIFJFRElTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1JFRElTCiAgICBkZXBlbmRzX29uOgogICAgICAtIHJlZGlzLXNlcnZpY2UKICAgICAgLSBtaW5pby1zZXJ2aWNlCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJy1xTy0nCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo0MDAzL2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDE1cwogICAgICB0aW1lb3V0OiAxNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogIG1pbmlvLXNlcnZpY2U6CiAgICBpbWFnZTogbWluaW8vbWluaW8KICAgIHZvbHVtZXM6CiAgICAgIC0gJ21pbmlvX2RhdGE6L2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBNSU5JT19ST09UX1VTRVI9JFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICAtIE1JTklPX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTUlOSU8KICAgICAgLSBNSU5JT19CUk9XU0VSPW9mZgogICAgY29tbWFuZDogJ3NlcnZlciAvZGF0YSAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjkwMDAvbWluaW8vaGVhbHRoL2xpdmUnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDMKICBwcm94eS1zZXJ2aWNlOgogICAgaW1hZ2U6IGJ1ZGliYXNlL3Byb3h5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQlVESUJBU0VfMTAwMDAKICAgICAgLSBQUk9YWV9SQVRFX0xJTUlUX1dFQkhPT0tTX1BFUl9TRUNPTkQ9MTAKICAgICAgLSBQUk9YWV9SQVRFX0xJTUlUX0FQSV9QRVJfU0VDT05EPTIwCiAgICAgIC0gJ0FQUFNfVVBTVFJFQU1fVVJMPWh0dHA6Ly9hcHAtc2VydmljZTo0MDAyJwogICAgICAtICdXT1JLRVJfVVBTVFJFQU1fVVJMPWh0dHA6Ly93b3JrZXItc2VydmljZTo0MDAzJwogICAgICAtICdNSU5JT19VUFNUUkVBTV9VUkw9aHR0cDovL21pbmlvLXNlcnZpY2U6OTAwMCcKICAgICAgLSAnQ09VQ0hEQl9VUFNUUkVBTV9VUkw9aHR0cDovL2NvdWNoZGItc2VydmljZTo1OTg0JwogICAgICAtICdXQVRDSFRPV0VSX1VQU1RSRUFNX1VSTD1odHRwOi8vd2F0Y2h0b3dlci1zZXJ2aWNlOjgwODAnCiAgICAgIC0gUkVTT0xWRVI9MTI3LjAuMC4xMQogICAgZGVwZW5kc19vbjoKICAgICAgLSBtaW5pby1zZXJ2aWNlCiAgICAgIC0gd29ya2VyLXNlcnZpY2UKICAgICAgLSBhcHAtc2VydmljZQogICAgICAtIGNvdWNoZGItc2VydmljZQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjEwMDAwLycKICAgICAgaW50ZXJ2YWw6IDE1cwogICAgICB0aW1lb3V0OiAxNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogIGNvdWNoZGItc2VydmljZToKICAgIGltYWdlOiBidWRpYmFzZS9jb3VjaGRiCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBDT1VDSERCX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX0NPVUNIREIKICAgICAgLSBDT1VDSERCX1VTRVI9JFNFUlZJQ0VfVVNFUl9DT1VDSERCCiAgICAgIC0gVEFSR0VUQlVJTEQ9ZG9ja2VyLWNvbXBvc2UKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo1OTg0LycKICAgICAgaW50ZXJ2YWw6IDE1cwogICAgICB0aW1lb3V0OiAxNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgdm9sdW1lczoKICAgICAgLSAnY291Y2hkYjNfZGF0YTovb3B0L2NvdWNoZGIvZGF0YScKICByZWRpcy1zZXJ2aWNlOgogICAgaW1hZ2U6IHJlZGlzCiAgICBjb21tYW5kOiAncmVkaXMtc2VydmVyIC0tcmVxdWlyZXBhc3MgIiRTRVJWSUNFX1BBU1NXT1JEX1JFRElTIicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3JlZGlzX2RhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSAnLWEnCiAgICAgICAgLSAkU0VSVklDRV9QQVNTV09SRF9SRURJUwogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogMTVzCiAgICAgIHRpbWVvdXQ6IDE1cwogICAgICByZXRyaWVzOiA1CiAgICAgIHN0YXJ0X3BlcmlvZDogMTBzCiAgd2F0Y2h0b3dlci1zZXJ2aWNlOgogICAgcmVzdGFydDogYWx3YXlzCiAgICBpbWFnZTogY29udGFpbnJyci93YXRjaHRvd2VyCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgIGNvbW1hbmQ6ICctLWRlYnVnIC0taHR0cC1hcGktdXBkYXRlIGJiYXBwcyBiYndvcmtlciBiYnByb3h5JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gV0FUQ0hUT1dFUl9IVFRQX0FQST10cnVlCiAgICAgIC0gV0FUQ0hUT1dFUl9IVFRQX0FQSV9UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9XQVRDSFRPV0VSCiAgICAgIC0gV0FUQ0hUT1dFUl9DTEVBTlVQPXRydWUKICAgIGxhYmVsczoKICAgICAgLSBjb20uY2VudHVyeWxpbmtsYWJzLndhdGNodG93ZXIuZW5hYmxlPWZhbHNlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUK", + "compose": "c2VydmljZXM6CiAgYXBwLXNlcnZpY2U6CiAgICBpbWFnZTogYnVkaWJhc2UuZG9ja2VyLnNjYXJmLnNoL2J1ZGliYXNlL2FwcHMKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFTEZfSE9TVEVEPTEKICAgICAgLSAnQ09VQ0hfREJfVVJMPWh0dHA6Ly8kU0VSVklDRV9VU0VSX0NPVUNIREI6JFNFUlZJQ0VfUEFTU1dPUkRfQ09VQ0hEQkBjb3VjaGRiLXNlcnZpY2U6NTk4NCcKICAgICAgLSAnV09SS0VSX1VSTD1odHRwOi8vd29ya2VyLXNlcnZpY2U6NDAwMycKICAgICAgLSAnTUlOSU9fVVJMPWh0dHA6Ly9taW5pby1zZXJ2aWNlOjkwMDAnCiAgICAgIC0gTUlOSU9fQUNDRVNTX0tFWT0kU0VSVklDRV9VU0VSX01JTklPCiAgICAgIC0gTUlOSU9fU0VDUkVUX0tFWT0kU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICAtIElOVEVSTkFMX0FQSV9LRVk9JFNFUlZJQ0VfQkFTRTY0XzEyOF9CVURJQkFTRQogICAgICAtICdCVURJQkFTRV9FTlZJUk9OTUVOVD0ke0JVRElCQVNFX0VOVklST05NRU5UOi1QUk9EVUNUSU9OfScKICAgICAgLSBQT1JUPTQwMDIKICAgICAgLSBBUElfRU5DUllQVElPTl9LRVk9JFNFUlZJQ0VfQkFTRTY0XzY0X0JVRElCQVNFQVBJCiAgICAgIC0gRU5DUllQVElPTl9LRVk9JFNFUlZJQ0VfQkFTRTY0XzY0X0JVRElCQVNFCiAgICAgIC0gSldUX1NFQ1JFVD0kU0VSVklDRV9CQVNFNjRfNjRfQlVESUJBU0VKV1QKICAgICAgLSBMT0dfTEVWRUw9aW5mbwogICAgICAtICdFTkFCTEVfQU5BTFlUSUNTPSR7RU5BQkxFX0FOQUxZVElDUzotdHJ1ZX0nCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpcy1zZXJ2aWNlOjYzNzknCiAgICAgIC0gUkVESVNfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUkVESVMKICAgICAgLSBCQl9BRE1JTl9VU0VSX0VNQUlMPQogICAgICAtIEJCX0FETUlOX1VTRVJfUEFTU1dPUkQ9CiAgICBkZXBlbmRzX29uOgogICAgICAtIHdvcmtlci1zZXJ2aWNlCiAgICAgIC0gcmVkaXMtc2VydmljZQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHdnZXQKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICctcU8tJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6NDAwMi9oZWFsdGgnCiAgICAgIGludGVydmFsOiAxNXMKICAgICAgdGltZW91dDogMTVzCiAgICAgIHJldHJpZXM6IDUKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICB3b3JrZXItc2VydmljZToKICAgIGltYWdlOiBidWRpYmFzZS5kb2NrZXIuc2NhcmYuc2gvYnVkaWJhc2Uvd29ya2VyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRUxGX0hPU1RFRD0xCiAgICAgIC0gUE9SVD00MDAzCiAgICAgIC0gQ0xVU1RFUl9QT1JUPTEwMDAwCiAgICAgIC0gQVBJX0VOQ1JZUFRJT05fS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9CVURJQkFTRQogICAgICAtIEpXVF9TRUNSRVQ9JFNFUlZJQ0VfQkFTRTY0XzY0X0JVRElCQVNFCiAgICAgIC0gTUlOSU9fQUNDRVNTX0tFWT0kU0VSVklDRV9VU0VSX01JTklPCiAgICAgIC0gTUlOSU9fU0VDUkVUX0tFWT0kU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICAtICdNSU5JT19VUkw9aHR0cDovL21pbmlvLXNlcnZpY2U6OTAwMCcKICAgICAgLSAnQVBQU19VUkw9aHR0cDovL2FwcC1zZXJ2aWNlOjQwMDInCiAgICAgIC0gQ09VQ0hfREJfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9DT1VDSERCCiAgICAgIC0gQ09VQ0hfREJfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfQ09VQ0hEQgogICAgICAtICdDT1VDSF9EQl9VUkw9aHR0cDovLyRTRVJWSUNFX1VTRVJfQ09VQ0hEQjokU0VSVklDRV9QQVNTV09SRF9DT1VDSERCQGNvdWNoZGItc2VydmljZTo1OTg0JwogICAgICAtIElOVEVSTkFMX0FQSV9LRVk9JFNFUlZJQ0VfQkFTRTY0XzEyOF9CVURJQkFTRQogICAgICAtICdSRURJU19VUkw9cmVkaXMtc2VydmljZTo2Mzc5JwogICAgICAtIFJFRElTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1JFRElTCiAgICBkZXBlbmRzX29uOgogICAgICAtIHJlZGlzLXNlcnZpY2UKICAgICAgLSBtaW5pby1zZXJ2aWNlCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJy1xTy0nCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo0MDAzL2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDE1cwogICAgICB0aW1lb3V0OiAxNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogIG1pbmlvLXNlcnZpY2U6CiAgICBpbWFnZTogbWluaW8vbWluaW8KICAgIHZvbHVtZXM6CiAgICAgIC0gJ21pbmlvX2RhdGE6L2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBNSU5JT19ST09UX1VTRVI9JFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICAtIE1JTklPX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTUlOSU8KICAgICAgLSBNSU5JT19CUk9XU0VSPW9mZgogICAgY29tbWFuZDogJ3NlcnZlciAvZGF0YSAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjkwMDAvbWluaW8vaGVhbHRoL2xpdmUnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDMKICBwcm94eS1zZXJ2aWNlOgogICAgaW1hZ2U6IGJ1ZGliYXNlL3Byb3h5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQlVESUJBU0VfMTAwMDAKICAgICAgLSBQUk9YWV9SQVRFX0xJTUlUX1dFQkhPT0tTX1BFUl9TRUNPTkQ9MTAKICAgICAgLSBQUk9YWV9SQVRFX0xJTUlUX0FQSV9QRVJfU0VDT05EPTIwCiAgICAgIC0gJ0FQUFNfVVBTVFJFQU1fVVJMPWh0dHA6Ly9hcHAtc2VydmljZTo0MDAyJwogICAgICAtICdXT1JLRVJfVVBTVFJFQU1fVVJMPWh0dHA6Ly93b3JrZXItc2VydmljZTo0MDAzJwogICAgICAtICdNSU5JT19VUFNUUkVBTV9VUkw9aHR0cDovL21pbmlvLXNlcnZpY2U6OTAwMCcKICAgICAgLSAnQ09VQ0hEQl9VUFNUUkVBTV9VUkw9aHR0cDovL2NvdWNoZGItc2VydmljZTo1OTg0JwogICAgICAtICdXQVRDSFRPV0VSX1VQU1RSRUFNX1VSTD1odHRwOi8vd2F0Y2h0b3dlci1zZXJ2aWNlOjgwODAnCiAgICAgIC0gUkVTT0xWRVI9MTI3LjAuMC4xMQogICAgZGVwZW5kc19vbjoKICAgICAgLSBtaW5pby1zZXJ2aWNlCiAgICAgIC0gd29ya2VyLXNlcnZpY2UKICAgICAgLSBhcHAtc2VydmljZQogICAgICAtIGNvdWNoZGItc2VydmljZQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjEwMDAwLycKICAgICAgaW50ZXJ2YWw6IDE1cwogICAgICB0aW1lb3V0OiAxNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogIGNvdWNoZGItc2VydmljZToKICAgIGltYWdlOiBidWRpYmFzZS9jb3VjaGRiCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBDT1VDSERCX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX0NPVUNIREIKICAgICAgLSBDT1VDSERCX1VTRVI9JFNFUlZJQ0VfVVNFUl9DT1VDSERCCiAgICAgIC0gVEFSR0VUQlVJTEQ9ZG9ja2VyLWNvbXBvc2UKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo1OTg0LycKICAgICAgaW50ZXJ2YWw6IDE1cwogICAgICB0aW1lb3V0OiAxNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgdm9sdW1lczoKICAgICAgLSAnY291Y2hkYjNfZGF0YTovb3B0L2NvdWNoZGIvZGF0YScKICByZWRpcy1zZXJ2aWNlOgogICAgaW1hZ2U6IHJlZGlzCiAgICBjb21tYW5kOiAncmVkaXMtc2VydmVyIC0tcmVxdWlyZXBhc3MgIiRTRVJWSUNFX1BBU1NXT1JEX1JFRElTIicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3JlZGlzX2RhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSAnLWEnCiAgICAgICAgLSAkU0VSVklDRV9QQVNTV09SRF9SRURJUwogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogMTVzCiAgICAgIHRpbWVvdXQ6IDE1cwogICAgICByZXRyaWVzOiA1CiAgICAgIHN0YXJ0X3BlcmlvZDogMTBzCiAgd2F0Y2h0b3dlci1zZXJ2aWNlOgogICAgcmVzdGFydDogYWx3YXlzCiAgICBpbWFnZTogY29udGFpbnJyci93YXRjaHRvd2VyCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgIGNvbW1hbmQ6ICctLWRlYnVnIC0taHR0cC1hcGktdXBkYXRlIGJiYXBwcyBiYndvcmtlciBiYnByb3h5JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gV0FUQ0hUT1dFUl9IVFRQX0FQST10cnVlCiAgICAgIC0gV0FUQ0hUT1dFUl9IVFRQX0FQSV9UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9XQVRDSFRPV0VSCiAgICAgIC0gV0FUQ0hUT1dFUl9DTEVBTlVQPXRydWUKICAgIGxhYmVsczoKICAgICAgLSBjb20uY2VudHVyeWxpbmtsYWJzLndhdGNodG93ZXIuZW5hYmxlPWZhbHNlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUK", "tags": [ "budibase", "low-code", @@ -1248,6 +1248,19 @@ "logo": "svgs/gitea.svg", "minversion": "0.0.0" }, + "github-runner": { + "documentation": "https://github.com/myoung34/docker-github-actions-runner/wiki/Usage?utm_source=coolify.io", + "slogan": "A GitHub Actions runner for Docker", + "compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdteW91bmczNC9naXRodWItcnVubmVyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtICdSRVBPX1VSTD0ke1JFUE9fVVJMfScKICAgICAgLSAnUlVOTkVSX05BTUVfUFJFRklYPSR7UlVOTkVSX05BTUVfUFJFRklYOi1jb29saWZ5LXJ1bm5lcn0nCiAgICAgIC0gJ1JVTk5FUl9OQU1FX1NVRkZJWD0ke1JVTk5FUl9OQU1FX1NVRkZJWDotZmFsc2V9JwogICAgICAtICdBQ0NFU1NfVE9LRU49JHtBQ0NFU1NfVE9LRU59JwogICAgICAtIFJVTk5FUl9XT1JLRElSPS9ydW5uZXIvd29yawogICAgICAtICdSVU5ORVJfU0NPUEU9JHtSVU5ORVJfU0NPUEU6LXJlcG99JwogICAgICAtICdMQUJFTFM9JHtMQUJFTFM6LWRlZmF1bHR9JwogICAgICAtICdPUkdfTkFNRT0ke09SR19OQU1FfScKICAgIHNlY3VyaXR5X29wdDoKICAgICAgLSAnbGFiZWw6ZGlzYWJsZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgICAtICdydW5uZXItZGF0YTovcnVubmVyJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICJwcyBhdXggfCBncmVwICdbUl11bm5lcicgPiAvZGV2L251bGwgfHwgZXhpdCAxIgogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", + "tags": [ + "github", + "actions", + "runner", + "docker" + ], + "logo": "svgs/github-runner.png", + "minversion": "0.0.0" + }, "gitlab": { "documentation": "https://docs.gitlab.com/ee/install/docker.html?utm_source=coolify.io", "slogan": "The all-in-one DevOps platform for seamless collaboration and continuous delivery.", @@ -1334,6 +1347,21 @@ "minversion": "0.0.0", "port": "3000" }, + "gowa": { + "documentation": "https://github.com/aldinokemal/go-whatsapp-web-multidevice?utm_source=coolify.io", + "slogan": "Golang WhatsApp - Built with Go for efficient memory use", + "compose": "c2VydmljZXM6CiAgZ293YToKICAgIGltYWdlOiAnYWxkaW5va2VtYWwyMTA0L2dvLXdoYXRzYXBwLXdlYi1tdWx0aWRldmljZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fR09XQV8zMDAwCiAgICAgIC0gU0VSVklDRV9VU0VSX0dPV0EKICAgICAgLSBTRVJWSUNFX1BBU1NXT1JEX0dPV0EKICAgICAgLSAnQVBQX0JBU0lDX0FVVEg9JHtTRVJWSUNFX1VTRVJfR09XQX06JHtTRVJWSUNFX1BBU1NXT1JEX0dPV0F9JwogICAgICAtIEFQUF9QT1JUPTMwMDAKICAgICAgLSAnQVBQX0RFQlVHPSR7QVBQX0RFQlVHOi1mYWxzZX0nCiAgICAgIC0gJ0FQUF9BQ0NPVU5UX1ZBTElEQVRJT049JHtBUFBfQUNDT1VOVF9WQUxJREFUSU9OOi1mYWxzZX0nCiAgICAgIC0gJ1dIQVRTQVBQX1dFQkhPT0s9JHtXSEFUU0FQUF9XRUJIT09LfScKICAgICAgLSAnV0hBVFNBUFBfV0VCSE9PS19TRUNSRVQ9JHtXSEFUU0FQUF9XRUJIT09LX1NFQ1JFVH0nCiAgICB2b2x1bWVzOgogICAgICAtICdnb3dhX2RhdGE6L2FwcC9zdG9yYWdlcycK", + "tags": [ + "whatsapp", + "golang", + "multidevice", + "api", + "go-whatsapp" + ], + "logo": "svgs/gowa.svg", + "minversion": "0.0.0", + "port": "3000" + }, "grafana-with-postgresql": { "documentation": "https://grafana.com?utm_source=coolify.io", "slogan": "Grafana is the open source analytics & monitoring solution for every database.", @@ -1918,6 +1946,22 @@ "minversion": "0.0.0", "port": "3000" }, + "matrix": { + "documentation": "https://matrix.org/docs/chat_basics/matrix-for-im/?utm_source=coolify.io", + "slogan": "Chat securely with your family, friends, community, or build great apps with Matrix!", + "compose": "c2VydmljZXM6CiAgbWF0cml4OgogICAgaW1hZ2U6ICdtYXRyaXhkb3Rvcmcvc3luYXBzZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTUFUUklYXzgwMDgKICAgICAgLSAnU1lOQVBTRV9TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ1NZTkFQU0VfUkVQT1JUX1NUQVRTPSR7U1lOQVBTRV9SRVBPUlRfU1RBVFM6LW5vfScKICAgICAgLSAnRU5BQkxFX1JFR0lTVFJBVElPTj0ke0VOQUJMRV9SRUdJU1RSQVRJT046LWZhbHNlfScKICAgICAgLSAnUkVDQVBUQ0hBX1BVQkxJQ19LRVk9JHtSRUNBUFRDSEFfUFVCTElDX0tFWX0nCiAgICAgIC0gJ1JFQ0FQVENIQV9QUklWQVRFX0tFWT0ke1JFQ0FQVENIQV9QUklWQVRFX0tFWX0nCiAgICAgIC0gJ19TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ19BRE1JTl9OQU1FPSR7U0VSVklDRV9VU0VSX0FETUlOfScKICAgICAgLSAnX0FETUlOX1BBU1M9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21hdHJpeC1kYXRhOi9kYXRhJwogICAgZW50cnlwb2ludDoKICAgICAgLSAvYmluL2Jhc2gKICAgICAgLSAnLWMnCiAgICAgIC0gIiEgdGVzdCAtZiAvZGF0YS9ob21lc2VydmVyLnlhbWwgJiYgL3N0YXJ0LnB5IGdlbmVyYXRlXG5cbiMgcmVnaXN0cmF0aW9uX3NoYXJlZF9zZWNyZXRcbmdyZXAgXCJyZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFwiIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxufCBhd2sgJ3twcmludCAkMn0nID4gLi9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFxuXG4jIG1hY2Fyb29uX3NlY3JldF9rZXlcbmdyZXAgXCJtYWNhcm9vbl9zZWNyZXRfa2V5XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL21hY2Fyb29uX3NlY3JldF9rZXlcblxuIyBmb3JtX3NlY3JldFxuZ3JlcCBcImZvcm1fc2VjcmV0XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL2Zvcm1fc2VjcmV0XG5cbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG4jICAgICAgICAgICAgICAgICAgICAgICAgI1xuIyBob21lc2VydmVyLnlhbWw6IHN0YXJ0ICNcbiMgICAgICAgICAgICAgICAgICAgICAgICAjXG4jIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuY2F0IDw8RU9GID4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG5zZXJ2ZXJfbmFtZTogXCIke1NFUlZJQ0VfVVJMX01BVFJJWH1cIlxucGlkX2ZpbGU6IC9kYXRhL2hvbWVzZXJ2ZXIucGlkXG5cbiMgc2VydmVyXG5saXN0ZW5lcnM6XG4gIC0gcG9ydDogODAwOFxuICAgIHRsczogZmFsc2VcbiAgICB0eXBlOiBodHRwXG4gICAgeF9mb3J3YXJkZWQ6IHRydWVcbiAgICByZXNvdXJjZXM6XG4gICAgICAtIG5hbWVzOiBbY2xpZW50LCBmZWRlcmF0aW9uXVxuICAgICAgICBjb21wcmVzczogZmFsc2VcblxuIyBkYXRhYmFzZVxuZGF0YWJhc2U6XG4gIG5hbWU6IHNxbGl0ZTNcbiAgYXJnczpcbiAgICBkYXRhYmFzZTogL2RhdGEvaG9tZXNlcnZlci5kYlxuXG4jIGdlbmVyYWxcbmxvZ19jb25maWc6IFwiL2RhdGEvJHtTRVJWSUNFX1VSTF9NQVRSSVh9LmxvZy5jb25maWdcIlxubWVkaWFfc3RvcmVfcGF0aDogL2RhdGEvbWVkaWFfc3RvcmVcbnJlcG9ydF9zdGF0czogZmFsc2VcblxuIyBzZWNyZXRzXG5yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldDogJCg8Li9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldClcbm1hY2Fyb29uX3NlY3JldF9rZXk6ICQoPC4vbWFjYXJvb25fc2VjcmV0X2tleSlcbmZvcm1fc2VjcmV0OiAkKDwuL2Zvcm1fc2VjcmV0KVxuc2lnbmluZ19rZXlfcGF0aDogXCIvZGF0YS8ke1NFUlZJQ0VfVVJMX01BVFJJWH0uc2lnbmluZy5rZXlcIlxuXG4jcm9vbXNcbmF1dG9fam9pbl9yb29tczpcbiAgLSBcIiNnZW5lcmFsOiR7U0VSVklDRV9VUkxfTUFUUklYfVwiXG5cbiMgZmVkZXJhdGlvblxudHJ1c3RlZF9rZXlfc2VydmVyczpcbiAgLSBzZXJ2ZXJfbmFtZTogXCJtYXRyaXgub3JnXCJcbmF1dG9jcmVhdGVfYXV0b19qb2luX3Jvb21zX2ZlZGVyYXRlZDogZmFsc2VcbmFsbG93X3B1YmxpY19yb29tc19vdmVyX2ZlZGVyYXRpb246IGZhbHNlXG5FT0ZcbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuIyAgICAgICAgICAgICAgICAgICAgICAjXG4jIGhvbWVzZXJ2ZXIueWFtbDogZW5kICNcbiMgICAgICAgICAgICAgICAgICAgICAgI1xuIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG5cbnRlc3QgLW4gXCIke0VOQUJMRV9SRUdJU1RSQVRJT059XCIgJiYgISBncmVwIFwiI3JlZ2lzdHJhdGlvblwiIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCAmPi9kZXYvbnVsbCBcXFxuJiYgZWNobyA+PiAvZGF0YS9ob21lc2VydmVyLnlhbWwgXFxcbiYmIGNhdCA8PEVPRiA+PiAvZGF0YS9ob21lc2VydmVyLnlhbWxcbiNyZWdpc3RyYXRpb25cbmVuYWJsZV9yZWdpc3RyYXRpb246IHRydWUgICMgQWxsb3dzIHVzZXJzIHRvIHJlZ2lzdGVyIG9uIHlvdXIgc2VydmVyLlxuRU9GXG5cbiEgZ3JlcCAke1JFQ0FQVENIQV9QVUJMSUNfS0VZfSAvZGF0YS9ob21lc2VydmVyLnlhbWwgJj4vZGV2L251bGwgXFxcbiYmIGVjaG8gPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG4mJiBjYXQgPDxFT0YgPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG4jIHJlQ0FQVENIQSBzZXR0aW5nc1xuZW5hYmxlX3JlZ2lzdHJhdGlvbl9jYXB0Y2hhOiB0cnVlICAjIEVuYWJsZXMgQ0FQVENIQSBmb3IgcmVnaXN0cmF0aW9ucy5cbnJlY2FwdGNoYV9wdWJsaWNfa2V5OiBcIiR7UkVDQVBUQ0hBX1BVQkxJQ19LRVl9XCJcbnJlY2FwdGNoYV9wcml2YXRlX2tleTogXCIke1JFQ0FQVENIQV9QUklWQVRFX0tFWX1cIlxucmVjYXB0Y2hhX3NpdGV2ZXJpZnlfYXBpOiBcImh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vcmVjYXB0Y2hhL2FwaS9zaXRldmVyaWZ5XCJcbkVPRlxuXG5yZWdpc3Rlcl9hZG1pbigpe1xuICB3aGlsZSAhIGN1cmwgLUkgbG9jYWxob3N0OjgwMDggJj4vZGV2L251bGw7IGRvXG4gICAgc2xlZXAgMVxuICBkb25lXG4gIHJlZ2lzdGVyX25ld19tYXRyaXhfdXNlciBcXFxuICAgIC1hIFxcXG4gICAgLXUgJHtTRVJWSUNFX1VTRVJfQURNSU59IFxcXG4gICAgLXAgJHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfSBcXFxuICAgIC1jIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxuICAgIGh0dHA6Ly9sb2NhbGhvc3Q6ODAwOCAmPi9kZXYvbnVsbFxufVxucmVnaXN0ZXJfYWRtaW4gJlxuXG4vc3RhcnQucHlcbiIKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLUknCiAgICAgICAgLSAnbG9jYWxob3N0OjgwMDgnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAzcwogICAgICByZXRyaWVzOiA1Cg==", + "tags": [ + "chat", + "slack", + "discord", + "voip", + "video", + "call" + ], + "logo": "svgs/matrix.svg", + "minversion": "0.0.0", + "port": "8008" + }, "mattermost": { "documentation": "https://docs.mattermost.com?utm_source=coolify.io", "slogan": "Mattermost is an open source, self-hosted Slack-alternative.", @@ -2341,7 +2385,7 @@ "nitropage-with-postgresql": { "documentation": "https://nitropage.org?utm_source=coolify.io", "slogan": "Nitropage is an extensible, visual website builder, offering a growing library of versatile building blocks, focal-point image cropping and sovereign font management.", - "compose": "c2VydmljZXM6CiAgbml0cm9wYWdlOgogICAgaW1hZ2U6IGNvZGViZXJnLm9yZy9uaXRyb3BhZ2Uvbml0cm9wYWdlCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTklUUk9QQUdFXzMwMDAKICAgICAgLSAnTlBfQVVUSF9TQUxUPSR7U0VSVklDRV9CQVNFNjRfU0FMVH0nCiAgICAgIC0gJ05QX0FVVEhfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT059JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfUBwb3N0Z3Jlc3FsOjU0MzIvJHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1uaXRyb3BhZ2V9JwogICAgdm9sdW1lczoKICAgICAgLSAnbml0cm9wYWdlLWRhdGE6L2FwcC8uZGF0YScKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDAwL2FkbWluJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAnbml0cm9wYWdlLXBvc3RncmVzcWwtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTUUxfREFUQUJBU0U6LW5pdHJvcGFnZX0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "c2VydmljZXM6CiAgbml0cm9wYWdlOgogICAgaW1hZ2U6IG5pdHJvcGFnZS9uaXRyb3BhZ2UKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9OSVRST1BBR0VfMzAwMAogICAgICAtICdOUF9BVVRIX1NBTFQ9JHtTRVJWSUNFX0JBU0U2NF9TQUxUfScKICAgICAgLSAnTlBfQVVUSF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfU0VTU0lPTn0nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3Jlc3FsOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH06JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9QHBvc3RncmVzcWw6NTQzMi8ke1BPU1RHUkVTUUxfREFUQUJBU0U6LW5pdHJvcGFnZX0nCiAgICB2b2x1bWVzOgogICAgICAtICduaXRyb3BhZ2UtZGF0YTovYXBwLy5kYXRhJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjMwMDAvYWRtaW4nCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICduaXRyb3BhZ2UtcG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotbml0cm9wYWdlfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", "tags": [ "nitropage", "builder", @@ -2358,7 +2402,7 @@ "nitropage": { "documentation": "https://nitropage.org?utm_source=coolify.io", "slogan": "Nitropage is an extensible, visual website builder, offering a growing library of versatile building blocks, focal-point image cropping and sovereign font management.", - "compose": "c2VydmljZXM6CiAgbml0cm9wYWdlOgogICAgaW1hZ2U6ICdjb2RlYmVyZy5vcmcvbml0cm9wYWdlL25pdHJvcGFnZTpzcWxpdGUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTklUUk9QQUdFXzMwMDAKICAgICAgLSAnTlBfQVVUSF9TQUxUPSR7U0VSVklDRV9CQVNFNjRfU0FMVH0nCiAgICAgIC0gJ05QX0FVVEhfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT059JwogICAgICAtICdEQVRBQkFTRV9VUkw9ZmlsZTouLi8uLi8uZGF0YS9kZXYuZGInCiAgICB2b2x1bWVzOgogICAgICAtICduaXRyb3BhZ2UtZGF0YTovYXBwLy5kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjMwMDAvYWRtaW4nCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "compose": "c2VydmljZXM6CiAgbml0cm9wYWdlOgogICAgaW1hZ2U6ICduaXRyb3BhZ2Uvbml0cm9wYWdlOnNxbGl0ZScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9OSVRST1BBR0VfMzAwMAogICAgICAtICdOUF9BVVRIX1NBTFQ9JHtTRVJWSUNFX0JBU0U2NF9TQUxUfScKICAgICAgLSAnTlBfQVVUSF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfU0VTU0lPTn0nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1maWxlOi4uLy5kYXRhL2Rldi5kYicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ25pdHJvcGFnZS1kYXRhOi9hcHAvLmRhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6MzAwMC9hZG1pbicKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=", "tags": [ "nitropage", "builder", @@ -3341,6 +3385,22 @@ "minversion": "0.0.0", "port": "3000" }, + "triliumnext": { + "documentation": "https://github.com/TriliumNext/Trilium?utm_source=coolify.io", + "slogan": "Build your personal knowledge base with TriliumNext Notes.", + "compose": "c2VydmljZXM6CiAgdHJpbGl1bW5leHQ6CiAgICBpbWFnZTogJ2doY3IuaW8vdHJpbGl1bW5leHQvdHJpbGl1bTpzdGFibGUnCiAgICBwbGF0Zm9ybTogbGludXgvYW1kNjQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9UUklMSVVNTkVYVF84MDgwCiAgICAgIC0gJ1RaPSR7VFo6LUV1cm9wZS9CZXJsaW59JwogICAgdm9sdW1lczoKICAgICAgLSAndHJpbGl1bW5leHRfZGF0YTovaG9tZS9ub2RlL3RyaWxpdW0tZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtLXF1aWV0IC0tdHJpZXM9MSAtLXNwaWRlciBodHRwOi8vMTI3LjAuMC4xOjgwODAvYXBpL2hlYWx0aC1jaGVjayB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "tags": [ + "self-hosted", + "notes", + "todo", + "organize", + "markdown", + "wiki" + ], + "logo": "svgs/triliumnext.svg", + "minversion": "0.0.0", + "port": "8080" + }, "typesense": { "documentation": "https://typesense.org/docs?utm_source=coolify.io", "slogan": "Cutting-edge, in-memory search engine for mere mortals. Knowledge of rocket science optional.", @@ -3674,7 +3734,7 @@ "wireguard-easy": { "documentation": "https://github.com/wg-easy/wg-easy?utm_source=coolify.io", "slogan": "The easiest way to run WireGuard VPN + Web-based Admin UI.", - "compose": "c2VydmljZXM6CiAgd2ctZWFzeToKICAgIGltYWdlOiAnZ2hjci5pby93Zy1lYXN5L3dnLWVhc3k6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1dJUkVHVUFSREVBU1lfODAwMAogICAgICAtICdXR19IT1NUPSR7U0VSVklDRV9GUUROX1dJUkVHVUFSREVBU1l9JwogICAgICAtICdMQU5HPSR7TEFORzotZW59JwogICAgICAtIFBPUlQ9ODAwMAogICAgICAtIFdHX1BPUlQ9NTE4MjAKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3dnLWVhc3k6L2V0Yy93aXJlZ3VhcmQnCiAgICBwb3J0czoKICAgICAgLSAnNTE4MjA6NTE4MjAvdWRwJwogICAgY2FwX2FkZDoKICAgICAgLSBORVRfQURNSU4KICAgICAgLSBTWVNfTU9EVUxFCiAgICBzeXNjdGxzOgogICAgICAtIG5ldC5pcHY0LmNvbmYuYWxsLnNyY192YWxpZF9tYXJrPTEKICAgICAgLSBuZXQuaXB2NC5pcF9mb3J3YXJkPTEK", + "compose": "c2VydmljZXM6CiAgd2ctZWFzeToKICAgIGltYWdlOiAnZ2hjci5pby93Zy1lYXN5L3dnLWVhc3k6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1dJUkVHVUFSREVBU1lfNTE4MjEKICAgICAgLSAnV0dfSE9TVD0ke1NFUlZJQ0VfVVJMX1dJUkVHVUFSREVBU1l9JwogICAgICAtICdMQU5HPSR7TEFORzotZW59JwogICAgICAtIFdHX1BPUlQ9NTE4MjAKICAgICAgLSAnX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0nCiAgICB2b2x1bWVzOgogICAgICAtICd3Zy1lYXN5Oi9ldGMvd2lyZWd1YXJkJwogICAgcG9ydHM6CiAgICAgIC0gJzUxODIwOjUxODIwL3VkcCcKICAgIGNhcF9hZGQ6CiAgICAgIC0gTkVUX0FETUlOCiAgICAgIC0gU1lTX01PRFVMRQogICAgc3lzY3RsczoKICAgICAgLSBuZXQuaXB2NC5jb25mLmFsbC5zcmNfdmFsaWRfbWFyaz0xCiAgICAgIC0gbmV0LmlwdjQuaXBfZm9yd2FyZD0xCiAgICBlbnRyeXBvaW50OgogICAgICAtIC9iaW4vYmFzaAogICAgICAtICctYycKICAgICAgLSAiZXZhbCBcIndncHcgJyR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0nID4gL3Bhc3MtaGFzaFwiXG5ldmFsIFwiJChjYXQgL3Bhc3MtaGFzaCkgZHVtYi1pbml0IG5vZGUgc2VydmVyLmpzXCJcbiIK", "tags": [ "wireguard", "vpn", From 43ff38d91430f227fddca4f2afcbc229f2bd05f3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:19:32 +0200 Subject: [PATCH 048/202] feat(terminal): dispatch focus event for terminal after connection and enhance focus handling in JavaScript --- .../Shared/ExecuteContainerCommand.php | 6 ++++++ resources/js/terminal.js | 21 +++++++++++++++++++ .../execute-container-command.blade.php | 3 ++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php index 9003e093a..2d55807c7 100644 --- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php +++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php @@ -158,6 +158,9 @@ class ExecuteContainerCommand extends Component data_get($server, 'name'), data_get($server, 'uuid') ); + + // Dispatch a frontend event to ensure terminal gets focus after connection + $this->dispatch('terminal-should-focus'); } catch (\Throwable $e) { return handleError($e, $this); } finally { @@ -213,6 +216,9 @@ class ExecuteContainerCommand extends Component data_get($container, 'container.Names'), data_get($container, 'server.uuid') ); + + // Dispatch a frontend event to ensure terminal gets focus after connection + $this->dispatch('terminal-should-focus'); } catch (\Throwable $e) { return handleError($e, $this); } finally { diff --git a/resources/js/terminal.js b/resources/js/terminal.js index 10535f3ea..b49aad9cf 100644 --- a/resources/js/terminal.js +++ b/resources/js/terminal.js @@ -48,6 +48,18 @@ export function initializeTerminalComponent() { this.sendCommandWhenReady({ command: command }); }); + this.$wire.on('terminal-should-focus', () => { + // Wait for terminal to be ready, then focus + const focusWhenReady = () => { + if (this.terminalActive && this.term) { + this.term.focus(); + } else { + setTimeout(focusWhenReady, 100); + } + }; + focusWhenReady(); + }); + this.keepAliveInterval = setInterval(this.keepAlive.bind(this), 30000); this.$watch('terminalActive', (active) => { @@ -353,6 +365,15 @@ export function initializeTerminalComponent() { this.resizeTerminal(); }, 200); + // Ensure terminal gets focus after connection with multiple attempts + setTimeout(() => { + this.term.focus(); + }, 100); + + setTimeout(() => { + this.term.focus(); + }, 500); + // Notify parent component that terminal is connected this.$wire.dispatch('terminalConnected'); } else if (event.data === 'unprocessable') { diff --git a/resources/views/livewire/project/shared/execute-container-command.blade.php b/resources/views/livewire/project/shared/execute-container-command.blade.php index 71d253103..7fe208a9b 100644 --- a/resources/views/livewire/project/shared/execute-container-command.blade.php +++ b/resources/views/livewire/project/shared/execute-container-command.blade.php @@ -45,7 +45,8 @@ @if ($servers->first()->isTerminalEnabled() && $servers->first()->isFunctional()) + x-data="{ autoConnected: false }" + x-on:terminal-websocket-ready.window="if (!autoConnected) { autoConnected = true; $wire.dispatchSelf('connectToServer'); }">

Terminal

{{ $isConnecting ? 'Connecting...' : 'Connect' }} From 9651e3a7ec6089fd8556eb3db2ce9ab05baa406b Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 18 Jul 2025 23:10:08 +0200 Subject: [PATCH 049/202] revert(parser): enhance FQDN generation logic for services and applications --- bootstrap/helpers/shared.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index de6c954e0..7ce511f2c 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -3049,7 +3049,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int // Get all SERVICE_ variables from keys and values $key = str($key); $value = str($value); - $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; preg_match_all($regex, $value, $valueMatches); if (count($valueMatches[1]) > 0) { @@ -3080,8 +3079,11 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } } elseif ($isService) { if (blank($savedService->fqdn)) { - // For services, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty - $fqdn = ''; + if ($fqdnFor) { + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + } else { + $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); + } } else { $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); } @@ -3147,10 +3149,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int if ($isApplication && $resource->build_pack === 'dockercompose') { continue; } - // For services, only generate FQDN if explicit FQDN is set - if ($isService && blank($savedService->fqdn)) { - continue; - } $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); if (str($fqdnFor)->contains('_')) { $fqdnFor = str($fqdnFor)->before('_'); From 11341d7c2c47ca3f8d149e363d7005fdab168f22 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 18 Jul 2025 23:15:51 +0200 Subject: [PATCH 050/202] refactor(jobs): remove logging for ScheduledJobManager and ServerResourceManager start and completion --- app/Jobs/ScheduledJobManager.php | 26 -------------------------- app/Jobs/ServerResourceManager.php | 6 ------ 2 files changed, 32 deletions(-) diff --git a/app/Jobs/ScheduledJobManager.php b/app/Jobs/ScheduledJobManager.php index 572491c0d..b90e853fc 100644 --- a/app/Jobs/ScheduledJobManager.php +++ b/app/Jobs/ScheduledJobManager.php @@ -58,10 +58,6 @@ class ScheduledJobManager implements ShouldQueue // Freeze the execution time at the start of the job $this->executionTime = Carbon::now(); - Log::channel('scheduled')->info('ScheduledJobManager started', [ - 'execution_time' => $this->executionTime->format('Y-m-d H:i:s T'), - ]); - // Process backups - don't let failures stop task processing try { $this->processScheduledBackups(); @@ -81,8 +77,6 @@ class ScheduledJobManager implements ShouldQueue 'trace' => $e->getTraceAsString(), ]); } - - Log::channel('scheduled')->info('ScheduledJobManager completed'); } private function processScheduledBackups(): void @@ -111,16 +105,6 @@ class ScheduledJobManager implements ShouldQueue } if ($this->shouldRunNow($frequency, $serverTimezone)) { - Log::channel('scheduled')->info('Dispatching backup job', [ - 'backup_id' => $backup->id, - 'backup_name' => $backup->name ?? 'unnamed', - 'server_name' => $server->name, - 'frequency' => $frequency, - 'timezone' => $serverTimezone, - 'execution_time' => $this->executionTime?->format('Y-m-d H:i:s T'), - 'current_time' => Carbon::now()->format('Y-m-d H:i:s T'), - ]); - DatabaseBackupJob::dispatch($backup); } } catch (\Exception $e) { @@ -157,16 +141,6 @@ class ScheduledJobManager implements ShouldQueue } if ($this->shouldRunNow($frequency, $serverTimezone)) { - Log::channel('scheduled')->info('Dispatching task job', [ - 'task_id' => $task->id, - 'task_name' => $task->name ?? 'unnamed', - 'server_name' => $server->name, - 'frequency' => $frequency, - 'timezone' => $serverTimezone, - 'execution_time' => $this->executionTime?->format('Y-m-d H:i:s T'), - 'current_time' => Carbon::now()->format('Y-m-d H:i:s T'), - ]); - ScheduledTaskJob::dispatch($task); } } catch (\Exception $e) { diff --git a/app/Jobs/ServerResourceManager.php b/app/Jobs/ServerResourceManager.php index 9059bc6cb..cdf8efc56 100644 --- a/app/Jobs/ServerResourceManager.php +++ b/app/Jobs/ServerResourceManager.php @@ -59,10 +59,6 @@ class ServerResourceManager implements ShouldQueue $this->instanceTimezone = config('app.timezone'); } - Log::channel('scheduled')->info('ServerResourceManager started', [ - 'execution_time' => $this->executionTime->format('Y-m-d H:i:s T'), - ]); - // Process server checks - don't let failures stop the job try { $this->processServerChecks(); @@ -72,8 +68,6 @@ class ServerResourceManager implements ShouldQueue 'trace' => $e->getTraceAsString(), ]); } - - Log::channel('scheduled')->info('ServerResourceManager completed'); } private function processServerChecks(): void From 039aa863212fa88457333f163aa3b20dedad9874 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 18 Jul 2025 23:16:12 +0200 Subject: [PATCH 051/202] fix(application): update service environment variables when generating domain for Docker Compose --- app/Livewire/Project/Application/General.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index ac2e6794d..03b67dce6 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -274,6 +274,7 @@ class General extends Component $this->application->save(); $this->dispatch('success', 'Domain generated.'); if ($this->application->build_pack === 'dockercompose') { + $this->updateServiceEnvironmentVariables(); $this->loadComposeFile(); } From 5ddaf3a61fa378c940f5eedf79223617bfd8889a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 18 Jul 2025 23:22:24 +0200 Subject: [PATCH 052/202] fix(application): add option to suppress toast notifications when loading compose file --- app/Livewire/Project/Application/General.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 03b67dce6..58a35caa0 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -215,25 +215,21 @@ class General extends Component } - public function loadComposeFile($isInit = false) + public function loadComposeFile($isInit = false, $showToast = true) { try { if ($isInit && $this->application->docker_compose_raw) { return; } - // Must reload the application to get the latest database changes - // Why? Not sure, but it works. - // $this->application->refresh(); - ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit); if (is_null($this->parsedServices)) { - $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); + $showToast && $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); return; } $this->application->parse(); - $this->dispatch('success', 'Docker compose file loaded.'); + $showToast && $this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('compose_loaded'); $this->dispatch('refreshStorages'); $this->dispatch('refreshEnvs'); @@ -275,7 +271,7 @@ class General extends Component $this->dispatch('success', 'Domain generated.'); if ($this->application->build_pack === 'dockercompose') { $this->updateServiceEnvironmentVariables(); - $this->loadComposeFile(); + $this->loadComposeFile(showToast: false); } return $domain; From ad8f4423180332d1732da0076ed226ab5ca91c8c Mon Sep 17 00:00:00 2001 From: Yanluis Fermin <32645451+Jacxk@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:59:35 -0400 Subject: [PATCH 053/202] refactor(services): update validation rules to be optional --- .../Controllers/Api/ServicesController.php | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 542be83de..f385b4d0e 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -7,6 +7,7 @@ use App\Actions\Service\StartService; use App\Actions\Service\StopService; use App\Http\Controllers\Controller; use App\Jobs\DeleteResourceJob; +use App\Models\Environment; use App\Models\EnvironmentVariable; use App\Models\Project; use App\Models\Server; @@ -550,7 +551,6 @@ class ServicesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid', 'docker_compose_raw'], properties: [ 'name' => ['type' => 'string', 'description' => 'The service name.'], 'description' => ['type' => 'string', 'description' => 'The service description.'], @@ -627,16 +627,16 @@ class ServicesController extends Controller { $allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network']; $validator = customApiValidator($request->all(), [ - 'project_uuid' => 'string|required', + 'project_uuid' => 'string|nullable', 'environment_name' => 'string|nullable', 'environment_uuid' => 'string|nullable', - 'server_uuid' => 'string|required', + 'server_uuid' => 'string|nullable', 'destination_uuid' => 'string', 'name' => 'string|max:255', 'description' => 'string|nullable', 'instant_deploy' => 'boolean', 'connect_to_docker_network' => 'boolean', - 'docker_compose_raw' => 'string|required', + 'docker_compose_raw' => 'string|nullable', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -654,14 +654,19 @@ class ServicesController extends Controller ], 422); } - $environmentUuid = $request->environment_uuid; - $environmentName = $request->environment_name; - if (blank($environmentUuid) && blank($environmentName)) { - return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); + $environmentUuid = null; + $environmentName = null; + + if ($request->environment_uuid) { + $environmentUuid = $request->environment_uuid; + } elseif ($request->environment_name) { + $environmentName = $request->environment_name; + } else { + $environmentUuid = $service->environment->uuid; } - $serverUuid = $request->server_uuid; - $instantDeploy = $request->instant_deploy ?? false; - $project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first(); + $serverUuid = $request->server_uuid ?? $service->server->uuid; + $projectUuid = $request->project_uuid ?? $service->environment->project->uuid; + $project = Project::whereTeamId($teamId)->whereUuid($projectUuid)->first(); if (! $project) { return response()->json(['message' => 'Project not found.'], 404); } @@ -684,39 +689,41 @@ class ServicesController extends Controller return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400); } $destination = $destinations->first(); - if (! isBase64Encoded($request->docker_compose_raw)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', - ], - ], 422); + if ($request->has('docker_compose_raw')) { + if (! isBase64Encoded($request->docker_compose_raw)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerComposeRaw = base64_decode($request->docker_compose_raw); + if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerCompose = base64_decode($request->docker_compose_raw); + $dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $service->docker_compose_raw = $dockerComposeRaw; } - $dockerComposeRaw = base64_decode($request->docker_compose_raw); - if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', - ], - ], 422); - } - $dockerCompose = base64_decode($request->docker_compose_raw); - $dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); $connectToDockerNetwork = $request->connect_to_docker_network ?? false; $service->name = $request->name ?? null; $service->description = $request->description ?? null; - $service->docker_compose_raw = $dockerComposeRaw; $service->environment_id = $environment->id; $service->server_id = $server->id; $service->destination_id = $destination->id; $service->destination_type = $destination->getMorphClass(); $service->connect_to_docker_network = $connectToDockerNetwork; $service->save(); - + $service->parse(); - if ($instantDeploy) { + if ($request->instant_deploy) { StartService::dispatch($service); } From 79fc9927d7e8af964467e2d1fa240d4bed1fe6c1 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:22:16 +0200 Subject: [PATCH 054/202] fix(git): tracking issue due to case sensitivity --- app/View/Components/services/advanced.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/View/Components/services/advanced.php b/app/View/Components/services/advanced.php index 8104eaad4..99729a262 100644 --- a/app/View/Components/services/advanced.php +++ b/app/View/Components/services/advanced.php @@ -1,13 +1,13 @@ Date: Sun, 3 Aug 2025 22:26:45 +0200 Subject: [PATCH 055/202] fix(git): tracking issue due to case sensitivity --- .../Components/{services/advanced.php => Services/Advanced.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/View/Components/{services/advanced.php => Services/Advanced.php} (100%) diff --git a/app/View/Components/services/advanced.php b/app/View/Components/Services/Advanced.php similarity index 100% rename from app/View/Components/services/advanced.php rename to app/View/Components/Services/Advanced.php From 7bb91faf920f3ce10524dbf022ec61a3f833859c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20Sobczy=C5=84ski?= <32263891+h4570@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:11:39 +0200 Subject: [PATCH 056/202] fix(ui): Delete button width on small screens (#6308) --- .../livewire/project/shared/environment-variable/show.blade.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 82ee789b6..7335124d1 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -88,6 +88,7 @@ @@ -101,6 +102,7 @@ From efdaf46d08ceb81ccc2658640d95973f90b70c21 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:27:31 +0200 Subject: [PATCH 057/202] fix(service): matrix entrypoint - make sure registration is enabled not just non empty - make sure RECAPTCHA_PUBLIC_KEY is set when adding the recaptcha block --- templates/compose/matrix.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/matrix.yaml b/templates/compose/matrix.yaml index 99232967e..efaf505c2 100644 --- a/templates/compose/matrix.yaml +++ b/templates/compose/matrix.yaml @@ -89,14 +89,14 @@ services: # # ######################## - test -n "${ENABLE_REGISTRATION}" && ! grep "#registration" /data/homeserver.yaml &>/dev/null \ + [ "${ENABLE_REGISTRATION}" = "true" ] && ! grep "#registration" /data/homeserver.yaml &>/dev/null \ && echo >> /data/homeserver.yaml \ && cat <> /data/homeserver.yaml #registration enable_registration: true # Allows users to register on your server. EOF - ! grep ${RECAPTCHA_PUBLIC_KEY} /data/homeserver.yaml &>/dev/null \ + [ -n "${RECAPTCHA_PUBLIC_KEY}" ] && ! grep "${RECAPTCHA_PUBLIC_KEY}" /data/homeserver.yaml &>/dev/null \ && echo >> /data/homeserver.yaml \ && cat <> /data/homeserver.yaml # reCAPTCHA settings From 554b566735509fb2b834dd0dbad5987aad31123a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20Sobczy=C5=84ski?= <32263891+h4570@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:09:26 +0200 Subject: [PATCH 058/202] fix(ui): add flex-wrap to prevent overflow on small screens (#6307) --- .../project/shared/environment-variable/show.blade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 7335124d1..5a197dc7a 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -21,7 +21,7 @@
@else @if ($isDisabled) -
+
@if ($is_shared) @@ -29,7 +29,7 @@ @endif
@else -
+
@if ($is_multiline) @@ -42,7 +42,7 @@ @endif
@endif -
+
@if (!$is_redis_credential) @if ($type === 'service') Date: Mon, 4 Aug 2025 14:13:33 +0200 Subject: [PATCH 059/202] feat(lang): add Polish language & improve forgot_password translation (#6306) --- lang/ar.json | 5 ++- lang/az.json | 5 ++- lang/cs.json | 5 ++- lang/de.json | 5 ++- lang/en.json | 5 ++- lang/es.json | 5 ++- lang/fa.json | 5 ++- lang/fr.json | 5 ++- lang/id.json | 5 ++- lang/it.json | 5 ++- lang/ja.json | 5 ++- lang/no.json | 5 ++- lang/pl.json | 44 +++++++++++++++++++ lang/pt-br.json | 5 ++- lang/pt.json | 5 ++- lang/ro.json | 5 ++- lang/tr.json | 5 ++- lang/vi.json | 5 ++- lang/zh-cn.json | 5 ++- lang/zh-tw.json | 5 ++- .../views/auth/forgot-password.blade.php | 2 +- resources/views/auth/login.blade.php | 4 +- 22 files changed, 104 insertions(+), 41 deletions(-) create mode 100644 lang/pl.json diff --git a/lang/ar.json b/lang/ar.json index 263924c24..c966cc686 100644 --- a/lang/ar.json +++ b/lang/ar.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "تسجيل الدخول باستخدام Infomaniak", "auth.already_registered": "هل سبق لك التسجيل؟", "auth.confirm_password": "تأكيد كلمة المرور", - "auth.forgot_password": "نسيت كلمة المرور", + "auth.forgot_password_link": "هل نسيت كلمة المرور؟", + "auth.forgot_password_heading": "استعادة كلمة المرور", "auth.forgot_password_send_email": "إرسال بريد إلكتروني لإعادة تعيين كلمة المرور", "auth.register_now": "تسجيل", "auth.logout": "تسجيل الخروج", @@ -39,4 +40,4 @@ "resource.delete_configurations": "حذف جميع ملفات التعريف من الخادم بشكل دائم.", "database.delete_backups_locally": "حذف كافة النسخ الاحتياطية نهائيًا من التخزين المحلي.", "warning.sslipdomain": "تم حفظ ملفات التعريف الخاصة بك، ولكن استخدام نطاق sslip مع https غير مستحسن، لأن خوادم Let's Encrypt مع هذا النطاق العام محدودة المعدل (ستفشل عملية التحقق من شهادة SSL).

استخدم نطاقك الخاص بدلاً من ذلك." -} +} \ No newline at end of file diff --git a/lang/az.json b/lang/az.json index 92f56ddbc..85cee7589 100644 --- a/lang/az.json +++ b/lang/az.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "Infomaniak ilə daxil ol", "auth.already_registered": "Qeytiyatınız var?", "auth.confirm_password": "Şifrəni təsdiqləyin", - "auth.forgot_password": "Şifrəmi unutdum", + "auth.forgot_password_link": "Şifrəmi unutdum?", + "auth.forgot_password_heading": "Şifrəni bərpa et", "auth.forgot_password_send_email": "Şifrəni sıfırlamaq üçün e-poçt göndər", "auth.register_now": "Qeydiyyat", "auth.logout": "Çıxış", @@ -39,4 +40,4 @@ "resource.delete_configurations": "Serverdən bütün konfiqurasiya faylları tamamilə silinəcək.", "database.delete_backups_locally": "Bütün ehtiyat nüsxələr lokal yaddaşdan tamamilə silinəcək.", "warning.sslipdomain": "Konfiqurasiya yadda saxlanıldı, lakin sslip domeni ilə https TÖVSİYƏ EDİLMİR, çünki Let's Encrypt serverləri bu ümumi domenlə məhdudlaşdırılır (SSL sertifikatının təsdiqlənməsi uğursuz olacaq).

Əvəzində öz domeninizdən istifadə edin." -} +} \ No newline at end of file diff --git a/lang/cs.json b/lang/cs.json index 00455aa81..9e5d2c44e 100644 --- a/lang/cs.json +++ b/lang/cs.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Přihlásit se pomocí Infomaniak", "auth.already_registered": "Již jste registrováni?", "auth.confirm_password": "Potvrďte heslo", - "auth.forgot_password": "Zapomněli jste heslo", + "auth.forgot_password_link": "Zapomněli jste heslo?", + "auth.forgot_password_heading": "Obnovení hesla", "auth.forgot_password_send_email": "Poslat e-mail pro resetování hesla", "auth.register_now": "Registrovat se", "auth.logout": "Odhlásit se", @@ -30,4 +31,4 @@ "input.recovery_code": "Obnovovací kód", "button.save": "Uložit", "repository.url": "Příklady
Pro veřejné repozitáře, použijte https://....
Pro soukromé repozitáře, použijte git@....

https://github.com/coollabsio/coolify-examples main branch bude zvolena
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch bude vybrána.
https://gitea.com/sedlav/expressjs.git main branch vybrána.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch bude vybrána." -} +} \ No newline at end of file diff --git a/lang/de.json b/lang/de.json index f56b21710..fd587de22 100644 --- a/lang/de.json +++ b/lang/de.json @@ -11,7 +11,8 @@ "auth.login.zitadel": "Mit Zitadel anmelden", "auth.already_registered": "Bereits registriert?", "auth.confirm_password": "Passwort bestätigen", - "auth.forgot_password": "Passwort vergessen", + "auth.forgot_password_link": "Passwort vergessen?", + "auth.forgot_password_heading": "Passwort-Wiederherstellung", "auth.forgot_password_send_email": "Passwort zurücksetzen E-Mail senden", "auth.register_now": "Registrieren", "auth.logout": "Abmelden", @@ -31,4 +32,4 @@ "input.recovery_code": "Wiederherstellungscode", "button.save": "Speichern", "repository.url": "Beispiele
Für öffentliche Repositories benutze https://....
Für private Repositories benutze git@....

https://github.com/coollabsio/coolify-examples main Branch wird ausgewählt
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify Branch wird ausgewählt.
https://gitea.com/sedlav/expressjs.git main Branch wird ausgewählt.
https://gitlab.com/andrasbacsai/nodejs-example.git main Branch wird ausgewählt." -} +} \ No newline at end of file diff --git a/lang/en.json b/lang/en.json index 4a398a9f9..af7f2145d 100644 --- a/lang/en.json +++ b/lang/en.json @@ -12,7 +12,8 @@ "auth.login.zitadel": "Login with Zitadel", "auth.already_registered": "Already registered?", "auth.confirm_password": "Confirm password", - "auth.forgot_password": "Forgot password", + "auth.forgot_password_link": "Forgot password?", + "auth.forgot_password_heading": "Password recovery", "auth.forgot_password_send_email": "Send password reset email", "auth.register_now": "Register", "auth.logout": "Logout", @@ -40,4 +41,4 @@ "resource.delete_configurations": "Permanently delete all configuration files from the server.", "database.delete_backups_locally": "All backups will be permanently deleted from local storage.", "warning.sslipdomain": "Your configuration is saved, but sslip domain with https is NOT recommended, because Let's Encrypt servers with this public domain are rate limited (SSL certificate validation will fail).

Use your own domain instead." -} +} \ No newline at end of file diff --git a/lang/es.json b/lang/es.json index 73363a9bf..f56387f05 100644 --- a/lang/es.json +++ b/lang/es.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Acceder con Infomaniak", "auth.already_registered": "¿Ya estás registrado?", "auth.confirm_password": "Confirmar contraseña", - "auth.forgot_password": "¿Olvidaste tu contraseña?", + "auth.forgot_password_link": "¿Olvidaste tu contraseña?", + "auth.forgot_password_heading": "Recuperación de contraseña", "auth.forgot_password_send_email": "Enviar correo de recuperación de contraseña", "auth.register_now": "Registrar", "auth.logout": "Cerrar sesión", @@ -30,4 +31,4 @@ "input.recovery_code": "Código de recuperación", "button.save": "Guardar", "repository.url": "Examples
Para repositorios públicos, usar https://....
Para repositorios privados, usar git@....

https://github.com/coollabsio/coolify-examples main la rama 'main' será seleccionada.
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify la rama 'nodejs-fastify' será seleccionada.
https://gitea.com/sedlav/expressjs.git main la rama 'main' será seleccionada.
https://gitlab.com/andrasbacsai/nodejs-example.git main la rama 'main' será seleccionada." -} +} \ No newline at end of file diff --git a/lang/fa.json b/lang/fa.json index d68049e77..ae22ee946 100644 --- a/lang/fa.json +++ b/lang/fa.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "ورود با Infomaniak", "auth.already_registered": "قبلاً ثبت نام کرده‌اید؟", "auth.confirm_password": "تایید رمز عبور", - "auth.forgot_password": "فراموشی رمز عبور", + "auth.forgot_password_link": "رمز عبور را فراموش کرده‌اید؟", + "auth.forgot_password_heading": "بازیابی رمز عبور", "auth.forgot_password_send_email": "ارسال ایمیل بازیابی رمز عبور", "auth.register_now": "ثبت نام", "auth.logout": "خروج", @@ -30,4 +31,4 @@ "input.recovery_code": "کد بازیابی", "button.save": "ذخیره", "repository.url": "مثال‌ها
برای مخازن عمومی، از https://... استفاده کنید.
برای مخازن خصوصی، از git@... استفاده کنید.

شاخه main انتخاب خواهد شد.
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify شاخه nodejs-fastify انتخاب خواهد شد.
https://gitea.com/sedlav/expressjs.git شاخه main انتخاب خواهد شد.
https://gitlab.com/andrasbacsai/nodejs-example.git شاخه main انتخاب خواهد شد." -} +} \ No newline at end of file diff --git a/lang/fr.json b/lang/fr.json index 2516d0f69..d98a1ebc8 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "Connexion avec Infomaniak", "auth.already_registered": "Déjà enregistré ?", "auth.confirm_password": "Confirmer le mot de passe", - "auth.forgot_password": "Mot de passe oublié", + "auth.forgot_password_link": "Mot de passe oublié ?", + "auth.forgot_password_heading": "Récupération du mot de passe", "auth.forgot_password_send_email": "Envoyer l'email de réinitialisation de mot de passe", "auth.register_now": "S'enregistrer", "auth.logout": "Déconnexion", @@ -39,4 +40,4 @@ "resource.delete_configurations": "Supprimer définitivement tous les fichiers de configuration du serveur.", "database.delete_backups_locally": "Toutes les sauvegardes seront définitivement supprimées du stockage local.", "warning.sslipdomain": "Votre configuration est enregistrée, mais l'utilisation du domaine sslip avec https N'EST PAS recommandée, car les serveurs Let's Encrypt avec ce domaine public sont limités en taux (la validation du certificat SSL échouera).

Utilisez plutôt votre propre domaine." -} +} \ No newline at end of file diff --git a/lang/id.json b/lang/id.json index b0e38197a..d85176cda 100644 --- a/lang/id.json +++ b/lang/id.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "Masuk dengan Infomaniak", "auth.already_registered": "Sudah terdaftar?", "auth.confirm_password": "Konfirmasi kata sandi", - "auth.forgot_password": "Lupa kata sandi", + "auth.forgot_password_link": "Lupa kata sandi?", + "auth.forgot_password_heading": "Pemulihan Kata Sandi", "auth.forgot_password_send_email": "Kirim email reset kata sandi", "auth.register_now": "Daftar", "auth.logout": "Keluar", @@ -39,4 +40,4 @@ "resource.delete_configurations": "Hapus permanen semua file konfigurasi dari server.", "database.delete_backups_locally": "Semua backup akan dihapus permanen dari penyimpanan lokal.", "warning.sslipdomain": "Konfigurasi Anda disimpan, tetapi domain sslip dengan https TIDAK direkomendasikan, karena server Let's Encrypt dengan domain publik ini dibatasi (validasi sertifikat SSL akan gagal).

Gunakan domain Anda sendiri sebagai gantinya." -} +} \ No newline at end of file diff --git a/lang/it.json b/lang/it.json index c0edc314b..e4c1a9c05 100644 --- a/lang/it.json +++ b/lang/it.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "Accedi con Infomaniak", "auth.already_registered": "Già registrato?", "auth.confirm_password": "Conferma password", - "auth.forgot_password": "Password dimenticata", + "auth.forgot_password_link": "Hai dimenticato la password?", + "auth.forgot_password_heading": "Recupero password", "auth.forgot_password_send_email": "Invia email per reimpostare la password", "auth.register_now": "Registrati", "auth.logout": "Esci", @@ -39,4 +40,4 @@ "resource.delete_configurations": "Elimina definitivamente tutti i file di configurazione dal server.", "database.delete_backups_locally": "Tutti i backup verranno eliminati definitivamente dall'archiviazione locale.", "warning.sslipdomain": "La tua configurazione è stata salvata, ma il dominio sslip con https NON è raccomandato, poiché i server di Let's Encrypt con questo dominio pubblico hanno limitazioni di frequenza (la convalida del certificato SSL fallirà).

Utilizza invece il tuo dominio personale." -} +} \ No newline at end of file diff --git a/lang/ja.json b/lang/ja.json index 87d87d99b..05987e7ce 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Infomaniakでログイン", "auth.already_registered": "すでに登録済みですか?", "auth.confirm_password": "パスワードを確認", - "auth.forgot_password": "パスワードを忘れた", + "auth.forgot_password_link": "パスワードをお忘れですか?", + "auth.forgot_password_heading": "パスワードの再設定", "auth.forgot_password_send_email": "パスワードリセットメールを送信", "auth.register_now": "今すぐ登録", "auth.logout": "ログアウト", @@ -30,4 +31,4 @@ "input.recovery_code": "リカバリーコード", "button.save": "保存", "repository.url": "
公開リポジトリの場合はhttps://...を使用してください。
プライベートリポジトリの場合はgit@...を使用してください。

https://github.com/coollabsio/coolify-examples mainブランチが選択されます
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastifyブランチが選択されます。
https://gitea.com/sedlav/expressjs.git mainブランチが選択されます。
https://gitlab.com/andrasbacsai/nodejs-example.git mainブランチが選択されます。" -} +} \ No newline at end of file diff --git a/lang/no.json b/lang/no.json index a84f6aa6c..967bdf606 100644 --- a/lang/no.json +++ b/lang/no.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "Logg inn med Infomaniak", "auth.already_registered": "Allerede registrert?", "auth.confirm_password": "Bekreft passord", - "auth.forgot_password": "Glemt passord", + "auth.forgot_password_link": "Glemt passord?", + "auth.forgot_password_heading": "Gjenoppretting av passord", "auth.forgot_password_send_email": "Send e-post for tilbakestilling av passord", "auth.register_now": "Registrer deg", "auth.logout": "Logg ut", @@ -39,4 +40,4 @@ "resource.delete_configurations": "Slett alle konfigurasjonsfiler fra serveren permanent.", "database.delete_backups_locally": "Alle sikkerhetskopier vil bli slettet permanent fra lokal lagring.", "warning.sslipdomain": "Konfigurasjonen din er lagret, men sslip-domene med https er IKKE anbefalt, fordi Let's Encrypt-servere med dette offentlige domenet er hastighetsbegrenset (SSL-sertifikatvalidering vil mislykkes).

Bruk ditt eget domene i stedet." -} +} \ No newline at end of file diff --git a/lang/pl.json b/lang/pl.json new file mode 100644 index 000000000..bcd8e2393 --- /dev/null +++ b/lang/pl.json @@ -0,0 +1,44 @@ +{ + "auth.login": "Zaloguj", + "auth.login.authentik": "Zaloguj się przez Authentik", + "auth.login.azure": "Zaloguj się przez Microsoft", + "auth.login.bitbucket": "Zaloguj się przez Bitbucket", + "auth.login.clerk": "Zaloguj się przez Clerk", + "auth.login.discord": "Zaloguj się przez Discord", + "auth.login.github": "Zaloguj się przez GitHub", + "auth.login.gitlab": "Zaloguj się przez Gitlab", + "auth.login.google": "Zaloguj się przez Google", + "auth.login.infomaniak": "Zaloguj się przez Infomaniak", + "auth.login.zitadel": "Zaloguj się przez Zitadel", + "auth.already_registered": "Już zarejestrowany?", + "auth.confirm_password": "Potwierdź hasło", + "auth.forgot_password_link": "Zapomniałeś hasło?", + "auth.forgot_password_heading": "Odzyskiwanie hasła", + "auth.forgot_password_send_email": "Wyślij email resetujący hasło", + "auth.register_now": "Zarejestruj", + "auth.logout": "Wyloguj", + "auth.register": "Zarejestruj", + "auth.registration_disabled": "Rejestracja jest wyłączona. Skontaktuj się z administratorem.", + "auth.reset_password": "Zresetuj hasło", + "auth.failed": "Podane dane nie zgadzają się z naszymi rekordami.", + "auth.failed.callback": "Nie udało się przeprocesować callbacku od dostawcy logowania.", + "auth.failed.password": "Podane hasło jest nieprawidłowe.", + "auth.failed.email": "Nie znaleziono użytkownika z takim adresem email.", + "auth.throttle": "Zbyt wiele prób logowania. Spróbuj ponownie za :seconds sekund.", + "input.name": "Nazwa", + "input.email": "Email", + "input.password": "Hasło", + "input.password.again": "Hasło ponownie", + "input.code": "Jednorazowy kod", + "input.recovery_code": "Kod odzyskiwania", + "button.save": "Zapisz", + "repository.url": "Przykłady
Dla publicznych repozytoriów użyj https://....
Dla prywatnych repozytoriów, użyj git@....

https://github.com/coollabsio/coolify-examples - zostanie wybrany branch main
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify - zostanie wybrany branch nodejs-fastify
https://gitea.com/sedlav/expressjs.git - zostanie wybrany branch main
https://gitlab.com/andrasbacsai/nodejs-example.git - zostanie wybrany branch main", + "service.stop": "Ten serwis zostanie zatrzymany.", + "resource.docker_cleanup": "Uruchom Docker Cleanup (usunie nieużywane obrazy i cache buildera).", + "resource.non_persistent": "Wszystkie nietrwałe dane zostaną usunięte.", + "resource.delete_volumes": "Trwale usuń wszystkie wolumeny powiązane z tym zasobem.", + "resource.delete_connected_networks": "Trwale usuń wszystkie niepredefiniowane sieci powiązane z tym zasobem.", + "resource.delete_configurations": "Trwale usuń wszystkie pliki konfiguracyjne z serwera.", + "database.delete_backups_locally": "Wszystkie backupy zostaną trwale usunięte z lokalnej pamięci.", + "warning.sslipdomain": "Twoja konfiguracja została zapisana, lecz domena sslip z https jest NIEZALECANA, ponieważ serwery Let's Encrypt z tą publiczną domeną są pod rate limitem (walidacja certyfikatu SSL certificate się nie powiedzie).

Lepiej użyj własnej domeny." +} \ No newline at end of file diff --git a/lang/pt-br.json b/lang/pt-br.json index c3a102995..f3ebb6c69 100644 --- a/lang/pt-br.json +++ b/lang/pt-br.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "Entrar com Infomaniak", "auth.already_registered": "Já tem uma conta?", "auth.confirm_password": "Confirmar senha", - "auth.forgot_password": "Esqueceu a senha", + "auth.forgot_password_link": "Esqueceu a senha?", + "auth.forgot_password_heading": "Recuperação de senha", "auth.forgot_password_send_email": "Enviar e-mail para redefinir senha", "auth.register_now": "Cadastre-se", "auth.logout": "Sair", @@ -39,4 +40,4 @@ "resource.delete_configurations": "Excluir permanentemente todos os arquivos de configuração do servidor.", "database.delete_backups_locally": "Todos os backups serão excluídos permanentemente do armazenamento local.", "warning.sslipdomain": "Sua configuração foi salva, mas o domínio sslip com https NÃO é recomendado, porque os servidores do Let's Encrypt com este domínio público têm limitação de taxa (a validação do certificado SSL falhará).

Use seu próprio domínio em vez disso." -} +} \ No newline at end of file diff --git a/lang/pt.json b/lang/pt.json index 80ff8c146..08ad19df3 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Entrar com Infomaniak", "auth.already_registered": "Já tem uma conta?", "auth.confirm_password": "Confirmar senha", - "auth.forgot_password": "Esqueceu a senha?", + "auth.forgot_password_link": "Esqueceu a senha?", + "auth.forgot_password_heading": "Recuperação de senha", "auth.forgot_password_send_email": "Enviar e-mail de redefinição de senha", "auth.register_now": "Cadastrar-se", "auth.logout": "Sair", @@ -30,4 +31,4 @@ "input.recovery_code": "Código de recuperação", "button.save": "Salvar", "repository.url": "Exemplos
Para repositórios públicos, use https://....
Para repositórios privados, use git@....

https://github.com/coollabsio/coolify-examples a branch main será selecionada
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify a branch nodejs-fastify será selecionada.
https://gitea.com/sedlav/expressjs.git a branch main será selecionada.
https://gitlab.com/andrasbacsai/nodejs-example.git a branch main será selecionada." -} +} \ No newline at end of file diff --git a/lang/ro.json b/lang/ro.json index 5588ea6f4..18028d087 100644 --- a/lang/ro.json +++ b/lang/ro.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Autentificare prin Infomaniak", "auth.already_registered": "Sunteți deja înregistrat?", "auth.confirm_password": "Confirmați parola", - "auth.forgot_password": "Ați uitat parola", + "auth.forgot_password_link": "Ați uitat parola?", + "auth.forgot_password_heading": "Recuperare parolă", "auth.forgot_password_send_email": "Trimiteți e-mail-ul pentru resetarea parolei", "auth.register_now": "Înregistrare", "auth.logout": "Deconectare", @@ -37,4 +38,4 @@ "resource.delete_connected_networks": "Ștergeți definitiv toate rețelele non-predefinite asociate cu această resursă.", "resource.delete_configurations": "Ștergeți definitiv toate fișierele de configurare de pe server.", "database.delete_backups_locally": "Toate copiile de rezervă vor fi șterse definitiv din stocarea locală." -} +} \ No newline at end of file diff --git a/lang/tr.json b/lang/tr.json index 74f693dc9..e3f34aa14 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Infomaniak ile Giriş Yap", "auth.already_registered": "Zaten kayıtlı mısınız?", "auth.confirm_password": "Şifreyi Onayla", - "auth.forgot_password": "Şifremi Unuttum", + "auth.forgot_password_link": "Şifrenizi mi unuttunuz?", + "auth.forgot_password_heading": "Şifre Kurtarma", "auth.forgot_password_send_email": "Şifre sıfırlama e-postası gönder", "auth.register_now": "Kayıt Ol", "auth.logout": "Çıkış Yap", @@ -38,4 +39,4 @@ "resource.delete_configurations": "Sunucudaki tüm yapılandırma dosyaları kalıcı olarak silinecek.", "database.delete_backups_locally": "Tüm yedekler yerel depolamadan kalıcı olarak silinecek.", "warning.sslipdomain": "Yapılandırmanız kaydedildi, ancak sslip domain ile https ÖNERİLMEZ, çünkü Let's Encrypt sunucuları bu genel domain ile sınırlandırılmıştır (SSL sertifikası doğrulaması başarısız olur).

Bunun yerine kendi domaininizi kullanın." -} +} \ No newline at end of file diff --git a/lang/vi.json b/lang/vi.json index 46edac599..76e380477 100644 --- a/lang/vi.json +++ b/lang/vi.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Đăng Nhập Bằng Infomaniak", "auth.already_registered": "Đã đăng ký?", "auth.confirm_password": "Nhập lại mật khẩu", - "auth.forgot_password": "Quên mật khẩu", + "auth.forgot_password_link": "Quên mật khẩu?", + "auth.forgot_password_heading": "Khôi phục mật khẩu", "auth.forgot_password_send_email": "Gửi email đặt lại mật khẩu", "auth.register_now": "Đăng ký ngay", "auth.logout": "Đăng xuất", @@ -30,4 +31,4 @@ "input.recovery_code": "Mã khôi phục", "button.save": "Lưu", "repository.url": "Ví dụ
Với repo công khai, sử dụng https://....
Với repo riêng tư, sử dụng git@....

https://github.com/coollabsio/coolify-examples nhánh chính sẽ được chọn
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nhánh nodejs-fastify sẽ được chọn.
https://gitea.com/sedlav/expressjs.git nhánh chính sẽ được chọn.
https://gitlab.com/andrasbacsai/nodejs-example.git nhánh chính sẽ được chọn." -} +} \ No newline at end of file diff --git a/lang/zh-cn.json b/lang/zh-cn.json index d46c71e07..530621ee1 100644 --- a/lang/zh-cn.json +++ b/lang/zh-cn.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "使用 Infomaniak 登录", "auth.already_registered": "已经注册?", "auth.confirm_password": "确认密码", - "auth.forgot_password": "忘记密码", + "auth.forgot_password_link": "忘记密码?", + "auth.forgot_password_heading": "密码找回", "auth.forgot_password_send_email": "发送密码重置邮件", "auth.register_now": "注册", "auth.logout": "退出登录", @@ -30,4 +31,4 @@ "input.recovery_code": "恢复码", "button.save": "保存", "repository.url": "示例
对于公共代码仓库,请使用 https://...
对于私有代码仓库,请使用 git@...

https://github.com/coollabsio/coolify-examples main 分支将被选择
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify 分支将被选择。
https://gitea.com/sedlav/expressjs.git main 分支将被选择。
https://gitlab.com/andrasbacsai/nodejs-example.git main 分支将被选择" -} +} \ No newline at end of file diff --git a/lang/zh-tw.json b/lang/zh-tw.json index c0784c7b7..aa078104b 100644 --- a/lang/zh-tw.json +++ b/lang/zh-tw.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "使用 Infomaniak 登入", "auth.already_registered": "已經註冊?", "auth.confirm_password": "確認密碼", - "auth.forgot_password": "忘記密碼", + "auth.forgot_password_link": "忘記密碼?", + "auth.forgot_password_heading": "密碼找回", "auth.forgot_password_send_email": "發送重設密碼電郵", "auth.register_now": "註冊", "auth.logout": "登出", @@ -30,4 +31,4 @@ "input.recovery_code": "恢復碼", "button.save": "儲存", "repository.url": "例子
對於公共代碼倉庫,請使用 https://...
對於私有代碼倉庫,請使用 git@...

https://github.com/coollabsio/coolify-examples main 分支將被選擇
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify 分支將被選擇。
https://gitea.com/sedlav/expressjs.git main 分支將被選擇。
https://gitlab.com/andrasbacsai/nodejs-example.git main 分支將被選擇。" -} +} \ No newline at end of file diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php index 249aa18f9..66a924fb8 100644 --- a/resources/views/auth/forgot-password.blade.php +++ b/resources/views/auth/forgot-password.blade.php @@ -4,7 +4,7 @@ Coolify
- {{ __('auth.forgot_password') }} + {{ __('auth.forgot_password_heading') }}
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 42faf517f..8bd8e81fc 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -23,7 +23,7 @@ required label="{{ __('input.password') }}" /> - {{ __('auth.forgot_password') }}? + {{ __('auth.forgot_password_link') }} @else - {{ __('auth.forgot_password') }}? + {{ __('auth.forgot_password_link') }} @endenv From 84a2073f7ff9f81d68efc4dee703d2ae1e83d006 Mon Sep 17 00:00:00 2001 From: Datenschmutz <63157166+Datenschmutz@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:15:37 +0200 Subject: [PATCH 060/202] feat(service): update Authentik template (#6264) --- templates/compose/authentik.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/authentik.yaml b/templates/compose/authentik.yaml index d50110a44..06637d88b 100644 --- a/templates/compose/authentik.yaml +++ b/templates/compose/authentik.yaml @@ -6,7 +6,7 @@ services: authentik-server: - image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.3} + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.4} restart: unless-stopped command: server environment: @@ -35,7 +35,7 @@ services: redis: condition: service_healthy authentik-worker: - image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.3} + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.4} restart: unless-stopped command: worker environment: From 8332311999def671e6bfe0505acd82890768c2f0 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:53:18 +0200 Subject: [PATCH 061/202] chore(deps): update node and php deps --- composer.lock | 409 +++++++++++++++++++++++++-------------------- package-lock.json | 412 +++++++++++++++++++++++----------------------- 2 files changed, 439 insertions(+), 382 deletions(-) diff --git a/composer.lock b/composer.lock index 8d170cdc1..acf153038 100644 --- a/composer.lock +++ b/composer.lock @@ -870,16 +870,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.351.1", + "version": "3.352.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805" + "reference": "7f3ad0da2545b24259273ea7ab892188bae7d91b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805", - "reference": "f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7f3ad0da2545b24259273ea7ab892188bae7d91b", + "reference": "7f3ad0da2545b24259273ea7ab892188bae7d91b", "shasum": "" }, "require": { @@ -961,9 +961,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.351.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.352.0" }, - "time": "2025-07-17T18:07:08+00:00" + "time": "2025-08-01T18:04:23+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1373,16 +1373,16 @@ }, { "name": "doctrine/dbal", - "version": "4.3.0", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "5fe09532be619202d59c70956c6fb20e97933ee3" + "reference": "ac336c95ea9e13433d56ca81c308b39db0e1a2a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/5fe09532be619202d59c70956c6fb20e97933ee3", - "reference": "5fe09532be619202d59c70956c6fb20e97933ee3", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/ac336c95ea9e13433d56ca81c308b39db0e1a2a7", + "reference": "ac336c95ea9e13433d56ca81c308b39db0e1a2a7", "shasum": "" }, "require": { @@ -1459,7 +1459,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.3.0" + "source": "https://github.com/doctrine/dbal/tree/4.3.1" }, "funding": [ { @@ -1475,7 +1475,7 @@ "type": "tidelift" } ], - "time": "2025-06-16T19:31:04+00:00" + "time": "2025-07-22T10:09:51+00:00" }, { "name": "doctrine/deprecations", @@ -2678,16 +2678,16 @@ }, { "name": "laravel/framework", - "version": "v12.20.0", + "version": "v12.21.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff" + "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/1b9a00f8caf5503c92aa436279172beae1a484ff", - "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff", + "url": "https://api.github.com/repos/laravel/framework/zipball/ac8c4e73bf1b5387b709f7736d41427e6af1c93b", + "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b", "shasum": "" }, "require": { @@ -2889,7 +2889,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-07-08T15:02:21+00:00" + "time": "2025-07-22T15:41:55+00:00" }, { "name": "laravel/horizon", @@ -3111,16 +3111,16 @@ }, { "name": "laravel/sanctum", - "version": "v4.1.2", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491" + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/e4c09e69aecd5a383e0c1b85a6bb501c997d7491", - "reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677", + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677", "shasum": "" }, "require": { @@ -3171,7 +3171,7 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2025-07-01T15:49:32+00:00" + "time": "2025-07-09T19:45:24+00:00" }, { "name": "laravel/serializable-closure", @@ -3236,16 +3236,16 @@ }, { "name": "laravel/socialite", - "version": "v5.21.0", + "version": "v5.23.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "d83639499ad14985c9a6a9713b70073300ce998d" + "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/d83639499ad14985c9a6a9713b70073300ce998d", - "reference": "d83639499ad14985c9a6a9713b70073300ce998d", + "url": "https://api.github.com/repos/laravel/socialite/zipball/e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5", + "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5", "shasum": "" }, "require": { @@ -3304,7 +3304,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2025-05-19T12:56:37+00:00" + "time": "2025-07-23T14:16:08+00:00" }, { "name": "laravel/tinker", @@ -3510,16 +3510,16 @@ }, { "name": "league/commonmark", - "version": "2.7.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", "shasum": "" }, "require": { @@ -3548,7 +3548,7 @@ "symfony/process": "^5.4 | ^6.0 | ^7.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0 || ^5.0.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -3613,7 +3613,7 @@ "type": "tidelift" } ], - "time": "2025-05-05T12:20:28+00:00" + "time": "2025-07-20T12:47:49+00:00" }, { "name": "league/config", @@ -4696,16 +4696,16 @@ }, { "name": "nesbot/carbon", - "version": "3.10.1", + "version": "3.10.2", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00" + "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1fd1935b2d90aef2f093c5e35f7ae1257c448d00", - "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", + "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", "shasum": "" }, "require": { @@ -4797,7 +4797,7 @@ "type": "tidelift" } ], - "time": "2025-06-21T15:19:35+00:00" + "time": "2025-08-02T09:36:06+00:00" }, { "name": "nette/schema", @@ -4949,16 +4949,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", "shasum": "" }, "require": { @@ -5001,9 +5001,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2025-07-27T20:03:57+00:00" }, { "name": "nubs/random-name-generator", @@ -6663,16 +6663,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.9", + "version": "v0.12.10", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "1b801844becfe648985372cb4b12ad6840245ace" + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1b801844becfe648985372cb4b12ad6840245ace", - "reference": "1b801844becfe648985372cb4b12ad6840245ace", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6e80abe6f2257121f1eb9a4c55bf29d921025b22", + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22", "shasum": "" }, "require": { @@ -6722,12 +6722,11 @@ "authors": [ { "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "email": "justin@justinhileman.info" } ], "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", + "homepage": "https://psysh.org", "keywords": [ "REPL", "console", @@ -6736,9 +6735,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.9" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.10" }, - "time": "2025-06-23T02:35:06+00:00" + "time": "2025-08-04T12:39:37+00:00" }, { "name": "purplepixie/phpdns", @@ -7247,16 +7246,16 @@ }, { "name": "sentry/sentry", - "version": "4.14.1", + "version": "4.14.2", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda" + "reference": "bfeec74303d60d3f8bc33701ab3e86f8a8729f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/a28c4a6f5fda2bf730789a638501d7a737a64eda", - "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/bfeec74303d60d3f8bc33701ab3e86f8a8729f17", + "reference": "bfeec74303d60d3f8bc33701ab3e86f8a8729f17", "shasum": "" }, "require": { @@ -7320,7 +7319,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/4.14.1" + "source": "https://github.com/getsentry/sentry-php/tree/4.14.2" }, "funding": [ { @@ -7332,7 +7331,7 @@ "type": "custom" } ], - "time": "2025-06-23T15:25:52+00:00" + "time": "2025-07-21T08:28:29+00:00" }, { "name": "sentry/sentry-laravel", @@ -8779,16 +8778,16 @@ }, { "name": "symfony/console", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", "shasum": "" }, "require": { @@ -8853,7 +8852,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.1" + "source": "https://github.com/symfony/console/tree/v7.3.2" }, "funding": [ { @@ -8864,12 +8863,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-30T17:13:41+00:00" }, { "name": "symfony/css-selector", @@ -9005,16 +9008,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235" + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/35b55b166f6752d6aaf21aa042fc5ed280fce235", - "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3", "shasum": "" }, "require": { @@ -9062,7 +9065,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.1" + "source": "https://github.com/symfony/error-handler/tree/v7.3.2" }, "funding": [ { @@ -9073,12 +9076,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-13T07:48:40+00:00" + "time": "2025-07-07T08:17:57+00:00" }, { "name": "symfony/event-dispatcher", @@ -9238,16 +9245,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { @@ -9282,7 +9289,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -9293,25 +9300,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "23dd60256610c86a3414575b70c596e5deff6ed9" + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/23dd60256610c86a3414575b70c596e5deff6ed9", - "reference": "23dd60256610c86a3414575b70c596e5deff6ed9", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6877c122b3a6cc3695849622720054f6e6fa5fa6", + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6", "shasum": "" }, "require": { @@ -9361,7 +9372,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.3.1" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.2" }, "funding": [ { @@ -9372,25 +9383,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-23T15:07:14+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831" + "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1644879a66e4aa29c36fe33dfa6c54b450ce1831", - "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6ecc895559ec0097e221ed2fd5eb44d5fede083c", + "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c", "shasum": "" }, "require": { @@ -9475,7 +9490,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.3.1" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.2" }, "funding": [ { @@ -9486,25 +9501,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-28T08:24:55+00:00" + "time": "2025-07-31T10:45:04+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368" + "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/b5db5105b290bdbea5ab27b89c69effcf1cb3368", - "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368", + "url": "https://api.github.com/repos/symfony/mailer/zipball/d43e84d9522345f96ad6283d5dfccc8c1cfc299b", + "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b", "shasum": "" }, "require": { @@ -9555,7 +9574,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.1" + "source": "https://github.com/symfony/mailer/tree/v7.3.2" }, "funding": [ { @@ -9566,25 +9585,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/mime", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1", "shasum": "" }, "require": { @@ -9639,7 +9662,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.0" + "source": "https://github.com/symfony/mime/tree/v7.3.2" }, "funding": [ { @@ -9650,25 +9673,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-19T08:51:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37", + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37", "shasum": "" }, "require": { @@ -9706,7 +9733,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.2" }, "funding": [ { @@ -9717,12 +9744,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-04T13:12:05+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/polyfill-ctype", @@ -10587,16 +10618,16 @@ }, { "name": "symfony/routing", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8e213820c5fea844ecea29203d2a308019007c15" + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", - "reference": "8e213820c5fea844ecea29203d2a308019007c15", + "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", "shasum": "" }, "require": { @@ -10648,7 +10679,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.0" + "source": "https://github.com/symfony/routing/tree/v7.3.2" }, "funding": [ { @@ -10659,12 +10690,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T20:43:28+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/service-contracts", @@ -10813,16 +10848,16 @@ }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", "shasum": "" }, "require": { @@ -10880,7 +10915,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.3.2" }, "funding": [ { @@ -10891,25 +10926,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/translation", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "241d5ac4910d256660238a7ecf250deba4c73063" + "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/241d5ac4910d256660238a7ecf250deba4c73063", - "reference": "241d5ac4910d256660238a7ecf250deba4c73063", + "url": "https://api.github.com/repos/symfony/translation/zipball/81b48f4daa96272efcce9c7a6c4b58e629df3c90", + "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90", "shasum": "" }, "require": { @@ -10976,7 +11015,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.1" + "source": "https://github.com/symfony/translation/tree/v7.3.2" }, "funding": [ { @@ -10987,12 +11026,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-30T17:31:46+00:00" }, { "name": "symfony/translation-contracts", @@ -11148,16 +11191,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42" + "reference": "53205bea27450dc5c65377518b3275e126d45e75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", - "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75", + "reference": "53205bea27450dc5c65377518b3275e126d45e75", "shasum": "" }, "require": { @@ -11169,7 +11212,6 @@ "symfony/console": "<6.4" }, "require-dev": { - "ext-iconv": "*", "symfony/console": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", @@ -11212,7 +11254,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.1" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.2" }, "funding": [ { @@ -11223,25 +11265,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-29T20:02:46+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" + "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30", + "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30", "shasum": "" }, "require": { @@ -11284,7 +11330,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.1" + "source": "https://github.com/symfony/yaml/tree/v7.3.2" }, "funding": [ { @@ -11295,12 +11341,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-03T06:57:57+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -12039,16 +12089,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.15.4", + "version": "v3.16.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "c0667ea91f7185f1e074402c5788195e96bf8106" + "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0667ea91f7185f1e074402c5788195e96bf8106", - "reference": "c0667ea91f7185f1e074402c5788195e96bf8106", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f265cf5e38577d42311f1a90d619bcd3740bea23", + "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23", "shasum": "" }, "require": { @@ -12056,7 +12106,7 @@ "illuminate/session": "^9|^10|^11|^12", "illuminate/support": "^9|^10|^11|^12", "php": "^8.1", - "php-debugbar/php-debugbar": "~2.1.1", + "php-debugbar/php-debugbar": "~2.2.0", "symfony/finder": "^6|^7" }, "require-dev": { @@ -12076,7 +12126,7 @@ ] }, "branch-alias": { - "dev-master": "3.15-dev" + "dev-master": "3.16-dev" } }, "autoload": { @@ -12108,7 +12158,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.4" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.0" }, "funding": [ { @@ -12120,7 +12170,7 @@ "type": "github" } ], - "time": "2025-04-16T06:32:06+00:00" + "time": "2025-07-14T11:56:43+00:00" }, { "name": "brianium/paratest", @@ -12641,16 +12691,16 @@ }, { "name": "laravel/telescope", - "version": "v5.10.0", + "version": "v5.10.2", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "fc0a8662682c0375b534033873debb780c003486" + "reference": "6d249d93ab06dc147ac62ea02b4272c2e7a24b72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/fc0a8662682c0375b534033873debb780c003486", - "reference": "fc0a8662682c0375b534033873debb780c003486", + "url": "https://api.github.com/repos/laravel/telescope/zipball/6d249d93ab06dc147ac62ea02b4272c2e7a24b72", + "reference": "6d249d93ab06dc147ac62ea02b4272c2e7a24b72", "shasum": "" }, "require": { @@ -12704,9 +12754,9 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v5.10.0" + "source": "https://github.com/laravel/telescope/tree/v5.10.2" }, - "time": "2025-07-07T14:47:19+00:00" + "time": "2025-07-24T05:26:13+00:00" }, { "name": "mockery/mockery", @@ -12793,16 +12843,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.3", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -12841,7 +12891,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -12849,7 +12899,7 @@ "type": "tidelift" } ], - "time": "2025-07-05T12:25:42+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nunomaduro/collision", @@ -13394,16 +13444,16 @@ }, { "name": "php-debugbar/php-debugbar", - "version": "v2.1.6", + "version": "v2.2.4", "source": { "type": "git", "url": "https://github.com/php-debugbar/php-debugbar.git", - "reference": "16fa68da5617220594aa5e33fa9de415f94784a0" + "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/16fa68da5617220594aa5e33fa9de415f94784a0", - "reference": "16fa68da5617220594aa5e33fa9de415f94784a0", + "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/3146d04671f51f69ffec2a4207ac3bdcf13a9f35", + "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35", "shasum": "" }, "require": { @@ -13411,6 +13461,9 @@ "psr/log": "^1|^2|^3", "symfony/var-dumper": "^4|^5|^6|^7" }, + "replace": { + "maximebf/debugbar": "self.version" + }, "require-dev": { "dbrekelmans/bdi": "^1", "phpunit/phpunit": "^8|^9", @@ -13425,7 +13478,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -13458,9 +13511,9 @@ ], "support": { "issues": "https://github.com/php-debugbar/php-debugbar/issues", - "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.1.6" + "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.4" }, - "time": "2025-02-21T17:47:03+00:00" + "time": "2025-07-22T14:01:30+00:00" }, { "name": "php-webdriver/webdriver", @@ -13530,16 +13583,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.18", + "version": "2.1.21", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7" + "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ee1f390b7a70cdf74a2b737e554f68afea885db7", - "reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6", + "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6", "shasum": "" }, "require": { @@ -13584,7 +13637,7 @@ "type": "github" } ], - "time": "2025-07-17T17:22:31+00:00" + "time": "2025-07-28T19:35:08+00:00" }, { "name": "phpunit/php-code-coverage", @@ -15436,16 +15489,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64" + "reference": "1c064a0c67749923483216b081066642751cc2c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64", - "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64", + "url": "https://api.github.com/repos/symfony/http-client/zipball/1c064a0c67749923483216b081066642751cc2c7", + "reference": "1c064a0c67749923483216b081066642751cc2c7", "shasum": "" }, "require": { @@ -15511,7 +15564,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.1" + "source": "https://github.com/symfony/http-client/tree/v7.3.2" }, "funding": [ { @@ -15522,12 +15575,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-28T07:58:39+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/http-client-contracts", diff --git a/package-lock.json b/package-lock.json index 10489a7d4..34b2c1dd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,9 +90,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -104,9 +104,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", - "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", "cpu": [ "ppc64" ], @@ -121,9 +121,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", - "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", "cpu": [ "arm" ], @@ -138,9 +138,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", - "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", "cpu": [ "arm64" ], @@ -155,9 +155,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", - "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", "cpu": [ "x64" ], @@ -172,9 +172,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", - "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", "cpu": [ "arm64" ], @@ -189,9 +189,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", - "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", "cpu": [ "x64" ], @@ -206,9 +206,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", - "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", "cpu": [ "arm64" ], @@ -223,9 +223,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", - "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", "cpu": [ "x64" ], @@ -240,9 +240,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", - "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", "cpu": [ "arm" ], @@ -257,9 +257,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", - "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", "cpu": [ "arm64" ], @@ -274,9 +274,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", - "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", "cpu": [ "ia32" ], @@ -291,9 +291,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", - "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", "cpu": [ "loong64" ], @@ -308,9 +308,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", - "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", "cpu": [ "mips64el" ], @@ -325,9 +325,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", - "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", "cpu": [ "ppc64" ], @@ -342,9 +342,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", - "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", "cpu": [ "riscv64" ], @@ -359,9 +359,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", - "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", "cpu": [ "s390x" ], @@ -376,9 +376,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", - "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", "cpu": [ "x64" ], @@ -393,9 +393,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", - "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", "cpu": [ "arm64" ], @@ -410,9 +410,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", - "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", "cpu": [ "x64" ], @@ -427,9 +427,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", - "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", "cpu": [ "arm64" ], @@ -444,9 +444,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", - "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", "cpu": [ "x64" ], @@ -461,9 +461,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", - "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", "cpu": [ "arm64" ], @@ -478,9 +478,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", - "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", "cpu": [ "x64" ], @@ -495,9 +495,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", - "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", "cpu": [ "arm64" ], @@ -512,9 +512,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", - "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", "cpu": [ "ia32" ], @@ -529,9 +529,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", - "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.0.tgz", + "integrity": "sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==", "license": "MIT" }, "node_modules/@isaacs/fs-minipass": { @@ -604,9 +604,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", - "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", "cpu": [ "arm" ], @@ -618,9 +618,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", - "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", "cpu": [ "arm64" ], @@ -632,9 +632,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", - "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", "cpu": [ "arm64" ], @@ -646,9 +646,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", - "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", "cpu": [ "x64" ], @@ -660,9 +660,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", - "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", "cpu": [ "arm64" ], @@ -674,9 +674,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", - "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", "cpu": [ "x64" ], @@ -688,9 +688,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", - "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", "cpu": [ "arm" ], @@ -702,9 +702,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", - "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", "cpu": [ "arm" ], @@ -716,9 +716,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", - "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", "cpu": [ "arm64" ], @@ -730,9 +730,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", - "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", "cpu": [ "arm64" ], @@ -744,9 +744,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", - "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", "cpu": [ "loong64" ], @@ -757,10 +757,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", - "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", "cpu": [ "ppc64" ], @@ -772,9 +772,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", - "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", "cpu": [ "riscv64" ], @@ -786,9 +786,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", - "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", "cpu": [ "riscv64" ], @@ -800,9 +800,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", - "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", "cpu": [ "s390x" ], @@ -814,9 +814,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", - "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", "cpu": [ "x64" ], @@ -828,9 +828,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", - "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", "cpu": [ "x64" ], @@ -842,9 +842,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", - "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", "cpu": [ "arm64" ], @@ -856,9 +856,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", - "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", "cpu": [ "ia32" ], @@ -870,9 +870,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", - "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", "cpu": [ "x64" ], @@ -1623,9 +1623,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", - "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1636,32 +1636,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.6", - "@esbuild/android-arm": "0.25.6", - "@esbuild/android-arm64": "0.25.6", - "@esbuild/android-x64": "0.25.6", - "@esbuild/darwin-arm64": "0.25.6", - "@esbuild/darwin-x64": "0.25.6", - "@esbuild/freebsd-arm64": "0.25.6", - "@esbuild/freebsd-x64": "0.25.6", - "@esbuild/linux-arm": "0.25.6", - "@esbuild/linux-arm64": "0.25.6", - "@esbuild/linux-ia32": "0.25.6", - "@esbuild/linux-loong64": "0.25.6", - "@esbuild/linux-mips64el": "0.25.6", - "@esbuild/linux-ppc64": "0.25.6", - "@esbuild/linux-riscv64": "0.25.6", - "@esbuild/linux-s390x": "0.25.6", - "@esbuild/linux-x64": "0.25.6", - "@esbuild/netbsd-arm64": "0.25.6", - "@esbuild/netbsd-x64": "0.25.6", - "@esbuild/openbsd-arm64": "0.25.6", - "@esbuild/openbsd-x64": "0.25.6", - "@esbuild/openharmony-arm64": "0.25.6", - "@esbuild/sunos-x64": "0.25.6", - "@esbuild/win32-arm64": "0.25.6", - "@esbuild/win32-ia32": "0.25.6", - "@esbuild/win32-x64": "0.25.6" + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" } }, "node_modules/estree-walker": { @@ -1687,9 +1687,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -1875,9 +1875,9 @@ } }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "dev": true, "license": "MIT", "bin": { @@ -2397,9 +2397,9 @@ } }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "dev": true, "license": "MIT", "peer": true, @@ -2429,9 +2429,9 @@ } }, "node_modules/rollup": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", - "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", "dev": true, "license": "MIT", "dependencies": { @@ -2445,26 +2445,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.45.1", - "@rollup/rollup-android-arm64": "4.45.1", - "@rollup/rollup-darwin-arm64": "4.45.1", - "@rollup/rollup-darwin-x64": "4.45.1", - "@rollup/rollup-freebsd-arm64": "4.45.1", - "@rollup/rollup-freebsd-x64": "4.45.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", - "@rollup/rollup-linux-arm-musleabihf": "4.45.1", - "@rollup/rollup-linux-arm64-gnu": "4.45.1", - "@rollup/rollup-linux-arm64-musl": "4.45.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-musl": "4.45.1", - "@rollup/rollup-linux-s390x-gnu": "4.45.1", - "@rollup/rollup-linux-x64-gnu": "4.45.1", - "@rollup/rollup-linux-x64-musl": "4.45.1", - "@rollup/rollup-win32-arm64-msvc": "4.45.1", - "@rollup/rollup-win32-ia32-msvc": "4.45.1", - "@rollup/rollup-win32-x64-msvc": "4.45.1", + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", "fsevents": "~2.3.2" } }, From f06d80e80e364628125ce35e887b16f186f0419c Mon Sep 17 00:00:00 2001 From: Gauthier POGAM--LE MONTAGNER Date: Mon, 4 Aug 2025 15:32:15 +0200 Subject: [PATCH 062/202] feat(service): add sequin template (#6105) --- public/svgs/sequin.svg | 16 +++++++++ templates/compose/sequin.yaml | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 public/svgs/sequin.svg create mode 100644 templates/compose/sequin.yaml diff --git a/public/svgs/sequin.svg b/public/svgs/sequin.svg new file mode 100644 index 000000000..623bc1159 --- /dev/null +++ b/public/svgs/sequin.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/templates/compose/sequin.yaml b/templates/compose/sequin.yaml new file mode 100644 index 000000000..18f88fd89 --- /dev/null +++ b/templates/compose/sequin.yaml @@ -0,0 +1,68 @@ +# documentation: https://sequinstream.com/docs/ +# slogan: The fastest Postgres change data capture +# tags: postgres, sync, data +# logo: svgs/sequin.svg +# port: 7376 + +services: + sequin: + image: sequin/sequin:latest + environment: + - SERVICE_FQDN_SEQUIN_7376 + - SERVER_HOST=${SERVICE_FQDN_SEQUIN} + - PG_HOSTNAME=postgres + - PG_DATABASE=${POSTGRES_DB:-sequin-db} + - PG_PORT=5432 + - PG_USERNAME=${SERVICE_USER_POSTGRES} + - PG_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - PG_POOL_SIZE=20 + - SECRET_KEY_BASE=${SERVICE_REALBASE64_64_SECRETKEY} + - VAULT_KEY=${SERVICE_REALBASE64_VAULTKEY} + - REDIS_URL=redis://redis:6379 + - CONFIG_FILE_PATH=/config/playground.yml + - FEATURE_ACCOUNT_SELF_SIGNUP=${FEATURE_ACCOUNT_SELF_SIGNUP:-false} + - SEQUIN_TELEMETRY_DISABLED=${SEQUIN_TELEMETRY_DISABLED:-false} + - CRASH_REPORTING_DISABLED=${CRASH_REPORTING_DISABLED:-false} + depends_on: + redis: + condition: service_started + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "http://localhost:7376/health"] + + postgres: + image: 'postgres:16' + environment: + - POSTGRES_DB=${POSTGRES_DB:-sequin-db} + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + command: + - postgres + - '-c' + - wal_level=logical + healthcheck: + test: + - CMD-SHELL + - 'pg_isready -U ${SERVICE_USER_POSTGRES} -d sequin' + interval: 10s + timeout: 2s + retries: 5 + start_period: 2s + start_interval: 1s + volumes: + - 'postgres_data:/var/lib/postgresql/data' + + redis: + image: 'redis:7' + command: + - redis-server + - '--port' + - '6379' + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 20s + retries: 10 + volumes: + - 'redis_data:/data' From b165d506c968480fed25219b7ab54cab1add813a Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:35:27 +0200 Subject: [PATCH 063/202] chore(service): improve sequin - make sure Redis is healthy - formatting --- templates/compose/sequin.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/templates/compose/sequin.yaml b/templates/compose/sequin.yaml index 18f88fd89..35e210bdf 100644 --- a/templates/compose/sequin.yaml +++ b/templates/compose/sequin.yaml @@ -9,7 +9,7 @@ services: image: sequin/sequin:latest environment: - SERVICE_FQDN_SEQUIN_7376 - - SERVER_HOST=${SERVICE_FQDN_SEQUIN} + - SERVER_HOST=${SERVICE_URL_SEQUIN} - PG_HOSTNAME=postgres - PG_DATABASE=${POSTGRES_DB:-sequin-db} - PG_PORT=5432 @@ -25,44 +25,44 @@ services: - CRASH_REPORTING_DISABLED=${CRASH_REPORTING_DISABLED:-false} depends_on: redis: - condition: service_started + condition: service_healthy postgres: condition: service_healthy healthcheck: test: ["CMD", "curl", "http://localhost:7376/health"] postgres: - image: 'postgres:16' + image: postgres:16 environment: - POSTGRES_DB=${POSTGRES_DB:-sequin-db} - POSTGRES_USER=${SERVICE_USER_POSTGRES} - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} command: - postgres - - '-c' + - "-c" - wal_level=logical healthcheck: test: - CMD-SHELL - - 'pg_isready -U ${SERVICE_USER_POSTGRES} -d sequin' + - "pg_isready -U ${SERVICE_USER_POSTGRES} -d sequin" interval: 10s timeout: 2s retries: 5 start_period: 2s start_interval: 1s volumes: - - 'postgres_data:/var/lib/postgresql/data' + - postgres_data:/var/lib/postgresql/data redis: - image: 'redis:7' + image: redis:7 command: - redis-server - - '--port' - - '6379' + - "--port" + - "6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 20s retries: 10 volumes: - - 'redis_data:/data' + - redis_data:/data From 10823666b2a0b032b96025b40c04e6ed7410e7b2 Mon Sep 17 00:00:00 2001 From: Jonas <56066318+OG-Jons@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:48:54 +0900 Subject: [PATCH 064/202] feat(service): add pi-hole template (#6020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 🏔️ Peak <122374094+peaklabs-dev@users.noreply.github.com> --- public/svgs/pihole.svg | 1 + templates/compose/pi-hole.yaml | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 public/svgs/pihole.svg create mode 100644 templates/compose/pi-hole.yaml diff --git a/public/svgs/pihole.svg b/public/svgs/pihole.svg new file mode 100644 index 000000000..a4efefcc8 --- /dev/null +++ b/public/svgs/pihole.svg @@ -0,0 +1 @@ +NewVortex \ No newline at end of file diff --git a/templates/compose/pi-hole.yaml b/templates/compose/pi-hole.yaml new file mode 100644 index 000000000..476067b16 --- /dev/null +++ b/templates/compose/pi-hole.yaml @@ -0,0 +1,30 @@ +# documentation: https://pi-hole.net/ +# slogan: Network-wide Ad Blocking +# tags: ad-block,dns,sinkhole,ntp,dhcp +# logo: svgs/pihole.svg +# port: 80 + +services: + pihole: + image: pihole/pihole:latest + ports: + # DNS Ports + - "53:53/tcp" + - "53:53/udp" + # Uncomment the below if using Pi-hole as your DHCP Server + - "67:67/udp" + # Uncomment the line below if you are using Pi-hole as your NTP server + - "123:123/udp" + environment: + - SERVICE_FQDN_PIHOLE_80 + - TZ=${TZ:-Europe/London} + - FTLCONF_webserver_api_password=${SERVICE_PASSWORD_PIHOLE} + - FTLCONF_dns_listeningMode=${FTLCONF_dns_listeningMode:-all} + volumes: + - pihole-data:/etc/pihole + cap_add: + # See https://github.com/pi-hole/docker-pi-hole#note-on-capabilities + # Required if you are using Pi-hole as your DHCP server, else not needed + - NET_ADMIN + # Required if you are using Pi-hole as your NTP client to be able to set the host's system time + - SYS_TIME From 2a526c54d5ed7d2e52a307266ec9789b8c1d5d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?= <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:15:56 +0200 Subject: [PATCH 065/202] fix(docker): volumes get delete when stopping a service if `Delete Unused Volumes` is activated (#6317) --- app/Actions/Application/StopApplication.php | 2 +- app/Actions/Database/StopDatabase.php | 2 +- app/Actions/Server/CleanupDocker.php | 6 ++--- app/Actions/Server/UpdateCoolify.php | 2 +- app/Actions/Service/DeleteService.php | 4 +-- app/Actions/Service/StopService.php | 2 +- .../Api/ApplicationsController.php | 6 ++--- .../Controllers/Api/DatabasesController.php | 6 ++--- .../Controllers/Api/ServicesController.php | 6 ++--- app/Jobs/DeleteResourceJob.php | 10 ++++---- app/Jobs/DockerCleanupJob.php | 25 ++++++++++++++++--- app/Jobs/ServerResourceManager.php | 2 +- app/Livewire/Project/Shared/Danger.php | 6 ++--- app/Livewire/Server/DockerCleanup.php | 2 +- 14 files changed, 49 insertions(+), 32 deletions(-) diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php index 0ca703fce..ee3398b04 100644 --- a/app/Actions/Application/StopApplication.php +++ b/app/Actions/Application/StopApplication.php @@ -49,7 +49,7 @@ class StopApplication } if ($dockerCleanup) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } } catch (\Exception $e) { return $e->getMessage(); diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index a03c9269e..6fcdedeeb 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -29,7 +29,7 @@ class StopDatabase $this->stopContainer($database, $database->uuid, 30); if ($dockerCleanup) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } if ($database->is_public) { diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php index 754feecb1..392562167 100644 --- a/app/Actions/Server/CleanupDocker.php +++ b/app/Actions/Server/CleanupDocker.php @@ -11,7 +11,7 @@ class CleanupDocker public string $jobQueue = 'high'; - public function handle(Server $server) + public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $deleteUnusedNetworks = false) { $settings = instanceSettings(); $realtimeImage = config('constants.coolify.realtime_image'); @@ -36,11 +36,11 @@ class CleanupDocker "docker images --filter before=$realtimeImageWithoutPrefixVersion --filter reference=$realtimeImageWithoutPrefix | grep $realtimeImageWithoutPrefix | awk '{print $3}' | xargs -r docker rmi -f", ]; - if ($server->settings->delete_unused_volumes) { + if ($deleteUnusedVolumes) { $commands[] = 'docker volume prune -af'; } - if ($server->settings->delete_unused_networks) { + if ($deleteUnusedNetworks) { $commands[] = 'docker network prune -f'; } diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index 9a6cc140b..2a06428e2 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -29,7 +29,7 @@ class UpdateCoolify if (! $this->server) { return; } - CleanupDocker::dispatch($this->server); + CleanupDocker::dispatch($this->server, false, false); $this->latestVersion = get_latest_version_of_coolify(); $this->currentVersion = config('constants.coolify.version'); if (! $manual_update) { diff --git a/app/Actions/Service/DeleteService.php b/app/Actions/Service/DeleteService.php index 404e11559..8790901cd 100644 --- a/app/Actions/Service/DeleteService.php +++ b/app/Actions/Service/DeleteService.php @@ -11,7 +11,7 @@ class DeleteService { use AsAction; - public function handle(Service $service, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks) + public function handle(Service $service, bool $deleteVolumes, bool $deleteConnectedNetworks, bool $deleteConfigurations, bool $dockerCleanup) { try { $server = data_get($service, 'server'); @@ -71,7 +71,7 @@ class DeleteService $service->forceDelete(); if ($dockerCleanup) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } } } diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index a7fa4b8b2..190b8885d 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -40,7 +40,7 @@ class StopService $service->deleteConnectedNetworks(); } if ($dockerCleanup) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } } catch (\Exception $e) { return $e->getMessage(); diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 0860c7133..dcf0e5fbe 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1699,10 +1699,10 @@ class ApplicationsController extends Controller DeleteResourceJob::dispatch( resource: $application, - deleteConfigurations: $request->query->get('delete_configurations', true), deleteVolumes: $request->query->get('delete_volumes', true), - dockerCleanup: $request->query->get('docker_cleanup', true), - deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) + deleteConnectedNetworks: $request->query->get('delete_connected_networks', true), + deleteConfigurations: $request->query->get('delete_configurations', true), + dockerCleanup: $request->query->get('docker_cleanup', true) ); return response()->json([ diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 504665f6a..6ac052b3c 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -1608,10 +1608,10 @@ class DatabasesController extends Controller DeleteResourceJob::dispatch( resource: $database, - deleteConfigurations: $request->query->get('delete_configurations', true), deleteVolumes: $request->query->get('delete_volumes', true), - dockerCleanup: $request->query->get('docker_cleanup', true), - deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) + deleteConnectedNetworks: $request->query->get('delete_connected_networks', true), + deleteConfigurations: $request->query->get('delete_configurations', true), + dockerCleanup: $request->query->get('docker_cleanup', true) ); return response()->json([ diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 542be83de..34d8ee669 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -510,10 +510,10 @@ class ServicesController extends Controller DeleteResourceJob::dispatch( resource: $service, - deleteConfigurations: $request->query->get('delete_configurations', true), deleteVolumes: $request->query->get('delete_volumes', true), - dockerCleanup: $request->query->get('docker_cleanup', true), - deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) + deleteConnectedNetworks: $request->query->get('delete_connected_networks', true), + deleteConfigurations: $request->query->get('delete_configurations', true), + dockerCleanup: $request->query->get('docker_cleanup', true) ); return response()->json([ diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 2750110f2..a725df52f 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -32,10 +32,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public Application|ApplicationPreview|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, - public bool $deleteConfigurations = true, public bool $deleteVolumes = true, - public bool $dockerCleanup = true, - public bool $deleteConnectedNetworks = true + public bool $deleteConnectedNetworks = true, + public bool $deleteConfigurations = true, + public bool $dockerCleanup = true ) { $this->onQueue('high'); } @@ -66,7 +66,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue break; case 'service': StopService::run($this->resource, true); - DeleteService::run($this->resource, $this->deleteConfigurations, $this->deleteVolumes, $this->dockerCleanup, $this->deleteConnectedNetworks); + DeleteService::run($this->resource, $this->deleteVolumes, $this->deleteConnectedNetworks, $this->deleteConfigurations, $this->dockerCleanup); return; } @@ -106,7 +106,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue if ($this->dockerCleanup) { $server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server'); if ($server) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } } Artisan::queue('cleanup:stucked-resources'); diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 519728ab0..f3f3a2ae4 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -34,7 +34,12 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue return [(new WithoutOverlapping('docker-cleanup-'.$this->server->uuid))->expireAfter(600)->dontRelease()]; } - public function __construct(public Server $server, public bool $manualCleanup = false) {} + public function __construct( + public Server $server, + public bool $manualCleanup = false, + public bool $deleteUnusedVolumes = false, + public bool $deleteUnusedNetworks = false + ) {} public function handle(): void { @@ -50,7 +55,11 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue $this->usageBefore = $this->server->getDiskUsage(); if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) { - $cleanup_log = CleanupDocker::run(server: $this->server); + $cleanup_log = CleanupDocker::run( + server: $this->server, + deleteUnusedVolumes: $this->deleteUnusedVolumes, + deleteUnusedNetworks: $this->deleteUnusedNetworks + ); $usageAfter = $this->server->getDiskUsage(); $message = ($this->manualCleanup ? 'Manual' : 'Forced').' Docker cleanup job executed successfully. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'; @@ -67,7 +76,11 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue } if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) { - $cleanup_log = CleanupDocker::run(server: $this->server); + $cleanup_log = CleanupDocker::run( + server: $this->server, + deleteUnusedVolumes: $this->deleteUnusedVolumes, + deleteUnusedNetworks: $this->deleteUnusedNetworks + ); $message = 'Docker cleanup job executed successfully, but no disk usage could be determined.'; $this->execution_log->update([ @@ -81,7 +94,11 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue } if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) { - $cleanup_log = CleanupDocker::run(server: $this->server); + $cleanup_log = CleanupDocker::run( + server: $this->server, + deleteUnusedVolumes: $this->deleteUnusedVolumes, + deleteUnusedNetworks: $this->deleteUnusedNetworks + ); $usageAfter = $this->server->getDiskUsage(); $diskSaved = $this->usageBefore - $usageAfter; diff --git a/app/Jobs/ServerResourceManager.php b/app/Jobs/ServerResourceManager.php index cdf8efc56..8a4b55a3f 100644 --- a/app/Jobs/ServerResourceManager.php +++ b/app/Jobs/ServerResourceManager.php @@ -133,7 +133,7 @@ class ServerResourceManager implements ShouldQueue $dockerCleanupFrequency = VALID_CRON_STRINGS[$dockerCleanupFrequency]; } if ($this->shouldRunNow($dockerCleanupFrequency, $serverTimezone)) { - DockerCleanupJob::dispatch($server); + DockerCleanupJob::dispatch($server, false, $server->settings->delete_unused_volumes, $server->settings->delete_unused_networks); } // Dispatch ServerPatchCheckJob if due (weekly) diff --git a/app/Livewire/Project/Shared/Danger.php b/app/Livewire/Project/Shared/Danger.php index 7da48f9fb..94a4c161c 100644 --- a/app/Livewire/Project/Shared/Danger.php +++ b/app/Livewire/Project/Shared/Danger.php @@ -99,10 +99,10 @@ class Danger extends Component $this->resource->delete(); DeleteResourceJob::dispatch( $this->resource, - $this->delete_configurations, $this->delete_volumes, - $this->docker_cleanup, - $this->delete_connected_networks + $this->delete_connected_networks, + $this->delete_configurations, + $this->docker_cleanup ); return redirect()->route('project.resource.index', [ diff --git a/app/Livewire/Server/DockerCleanup.php b/app/Livewire/Server/DockerCleanup.php index d3378d63f..c97a8f2c9 100644 --- a/app/Livewire/Server/DockerCleanup.php +++ b/app/Livewire/Server/DockerCleanup.php @@ -71,7 +71,7 @@ class DockerCleanup extends Component public function manualCleanup() { try { - DockerCleanupJob::dispatch($this->server, true); + DockerCleanupJob::dispatch($this->server, true, $this->deleteUnusedVolumes, $this->deleteUnusedNetworks); $this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.'); } catch (\Throwable $e) { return handleError($e, $this); From 0998be35036a69110e44ee5fa6ef13d9b0389889 Mon Sep 17 00:00:00 2001 From: Lucas Castelo Date: Mon, 4 Aug 2025 21:22:07 +0200 Subject: [PATCH 066/202] feat(services): add Chroma service (#6201) --- public/svgs/chroma.svg | 13 +++++++++++++ templates/compose/chroma.yaml | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 public/svgs/chroma.svg create mode 100644 templates/compose/chroma.yaml diff --git a/public/svgs/chroma.svg b/public/svgs/chroma.svg new file mode 100644 index 000000000..930288fbf --- /dev/null +++ b/public/svgs/chroma.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/templates/compose/chroma.yaml b/templates/compose/chroma.yaml new file mode 100644 index 000000000..11029113b --- /dev/null +++ b/templates/compose/chroma.yaml @@ -0,0 +1,20 @@ +# documentation: https://cookbook.chromadb.dev/ +# slogan: Chroma is the open-source search and retrieval database for AI applications. +# tags: ai,vector-database,semantic-search,machine-learning,bm25,embeddings,llm +# logo: svgs/chroma.svg +# port: 8000 + +services: + chromadb: + image: chromadb/chroma:1.0.15 + volumes: + - chroma-data:/data + environment: + - SERVICE_FQDN_CHROMA_8000 + - IS_PERSISTENT=TRUE + - PERSIST_DIRECTORY=/data + healthcheck: + test: [ "CMD", "/bin/bash", "-c", "cat < /dev/null > /dev/tcp/localhost/8000" ] + interval: 30s + timeout: 10s + retries: 3 From 1d97e3549452fed8d888f5786d8731a8e2ab4017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Andrade=20Guzm=C3=A1n?= Date: Mon, 4 Aug 2025 15:33:17 -0400 Subject: [PATCH 067/202] chore(service): add `NOT_SECURED` env to Postiz (#6243) --- templates/compose/postiz.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/postiz.yaml b/templates/compose/postiz.yaml index 2631e16fe..5e4fbcf2f 100644 --- a/templates/compose/postiz.yaml +++ b/templates/compose/postiz.yaml @@ -77,6 +77,7 @@ services: - NEXT_PUBLIC_POLOTNO=${NEXT_PUBLIC_POLOTNO} - IS_GENERAL=true - NX_ADD_PLUGINS=${NX_ADD_PLUGINS:-false} + - NOT_SECURED=${NOT_SECURED:-false} # Payment Settings - FEE_AMOUNT=${FEE_AMOUNT:-0.05} From af22514cad4e6355d4e7a646a296a84c78a0a898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Gon=C3=A7alves=20de=20Andrade?= <0vetor0@gmail.com> Date: Mon, 4 Aug 2025 16:36:38 -0300 Subject: [PATCH 068/202] chore(service): improve evolution-api environment variables (#6283) --- templates/compose/evolution-api.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/compose/evolution-api.yaml b/templates/compose/evolution-api.yaml index 87db576f9..ad5718e9d 100644 --- a/templates/compose/evolution-api.yaml +++ b/templates/compose/evolution-api.yaml @@ -105,10 +105,11 @@ services: - WEBHOOK_EVENTS_ERRORS_WEBHOOK=${WEBHOOK_EVENTS_ERRORS_WEBHOOK:-} - 'CONFIG_SESSION_PHONE_CLIENT=${CONFIG_SESSION_PHONE_CLIENT:-Evolution API V2}' - CONFIG_SESSION_PHONE_NAME=${CONFIG_SESSION_PHONE_NAME:-Chrome} - - CONFIG_SESSION_PHONE_VERSION=${CONFIG_SESSION_PHONE_VERSION:-2.3000.1020885143} - QRCODE_LIMIT=${QRCODE_LIMIT:-30} - OPENAI_ENABLED=${OPENAI_ENABLED:-true} - DIFY_ENABLED=${DIFY_ENABLED:-true} + - FLOWISE_ENABLED=${FLOWISE_ENABLED:-true} + - N8N_ENABLED=${N8N_ENABLED:-true} - TYPEBOT_ENABLED=${TYPEBOT_ENABLED:-true} - TYPEBOT_API_VERSION=${TYPEBOT_API_VERSION:-latest} - CHATWOOT_ENABLED=${CHATWOOT_ENABLED:-true} From c483f1bdd7d2fb2a6a4197c08f5292cc0e26fa78 Mon Sep 17 00:00:00 2001 From: Slawa Gladkov Date: Mon, 4 Aug 2025 21:48:48 +0200 Subject: [PATCH 069/202] chore(service): update Langfuse template to v3 (#6301) --- public/svgs/langfuse.png | Bin 9788 -> 0 bytes public/svgs/langfuse.svg | 9 ++ templates/compose/langfuse.yaml | 151 +++++++++++++++++++++++++++----- 3 files changed, 136 insertions(+), 24 deletions(-) delete mode 100644 public/svgs/langfuse.png create mode 100644 public/svgs/langfuse.svg diff --git a/public/svgs/langfuse.png b/public/svgs/langfuse.png deleted file mode 100644 index 8dec0fe4ae1dd9c91ea9e8e0d6ed89227fef4143..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9788 zcmd_wAZW3k3)?E008jSRF(8z`rrR+SQsz0+N35A0ANQ`Q<67; z@IC#hYV4cQOJVjW z=+2CTZLHv@3x3hp2Vr z-53A{fC-BAf5EkWGC{Z~IR~wS-b_spN7U%U<0LkTXagX4-OYgU1$tb- z16jB35xt=ZhbU;t7?5N3KRITLU()PUU)H{XuWN!E5CO3ERm`gI@iI>YLC@&m z+P#l=WRJ>>eM&KYT~u{Dq;pFh*z0S6V_a1pO)5AFNMbjVB3$A##fYVRjee4S$@{W3+^?4$Yz6pafw`BEI^P23RoW1HhRA$`Z771WZ6u5&SI!bVYLX zSta>!1tRnf!rv&$IjHw#G17bk-tZobp#g~@dFjglN|Ts7vLM-*)sjkn0^vtjROItd zLXJ>&xooh|%v6K?g$bqhQb2}D&RA{V*ON8yb+y|Gi*wEy9bg34Ti2(!7Ux5agS+g) zYL9Moh&HuE{H*8bq$r+In>d{k=KuwKz`QvQ&OgJ0r(QqWT)*gzY;@f8T5>%(f@npCbxIjY{QPjA&4ywS%-jr&|(B0`;Vd(k>k|j3So`zx85v3bG+|r&){On%mDok1P8jHl3TSS0b0H`5mw-O!s)0j3C zyN5I_tpm>C?Ji)qPi=?r9sla`*WPCI;Zr^lrebww&>p9FNGieAao0P%Wyugznh8|c z2zUHKR>Ti3Ze+`ukejjIN*|=*9cP%_PU+Xlcqz=qR6J>cjb+01gHS0r#~c8+Axv0* z8mVN+Ht4XCAW9I?Srg+yL+$>hmxe>5V4{O)P>?bL^6^%_oHqWq+OJ`XKefstp_Es_ zvQe40jSVHSL=Qh3fnSVM|I4WOOj?tuWz7Ukjwjc1`q2kEK&2Y8Kq-s3rgkN=j7O2# zeZ_VNpDL%bSHXLzgwl#C3(f7tUN(oKdtE|io0q%~1gVCesqX$wMEE%RD04D|{Eqqs zgr@CV*h)#@bu$8fk%S(*L58Sb(@0Pl8*OS#A$dY|R(V_wrIg-vp?v6eI5`#uQ~|+8 z-&;Zi4S)l2A&`*tK`5-gkxYeOpxjygG+zl7mxj7-dTW3=bdcq4Kf54KihTyb4OCa= zWj+tYQR32TkHFRUAq_f-66MNquL#K=R(E%LgAna?c~H3b_L{Vb%)9g3oBjn%Z1geU z)~sm>zo)Rx?SmRj^z@&8`zhq#Qe12dfr!l$ysLHV-qsp(P>l*Lo3zVG*bB)nFu2P8 z?apCXR1i0o(MN|MAA}fg)2q%o<V|GNqUIHr;jn6hW7pC+OPWg0TI{71@Z~_K?ENI5| zyJR-0q(G_06pF8J&;NmdfhY!ak6>*vanO6G@}_BG3#56Y4EU>W(zt*EIKb3#K7D6I zVyTCGOv}W^zUCFPVUf*f7v4d^X`zKV)4G$2cL%QDGzo~I6DK2c%HFx|{x&jrL_=Mm zI2}T#8;fd_;kKGtA9A$#mn4D5AVGw!jGB>Z*(|F4P&>GVcQ+>Uyl?upAadE;3HLDv4i=^07zQV zj>$%;WLQXgjG?-*N8!Dyx(29)HE8EevL%7Fj6hj#UcTw*IAMBKR^9UeZsd zKUmAULllR;H#om|N33fD<8jek{WLT`KT$fI(|5Hlt>Rty4szg7s4#Ce1}dTPzgPBR>LKCm;Eh)%{okTklV!w_vmbww4VlroHO(<2WU5-m%|)QqZa7qADqSPc|)zuP;K57F~=oLaEF`)nVt%k6=So2EfS0* zc)f(Rfy+UlYd9*u!?;qII&smWGpx@=Ldq!6=e zRi0b?W!~B!*Y&cC#zngCNn)T?3E3bUeXQWd%x2ek@(K6mUnx(S3!_6c;RtD?MX?&^ zPlKOcJMC2u6~tu@DHG4JGQ`1IP7k%$kImCp#VrlN8m6z`YY^9yv!F8nK*W~Z#;sRyPmyhc8GTqb} zqEDEA)zX*o^&M=}+HQ6eA=F>rJ?U8M2`(8O|E~4*g@AP%e zrtl7){dwg+;XA|G4*uMgV-TplkeHBlv^jT5oywy|AQj zKXC8CgiQE+uXAy??Z#G-#mIM%ruY*X^z~x}$DI8n1dT-deG>5>e;=Miu7oH3K=PcFM%){0hRUr`tO^CFSX)vb`wSQLXCN4g{$#GP( z;U{)xq31T&G%PIY7DsWCm_SssmN%UzLQL^b!(2-{c>}vuv};gqJ=6g1HMow0@ozdP z{pyN8{jg)UOBpLLqc(5{-&6#=V9>E$E0j|_{+>WXbEUheMtq;>rB#sPYk`ay4_4a@RfWP4f@(z@4xc+}la2J{%;v#1x1jYmE0$L|4 zDPh>^_46X9Ct*>>Je9)Y2BE(T$#HDjROY{km;UYQ5G}w#L%TX{2)^xTlW=xVNx3a{ z#)9Iy@tXW|5gTuAv3|NvD2S76Aml{{yBY~jngYJ2f0EN9BChndV+|q2r!(yQVELg1 zVH@>v1AV#|_j}Y1fxAYNBp16DuADU00C>-LKw5F=`h7uct;X@) zc+JF|mn)P&2TO%ReGh75H%$QF4m}cgp?PlNsd8EK-%PAfgpF9yNG3dMU5$CuxeDU*UhhC#k>kW^L4EAbjxa&>8yYYQ~Ob59hMX8bxHDfdSo@ z3RFjJpY(o^MO_?ImH@hN*K-Vnz;>W6n47Xc&6wOc_sNKg_}hp z_5u>(o+UbG_4;%@KA_mU#Z8#h>=XvP$8718TQNO?|61CUpiwYCOixB+7bf8!Va(_e zhS|6twh{_f*^y`DH>dE1Jn0-i5fTR;erD7ouuWx$Tb}a{lD?9QVON|t%ykPZ>!|un zeGnue)~#gSbk?*gGKEE5i{`SBUd2DI`GWve2z^X}OdKc4GrS4FN3k&V3v1n@_hERC z&qT6w!RvPJm9B0(s{()yD4{k^TX|%%I{N-2OR0+b)L{dxLQ#sXVwPCAHG9SFEaj4m zUggUrB7|8pI9ByhK#GvPht$rfFUKJak@A6YqKb^I?-;eN=-cih=crKgY^3I%M}Nl0 z&&IHKP`&zh6_&Bbn!CrJ|Z_P%oL0EUXd2JT4)Ai=`;4}$qFpJQGKs| z+F=S#C`iY*Wp=_2vRA@C^{;zNM$%^dKu_#w{sWcjb#g!gQ4|z2#w`B!RY59Vn9LI} zaX3lG4=WYU_hP2nr6NH7zi4mNM75=#(?)D8MK*Bbn-S?GqP@}f9e4@9; zG^E=_p|nMp9N$=Ko_{|_;4yd~ol>4~3J!XOww=Ruo!sr96eMHW z1GHQ=IIPk-U^H(*J95MXuO<_Aw4%Oj;$QimyoK>hS>Br)G$vfuM_^Tu1#;%vSS^S) z=}x|!iuf=ycaump!?NlE|268@Z&4?;kEn_>g$#V`3!jwGZECqC@7>&Dz-M7MOCmAV zYR>rdlqZ%OJ6f1}$p?y|ouQ-hfhq7!4%YuoUrpypns2BP*2y9Ka%R^V4NS#a3-l&b zAJEtgFf~JIj7;AS5?Iiy`P;#c2T~Ow{tccSse=)(6>$dC zFz)+|%Ay(k@2IqreAC6PhSGcz24&OIB5?&EWrDs8e6Vq%?-EA$6O`p;4$vOn&Cz=e zPaL$oWn6mfuhmOo zoQW4&CpeJga*GMYViH!fB5f+_2!boC3A^@uHBD{Y&LX-ZW(V>;!0SV=PG!L6iyy`={8 zgCoa$@AGxu(xZ9xPwBi3Kvnu9m7i_84RLuo{+<8wZ{op^0+Q~Ygnvw|dfN}yok7sI zw-^li!CAZyY^reFNnTmo6a_7rGjp-_-KEWU`=c3YekC}QgM?-ecYp`_{U zw3245Yn0W{B;6HjHhdq)bHh86g&myJE(z0W-*AQyU$xmoKF;$vCuyF1%TpWE4+s4v zo~U}hHxr3=8-Q}0upuTJ7EanAtTvlZN+H)gZ&D2u^UvNmY-uYX9(N(QiSb0^i6Nxk zq>biS=o%GVf?Ne@;aW8@<(LP5Bf7-4Mg&Up$bYx`p?dLv5c#1^XFXS8y|Kq9fDDXv z>Jyy<4y``?k`k)5n92dWhM7j>E`-~%{&4w8+6=va8inW{0F=@SM221Vn}*(?Z$SUC zH2wFYg=oWZABjTXJlwfMY3x}A+MqXy9@juSd+yLd`D_@V_=EM&;!K}v@1zN{fBc)5NfbG}1dnfr;B7|017J|SQ*9tGd9AHe?& z8^NHe@_;)E#kkF1h*;Wm(uG-ZKKL{#~a&{7{%y>E&?1GS0lMq5elHh%Q3-ryvb?V=rIW<^Y)G-v3G;A^vM(ynJp(!%`a zx~$o*`3Ul+_-4IvBEy{(U$C$$3}7_Jj9+=_T$H|_oq;-IC^q+($pSGcT_Vrmn)wh7z=F@1$4FSV}y01cFHh6}H zIplHH>07?L+Ips+=!bW|P;PPtin0;e+1ulWr4od7?qKJY!O**3d(yF>K5c1Fip=O! zy8ZZ>+mEmbyrEV7!^k0D`yUn-bOxd~E^$Z5g1(nv|D8Z)B6c-6I>ZL2hbej(jpc_UHNPpb3iQzZ z{>l&YBEq<4odjFbI6T9_J9kF<`7OJa=-3At`_6SSVlFvZG0Qp9eehv-a z8#2+%vDNs{tT8FEXt9&PY=zUm+DNt&^PKYZxIc`e zIjX;+z<*g2V{X0$+^H-CQ5-XfA;=l_% znXR#m633e_d49zF>?KlU_jyXFZVS29)U1qXLuZ$BpN>5F=%M6mFKh1BpNlt2XcrFS zIKzzYh!&*E_yP>rcwCgTb*L;kFzK$Prj~%{xiAl&r?HB-)ysw-uSS2CL2`>^(_&6! zuv<3rbT}@kdR{RYpbbxQPO30qUM41xA3dlSg0HGJl+{GpMo35+P9@unODK zn)8L_7r*fr@_<2+B|+^ghyCTLnAQ^Jmft!LY(V!in(UEfRNswBe^4<^sasO&(FM>h z??@NmyTqWjwDHlaW|T4E)aovfC%{k4%sa`o1Cu6-KP%6M7DviHD#9d_Z}En)k^Y9{ z31>Vi)bAaa=s)HQSEmH^%pA~ha!5r)MC($sf{+N_5-km(!5VUoML=ZyXVNsdd) zTphgK5IfpTvrwyYvr{_3DuGfg+PwbEPa{2PMDmrt1oB&?{%`Z`*=F=R>OO@lM&$*b zX(hB;mF!Km>0Y5q82i8?%L|>+UEiMx@zl}>WxuGQ`2^STdIoq-I`jtWcG}8dKNOAR z1L2k7et91BCgg4A8Lh1xvNALGanKReF<_@?GPCLQqCA{U*uI&?Bn^38!G-(%nNzj- z4Z*Pn>kZCO=h4GPT2|C#g4DNKzQYk6xQgwm_B3fP^RE*^Kn|vL^mRjn2!2a?dXwar zzb73gQsWMnMgAkhWpHM_jhUZT1jdFBMbHk1A(j^);-uHkfGmpmIA!MYX_sdL9?NhG z2y#jOlJ5{^IVf!oAU2vWGrp@vU-R1>vqm(qB*n`6Goh2*W$%cc zC~xjF^!jeaJG)Eqo28*MH%J&s5{x@JC>5F0uDBW7u+L{sVY$|F44oUMfu;q7gyh=& z<`_>en(B zoZ(GZoc#<>7|RwXK7_MT%0}nPgBM?vC=>`t|Deak6-w6?fD8;ze{Fw+6jqSfzl{}h z!}hZ7e6nZ#zML_Nxjf=d^M&!L->~3b9P(uiZGF&NCJ%rNNV?;AWS|%4<*9O8k0&Gp zHK}p1Q}{XT{56LtZ1GS;Ni{B*UPPK$`dEG%@-!UCCEuo+)EKLFo|_lr`{cZdsMSx5 zHha;w9nKJ6mhtiHM94;9kdlh+eIU+sh493*4j{qTgT$D$x9@;KXqv(>pi4vcWxJk0 z$-qq7NaWzEYB||>H7zZ$bRd*;+ri@L{%|o9f2zs4{ZTsph?C;Lg0g{N<|73K6Ibk3 zO^}$KD}-_m|Asoh<*CO{i7*fVR-=>%E>j)xwZt4cA(~TJ<>=2AB>+4lglJ|~p1iLV z{ggjcKuS{1VYZK_#WQlQvLz6Z&DpF=rv)o->So&xMMriDD6aR`fpa@WwOH94Vj2Y1ZD`NE9Jb;Kw}d1I zp7sn)MqD*sXAvEufeC_s_(ASoG1Kf?>kc6J!&RA$%Q)n`J$_+;bHFFk%&P4X;jgZxZE*{A5 z#af=ez$91y$TR}G(*ghZN>A9xBs7hPJC$|fBehX(QUta@hGFaKy0S%Ws^Ndv+aPm| z$m}_z!{3LZ8Ldd+jRQ^TqR0%*_DOSCej#2S(B(|w_=XYPl3BAFWF_*k+6?#w@$<0H zi=;B$=nx#<+dfdN}bx7*~OSh zmX8jO;keI;LgQ*AgjE3Lj z%Be8)%+N?GF)mEZ<82SXC!|^;sMTIzn&wlsM$cyz7^c`AIiF}a;*njjaA|kX2~|zd zpmErW9QvmqnTWO)k>SMZ@dllIscG8%W-&3HCx##HG87vEHeF?-#goUgEP^7(DZ{xN ziJVf!u}-!u0tQkKBD=T}$wHMfh6fhV-t)5zR19!tMb}ph8{@vDbH3ukyg_KHeazZ+ z+x!7+eqBQ9)skOxT-=*VTww>2Nh(6V`npM(@U$w@+k`?zFk4^` zfvVPHHamfKlLc`nXd8))?5I=9(8^^8{|>`9vqA-n2VtJ)MoHls0{!TDhtV~G;4%je z36gkj->^8qTx1*H36{JF8FDcu;)$7vzfXyLAqC(sL|8B7Jo$(k^aCLEeP|W>IyH)S z`}lJhgvPhsT+HFFNUozU??Z*dM|%K>q}xQ$eCbvBoOfrLN0$QZlko-Rd6Deo8v18} z{;;T0tOIl)?{b_s5FBunOPY}iTPCi|3%q&Mg;(#pBHkjZi|vh(q3ML0o0V= KDb*-ghx{KZU0w?S diff --git a/public/svgs/langfuse.svg b/public/svgs/langfuse.svg new file mode 100644 index 000000000..b04e07490 --- /dev/null +++ b/public/svgs/langfuse.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/templates/compose/langfuse.yaml b/templates/compose/langfuse.yaml index 125a85351..5797dd4a1 100644 --- a/templates/compose/langfuse.yaml +++ b/templates/compose/langfuse.yaml @@ -1,30 +1,83 @@ # documentation: https://langfuse.com/docs # slogan: Langfuse is an open-source LLM engineering platform that helps teams collaboratively debug, analyze, and iterate on their LLM applications. # tags: ai, qdrant, weaviate, langchain, openai, gpt, llm, lmops, langfuse, llmops, tracing, observation, metrics -# logo: svgs/langfuse.png +# logo: svgs/langfuse.svg # port: 3000 +x-app-env: &app-env + - NEXTAUTH_URL=${SERVICE_FQDN_LANGFUSE} + - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse} + - SALT=${SERVICE_PASSWORD_SALT} + - ENCRYPTION_KEY=${SERVICE_PASSWORD_64_LANGFUSE} + - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true} + - LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false} + - CLICKHOUSE_MIGRATION_URL=clickhouse://clickhouse:9000 + - CLICKHOUSE_URL=http://clickhouse:8123 + - CLICKHOUSE_USER=clickhouse + - CLICKHOUSE_PASSWORD=$SERVICE_PASSWORD_CLICKHOUSE + - CLICKHOUSE_CLUSTER_ENABLED=false + - LANGFUSE_USE_AZURE_BLOB=false + - LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse + - LANGFUSE_S3_EVENT_UPLOAD_REGION=auto + - LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio + - LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO + - LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://minio:9000 + - LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true + - LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/ + - LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse + - LANGFUSE_S3_MEDIA_UPLOAD_REGION=auto + - LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio + - LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO + - LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090 + - LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true + - LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/ + - LANGFUSE_S3_BATCH_EXPORT_ENABLED=false + - LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse + - LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/ + - LANGFUSE_S3_BATCH_EXPORT_REGION=auto + - LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://minio:9000 + - LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT=http://localhost:9090 + - LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio + - LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO + - LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true + - LANGFUSE_INGESTION_QUEUE_DELAY_MS=1 + - LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=1000 + - REDIS_HOST=redis + - REDIS_PORT=6379 + - REDIS_AUTH=$SERVICE_PASSWORD_REDIS + - REDIS_TLS_ENABLED=false + - REDIS_TLS_CA=/certs/ca.crt + - REDIS_TLS_CERT=/certs/redis.crt + - REDIS_TLS_KEY=/certs/redis.key + - EMAIL_FROM_ADDRESS= + - SMTP_CONNECTION_URL= + - NEXTAUTH_SECRET=${SERVICE_BASE64_NEXTAUTHSECRET} # + - AUTH_DISABLE_SIGNUP=${AUTH_DISABLE_SIGNUP:-true} + - HOSTNAME=${HOSTNAME:-0.0.0.0} + - LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-my-org} + - LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-My Org} + - LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-my-project} + - LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-My Project} + - LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-admin@example.com} + - LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-Admin} + - LANGFUSE_INIT_USER_PASSWORD=${SERVICE_PASSWORD_LANGFUSE} + services: langfuse: - image: langfuse/langfuse:2 + image: docker.io/langfuse/langfuse:3 + restart: always + depends_on: &langfuse-depends-on + postgres: + condition: service_healthy + minio: + condition: service_healthy + redis: + condition: service_healthy + clickhouse: + condition: service_healthy environment: - - SERVICE_FQDN_LANGFUSE_3000 - - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse} - - DIRECT_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse} - - SALT=$SERVICE_PASSWORD_SALT - - AUTH_DISABLE_SIGNUP=${AUTH_DISABLE_SIGNUP:-false} - - NEXTAUTH_URL=$SERVICE_FQDN_LANGFUSE_3000 - - NEXTAUTH_SECRET=${SERVICE_BASE64_64_NEXTAUTHSECRET} - - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false} - - LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false} - - HOSTNAME=${HOSTNAME:-0.0.0.0} - - LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-Admin} - - LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-admin@example.com} - - LANGFUSE_INIT_USER_PASSWORD=${SERVICE_PASSWORD_LANGFUSE} - - LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-my-org} - - LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-My Org} - - LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-my-project} - - LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-My Project} + <<: *app-env + SERVICE_FQDN_LANGFUSE_3000: ${SERVICE_FQDN_LANGFUSE_3000} healthcheck: test: - CMD @@ -35,17 +88,21 @@ services: interval: 5s timeout: 5s retries: 3 - depends_on: - postgres: - condition: service_healthy + langfuse-worker: + image: langfuse/langfuse-worker:3 + restart: always + environment: + <<: *app-env + depends_on: *langfuse-depends-on postgres: - image: "postgres:16-alpine" + image: postgres:17-alpine + restart: always environment: - POSTGRES_DB=${POSTGRES_DB:-langfuse} - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES - POSTGRES_USER=$SERVICE_USER_POSTGRES volumes: - - "pg-data:/var/lib/postgresql/data" + - langfuse_postgres_data:/var/lib/postgresql/data healthcheck: test: - CMD-SHELL @@ -53,3 +110,49 @@ services: interval: 5s timeout: 5s retries: 10 + redis: + image: redis:8 + restart: always + command: ["sh", "-c", "redis-server --requirepass \"$SERVICE_PASSWORD_REDIS\""] + volumes: + - langfuse_redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "-a", "$SERVICE_PASSWORD_REDIS", "PING"] + interval: 3s + timeout: 10s + retries: 10 + minio: + image: minio/minio:latest + restart: always + entrypoint: sh + # create the 'langfuse' bucket before starting the service + command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data' + environment: + - MINIO_ROOT_USER=minio + - MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO + - MINIO_BROWSER=off + volumes: + - langfuse_minio_data:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 1s + timeout: 5s + retries: 5 + start_period: 1s + clickhouse: + image: clickhouse/clickhouse-server:latest + restart: always + user: "101:101" + environment: + - CLICKHOUSE_DB=default + - CLICKHOUSE_USER=clickhouse + - CLICKHOUSE_PASSWORD=$SERVICE_PASSWORD_CLICKHOUSE + volumes: + - langfuse_clickhouse_data:/var/lib/clickhouse + - langfuse_clickhouse_logs:/var/log/clickhouse-server + healthcheck: + test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 + interval: 5s + timeout: 5s + retries: 10 + start_period: 1s From dcf3b43b30589e855cf5d5296ea18454fef28773 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:10:47 +0200 Subject: [PATCH 070/202] refactor(service): improve langfuse - remove minio - adjust ENVs - autogenerate more stuff - formatting --- templates/compose/langfuse.yaml | 120 +++++++++++++------------------- 1 file changed, 48 insertions(+), 72 deletions(-) diff --git a/templates/compose/langfuse.yaml b/templates/compose/langfuse.yaml index 5797dd4a1..ddfa8b944 100644 --- a/templates/compose/langfuse.yaml +++ b/templates/compose/langfuse.yaml @@ -6,52 +6,48 @@ x-app-env: &app-env - NEXTAUTH_URL=${SERVICE_FQDN_LANGFUSE} - - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse} + - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse-db} - SALT=${SERVICE_PASSWORD_SALT} - ENCRYPTION_KEY=${SERVICE_PASSWORD_64_LANGFUSE} - - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true} + - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false} - LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false} - CLICKHOUSE_MIGRATION_URL=clickhouse://clickhouse:9000 - CLICKHOUSE_URL=http://clickhouse:8123 - - CLICKHOUSE_USER=clickhouse - - CLICKHOUSE_PASSWORD=$SERVICE_PASSWORD_CLICKHOUSE + - CLICKHOUSE_USER=${SERVICE_USER_CLICKHOUSE} + - CLICKHOUSE_PASSWORD=${SERVICE_PASSWORD_CLICKHOUSE} - CLICKHOUSE_CLUSTER_ENABLED=false - - LANGFUSE_USE_AZURE_BLOB=false - - LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse - - LANGFUSE_S3_EVENT_UPLOAD_REGION=auto - - LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio - - LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO - - LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://minio:9000 - - LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true - - LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/ - - LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse - - LANGFUSE_S3_MEDIA_UPLOAD_REGION=auto - - LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio - - LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO - - LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090 - - LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true - - LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/ - - LANGFUSE_S3_BATCH_EXPORT_ENABLED=false - - LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse - - LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/ - - LANGFUSE_S3_BATCH_EXPORT_REGION=auto - - LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://minio:9000 - - LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT=http://localhost:9090 - - LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio - - LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO - - LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true - - LANGFUSE_INGESTION_QUEUE_DELAY_MS=1 - - LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=1000 + - LANGFUSE_USE_AZURE_BLOB=${LANGFUSE_USE_AZURE_BLOB:-false} + - LANGFUSE_S3_EVENT_UPLOAD_BUCKET=${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse} + - LANGFUSE_S3_EVENT_UPLOAD_REGION=${LANGFUSE_S3_EVENT_UPLOAD_REGION:-auto} + - LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID} + - LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY} + - LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT} + - LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true} + - LANGFUSE_S3_EVENT_UPLOAD_PREFIX=${LANGFUSE_S3_EVENT_UPLOAD_PREFIX:-events/} + - LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=${LANGFUSE_S3_MEDIA_UPLOAD_BUCKET:-langfuse} + - LANGFUSE_S3_MEDIA_UPLOAD_REGION=${LANGFUSE_S3_MEDIA_UPLOAD_REGION:-auto} + - LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=${LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID} + - LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=${LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY} + - LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT} + - LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=${LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE:-true} + - LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=${LANGFUSE_S3_MEDIA_UPLOAD_PREFIX:-media/} + - LANGFUSE_S3_BATCH_EXPORT_ENABLED=${LANGFUSE_S3_BATCH_EXPORT_ENABLED:-false} + - LANGFUSE_S3_BATCH_EXPORT_BUCKET=${LANGFUSE_S3_BATCH_EXPORT_BUCKET:-langfuse} + - LANGFUSE_S3_BATCH_EXPORT_PREFIX=${LANGFUSE_S3_BATCH_EXPORT_PREFIX:-exports/} + - LANGFUSE_S3_BATCH_EXPORT_REGION=${LANGFUSE_S3_BATCH_EXPORT_REGION:-auto} + - LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=${LANGFUSE_S3_BATCH_EXPORT_ENDPOINT} + - LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT=${LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT} + - LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=${LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID} + - LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=${LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY} + - LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=${LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE:-true} + - LANGFUSE_INGESTION_QUEUE_DELAY_MS=${LANGFUSE_INGESTION_QUEUE_DELAY_MS:-1} + - LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=${LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS:-1000} - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_AUTH=$SERVICE_PASSWORD_REDIS - - REDIS_TLS_ENABLED=false - - REDIS_TLS_CA=/certs/ca.crt - - REDIS_TLS_CERT=/certs/redis.crt - - REDIS_TLS_KEY=/certs/redis.key - - EMAIL_FROM_ADDRESS= - - SMTP_CONNECTION_URL= - - NEXTAUTH_SECRET=${SERVICE_BASE64_NEXTAUTHSECRET} # + - REDIS_AUTH=${SERVICE_PASSWORD_REDIS} + - EMAIL_FROM_ADDRESS=${EMAIL_FROM_ADDRESS:-admin@example.com} + - SMTP_CONNECTION_URL=${SMTP_CONNECTION_URL:-} + - NEXTAUTH_SECRET=${SERVICE_BASE64_NEXTAUTHSECRET} - AUTH_DISABLE_SIGNUP=${AUTH_DISABLE_SIGNUP:-true} - HOSTNAME=${HOSTNAME:-0.0.0.0} - LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-my-org} @@ -59,18 +55,15 @@ x-app-env: &app-env - LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-my-project} - LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-My Project} - LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-admin@example.com} - - LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-Admin} + - LANGFUSE_INIT_USER_NAME=${SERVICE_USER_LANGFUSE} - LANGFUSE_INIT_USER_PASSWORD=${SERVICE_PASSWORD_LANGFUSE} services: langfuse: - image: docker.io/langfuse/langfuse:3 - restart: always + image: langfuse/langfuse:3 depends_on: &langfuse-depends-on postgres: condition: service_healthy - minio: - condition: service_healthy redis: condition: service_healthy clickhouse: @@ -88,19 +81,19 @@ services: interval: 5s timeout: 5s retries: 3 + langfuse-worker: image: langfuse/langfuse-worker:3 - restart: always environment: <<: *app-env depends_on: *langfuse-depends-on + postgres: image: postgres:17-alpine - restart: always environment: - - POSTGRES_DB=${POSTGRES_DB:-langfuse} - - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES - - POSTGRES_USER=$SERVICE_USER_POSTGRES + - POSTGRES_DB=${POSTGRES_DB:-langfuse-db} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - POSTGRES_USER=${SERVICE_USER_POSTGRES} volumes: - langfuse_postgres_data:/var/lib/postgresql/data healthcheck: @@ -110,10 +103,12 @@ services: interval: 5s timeout: 5s retries: 10 + redis: image: redis:8 - restart: always command: ["sh", "-c", "redis-server --requirepass \"$SERVICE_PASSWORD_REDIS\""] + environment: + - REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS} volumes: - langfuse_redis_data:/data healthcheck: @@ -121,32 +116,14 @@ services: interval: 3s timeout: 10s retries: 10 - minio: - image: minio/minio:latest - restart: always - entrypoint: sh - # create the 'langfuse' bucket before starting the service - command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data' - environment: - - MINIO_ROOT_USER=minio - - MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO - - MINIO_BROWSER=off - volumes: - - langfuse_minio_data:/data - healthcheck: - test: ["CMD", "mc", "ready", "local"] - interval: 1s - timeout: 5s - retries: 5 - start_period: 1s + clickhouse: image: clickhouse/clickhouse-server:latest - restart: always user: "101:101" environment: - - CLICKHOUSE_DB=default - - CLICKHOUSE_USER=clickhouse - - CLICKHOUSE_PASSWORD=$SERVICE_PASSWORD_CLICKHOUSE + - CLICKHOUSE_DB=${CLICKHOUSE_DB:-default} + - CLICKHOUSE_USER=${SERVICE_USER_CLICKHOUSE} + - CLICKHOUSE_PASSWORD=${SERVICE_PASSWORD_CLICKHOUSE} volumes: - langfuse_clickhouse_data:/var/lib/clickhouse - langfuse_clickhouse_logs:/var/log/clickhouse-server @@ -155,4 +132,3 @@ services: interval: 5s timeout: 5s retries: 10 - start_period: 1s From 36b2bb56c5969ebe63a695094a6c5e7697f2a007 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:11:09 +0200 Subject: [PATCH 071/202] Update service-templates.json --- templates/service-templates.json | 57 ++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/templates/service-templates.json b/templates/service-templates.json index 6aa4e1eb8..c037a8889 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -144,7 +144,7 @@ "authentik": { "documentation": "https://docs.goauthentik.io/docs/installation/docker-compose?utm_source=coolify.io", "slogan": "An open-source Identity Provider, focused on flexibility and versatility.", - "compose": "c2VydmljZXM6CiAgYXV0aGVudGlrLXNlcnZlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjYuM30nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogc2VydmVyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVVUSEVOVElLU0VSVkVSXzkwMDAKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEOi10cnVlfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL21lZGlhOi9tZWRpYScKICAgICAgLSAnLi9jdXN0b20tdGVtcGxhdGVzOi90ZW1wbGF0ZXMnCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgYXV0aGVudGlrLXdvcmtlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjYuM30nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogd29ya2VyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB1c2VyOiByb290CiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnLi9tZWRpYTovbWVkaWEnCiAgICAgIC0gJy4vY2VydHM6L2NlcnRzJwogICAgICAtICcuL2N1c3RvbS10ZW1wbGF0ZXM6L3RlbXBsYXRlcycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1kICQke1BPU1RHUkVTX0RCfSAtVSAkJHtQT1NUR1JFU19VU0VSfScKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAnYXV0aGVudGlrLWRiOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gUE9TVEdSRVNfREI9YXV0aGVudGlrCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGNvbW1hbmQ6ICctLXNhdmUgNjAgMSAtLWxvZ2xldmVsIHdhcm5pbmcnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdyZWRpcy1jbGkgcGluZyB8IGdyZXAgUE9ORycKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXM6L2RhdGEnCg==", + "compose": "c2VydmljZXM6CiAgYXV0aGVudGlrLXNlcnZlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjYuNH0nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogc2VydmVyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVVUSEVOVElLU0VSVkVSXzkwMDAKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEOi10cnVlfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL21lZGlhOi9tZWRpYScKICAgICAgLSAnLi9jdXN0b20tdGVtcGxhdGVzOi90ZW1wbGF0ZXMnCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgYXV0aGVudGlrLXdvcmtlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjYuNH0nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogd29ya2VyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB1c2VyOiByb290CiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnLi9tZWRpYTovbWVkaWEnCiAgICAgIC0gJy4vY2VydHM6L2NlcnRzJwogICAgICAtICcuL2N1c3RvbS10ZW1wbGF0ZXM6L3RlbXBsYXRlcycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1kICQke1BPU1RHUkVTX0RCfSAtVSAkJHtQT1NUR1JFU19VU0VSfScKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAnYXV0aGVudGlrLWRiOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gUE9TVEdSRVNfREI9YXV0aGVudGlrCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGNvbW1hbmQ6ICctLXNhdmUgNjAgMSAtLWxvZ2xldmVsIHdhcm5pbmcnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdyZWRpcy1jbGkgcGluZyB8IGdyZXAgUE9ORycKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXM6L2RhdGEnCg==", "tags": [ "identity", "login", @@ -402,6 +402,23 @@ "minversion": "0.0.0", "port": "80" }, + "chroma": { + "documentation": "https://cookbook.chromadb.dev/?utm_source=coolify.io", + "slogan": "Chroma is the open-source search and retrieval database for AI applications.", + "compose": "c2VydmljZXM6CiAgY2hyb21hZGI6CiAgICBpbWFnZTogJ2Nocm9tYWRiL2Nocm9tYToxLjAuMTUnCiAgICB2b2x1bWVzOgogICAgICAtICdjaHJvbWEtZGF0YTovZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9DSFJPTUFfODAwMAogICAgICAtIElTX1BFUlNJU1RFTlQ9VFJVRQogICAgICAtIFBFUlNJU1RfRElSRUNUT1JZPS9kYXRhCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gL2Jpbi9iYXNoCiAgICAgICAgLSAnLWMnCiAgICAgICAgLSAnY2F0IDwgL2Rldi9udWxsID4gL2Rldi90Y3AvbG9jYWxob3N0LzgwMDAnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMK", + "tags": [ + "ai", + "vector-database", + "semantic-search", + "machine-learning", + "bm25", + "embeddings", + "llm" + ], + "logo": "svgs/chroma.svg", + "minversion": "0.0.0", + "port": "8000" + }, "classicpress-with-mariadb": { "documentation": "https://www.classicpress.net/?utm_source=coolify.io", "slogan": "A lightweight, stable, instantly familiar free open-source content management system, based on WordPress without the block editor (Gutenberg).", @@ -831,7 +848,7 @@ "evolution-api": { "documentation": "https://doc.evolution-api.com/v1/pt/get-started/introduction?utm_source=coolify.io", "slogan": "Evolution API Installation with Postgres and Redis", - "compose": "dmVyc2lvbjogJzMuOCcKc2VydmljZXM6CiAgYXBpOgogICAgaW1hZ2U6ICdldm9hcGljbG91ZC9ldm9sdXRpb24tYXBpOmxhdGVzdCcKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgICAtIHBvc3RncmVzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRVZPXzgwODAKICAgICAgLSBTRVJWRVJfVVJMPSRTRVJWSUNFX0ZRRE5fRVZPCiAgICAgIC0gJ0RCX1RZUEU9JHtEQl9UWVBFOi1wb3N0Z3Jlc2RifScKICAgICAgLSAnREJfUE9TVEdSRVNEQl9EQVRBQkFTRT0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0RCX1BPU1RHUkVTREJfSE9TVD0ke0RCX1BPU1RHUkVTREJfSE9TVDotcG9zdGdyZXN9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1BPUlQ9JHtEQl9QT1NUR1JFU0RCX1BPUlQ6LTU0MzJ9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RBVEFCQVNFX1BST1ZJREVSPSR7REFUQUJBU0VfUFJPVklERVI6LXBvc3RncmVzcWx9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdEQVRBQkFTRV9DT05ORUNUSU9OX1VSST1wb3N0Z3Jlc3FsOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0RFTF9JTlNUQU5DRT0ke0RFTF9JTlNUQU5DRTotZmFsc2V9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX0RBVEFfSU5TVEFOQ0U9JHtEQVRBQkFTRV9TQVZFX0RBVEFfSU5TVEFOQ0U6LXRydWV9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX0RBVEFfTkVXX01FU1NBR0U9JHtEQVRBQkFTRV9TQVZFX0RBVEFfTkVXX01FU1NBR0U6LXRydWV9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX01FU1NBR0VfVVBEQVRFPSR7REFUQUJBU0VfU0FWRV9NRVNTQUdFX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9DT05UQUNUUz0ke0RBVEFCQVNFX1NBVkVfREFUQV9DT05UQUNUUzotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9DSEFUUz0ke0RBVEFCQVNFX1NBVkVfREFUQV9DSEFUUzotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9MQUJFTFM9JHtEQVRBQkFTRV9TQVZFX0RBVEFfTEFCRUxTOi10cnVlfScKICAgICAgLSAnREFUQUJBU0VfU0FWRV9EQVRBX0hJU1RPUklDPSR7REFUQUJBU0VfU0FWRV9EQVRBX0hJU1RPUklDOi10cnVlfScKICAgICAgLSAnREFUQUJBU0VfQ09OTkVDVElPTl9DTElFTlRfTkFNRT0ke0RBVEFCQVNFX0NPTk5FQ1RJT05fQ0xJRU5UX05BTUU6LWV2b2x1dGlvbl92Mn0nCiAgICAgIC0gJ1JBQkJJVE1RX0VOQUJMRUQ9JHtSQUJCSVRNUV9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX1VSST0ke1JBQkJJVE1RX1VSSTotYW1xcDovL2FkbWluOmFkbWluQHJhYmJpdG1xOjU2NzIvZGVmYXVsdH0nCiAgICAgIC0gJ1JBQkJJVE1RX0VYQ0hBTkdFX05BTUU9JHtSQUJCSVRNUV9FWENIQU5HRV9OQU1FOi1ldm9sdXRpb25fdjJ9JwogICAgICAtICdSQUJCSVRNUV9HTE9CQUxfRU5BQkxFRD0ke1JBQkJJVE1RX0dMT0JBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19BUFBMSUNBVElPTl9TVEFSVFVQPSR7UkFCQklUTVFfRVZFTlRTX0FQUExJQ0FUSU9OX1NUQVJUVVA6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0lOU1RBTkNFX0NSRUFURT0ke1JBQkJJVE1RX0VWRU5UU19JTlNUQU5DRV9DUkVBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0lOU1RBTkNFX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19JTlNUQU5DRV9ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1FSQ09ERV9VUERBVEVEPSR7UkFCQklUTVFfRVZFTlRTX1FSQ09ERV9VUERBVEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19TRVQ9JHtSQUJCSVRNUV9FVkVOVFNfTUVTU0FHRVNfU0VUOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19VUFNFUlQ9JHtSQUJCSVRNUV9FVkVOVFNfTUVTU0FHRVNfVVBTRVJUOi10cnVlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX0VESVRFRD0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19FRElURUQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1NFTkRfTUVTU0FHRT0ke1JBQkJJVE1RX0VWRU5UU19TRU5EX01FU1NBR0U6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1NFVD0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19TRVQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVD0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19VUFNFUlQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1BSRVNFTkNFX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19QUkVTRU5DRV9VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1NFVD0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19TRVQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1VQU0VSVD0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19VUFNFUlQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0dST1VQU19VUFNFUlQ9JHtSQUJCSVRNUV9FVkVOVFNfR1JPVVBTX1VQU0VSVDotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfR1JPVVBfVVBEQVRFPSR7UkFCQklUTVFfRVZFTlRTX0dST1VQX1VQREFURTotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfR1JPVVBfUEFSVElDSVBBTlRTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19HUk9VUF9QQVJUSUNJUEFOVFNfVVBEQVRFOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19DT05ORUNUSU9OX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DT05ORUNUSU9OX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19DQUxMPSR7UkFCQklUTVFfRVZFTlRTX0NBTEw6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1RZUEVCT1RfU1RBUlQ9JHtSQUJCSVRNUV9FVkVOVFNfVFlQRUJPVF9TVEFSVDotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfVFlQRUJPVF9DSEFOR0VfU1RBVFVTPSR7UkFCQklUTVFfRVZFTlRTX1RZUEVCT1RfQ0hBTkdFX1NUQVRVUzotZmFsc2V9JwogICAgICAtICdTUVNfRU5BQkxFRD0ke1NRU19FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1NRU19BQ0NFU1NfS0VZX0lEPSR7U1FTX0FDQ0VTU19LRVlfSUQ6LX0nCiAgICAgIC0gJ1NRU19TRUNSRVRfQUNDRVNTX0tFWT0ke1NRU19TRUNSRVRfQUNDRVNTX0tFWTotfScKICAgICAgLSAnU1FTX0FDQ09VTlRfSUQ9JHtTUVNfQUNDT1VOVF9JRDotfScKICAgICAgLSAnU1FTX1JFR0lPTj0ke1NRU19SRUdJT046LX0nCiAgICAgIC0gJ1dFQlNPQ0tFVF9FTkFCTEVEPSR7V0VCU09DS0VUX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnV0VCU09DS0VUX0dMT0JBTF9FVkVOVFM9JHtXRUJTT0NLRVRfR0xPQkFMX0VWRU5UUzotZmFsc2V9JwogICAgICAtICdXQV9CVVNJTkVTU19UT0tFTl9XRUJIT09LPSR7V0FfQlVTSU5FU1NfVE9LRU5fV0VCSE9PSzotZXZvbHV0aW9ufScKICAgICAgLSAnV0FfQlVTSU5FU1NfVVJMPSR7V0FfQlVTSU5FU1NfVVJMOi1odHRwczovL2dyYXBoLmZhY2Vib29rLmNvbX0nCiAgICAgIC0gJ1dBX0JVU0lORVNTX1ZFUlNJT049JHtXQV9CVVNJTkVTU19WRVJTSU9OOi12MjAuMH0nCiAgICAgIC0gJ1dBX0JVU0lORVNTX0xBTkdVQUdFPSR7V0FfQlVTSU5FU1NfTEFOR1VBR0U6LXB0X0JSfScKICAgICAgLSAiV0VCSE9PS19HTE9CQUxfVVJMPSR7V0VCSE9PS19HTE9CQUxfVVJMOi0nJ30iCiAgICAgIC0gJ1dFQkhPT0tfR0xPQkFMX0VOQUJMRUQ9JHtXRUJIT09LX0dMT0JBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1dFQkhPT0tfR0xPQkFMX1dFQkhPT0tfQllfRVZFTlRTPSR7V0VCSE9PS19HTE9CQUxfV0VCSE9PS19CWV9FVkVOVFM6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfQVBQTElDQVRJT05fU1RBUlRVUD0ke1dFQkhPT0tfRVZFTlRTX0FQUExJQ0FUSU9OX1NUQVJUVVA6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfUVJDT0RFX1VQREFURUQ9JHtXRUJIT09LX0VWRU5UU19RUkNPREVfVVBEQVRFRDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0VESVRFRD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0VESVRFRDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0RFTEVURT0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0RFTEVURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX1NFTkRfTUVTU0FHRT0ke1dFQkhPT0tfRVZFTlRTX1NFTkRfTUVTU0FHRTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX1BSRVNFTkNFX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX1BSRVNFTkNFX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX0RFTEVURT0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX0RFTEVURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0dST1VQU19VUFNFUlQ9JHtXRUJIT09LX0VWRU5UU19HUk9VUFNfVVBTRVJUOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfR1JPVVBTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0dST1VQU19VUERBVEU6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19HUk9VUF9QQVJUSUNJUEFOVFNfVVBEQVRFPSR7V0VCSE9PS19FVkVOVFNfR1JPVVBfUEFSVElDSVBBTlRTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTk5FQ1RJT05fVVBEQVRFPSR7V0VCSE9PS19FVkVOVFNfQ09OTkVDVElPTl9VUERBVEU6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19MQUJFTFNfRURJVD0ke1dFQkhPT0tfRVZFTlRTX0xBQkVMU19FRElUOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfTEFCRUxTX0FTU09DSUFUSU9OPSR7V0VCSE9PS19FVkVOVFNfTEFCRUxTX0FTU09DSUFUSU9OOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfQ0FMTD0ke1dFQkhPT0tfRVZFTlRTX0NBTEw6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19UWVBFQk9UX1NUQVJUPSR7V0VCSE9PS19FVkVOVFNfVFlQRUJPVF9TVEFSVDotZmFsc2V9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19UWVBFQk9UX0NIQU5HRV9TVEFUVVM9JHtXRUJIT09LX0VWRU5UU19UWVBFQk9UX0NIQU5HRV9TVEFUVVM6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfRVJST1JTPSR7V0VCSE9PS19FVkVOVFNfRVJST1JTOi1mYWxzZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0VSUk9SU19XRUJIT09LPSR7V0VCSE9PS19FVkVOVFNfRVJST1JTX1dFQkhPT0s6LX0nCiAgICAgIC0gJ0NPTkZJR19TRVNTSU9OX1BIT05FX0NMSUVOVD0ke0NPTkZJR19TRVNTSU9OX1BIT05FX0NMSUVOVDotRXZvbHV0aW9uIEFQSSBWMn0nCiAgICAgIC0gJ0NPTkZJR19TRVNTSU9OX1BIT05FX05BTUU9JHtDT05GSUdfU0VTU0lPTl9QSE9ORV9OQU1FOi1DaHJvbWV9JwogICAgICAtICdDT05GSUdfU0VTU0lPTl9QSE9ORV9WRVJTSU9OPSR7Q09ORklHX1NFU1NJT05fUEhPTkVfVkVSU0lPTjotMi4zMDAwLjEwMjA4ODUxNDN9JwogICAgICAtICdRUkNPREVfTElNSVQ9JHtRUkNPREVfTElNSVQ6LTMwfScKICAgICAgLSAnT1BFTkFJX0VOQUJMRUQ9JHtPUEVOQUlfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0RJRllfRU5BQkxFRD0ke0RJRllfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ1RZUEVCT1RfRU5BQkxFRD0ke1RZUEVCT1RfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ1RZUEVCT1RfQVBJX1ZFUlNJT049JHtUWVBFQk9UX0FQSV9WRVJTSU9OOi1sYXRlc3R9JwogICAgICAtICdDSEFUV09PVF9FTkFCTEVEPSR7Q0hBVFdPT1RfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0NIQVRXT09UX01FU1NBR0VfUkVBRD0ke0NIQVRXT09UX01FU1NBR0VfUkVBRDotdHJ1ZX0nCiAgICAgIC0gJ0NIQVRXT09UX01FU1NBR0VfREVMRVRFPSR7Q0hBVFdPT1RfTUVTU0FHRV9ERUxFVEU6LXRydWV9JwogICAgICAtICdDSEFUV09PVF9JTVBPUlRfREFUQUJBU0VfQ09OTkVDVElPTl9VUkk9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotY2hhdHdvb3R9JwogICAgICAtICdDSEFUV09PVF9JTVBPUlRfUExBQ0VIT0xERVJfTUVESUFfTUVTU0FHRT0ke0NIQVRXT09UX0lNUE9SVF9QTEFDRUhPTERFUl9NRURJQV9NRVNTQUdFOi10cnVlfScKICAgICAgLSAnQ0FDSEVfUkVESVNfRU5BQkxFRD0ke0NBQ0hFX1JFRElTX0VOQUJMRUQ6LXRydWV9JwogICAgICAtICdDQUNIRV9SRURJU19VUkk9JHtDQUNIRV9SRURJU19VUkk6LXJlZGlzOi8vcmVkaXM6NjM3OS82fScKICAgICAgLSAnQ0FDSEVfUkVESVNfUFJFRklYX0tFWT0ke0NBQ0hFX1JFRElTX1BSRUZJWF9LRVk6LWV2b2x1dGlvbl92Mn0nCiAgICAgIC0gJ0NBQ0hFX1JFRElTX1NBVkVfSU5TVEFOQ0VTPSR7Q0FDSEVfUkVESVNfU0FWRV9JTlNUQU5DRVM6LWZhbHNlfScKICAgICAgLSAnQ0FDSEVfTE9DQUxfRU5BQkxFRD0ke0NBQ0hFX0xPQ0FMX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnUzNfRU5BQkxFRD0ke1MzX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnUzNfQUNDRVNTX0tFWT0ke1MzX0FDQ0VTU19LRVk6LX0nCiAgICAgIC0gJ1MzX1NFQ1JFVF9LRVk9JHtTM19TRUNSRVRfS0VZOi19JwogICAgICAtICdTM19CVUNLRVQ9JHtTM19CVUNLRVQ6LWV2b2x1dGlvbn0nCiAgICAgIC0gJ1MzX1BPUlQ9JHtTM19QT1JUOi00NDN9JwogICAgICAtICdTM19SRUdJT049JHtTM19SRUdJT046LXVzLWVhc3QtMX0nCiAgICAgIC0gJ1MzX0VORFBPSU5UPSR7UzNfRU5EUE9JTlQ6LWZpbGVzLnNpdGUuY29tfScKICAgICAgLSAnUzNfVVNFX1NTTD0ke1MzX1VTRV9TU0w6LXRydWV9JwogICAgICAtICdBVVRIRU5USUNBVElPTl9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9BVVRIRU5USUNBVElPTkFQSUtFWX0nCiAgICAgIC0gJ0FVVEhFTlRJQ0FUSU9OX0VYUE9TRV9JTl9GRVRDSF9JTlNUQU5DRVM9JHtBVVRIRU5USUNBVElPTl9FWFBPU0VfSU5fRkVUQ0hfSU5TVEFOQ0VTOi10cnVlfScKICAgICAgLSAnTEFOR1VBR0U9JHtMQU5HVUFHRTotZW59JwogICAgdm9sdW1lczoKICAgICAgLSAnZXZvbHV0aW9uX2luc3RhbmNlczovZXZvbHV0aW9uL2luc3RhbmNlcycKICAgIGV4cG9zZToKICAgICAgLSA4MDgwCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmxhdGVzdCcKICAgIGNvbW1hbmQ6ICJyZWRpcy1zZXJ2ZXIgLS1wb3J0IDYzNzkgLS1hcHBlbmRvbmx5IHllc1xuIgogICAgcmVzdGFydDogYWx3YXlzCiAgICB2b2x1bWVzOgogICAgICAtICdldm9sdXRpb25fcmVkaXM6L2RhdGEnCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIGNvbW1hbmQ6CiAgICAgIC0gcG9zdGdyZXMKICAgICAgLSAnLWMnCiAgICAgIC0gbWF4X2Nvbm5lY3Rpb25zPTEwMDAKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdBVVRIRU5USUNBVElPTl9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9BVVRIRU5USUNBVElPTkFQSUtFWX0nCiAgICByZXN0YXJ0OiBhbHdheXMKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3RncmVzX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwp2b2x1bWVzOgogIGV2b2x1dGlvbl9pbnN0YW5jZXM6IG51bGwKICBldm9sdXRpb25fcmVkaXM6IG51bGwKICBwb3N0Z3Jlc19kYXRhOiBudWxsCg==", + "compose": "dmVyc2lvbjogJzMuOCcKc2VydmljZXM6CiAgYXBpOgogICAgaW1hZ2U6ICdldm9hcGljbG91ZC9ldm9sdXRpb24tYXBpOmxhdGVzdCcKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgICAtIHBvc3RncmVzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRVZPXzgwODAKICAgICAgLSBTRVJWRVJfVVJMPSRTRVJWSUNFX0ZRRE5fRVZPCiAgICAgIC0gJ0RCX1RZUEU9JHtEQl9UWVBFOi1wb3N0Z3Jlc2RifScKICAgICAgLSAnREJfUE9TVEdSRVNEQl9EQVRBQkFTRT0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0RCX1BPU1RHUkVTREJfSE9TVD0ke0RCX1BPU1RHUkVTREJfSE9TVDotcG9zdGdyZXN9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1BPUlQ9JHtEQl9QT1NUR1JFU0RCX1BPUlQ6LTU0MzJ9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RBVEFCQVNFX1BST1ZJREVSPSR7REFUQUJBU0VfUFJPVklERVI6LXBvc3RncmVzcWx9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdEQVRBQkFTRV9DT05ORUNUSU9OX1VSST1wb3N0Z3Jlc3FsOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0RFTF9JTlNUQU5DRT0ke0RFTF9JTlNUQU5DRTotZmFsc2V9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX0RBVEFfSU5TVEFOQ0U9JHtEQVRBQkFTRV9TQVZFX0RBVEFfSU5TVEFOQ0U6LXRydWV9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX0RBVEFfTkVXX01FU1NBR0U9JHtEQVRBQkFTRV9TQVZFX0RBVEFfTkVXX01FU1NBR0U6LXRydWV9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX01FU1NBR0VfVVBEQVRFPSR7REFUQUJBU0VfU0FWRV9NRVNTQUdFX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9DT05UQUNUUz0ke0RBVEFCQVNFX1NBVkVfREFUQV9DT05UQUNUUzotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9DSEFUUz0ke0RBVEFCQVNFX1NBVkVfREFUQV9DSEFUUzotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9MQUJFTFM9JHtEQVRBQkFTRV9TQVZFX0RBVEFfTEFCRUxTOi10cnVlfScKICAgICAgLSAnREFUQUJBU0VfU0FWRV9EQVRBX0hJU1RPUklDPSR7REFUQUJBU0VfU0FWRV9EQVRBX0hJU1RPUklDOi10cnVlfScKICAgICAgLSAnREFUQUJBU0VfQ09OTkVDVElPTl9DTElFTlRfTkFNRT0ke0RBVEFCQVNFX0NPTk5FQ1RJT05fQ0xJRU5UX05BTUU6LWV2b2x1dGlvbl92Mn0nCiAgICAgIC0gJ1JBQkJJVE1RX0VOQUJMRUQ9JHtSQUJCSVRNUV9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX1VSST0ke1JBQkJJVE1RX1VSSTotYW1xcDovL2FkbWluOmFkbWluQHJhYmJpdG1xOjU2NzIvZGVmYXVsdH0nCiAgICAgIC0gJ1JBQkJJVE1RX0VYQ0hBTkdFX05BTUU9JHtSQUJCSVRNUV9FWENIQU5HRV9OQU1FOi1ldm9sdXRpb25fdjJ9JwogICAgICAtICdSQUJCSVRNUV9HTE9CQUxfRU5BQkxFRD0ke1JBQkJJVE1RX0dMT0JBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19BUFBMSUNBVElPTl9TVEFSVFVQPSR7UkFCQklUTVFfRVZFTlRTX0FQUExJQ0FUSU9OX1NUQVJUVVA6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0lOU1RBTkNFX0NSRUFURT0ke1JBQkJJVE1RX0VWRU5UU19JTlNUQU5DRV9DUkVBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0lOU1RBTkNFX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19JTlNUQU5DRV9ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1FSQ09ERV9VUERBVEVEPSR7UkFCQklUTVFfRVZFTlRTX1FSQ09ERV9VUERBVEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19TRVQ9JHtSQUJCSVRNUV9FVkVOVFNfTUVTU0FHRVNfU0VUOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19VUFNFUlQ9JHtSQUJCSVRNUV9FVkVOVFNfTUVTU0FHRVNfVVBTRVJUOi10cnVlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX0VESVRFRD0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19FRElURUQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1NFTkRfTUVTU0FHRT0ke1JBQkJJVE1RX0VWRU5UU19TRU5EX01FU1NBR0U6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1NFVD0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19TRVQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVD0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19VUFNFUlQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1BSRVNFTkNFX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19QUkVTRU5DRV9VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1NFVD0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19TRVQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1VQU0VSVD0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19VUFNFUlQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0dST1VQU19VUFNFUlQ9JHtSQUJCSVRNUV9FVkVOVFNfR1JPVVBTX1VQU0VSVDotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfR1JPVVBfVVBEQVRFPSR7UkFCQklUTVFfRVZFTlRTX0dST1VQX1VQREFURTotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfR1JPVVBfUEFSVElDSVBBTlRTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19HUk9VUF9QQVJUSUNJUEFOVFNfVVBEQVRFOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19DT05ORUNUSU9OX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DT05ORUNUSU9OX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19DQUxMPSR7UkFCQklUTVFfRVZFTlRTX0NBTEw6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1RZUEVCT1RfU1RBUlQ9JHtSQUJCSVRNUV9FVkVOVFNfVFlQRUJPVF9TVEFSVDotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfVFlQRUJPVF9DSEFOR0VfU1RBVFVTPSR7UkFCQklUTVFfRVZFTlRTX1RZUEVCT1RfQ0hBTkdFX1NUQVRVUzotZmFsc2V9JwogICAgICAtICdTUVNfRU5BQkxFRD0ke1NRU19FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1NRU19BQ0NFU1NfS0VZX0lEPSR7U1FTX0FDQ0VTU19LRVlfSUQ6LX0nCiAgICAgIC0gJ1NRU19TRUNSRVRfQUNDRVNTX0tFWT0ke1NRU19TRUNSRVRfQUNDRVNTX0tFWTotfScKICAgICAgLSAnU1FTX0FDQ09VTlRfSUQ9JHtTUVNfQUNDT1VOVF9JRDotfScKICAgICAgLSAnU1FTX1JFR0lPTj0ke1NRU19SRUdJT046LX0nCiAgICAgIC0gJ1dFQlNPQ0tFVF9FTkFCTEVEPSR7V0VCU09DS0VUX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnV0VCU09DS0VUX0dMT0JBTF9FVkVOVFM9JHtXRUJTT0NLRVRfR0xPQkFMX0VWRU5UUzotZmFsc2V9JwogICAgICAtICdXQV9CVVNJTkVTU19UT0tFTl9XRUJIT09LPSR7V0FfQlVTSU5FU1NfVE9LRU5fV0VCSE9PSzotZXZvbHV0aW9ufScKICAgICAgLSAnV0FfQlVTSU5FU1NfVVJMPSR7V0FfQlVTSU5FU1NfVVJMOi1odHRwczovL2dyYXBoLmZhY2Vib29rLmNvbX0nCiAgICAgIC0gJ1dBX0JVU0lORVNTX1ZFUlNJT049JHtXQV9CVVNJTkVTU19WRVJTSU9OOi12MjAuMH0nCiAgICAgIC0gJ1dBX0JVU0lORVNTX0xBTkdVQUdFPSR7V0FfQlVTSU5FU1NfTEFOR1VBR0U6LXB0X0JSfScKICAgICAgLSAiV0VCSE9PS19HTE9CQUxfVVJMPSR7V0VCSE9PS19HTE9CQUxfVVJMOi0nJ30iCiAgICAgIC0gJ1dFQkhPT0tfR0xPQkFMX0VOQUJMRUQ9JHtXRUJIT09LX0dMT0JBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1dFQkhPT0tfR0xPQkFMX1dFQkhPT0tfQllfRVZFTlRTPSR7V0VCSE9PS19HTE9CQUxfV0VCSE9PS19CWV9FVkVOVFM6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfQVBQTElDQVRJT05fU1RBUlRVUD0ke1dFQkhPT0tfRVZFTlRTX0FQUExJQ0FUSU9OX1NUQVJUVVA6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfUVJDT0RFX1VQREFURUQ9JHtXRUJIT09LX0VWRU5UU19RUkNPREVfVVBEQVRFRDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0VESVRFRD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0VESVRFRDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0RFTEVURT0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0RFTEVURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX1NFTkRfTUVTU0FHRT0ke1dFQkhPT0tfRVZFTlRTX1NFTkRfTUVTU0FHRTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX1BSRVNFTkNFX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX1BSRVNFTkNFX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX0RFTEVURT0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX0RFTEVURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0dST1VQU19VUFNFUlQ9JHtXRUJIT09LX0VWRU5UU19HUk9VUFNfVVBTRVJUOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfR1JPVVBTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0dST1VQU19VUERBVEU6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19HUk9VUF9QQVJUSUNJUEFOVFNfVVBEQVRFPSR7V0VCSE9PS19FVkVOVFNfR1JPVVBfUEFSVElDSVBBTlRTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTk5FQ1RJT05fVVBEQVRFPSR7V0VCSE9PS19FVkVOVFNfQ09OTkVDVElPTl9VUERBVEU6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19MQUJFTFNfRURJVD0ke1dFQkhPT0tfRVZFTlRTX0xBQkVMU19FRElUOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfTEFCRUxTX0FTU09DSUFUSU9OPSR7V0VCSE9PS19FVkVOVFNfTEFCRUxTX0FTU09DSUFUSU9OOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfQ0FMTD0ke1dFQkhPT0tfRVZFTlRTX0NBTEw6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19UWVBFQk9UX1NUQVJUPSR7V0VCSE9PS19FVkVOVFNfVFlQRUJPVF9TVEFSVDotZmFsc2V9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19UWVBFQk9UX0NIQU5HRV9TVEFUVVM9JHtXRUJIT09LX0VWRU5UU19UWVBFQk9UX0NIQU5HRV9TVEFUVVM6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfRVJST1JTPSR7V0VCSE9PS19FVkVOVFNfRVJST1JTOi1mYWxzZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0VSUk9SU19XRUJIT09LPSR7V0VCSE9PS19FVkVOVFNfRVJST1JTX1dFQkhPT0s6LX0nCiAgICAgIC0gJ0NPTkZJR19TRVNTSU9OX1BIT05FX0NMSUVOVD0ke0NPTkZJR19TRVNTSU9OX1BIT05FX0NMSUVOVDotRXZvbHV0aW9uIEFQSSBWMn0nCiAgICAgIC0gJ0NPTkZJR19TRVNTSU9OX1BIT05FX05BTUU9JHtDT05GSUdfU0VTU0lPTl9QSE9ORV9OQU1FOi1DaHJvbWV9JwogICAgICAtICdRUkNPREVfTElNSVQ9JHtRUkNPREVfTElNSVQ6LTMwfScKICAgICAgLSAnT1BFTkFJX0VOQUJMRUQ9JHtPUEVOQUlfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0RJRllfRU5BQkxFRD0ke0RJRllfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0ZMT1dJU0VfRU5BQkxFRD0ke0ZMT1dJU0VfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ044Tl9FTkFCTEVEPSR7TjhOX0VOQUJMRUQ6LXRydWV9JwogICAgICAtICdUWVBFQk9UX0VOQUJMRUQ9JHtUWVBFQk9UX0VOQUJMRUQ6LXRydWV9JwogICAgICAtICdUWVBFQk9UX0FQSV9WRVJTSU9OPSR7VFlQRUJPVF9BUElfVkVSU0lPTjotbGF0ZXN0fScKICAgICAgLSAnQ0hBVFdPT1RfRU5BQkxFRD0ke0NIQVRXT09UX0VOQUJMRUQ6LXRydWV9JwogICAgICAtICdDSEFUV09PVF9NRVNTQUdFX1JFQUQ9JHtDSEFUV09PVF9NRVNTQUdFX1JFQUQ6LXRydWV9JwogICAgICAtICdDSEFUV09PVF9NRVNTQUdFX0RFTEVURT0ke0NIQVRXT09UX01FU1NBR0VfREVMRVRFOi10cnVlfScKICAgICAgLSAnQ0hBVFdPT1RfSU1QT1JUX0RBVEFCQVNFX0NPTk5FQ1RJT05fVVJJPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNfREI6LWNoYXR3b290fScKICAgICAgLSAnQ0hBVFdPT1RfSU1QT1JUX1BMQUNFSE9MREVSX01FRElBX01FU1NBR0U9JHtDSEFUV09PVF9JTVBPUlRfUExBQ0VIT0xERVJfTUVESUFfTUVTU0FHRTotdHJ1ZX0nCiAgICAgIC0gJ0NBQ0hFX1JFRElTX0VOQUJMRUQ9JHtDQUNIRV9SRURJU19FTkFCTEVEOi10cnVlfScKICAgICAgLSAnQ0FDSEVfUkVESVNfVVJJPSR7Q0FDSEVfUkVESVNfVVJJOi1yZWRpczovL3JlZGlzOjYzNzkvNn0nCiAgICAgIC0gJ0NBQ0hFX1JFRElTX1BSRUZJWF9LRVk9JHtDQUNIRV9SRURJU19QUkVGSVhfS0VZOi1ldm9sdXRpb25fdjJ9JwogICAgICAtICdDQUNIRV9SRURJU19TQVZFX0lOU1RBTkNFUz0ke0NBQ0hFX1JFRElTX1NBVkVfSU5TVEFOQ0VTOi1mYWxzZX0nCiAgICAgIC0gJ0NBQ0hFX0xPQ0FMX0VOQUJMRUQ9JHtDQUNIRV9MT0NBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1MzX0VOQUJMRUQ9JHtTM19FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1MzX0FDQ0VTU19LRVk9JHtTM19BQ0NFU1NfS0VZOi19JwogICAgICAtICdTM19TRUNSRVRfS0VZPSR7UzNfU0VDUkVUX0tFWTotfScKICAgICAgLSAnUzNfQlVDS0VUPSR7UzNfQlVDS0VUOi1ldm9sdXRpb259JwogICAgICAtICdTM19QT1JUPSR7UzNfUE9SVDotNDQzfScKICAgICAgLSAnUzNfUkVHSU9OPSR7UzNfUkVHSU9OOi11cy1lYXN0LTF9JwogICAgICAtICdTM19FTkRQT0lOVD0ke1MzX0VORFBPSU5UOi1maWxlcy5zaXRlLmNvbX0nCiAgICAgIC0gJ1MzX1VTRV9TU0w9JHtTM19VU0VfU1NMOi10cnVlfScKICAgICAgLSAnQVVUSEVOVElDQVRJT05fQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfQVVUSEVOVElDQVRJT05BUElLRVl9JwogICAgICAtICdBVVRIRU5USUNBVElPTl9FWFBPU0VfSU5fRkVUQ0hfSU5TVEFOQ0VTPSR7QVVUSEVOVElDQVRJT05fRVhQT1NFX0lOX0ZFVENIX0lOU1RBTkNFUzotdHJ1ZX0nCiAgICAgIC0gJ0xBTkdVQUdFPSR7TEFOR1VBR0U6LWVufScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2V2b2x1dGlvbl9pbnN0YW5jZXM6L2V2b2x1dGlvbi9pbnN0YW5jZXMnCiAgICBleHBvc2U6CiAgICAgIC0gODA4MAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczpsYXRlc3QnCiAgICBjb21tYW5kOiAicmVkaXMtc2VydmVyIC0tcG9ydCA2Mzc5IC0tYXBwZW5kb25seSB5ZXNcbiIKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgdm9sdW1lczoKICAgICAgLSAnZXZvbHV0aW9uX3JlZGlzOi9kYXRhJwogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICBjb21tYW5kOgogICAgICAtIHBvc3RncmVzCiAgICAgIC0gJy1jJwogICAgICAtIG1heF9jb25uZWN0aW9ucz0xMDAwCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnQVVUSEVOVElDQVRJT05fQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfQVVUSEVOVElDQVRJT05BUElLRVl9JwogICAgcmVzdGFydDogYWx3YXlzCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc19kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKdm9sdW1lczoKICBldm9sdXRpb25faW5zdGFuY2VzOiBudWxsCiAgZXZvbHV0aW9uX3JlZGlzOiBudWxsCiAgcG9zdGdyZXNfZGF0YTogbnVsbAo=", "tags": [ "evolution-api", "evo-api", @@ -1753,7 +1770,7 @@ "langfuse": { "documentation": "https://langfuse.com/docs?utm_source=coolify.io", "slogan": "Langfuse is an open-source LLM engineering platform that helps teams collaboratively debug, analyze, and iterate on their LLM applications.", - "compose": "c2VydmljZXM6CiAgbGFuZ2Z1c2U6CiAgICBpbWFnZTogJ2xhbmdmdXNlL2xhbmdmdXNlOjInCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTEFOR0ZVU0VfMzAwMAogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotbGFuZ2Z1c2V9JwogICAgICAtICdESVJFQ1RfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNfREI6LWxhbmdmdXNlfScKICAgICAgLSBTQUxUPSRTRVJWSUNFX1BBU1NXT1JEX1NBTFQKICAgICAgLSAnQVVUSF9ESVNBQkxFX1NJR05VUD0ke0FVVEhfRElTQUJMRV9TSUdOVVA6LWZhbHNlfScKICAgICAgLSBORVhUQVVUSF9VUkw9JFNFUlZJQ0VfRlFETl9MQU5HRlVTRV8zMDAwCiAgICAgIC0gJ05FWFRBVVRIX1NFQ1JFVD0ke1NFUlZJQ0VfQkFTRTY0XzY0X05FWFRBVVRIU0VDUkVUfScKICAgICAgLSAnVEVMRU1FVFJZX0VOQUJMRUQ9JHtURUxFTUVUUllfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdMQU5HRlVTRV9FTkFCTEVfRVhQRVJJTUVOVEFMX0ZFQVRVUkVTPSR7TEFOR0ZVU0VfRU5BQkxFX0VYUEVSSU1FTlRBTF9GRUFUVVJFUzotZmFsc2V9JwogICAgICAtICdIT1NUTkFNRT0ke0hPU1ROQU1FOi0wLjAuMC4wfScKICAgICAgLSAnTEFOR0ZVU0VfSU5JVF9VU0VSX05BTUU9JHtMQU5HRlVTRV9JTklUX1VTRVJfTkFNRTotQWRtaW59JwogICAgICAtICdMQU5HRlVTRV9JTklUX1VTRVJfRU1BSUw9JHtMQU5HRlVTRV9JTklUX1VTRVJfRU1BSUw6LWFkbWluQGV4YW1wbGUuY29tfScKICAgICAgLSAnTEFOR0ZVU0VfSU5JVF9VU0VSX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9MQU5HRlVTRX0nCiAgICAgIC0gJ0xBTkdGVVNFX0lOSVRfT1JHX0lEPSR7TEFOR0ZVU0VfSU5JVF9PUkdfSUQ6LW15LW9yZ30nCiAgICAgIC0gJ0xBTkdGVVNFX0lOSVRfT1JHX05BTUU9JHtMQU5HRlVTRV9JTklUX09SR19OQU1FOi1NeSBPcmd9JwogICAgICAtICdMQU5HRlVTRV9JTklUX1BST0pFQ1RfSUQ9JHtMQU5HRlVTRV9JTklUX1BST0pFQ1RfSUQ6LW15LXByb2plY3R9JwogICAgICAtICdMQU5HRlVTRV9JTklUX1BST0pFQ1RfTkFNRT0ke0xBTkdGVVNFX0lOSVRfUFJPSkVDVF9OQU1FOi1NeSBQcm9qZWN0fScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLXEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDAwL2FwaS9wdWJsaWMvaGVhbHRoJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWxhbmdmdXNlfScKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgdm9sdW1lczoKICAgICAgLSAncGctZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLWggbG9jYWxob3N0IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "eC1hcHAtZW52OgogIC0gJ05FWFRBVVRIX1VSTD0ke1NFUlZJQ0VfRlFETl9MQU5HRlVTRX0nCiAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNfREI6LWxhbmdmdXNlLWRifScKICAtICdTQUxUPSR7U0VSVklDRV9QQVNTV09SRF9TQUxUfScKICAtICdFTkNSWVBUSU9OX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfTEFOR0ZVU0V9JwogIC0gJ1RFTEVNRVRSWV9FTkFCTEVEPSR7VEVMRU1FVFJZX0VOQUJMRUQ6LWZhbHNlfScKICAtICdMQU5HRlVTRV9FTkFCTEVfRVhQRVJJTUVOVEFMX0ZFQVRVUkVTPSR7TEFOR0ZVU0VfRU5BQkxFX0VYUEVSSU1FTlRBTF9GRUFUVVJFUzotZmFsc2V9JwogIC0gJ0NMSUNLSE9VU0VfTUlHUkFUSU9OX1VSTD1jbGlja2hvdXNlOi8vY2xpY2tob3VzZTo5MDAwJwogIC0gJ0NMSUNLSE9VU0VfVVJMPWh0dHA6Ly9jbGlja2hvdXNlOjgxMjMnCiAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7U0VSVklDRV9VU0VSX0NMSUNLSE9VU0V9JwogIC0gJ0NMSUNLSE9VU0VfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0NMSUNLSE9VU0V9JwogIC0gQ0xJQ0tIT1VTRV9DTFVTVEVSX0VOQUJMRUQ9ZmFsc2UKICAtICdMQU5HRlVTRV9VU0VfQVpVUkVfQkxPQj0ke0xBTkdGVVNFX1VTRV9BWlVSRV9CTE9COi1mYWxzZX0nCiAgLSAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0JVQ0tFVD0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9CVUNLRVQ6LWxhbmdmdXNlfScKICAtICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUkVHSU9OPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1JFR0lPTjotYXV0b30nCiAgLSAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0FDQ0VTU19LRVlfSUQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfQUNDRVNTX0tFWV9JRH0nCiAgLSAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1NFQ1JFVF9BQ0NFU1NfS0VZPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1NFQ1JFVF9BQ0NFU1NfS0VZfScKICAtICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfRU5EUE9JTlQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfRU5EUE9JTlR9JwogIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9QUkVGSVg9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUFJFRklYOi1ldmVudHMvfScKICAtICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQlVDS0VUPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0JVQ0tFVDotbGFuZ2Z1c2V9JwogIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9SRUdJT049JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUkVHSU9OOi1hdXRvfScKICAtICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQUNDRVNTX0tFWV9JRD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9BQ0NFU1NfS0VZX0lEfScKICAtICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfU0VDUkVUX0FDQ0VTU19LRVk9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfU0VDUkVUX0FDQ0VTU19LRVl9JwogIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9FTkRQT0lOVH0nCiAgLSAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfRk9SQ0VfUEFUSF9TVFlMRTotdHJ1ZX0nCiAgLSAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1BSRUZJWD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9QUkVGSVg6LW1lZGlhL30nCiAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VOQUJMRUQ9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRU5BQkxFRDotZmFsc2V9JwogIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9CVUNLRVQ9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfQlVDS0VUOi1sYW5nZnVzZX0nCiAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1BSRUZJWD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9QUkVGSVg6LWV4cG9ydHMvfScKICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfUkVHSU9OPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1JFR0lPTjotYXV0b30nCiAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VORFBPSU5UPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VORFBPSU5UfScKICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRVhURVJOQUxfRU5EUE9JTlQ9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRVhURVJOQUxfRU5EUE9JTlR9JwogIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9BQ0NFU1NfS0VZX0lEPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0FDQ0VTU19LRVlfSUR9JwogIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9TRUNSRVRfQUNDRVNTX0tFWT0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0ZPUkNFX1BBVEhfU1RZTEU9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRk9SQ0VfUEFUSF9TVFlMRTotdHJ1ZX0nCiAgLSAnTEFOR0ZVU0VfSU5HRVNUSU9OX1FVRVVFX0RFTEFZX01TPSR7TEFOR0ZVU0VfSU5HRVNUSU9OX1FVRVVFX0RFTEFZX01TOi0xfScKICAtICdMQU5HRlVTRV9JTkdFU1RJT05fQ0xJQ0tIT1VTRV9XUklURV9JTlRFUlZBTF9NUz0ke0xBTkdGVVNFX0lOR0VTVElPTl9DTElDS0hPVVNFX1dSSVRFX0lOVEVSVkFMX01TOi0xMDAwfScKICAtIFJFRElTX0hPU1Q9cmVkaXMKICAtIFJFRElTX1BPUlQ9NjM3OQogIC0gJ1JFRElTX0FVVEg9JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAtICdFTUFJTF9GUk9NX0FERFJFU1M9JHtFTUFJTF9GUk9NX0FERFJFU1M6LWFkbWluQGV4YW1wbGUuY29tfScKICAtICdTTVRQX0NPTk5FQ1RJT05fVVJMPSR7U01UUF9DT05ORUNUSU9OX1VSTDotfScKICAtICdORVhUQVVUSF9TRUNSRVQ9JHtTRVJWSUNFX0JBU0U2NF9ORVhUQVVUSFNFQ1JFVH0nCiAgLSAnQVVUSF9ESVNBQkxFX1NJR05VUD0ke0FVVEhfRElTQUJMRV9TSUdOVVA6LXRydWV9JwogIC0gJ0hPU1ROQU1FPSR7SE9TVE5BTUU6LTAuMC4wLjB9JwogIC0gJ0xBTkdGVVNFX0lOSVRfT1JHX0lEPSR7TEFOR0ZVU0VfSU5JVF9PUkdfSUQ6LW15LW9yZ30nCiAgLSAnTEFOR0ZVU0VfSU5JVF9PUkdfTkFNRT0ke0xBTkdGVVNFX0lOSVRfT1JHX05BTUU6LU15IE9yZ30nCiAgLSAnTEFOR0ZVU0VfSU5JVF9QUk9KRUNUX0lEPSR7TEFOR0ZVU0VfSU5JVF9QUk9KRUNUX0lEOi1teS1wcm9qZWN0fScKICAtICdMQU5HRlVTRV9JTklUX1BST0pFQ1RfTkFNRT0ke0xBTkdGVVNFX0lOSVRfUFJPSkVDVF9OQU1FOi1NeSBQcm9qZWN0fScKICAtICdMQU5HRlVTRV9JTklUX1VTRVJfRU1BSUw9JHtMQU5HRlVTRV9JTklUX1VTRVJfRU1BSUw6LWFkbWluQGV4YW1wbGUuY29tfScKICAtICdMQU5HRlVTRV9JTklUX1VTRVJfTkFNRT0ke1NFUlZJQ0VfVVNFUl9MQU5HRlVTRX0nCiAgLSAnTEFOR0ZVU0VfSU5JVF9VU0VSX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9MQU5HRlVTRX0nCnNlcnZpY2VzOgogIGxhbmdmdXNlOgogICAgaW1hZ2U6ICdsYW5nZnVzZS9sYW5nZnVzZTozJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIDA6ICdORVhUQVVUSF9VUkw9JHtTRVJWSUNFX0ZRRE5fTEFOR0ZVU0V9JwogICAgICAxOiAnREFUQUJBU0VfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNfREI6LWxhbmdmdXNlLWRifScKICAgICAgMjogJ1NBTFQ9JHtTRVJWSUNFX1BBU1NXT1JEX1NBTFR9JwogICAgICAzOiAnRU5DUllQVElPTl9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0xBTkdGVVNFfScKICAgICAgNDogJ1RFTEVNRVRSWV9FTkFCTEVEPSR7VEVMRU1FVFJZX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgNTogJ0xBTkdGVVNFX0VOQUJMRV9FWFBFUklNRU5UQUxfRkVBVFVSRVM9JHtMQU5HRlVTRV9FTkFCTEVfRVhQRVJJTUVOVEFMX0ZFQVRVUkVTOi1mYWxzZX0nCiAgICAgIDY6ICdDTElDS0hPVVNFX01JR1JBVElPTl9VUkw9Y2xpY2tob3VzZTovL2NsaWNraG91c2U6OTAwMCcKICAgICAgNzogJ0NMSUNLSE9VU0VfVVJMPWh0dHA6Ly9jbGlja2hvdXNlOjgxMjMnCiAgICAgIDg6ICdDTElDS0hPVVNFX1VTRVI9JHtTRVJWSUNFX1VTRVJfQ0xJQ0tIT1VTRX0nCiAgICAgIDk6ICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgICAgMTA6IENMSUNLSE9VU0VfQ0xVU1RFUl9FTkFCTEVEPWZhbHNlCiAgICAgIDExOiAnTEFOR0ZVU0VfVVNFX0FaVVJFX0JMT0I9JHtMQU5HRlVTRV9VU0VfQVpVUkVfQkxPQjotZmFsc2V9JwogICAgICAxMjogJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9CVUNLRVQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfQlVDS0VUOi1sYW5nZnVzZX0nCiAgICAgIDEzOiAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1JFR0lPTj0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9SRUdJT046LWF1dG99JwogICAgICAxNDogJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9BQ0NFU1NfS0VZX0lEPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0FDQ0VTU19LRVlfSUR9JwogICAgICAxNTogJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWT0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgICAgIDE2OiAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0VORFBPSU5UPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0VORFBPSU5UfScKICAgICAgMTc6ICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfRk9SQ0VfUEFUSF9TVFlMRT0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFOi10cnVlfScKICAgICAgMTg6ICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUFJFRklYPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1BSRUZJWDotZXZlbnRzL30nCiAgICAgIDE5OiAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0JVQ0tFVD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9CVUNLRVQ6LWxhbmdmdXNlfScKICAgICAgMjA6ICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUkVHSU9OPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1JFR0lPTjotYXV0b30nCiAgICAgIDIxOiAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0FDQ0VTU19LRVlfSUQ9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQUNDRVNTX0tFWV9JRH0nCiAgICAgIDIyOiAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1NFQ1JFVF9BQ0NFU1NfS0VZPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1NFQ1JFVF9BQ0NFU1NfS0VZfScKICAgICAgMjM6ICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfRU5EUE9JTlQ9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfRU5EUE9JTlR9JwogICAgICAyNDogJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogICAgICAyNTogJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9QUkVGSVg9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUFJFRklYOi1tZWRpYS99JwogICAgICAyNjogJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkFCTEVEPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgMjc6ICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfQlVDS0VUPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0JVQ0tFVDotbGFuZ2Z1c2V9JwogICAgICAyODogJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9QUkVGSVg9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfUFJFRklYOi1leHBvcnRzL30nCiAgICAgIDI5OiAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1JFR0lPTj0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9SRUdJT046LWF1dG99JwogICAgICAzMDogJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkRQT0lOVH0nCiAgICAgIDMxOiAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VYVEVSTkFMX0VORFBPSU5UPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VYVEVSTkFMX0VORFBPSU5UfScKICAgICAgMzI6ICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfQUNDRVNTX0tFWV9JRD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9BQ0NFU1NfS0VZX0lEfScKICAgICAgMzM6ICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfU0VDUkVUX0FDQ0VTU19LRVk9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfU0VDUkVUX0FDQ0VTU19LRVl9JwogICAgICAzNDogJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogICAgICAzNTogJ0xBTkdGVVNFX0lOR0VTVElPTl9RVUVVRV9ERUxBWV9NUz0ke0xBTkdGVVNFX0lOR0VTVElPTl9RVUVVRV9ERUxBWV9NUzotMX0nCiAgICAgIDM2OiAnTEFOR0ZVU0VfSU5HRVNUSU9OX0NMSUNLSE9VU0VfV1JJVEVfSU5URVJWQUxfTVM9JHtMQU5HRlVTRV9JTkdFU1RJT05fQ0xJQ0tIT1VTRV9XUklURV9JTlRFUlZBTF9NUzotMTAwMH0nCiAgICAgIDM3OiBSRURJU19IT1NUPXJlZGlzCiAgICAgIDM4OiBSRURJU19QT1JUPTYzNzkKICAgICAgMzk6ICdSRURJU19BVVRIPSR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICAgIDQwOiAnRU1BSUxfRlJPTV9BRERSRVNTPSR7RU1BSUxfRlJPTV9BRERSRVNTOi1hZG1pbkBleGFtcGxlLmNvbX0nCiAgICAgIDQxOiAnU01UUF9DT05ORUNUSU9OX1VSTD0ke1NNVFBfQ09OTkVDVElPTl9VUkw6LX0nCiAgICAgIDQyOiAnTkVYVEFVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfTkVYVEFVVEhTRUNSRVR9JwogICAgICA0MzogJ0FVVEhfRElTQUJMRV9TSUdOVVA9JHtBVVRIX0RJU0FCTEVfU0lHTlVQOi10cnVlfScKICAgICAgNDQ6ICdIT1NUTkFNRT0ke0hPU1ROQU1FOi0wLjAuMC4wfScKICAgICAgNDU6ICdMQU5HRlVTRV9JTklUX09SR19JRD0ke0xBTkdGVVNFX0lOSVRfT1JHX0lEOi1teS1vcmd9JwogICAgICA0NjogJ0xBTkdGVVNFX0lOSVRfT1JHX05BTUU9JHtMQU5HRlVTRV9JTklUX09SR19OQU1FOi1NeSBPcmd9JwogICAgICA0NzogJ0xBTkdGVVNFX0lOSVRfUFJPSkVDVF9JRD0ke0xBTkdGVVNFX0lOSVRfUFJPSkVDVF9JRDotbXktcHJvamVjdH0nCiAgICAgIDQ4OiAnTEFOR0ZVU0VfSU5JVF9QUk9KRUNUX05BTUU9JHtMQU5HRlVTRV9JTklUX1BST0pFQ1RfTkFNRTotTXkgUHJvamVjdH0nCiAgICAgIDQ5OiAnTEFOR0ZVU0VfSU5JVF9VU0VSX0VNQUlMPSR7TEFOR0ZVU0VfSU5JVF9VU0VSX0VNQUlMOi1hZG1pbkBleGFtcGxlLmNvbX0nCiAgICAgIDUwOiAnTEFOR0ZVU0VfSU5JVF9VU0VSX05BTUU9JHtTRVJWSUNFX1VTRVJfTEFOR0ZVU0V9JwogICAgICA1MTogJ0xBTkdGVVNFX0lOSVRfVVNFUl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTEFOR0ZVU0V9JwogICAgICBTRVJWSUNFX0ZRRE5fTEFOR0ZVU0VfMzAwMDogJyR7U0VSVklDRV9GUUROX0xBTkdGVVNFXzMwMDB9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHdnZXQKICAgICAgICAtICctcScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjMwMDAvYXBpL3B1YmxpYy9oZWFsdGgnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCiAgbGFuZ2Z1c2Utd29ya2VyOgogICAgaW1hZ2U6ICdsYW5nZnVzZS9sYW5nZnVzZS13b3JrZXI6MycKICAgIGVudmlyb25tZW50OgogICAgICAtICdORVhUQVVUSF9VUkw9JHtTRVJWSUNFX0ZRRE5fTEFOR0ZVU0V9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotbGFuZ2Z1c2UtZGJ9JwogICAgICAtICdTQUxUPSR7U0VSVklDRV9QQVNTV09SRF9TQUxUfScKICAgICAgLSAnRU5DUllQVElPTl9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0xBTkdGVVNFfScKICAgICAgLSAnVEVMRU1FVFJZX0VOQUJMRUQ9JHtURUxFTUVUUllfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdMQU5HRlVTRV9FTkFCTEVfRVhQRVJJTUVOVEFMX0ZFQVRVUkVTPSR7TEFOR0ZVU0VfRU5BQkxFX0VYUEVSSU1FTlRBTF9GRUFUVVJFUzotZmFsc2V9JwogICAgICAtICdDTElDS0hPVVNFX01JR1JBVElPTl9VUkw9Y2xpY2tob3VzZTovL2NsaWNraG91c2U6OTAwMCcKICAgICAgLSAnQ0xJQ0tIT1VTRV9VUkw9aHR0cDovL2NsaWNraG91c2U6ODEyMycKICAgICAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7U0VSVklDRV9VU0VSX0NMSUNLSE9VU0V9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgICAgLSBDTElDS0hPVVNFX0NMVVNURVJfRU5BQkxFRD1mYWxzZQogICAgICAtICdMQU5HRlVTRV9VU0VfQVpVUkVfQkxPQj0ke0xBTkdGVVNFX1VTRV9BWlVSRV9CTE9COi1mYWxzZX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9CVUNLRVQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfQlVDS0VUOi1sYW5nZnVzZX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9SRUdJT049JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUkVHSU9OOi1hdXRvfScKICAgICAgLSAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0FDQ0VTU19LRVlfSUQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfQUNDRVNTX0tFWV9JRH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWT0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9FTkRQT0lOVH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogICAgICAtICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUFJFRklYPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1BSRUZJWDotZXZlbnRzL30nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9CVUNLRVQ9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQlVDS0VUOi1sYW5nZnVzZX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9SRUdJT049JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUkVHSU9OOi1hdXRvfScKICAgICAgLSAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0FDQ0VTU19LRVlfSUQ9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQUNDRVNTX0tFWV9JRH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWT0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9FTkRQT0lOVH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogICAgICAtICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUFJFRklYPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1BSRUZJWDotbWVkaWEvfScKICAgICAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VOQUJMRUQ9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfQlVDS0VUPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0JVQ0tFVDotbGFuZ2Z1c2V9JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfUFJFRklYPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1BSRUZJWDotZXhwb3J0cy99JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfUkVHSU9OPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1JFR0lPTjotYXV0b30nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkRQT0lOVH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FWFRFUk5BTF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FWFRFUk5BTF9FTkRQT0lOVH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9BQ0NFU1NfS0VZX0lEPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0FDQ0VTU19LRVlfSUR9JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfU0VDUkVUX0FDQ0VTU19LRVk9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfU0VDUkVUX0FDQ0VTU19LRVl9JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRk9SQ0VfUEFUSF9TVFlMRT0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9GT1JDRV9QQVRIX1NUWUxFOi10cnVlfScKICAgICAgLSAnTEFOR0ZVU0VfSU5HRVNUSU9OX1FVRVVFX0RFTEFZX01TPSR7TEFOR0ZVU0VfSU5HRVNUSU9OX1FVRVVFX0RFTEFZX01TOi0xfScKICAgICAgLSAnTEFOR0ZVU0VfSU5HRVNUSU9OX0NMSUNLSE9VU0VfV1JJVEVfSU5URVJWQUxfTVM9JHtMQU5HRlVTRV9JTkdFU1RJT05fQ0xJQ0tIT1VTRV9XUklURV9JTlRFUlZBTF9NUzotMTAwMH0nCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIFJFRElTX1BPUlQ9NjM3OQogICAgICAtICdSRURJU19BVVRIPSR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICAgIC0gJ0VNQUlMX0ZST01fQUREUkVTUz0ke0VNQUlMX0ZST01fQUREUkVTUzotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdTTVRQX0NPTk5FQ1RJT05fVVJMPSR7U01UUF9DT05ORUNUSU9OX1VSTDotfScKICAgICAgLSAnTkVYVEFVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfTkVYVEFVVEhTRUNSRVR9JwogICAgICAtICdBVVRIX0RJU0FCTEVfU0lHTlVQPSR7QVVUSF9ESVNBQkxFX1NJR05VUDotdHJ1ZX0nCiAgICAgIC0gJ0hPU1ROQU1FPSR7SE9TVE5BTUU6LTAuMC4wLjB9JwogICAgICAtICdMQU5HRlVTRV9JTklUX09SR19JRD0ke0xBTkdGVVNFX0lOSVRfT1JHX0lEOi1teS1vcmd9JwogICAgICAtICdMQU5HRlVTRV9JTklUX09SR19OQU1FPSR7TEFOR0ZVU0VfSU5JVF9PUkdfTkFNRTotTXkgT3JnfScKICAgICAgLSAnTEFOR0ZVU0VfSU5JVF9QUk9KRUNUX0lEPSR7TEFOR0ZVU0VfSU5JVF9QUk9KRUNUX0lEOi1teS1wcm9qZWN0fScKICAgICAgLSAnTEFOR0ZVU0VfSU5JVF9QUk9KRUNUX05BTUU9JHtMQU5HRlVTRV9JTklUX1BST0pFQ1RfTkFNRTotTXkgUHJvamVjdH0nCiAgICAgIC0gJ0xBTkdGVVNFX0lOSVRfVVNFUl9FTUFJTD0ke0xBTkdGVVNFX0lOSVRfVVNFUl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdMQU5HRlVTRV9JTklUX1VTRVJfTkFNRT0ke1NFUlZJQ0VfVVNFUl9MQU5HRlVTRX0nCiAgICAgIC0gJ0xBTkdGVVNFX0lOSVRfVVNFUl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTEFOR0ZVU0V9JwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNy1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotbGFuZ2Z1c2UtZGJ9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xhbmdmdXNlX3Bvc3RncmVzX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1oIGxvY2FsaG9zdCAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo4JwogICAgY29tbWFuZDoKICAgICAgLSBzaAogICAgICAtICctYycKICAgICAgLSAncmVkaXMtc2VydmVyIC0tcmVxdWlyZXBhc3MgIiRTRVJWSUNFX1BBU1NXT1JEX1JFRElTIicKICAgIGVudmlyb25tZW50OgogICAgICAtICdSRURJU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgdm9sdW1lczoKICAgICAgLSAnbGFuZ2Z1c2VfcmVkaXNfZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSByZWRpcy1jbGkKICAgICAgICAtICctYScKICAgICAgICAtICRTRVJWSUNFX1BBU1NXT1JEX1JFRElTCiAgICAgICAgLSBQSU5HCiAgICAgIGludGVydmFsOiAzcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTAKICBjbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOmxhdGVzdCcKICAgIHVzZXI6ICcxMDE6MTAxJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0NMSUNLSE9VU0VfREI9JHtDTElDS0hPVVNFX0RCOi1kZWZhdWx0fScKICAgICAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7U0VSVklDRV9VU0VSX0NMSUNLSE9VU0V9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xhbmdmdXNlX2NsaWNraG91c2VfZGF0YTovdmFyL2xpYi9jbGlja2hvdXNlJwogICAgICAtICdsYW5nZnVzZV9jbGlja2hvdXNlX2xvZ3M6L3Zhci9sb2cvY2xpY2tob3VzZS1zZXJ2ZXInCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogJ3dnZXQgLS1uby12ZXJib3NlIC0tdHJpZXM9MSAtLXNwaWRlciBodHRwOi8vbG9jYWxob3N0OjgxMjMvcGluZyB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAo=", "tags": [ "ai", "qdrant", @@ -1769,7 +1786,7 @@ "observation", "metrics" ], - "logo": "svgs/langfuse.png", + "logo": "svgs/langfuse.svg", "minversion": "0.0.0", "port": "3000" }, @@ -1949,7 +1966,7 @@ "matrix": { "documentation": "https://matrix.org/docs/chat_basics/matrix-for-im/?utm_source=coolify.io", "slogan": "Chat securely with your family, friends, community, or build great apps with Matrix!", - "compose": "c2VydmljZXM6CiAgbWF0cml4OgogICAgaW1hZ2U6ICdtYXRyaXhkb3Rvcmcvc3luYXBzZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTUFUUklYXzgwMDgKICAgICAgLSAnU1lOQVBTRV9TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ1NZTkFQU0VfUkVQT1JUX1NUQVRTPSR7U1lOQVBTRV9SRVBPUlRfU1RBVFM6LW5vfScKICAgICAgLSAnRU5BQkxFX1JFR0lTVFJBVElPTj0ke0VOQUJMRV9SRUdJU1RSQVRJT046LWZhbHNlfScKICAgICAgLSAnUkVDQVBUQ0hBX1BVQkxJQ19LRVk9JHtSRUNBUFRDSEFfUFVCTElDX0tFWX0nCiAgICAgIC0gJ1JFQ0FQVENIQV9QUklWQVRFX0tFWT0ke1JFQ0FQVENIQV9QUklWQVRFX0tFWX0nCiAgICAgIC0gJ19TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ19BRE1JTl9OQU1FPSR7U0VSVklDRV9VU0VSX0FETUlOfScKICAgICAgLSAnX0FETUlOX1BBU1M9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21hdHJpeC1kYXRhOi9kYXRhJwogICAgZW50cnlwb2ludDoKICAgICAgLSAvYmluL2Jhc2gKICAgICAgLSAnLWMnCiAgICAgIC0gIiEgdGVzdCAtZiAvZGF0YS9ob21lc2VydmVyLnlhbWwgJiYgL3N0YXJ0LnB5IGdlbmVyYXRlXG5cbiMgcmVnaXN0cmF0aW9uX3NoYXJlZF9zZWNyZXRcbmdyZXAgXCJyZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFwiIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxufCBhd2sgJ3twcmludCAkMn0nID4gLi9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFxuXG4jIG1hY2Fyb29uX3NlY3JldF9rZXlcbmdyZXAgXCJtYWNhcm9vbl9zZWNyZXRfa2V5XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL21hY2Fyb29uX3NlY3JldF9rZXlcblxuIyBmb3JtX3NlY3JldFxuZ3JlcCBcImZvcm1fc2VjcmV0XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL2Zvcm1fc2VjcmV0XG5cbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG4jICAgICAgICAgICAgICAgICAgICAgICAgI1xuIyBob21lc2VydmVyLnlhbWw6IHN0YXJ0ICNcbiMgICAgICAgICAgICAgICAgICAgICAgICAjXG4jIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuY2F0IDw8RU9GID4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG5zZXJ2ZXJfbmFtZTogXCIke1NFUlZJQ0VfVVJMX01BVFJJWH1cIlxucGlkX2ZpbGU6IC9kYXRhL2hvbWVzZXJ2ZXIucGlkXG5cbiMgc2VydmVyXG5saXN0ZW5lcnM6XG4gIC0gcG9ydDogODAwOFxuICAgIHRsczogZmFsc2VcbiAgICB0eXBlOiBodHRwXG4gICAgeF9mb3J3YXJkZWQ6IHRydWVcbiAgICByZXNvdXJjZXM6XG4gICAgICAtIG5hbWVzOiBbY2xpZW50LCBmZWRlcmF0aW9uXVxuICAgICAgICBjb21wcmVzczogZmFsc2VcblxuIyBkYXRhYmFzZVxuZGF0YWJhc2U6XG4gIG5hbWU6IHNxbGl0ZTNcbiAgYXJnczpcbiAgICBkYXRhYmFzZTogL2RhdGEvaG9tZXNlcnZlci5kYlxuXG4jIGdlbmVyYWxcbmxvZ19jb25maWc6IFwiL2RhdGEvJHtTRVJWSUNFX1VSTF9NQVRSSVh9LmxvZy5jb25maWdcIlxubWVkaWFfc3RvcmVfcGF0aDogL2RhdGEvbWVkaWFfc3RvcmVcbnJlcG9ydF9zdGF0czogZmFsc2VcblxuIyBzZWNyZXRzXG5yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldDogJCg8Li9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldClcbm1hY2Fyb29uX3NlY3JldF9rZXk6ICQoPC4vbWFjYXJvb25fc2VjcmV0X2tleSlcbmZvcm1fc2VjcmV0OiAkKDwuL2Zvcm1fc2VjcmV0KVxuc2lnbmluZ19rZXlfcGF0aDogXCIvZGF0YS8ke1NFUlZJQ0VfVVJMX01BVFJJWH0uc2lnbmluZy5rZXlcIlxuXG4jcm9vbXNcbmF1dG9fam9pbl9yb29tczpcbiAgLSBcIiNnZW5lcmFsOiR7U0VSVklDRV9VUkxfTUFUUklYfVwiXG5cbiMgZmVkZXJhdGlvblxudHJ1c3RlZF9rZXlfc2VydmVyczpcbiAgLSBzZXJ2ZXJfbmFtZTogXCJtYXRyaXgub3JnXCJcbmF1dG9jcmVhdGVfYXV0b19qb2luX3Jvb21zX2ZlZGVyYXRlZDogZmFsc2VcbmFsbG93X3B1YmxpY19yb29tc19vdmVyX2ZlZGVyYXRpb246IGZhbHNlXG5FT0ZcbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuIyAgICAgICAgICAgICAgICAgICAgICAjXG4jIGhvbWVzZXJ2ZXIueWFtbDogZW5kICNcbiMgICAgICAgICAgICAgICAgICAgICAgI1xuIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG5cbnRlc3QgLW4gXCIke0VOQUJMRV9SRUdJU1RSQVRJT059XCIgJiYgISBncmVwIFwiI3JlZ2lzdHJhdGlvblwiIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCAmPi9kZXYvbnVsbCBcXFxuJiYgZWNobyA+PiAvZGF0YS9ob21lc2VydmVyLnlhbWwgXFxcbiYmIGNhdCA8PEVPRiA+PiAvZGF0YS9ob21lc2VydmVyLnlhbWxcbiNyZWdpc3RyYXRpb25cbmVuYWJsZV9yZWdpc3RyYXRpb246IHRydWUgICMgQWxsb3dzIHVzZXJzIHRvIHJlZ2lzdGVyIG9uIHlvdXIgc2VydmVyLlxuRU9GXG5cbiEgZ3JlcCAke1JFQ0FQVENIQV9QVUJMSUNfS0VZfSAvZGF0YS9ob21lc2VydmVyLnlhbWwgJj4vZGV2L251bGwgXFxcbiYmIGVjaG8gPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG4mJiBjYXQgPDxFT0YgPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG4jIHJlQ0FQVENIQSBzZXR0aW5nc1xuZW5hYmxlX3JlZ2lzdHJhdGlvbl9jYXB0Y2hhOiB0cnVlICAjIEVuYWJsZXMgQ0FQVENIQSBmb3IgcmVnaXN0cmF0aW9ucy5cbnJlY2FwdGNoYV9wdWJsaWNfa2V5OiBcIiR7UkVDQVBUQ0hBX1BVQkxJQ19LRVl9XCJcbnJlY2FwdGNoYV9wcml2YXRlX2tleTogXCIke1JFQ0FQVENIQV9QUklWQVRFX0tFWX1cIlxucmVjYXB0Y2hhX3NpdGV2ZXJpZnlfYXBpOiBcImh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vcmVjYXB0Y2hhL2FwaS9zaXRldmVyaWZ5XCJcbkVPRlxuXG5yZWdpc3Rlcl9hZG1pbigpe1xuICB3aGlsZSAhIGN1cmwgLUkgbG9jYWxob3N0OjgwMDggJj4vZGV2L251bGw7IGRvXG4gICAgc2xlZXAgMVxuICBkb25lXG4gIHJlZ2lzdGVyX25ld19tYXRyaXhfdXNlciBcXFxuICAgIC1hIFxcXG4gICAgLXUgJHtTRVJWSUNFX1VTRVJfQURNSU59IFxcXG4gICAgLXAgJHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfSBcXFxuICAgIC1jIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxuICAgIGh0dHA6Ly9sb2NhbGhvc3Q6ODAwOCAmPi9kZXYvbnVsbFxufVxucmVnaXN0ZXJfYWRtaW4gJlxuXG4vc3RhcnQucHlcbiIKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLUknCiAgICAgICAgLSAnbG9jYWxob3N0OjgwMDgnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAzcwogICAgICByZXRyaWVzOiA1Cg==", + "compose": "c2VydmljZXM6CiAgbWF0cml4OgogICAgaW1hZ2U6ICdtYXRyaXhkb3Rvcmcvc3luYXBzZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTUFUUklYXzgwMDgKICAgICAgLSAnU1lOQVBTRV9TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ1NZTkFQU0VfUkVQT1JUX1NUQVRTPSR7U1lOQVBTRV9SRVBPUlRfU1RBVFM6LW5vfScKICAgICAgLSAnRU5BQkxFX1JFR0lTVFJBVElPTj0ke0VOQUJMRV9SRUdJU1RSQVRJT046LWZhbHNlfScKICAgICAgLSAnUkVDQVBUQ0hBX1BVQkxJQ19LRVk9JHtSRUNBUFRDSEFfUFVCTElDX0tFWX0nCiAgICAgIC0gJ1JFQ0FQVENIQV9QUklWQVRFX0tFWT0ke1JFQ0FQVENIQV9QUklWQVRFX0tFWX0nCiAgICAgIC0gJ19TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ19BRE1JTl9OQU1FPSR7U0VSVklDRV9VU0VSX0FETUlOfScKICAgICAgLSAnX0FETUlOX1BBU1M9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21hdHJpeC1kYXRhOi9kYXRhJwogICAgZW50cnlwb2ludDoKICAgICAgLSAvYmluL2Jhc2gKICAgICAgLSAnLWMnCiAgICAgIC0gIiEgdGVzdCAtZiAvZGF0YS9ob21lc2VydmVyLnlhbWwgJiYgL3N0YXJ0LnB5IGdlbmVyYXRlXG5cbiMgcmVnaXN0cmF0aW9uX3NoYXJlZF9zZWNyZXRcbmdyZXAgXCJyZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFwiIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxufCBhd2sgJ3twcmludCAkMn0nID4gLi9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFxuXG4jIG1hY2Fyb29uX3NlY3JldF9rZXlcbmdyZXAgXCJtYWNhcm9vbl9zZWNyZXRfa2V5XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL21hY2Fyb29uX3NlY3JldF9rZXlcblxuIyBmb3JtX3NlY3JldFxuZ3JlcCBcImZvcm1fc2VjcmV0XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL2Zvcm1fc2VjcmV0XG5cbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG4jICAgICAgICAgICAgICAgICAgICAgICAgI1xuIyBob21lc2VydmVyLnlhbWw6IHN0YXJ0ICNcbiMgICAgICAgICAgICAgICAgICAgICAgICAjXG4jIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuY2F0IDw8RU9GID4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG5zZXJ2ZXJfbmFtZTogXCIke1NFUlZJQ0VfVVJMX01BVFJJWH1cIlxucGlkX2ZpbGU6IC9kYXRhL2hvbWVzZXJ2ZXIucGlkXG5cbiMgc2VydmVyXG5saXN0ZW5lcnM6XG4gIC0gcG9ydDogODAwOFxuICAgIHRsczogZmFsc2VcbiAgICB0eXBlOiBodHRwXG4gICAgeF9mb3J3YXJkZWQ6IHRydWVcbiAgICByZXNvdXJjZXM6XG4gICAgICAtIG5hbWVzOiBbY2xpZW50LCBmZWRlcmF0aW9uXVxuICAgICAgICBjb21wcmVzczogZmFsc2VcblxuIyBkYXRhYmFzZVxuZGF0YWJhc2U6XG4gIG5hbWU6IHNxbGl0ZTNcbiAgYXJnczpcbiAgICBkYXRhYmFzZTogL2RhdGEvaG9tZXNlcnZlci5kYlxuXG4jIGdlbmVyYWxcbmxvZ19jb25maWc6IFwiL2RhdGEvJHtTRVJWSUNFX1VSTF9NQVRSSVh9LmxvZy5jb25maWdcIlxubWVkaWFfc3RvcmVfcGF0aDogL2RhdGEvbWVkaWFfc3RvcmVcbnJlcG9ydF9zdGF0czogZmFsc2VcblxuIyBzZWNyZXRzXG5yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldDogJCg8Li9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldClcbm1hY2Fyb29uX3NlY3JldF9rZXk6ICQoPC4vbWFjYXJvb25fc2VjcmV0X2tleSlcbmZvcm1fc2VjcmV0OiAkKDwuL2Zvcm1fc2VjcmV0KVxuc2lnbmluZ19rZXlfcGF0aDogXCIvZGF0YS8ke1NFUlZJQ0VfVVJMX01BVFJJWH0uc2lnbmluZy5rZXlcIlxuXG4jcm9vbXNcbmF1dG9fam9pbl9yb29tczpcbiAgLSBcIiNnZW5lcmFsOiR7U0VSVklDRV9VUkxfTUFUUklYfVwiXG5cbiMgZmVkZXJhdGlvblxudHJ1c3RlZF9rZXlfc2VydmVyczpcbiAgLSBzZXJ2ZXJfbmFtZTogXCJtYXRyaXgub3JnXCJcbmF1dG9jcmVhdGVfYXV0b19qb2luX3Jvb21zX2ZlZGVyYXRlZDogZmFsc2VcbmFsbG93X3B1YmxpY19yb29tc19vdmVyX2ZlZGVyYXRpb246IGZhbHNlXG5FT0ZcbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuIyAgICAgICAgICAgICAgICAgICAgICAjXG4jIGhvbWVzZXJ2ZXIueWFtbDogZW5kICNcbiMgICAgICAgICAgICAgICAgICAgICAgI1xuIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG5cblsgXCIke0VOQUJMRV9SRUdJU1RSQVRJT059XCIgPSBcInRydWVcIiBdICYmICEgZ3JlcCBcIiNyZWdpc3RyYXRpb25cIiAvZGF0YS9ob21lc2VydmVyLnlhbWwgJj4vZGV2L251bGwgXFxcbiYmIGVjaG8gPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG4mJiBjYXQgPDxFT0YgPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG4jcmVnaXN0cmF0aW9uXG5lbmFibGVfcmVnaXN0cmF0aW9uOiB0cnVlICAjIEFsbG93cyB1c2VycyB0byByZWdpc3RlciBvbiB5b3VyIHNlcnZlci5cbkVPRlxuXG5bIC1uIFwiJHtSRUNBUFRDSEFfUFVCTElDX0tFWX1cIiBdICYmICEgZ3JlcCBcIiR7UkVDQVBUQ0hBX1BVQkxJQ19LRVl9XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sICY+L2Rldi9udWxsIFxcXG4mJiBlY2hvID4+IC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxuJiYgY2F0IDw8RU9GID4+IC9kYXRhL2hvbWVzZXJ2ZXIueWFtbFxuIyByZUNBUFRDSEEgc2V0dGluZ3NcbmVuYWJsZV9yZWdpc3RyYXRpb25fY2FwdGNoYTogdHJ1ZSAgIyBFbmFibGVzIENBUFRDSEEgZm9yIHJlZ2lzdHJhdGlvbnMuXG5yZWNhcHRjaGFfcHVibGljX2tleTogXCIke1JFQ0FQVENIQV9QVUJMSUNfS0VZfVwiXG5yZWNhcHRjaGFfcHJpdmF0ZV9rZXk6IFwiJHtSRUNBUFRDSEFfUFJJVkFURV9LRVl9XCJcbnJlY2FwdGNoYV9zaXRldmVyaWZ5X2FwaTogXCJodHRwczovL3d3dy5nb29nbGUuY29tL3JlY2FwdGNoYS9hcGkvc2l0ZXZlcmlmeVwiXG5FT0ZcblxucmVnaXN0ZXJfYWRtaW4oKXtcbiAgd2hpbGUgISBjdXJsIC1JIGxvY2FsaG9zdDo4MDA4ICY+L2Rldi9udWxsOyBkb1xuICAgIHNsZWVwIDFcbiAgZG9uZVxuICByZWdpc3Rlcl9uZXdfbWF0cml4X3VzZXIgXFxcbiAgICAtYSBcXFxuICAgIC11ICR7U0VSVklDRV9VU0VSX0FETUlOfSBcXFxuICAgIC1wICR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0gXFxcbiAgICAtYyAvZGF0YS9ob21lc2VydmVyLnlhbWwgXFxcbiAgICBodHRwOi8vbG9jYWxob3N0OjgwMDggJj4vZGV2L251bGxcbn1cbnJlZ2lzdGVyX2FkbWluICZcblxuL3N0YXJ0LnB5XG4iCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1JJwogICAgICAgIC0gJ2xvY2FsaG9zdDo4MDA4JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogM3MKICAgICAgcmV0cmllczogNQo=", "tags": [ "chat", "slack", @@ -2717,6 +2734,21 @@ "logo": "svgs/phpmyadmin.svg", "minversion": "0.0.0" }, + "pi-hole": { + "documentation": "https://pi-hole.net/?utm_source=coolify.io", + "slogan": "Network-wide Ad Blocking", + "compose": "c2VydmljZXM6CiAgcGlob2xlOgogICAgaW1hZ2U6ICdwaWhvbGUvcGlob2xlOmxhdGVzdCcKICAgIHBvcnRzOgogICAgICAtICc1Mzo1My90Y3AnCiAgICAgIC0gJzUzOjUzL3VkcCcKICAgICAgLSAnNjc6NjcvdWRwJwogICAgICAtICcxMjM6MTIzL3VkcCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9QSUhPTEVfODAKICAgICAgLSAnVFo9JHtUWjotRXVyb3BlL0xvbmRvbn0nCiAgICAgIC0gJ0ZUTENPTkZfd2Vic2VydmVyX2FwaV9wYXNzd29yZD0ke1NFUlZJQ0VfUEFTU1dPUkRfUElIT0xFfScKICAgICAgLSAnRlRMQ09ORl9kbnNfbGlzdGVuaW5nTW9kZT0ke0ZUTENPTkZfZG5zX2xpc3RlbmluZ01vZGU6LWFsbH0nCiAgICB2b2x1bWVzOgogICAgICAtICdwaWhvbGUtZGF0YTovZXRjL3BpaG9sZScKICAgIGNhcF9hZGQ6CiAgICAgIC0gTkVUX0FETUlOCiAgICAgIC0gU1lTX1RJTUUK", + "tags": [ + "ad-block", + "dns", + "sinkhole", + "ntp", + "dhcp" + ], + "logo": "svgs/pihole.svg", + "minversion": "0.0.0", + "port": "80" + }, "pingvinshare-with-clamav": { "documentation": "https://github.com/stonith404/pingvin-share?utm_source=coolify.io", "slogan": "A self-hosted file sharing platform that combines lightness and beauty, perfect for seamless and efficient file sharing.", @@ -2842,7 +2874,7 @@ "postiz": { "documentation": "https://docs.postiz.com?utm_source=coolify.io", "slogan": "Open source social media scheduling tool.", - "compose": "c2VydmljZXM6CiAgcG9zdGl6OgogICAgaW1hZ2U6ICdnaGNyLmlvL2dpdHJvb21ocS9wb3N0aXotYXBwOnYxLjYwLjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUE9TVElaXzUwMDAKICAgICAgLSAnTUFJTl9VUkw9JHtTRVJWSUNFX0ZRRE5fUE9TVElafScKICAgICAgLSAnRlJPTlRFTkRfVVJMPSR7U0VSVklDRV9GUUROX1BPU1RJWn0nCiAgICAgIC0gJ05FWFRfUFVCTElDX0JBQ0tFTkRfVVJMPSR7U0VSVklDRV9GUUROX1BPU1RJWn0vYXBpJwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1RTRUNSRVR9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovL3Bvc3RncmVzOiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNRTF9EQVRBQkFTRTotcG9zdGl6LWRifScKICAgICAgLSAnUkVESVNfVVJMPXJlZGlzOi8vZGVmYXVsdDoke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9QHJlZGlzOjYzNzknCiAgICAgIC0gJ0JBQ0tFTkRfSU5URVJOQUxfVVJMPWh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCcKICAgICAgLSAnQ0xPVURGTEFSRV9BQ0NPVU5UX0lEPSR7Q0xPVURGTEFSRV9BQ0NPVU5UX0lEfScKICAgICAgLSAnQ0xPVURGTEFSRV9BQ0NFU1NfS0VZPSR7Q0xPVURGTEFSRV9BQ0NFU1NfS0VZfScKICAgICAgLSAnQ0xPVURGTEFSRV9TRUNSRVRfQUNDRVNTX0tFWT0ke0NMT1VERkxBUkVfU0VDUkVUX0FDQ0VTU19LRVl9JwogICAgICAtICdDTE9VREZMQVJFX0JVQ0tFVE5BTUU9JHtDTE9VREZMQVJFX0JVQ0tFVE5BTUV9JwogICAgICAtICdDTE9VREZMQVJFX0JVQ0tFVF9VUkw9JHtDTE9VREZMQVJFX0JVQ0tFVF9VUkx9JwogICAgICAtICdDTE9VREZMQVJFX1JFR0lPTj0ke0NMT1VERkxBUkVfUkVHSU9OfScKICAgICAgLSAnU1RPUkFHRV9QUk9WSURFUj0ke1NUT1JBR0VfUFJPVklERVI6LWxvY2FsfScKICAgICAgLSAnVVBMT0FEX0RJUkVDVE9SWT0ke1VQTE9BRF9ESVJFQ1RPUlk6LS91cGxvYWRzfScKICAgICAgLSAnTkVYVF9QVUJMSUNfVVBMT0FEX0RJUkVDVE9SWT0ke05FWFRfUFVCTElDX1VQTE9BRF9ESVJFQ1RPUlk6LS91cGxvYWRzfScKICAgICAgLSAnTkVYVF9QVUJMSUNfVVBMT0FEX1NUQVRJQ19ESVJFQ1RPUlk9JHtORVhUX1BVQkxJQ19VUExPQURfU1RBVElDX0RJUkVDVE9SWX0nCiAgICAgIC0gJ1JFU0VORF9BUElfS0VZPSR7UkVTRU5EX0FQSV9LRVl9JwogICAgICAtICdFTUFJTF9GUk9NX0FERFJFU1M9JHtFTUFJTF9GUk9NX0FERFJFU1N9JwogICAgICAtICdFTUFJTF9GUk9NX05BTUU9JHtFTUFJTF9GUk9NX05BTUV9JwogICAgICAtICdFTUFJTF9QUk9WSURFUj0ke0VNQUlMX1BST1ZJREVSfScKICAgICAgLSAnWF9BUElfS0VZPSR7U0VSVklDRV9YX0FQSX0nCiAgICAgIC0gJ1hfQVBJX1NFQ1JFVD0ke1NFUlZJQ0VfWF9TRUNSRVR9JwogICAgICAtICdMSU5LRURJTl9DTElFTlRfSUQ9JHtTRVJWSUNFX0xJTktFRElOX0lEfScKICAgICAgLSAnTElOS0VESU5fQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfTElOS0VESU5fU0VDUkVUfScKICAgICAgLSAnUkVERElUX0NMSUVOVF9JRD0ke1NFUlZJQ0VfUkVERElUX0FQSX0nCiAgICAgIC0gJ1JFRERJVF9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9SRURESVRfU0VDUkVUfScKICAgICAgLSAnR0lUSFVCX0NMSUVOVF9JRD0ke1NFUlZJQ0VfR0lUSFVCX0lEfScKICAgICAgLSAnR0lUSFVCX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0dJVEhVQl9TRUNSRVR9JwogICAgICAtICdUSFJFQURTX0FQUF9JRD0ke1NFUlZJQ0VfVEhSRUFEU19JRH0nCiAgICAgIC0gJ1RIUkVBRFNfQVBQX1NFQ1JFVD0ke1NFUlZJQ0VfVEhSRUFEU19TRUNSRVR9JwogICAgICAtICdGQUNFQk9PS19BUFBfSUQ9JHtTRVJWSUNFX0ZBQ0VCT09LX0lEfScKICAgICAgLSAnRkFDRUJPT0tfQVBQX1NFQ1JFVD0ke1NFUlZJQ0VfRkFDRUJPT0tfU0VDUkVUfScKICAgICAgLSAnWU9VVFVCRV9DTElFTlRfSUQ9JHtTRVJWSUNFX1lPVVRVQkVfSUR9JwogICAgICAtICdZT1VUVUJFX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1lPVVRVQkVfU0VDUkVUfScKICAgICAgLSAnVElLVE9LX0NMSUVOVF9JRD0ke1NFUlZJQ0VfVElLVE9LX0lEfScKICAgICAgLSAnVElLVE9LX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1RJS1RPS19TRUNSRVR9JwogICAgICAtICdQSU5URVJFU1RfQ0xJRU5UX0lEPSR7U0VSVklDRV9QSU5URVJFU1RfSUR9JwogICAgICAtICdQSU5URVJFU1RfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfUElOVEVSRVNUX1NFQ1JFVH0nCiAgICAgIC0gJ0RSSUJCQkxFX0NMSUVOVF9JRD0ke1NFUlZJQ0VfRFJJQkJMRV9JRH0nCiAgICAgIC0gJ0RSSUJCQkxFX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0RSSUJCTEVfU0VDUkVUfScKICAgICAgLSAnRElTQ09SRF9DTElFTlRfSUQ9JHtTRVJWSUNFX0RJU0NPUkRfSUR9JwogICAgICAtICdESVNDT1JEX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0RJU0NPUkRfU0VDUkVUfScKICAgICAgLSAnRElTQ09SRF9CT1RfVE9LRU5fSUQ9JHtTRVJWSUNFX0RJU0NPUkRfVE9LRU59JwogICAgICAtICdTTEFDS19JRD0ke1NFUlZJQ0VfU0xBQ0tfSUR9JwogICAgICAtICdTTEFDS19TRUNSRVQ9JHtTRVJWSUNFX1NMQUNLX1NFQ1JFVH0nCiAgICAgIC0gJ1NMQUNLX1NJR05JTkdfU0VDUkVUPSR7U0xBQ0tfU0lHTklOR19TRUNSRVR9JwogICAgICAtICdNQVNUT0RPTl9DTElFTlRfSUQ9JHtTRVJWSUNFX01BU1RPRE9OX0lEfScKICAgICAgLSAnTUFTVE9ET05fQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfTUFTVE9ET05fU0VDUkVUfScKICAgICAgLSAnQkVFSElJVkVfQVBJX0tFWT0ke1NFUlZJQ0VfQkVFSElJVkVfS0VZfScKICAgICAgLSAnQkVFSElJVkVfUFVCTElDQVRJT05fSUQ9JHtTRVJWSUNFX0JFRUhJSVZFX1BVQklEfScKICAgICAgLSAnT1BFTkFJX0FQSV9LRVk9JHtTRVJWSUNFX09QRU5BSV9LRVl9JwogICAgICAtICdORVhUX1BVQkxJQ19ESVNDT1JEX1NVUFBPUlQ9JHtORVhUX1BVQkxJQ19ESVNDT1JEX1NVUFBPUlR9JwogICAgICAtICdORVhUX1BVQkxJQ19QT0xPVE5PPSR7TkVYVF9QVUJMSUNfUE9MT1ROT30nCiAgICAgIC0gSVNfR0VORVJBTD10cnVlCiAgICAgIC0gJ05YX0FERF9QTFVHSU5TPSR7TlhfQUREX1BMVUdJTlM6LWZhbHNlfScKICAgICAgLSAnRkVFX0FNT1VOVD0ke0ZFRV9BTU9VTlQ6LTAuMDV9JwogICAgICAtICdTVFJJUEVfUFVCTElTSEFCTEVfS0VZPSR7U1RSSVBFX1BVQkxJU0hBQkxFX0tFWX0nCiAgICAgIC0gJ1NUUklQRV9TRUNSRVRfS0VZPSR7U1RSSVBFX1NFQ1JFVF9LRVl9JwogICAgICAtICdTVFJJUEVfU0lHTklOR19LRVk9JHtTVFJJUEVfU0lHTklOR19LRVl9JwogICAgICAtICdTVFJJUEVfU0lHTklOR19LRVlfQ09OTkVDVD0ke1NUUklQRV9TSUdOSU5HX0tFWV9DT05ORUNUfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3Rpel9jb25maWc6L2NvbmZpZy8nCiAgICAgIC0gJ3Bvc3Rpel91cGxvYWRzOi91cGxvYWRzLycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLXFPLSBodHRwOi8vMTI3LjAuMC4xOjUwMDAvJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE0LjUnCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0aXpfcG9zdGdyZXNxbF9kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9cG9zdGdyZXMKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTUUxfREFUQUJBU0U6LXBvc3Rpei1kYn0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREI6LXBvc3Rpei1kYn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6Ny4yJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1JFRElTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICBjb21tYW5kOiAncmVkaXMtc2VydmVyIC0tcmVxdWlyZXBhc3MgJHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3Rpel9yZWRpc19kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gJy1hJwogICAgICAgIC0gJyR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAK", + "compose": "c2VydmljZXM6CiAgcG9zdGl6OgogICAgaW1hZ2U6ICdnaGNyLmlvL2dpdHJvb21ocS9wb3N0aXotYXBwOnYxLjYwLjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUE9TVElaXzUwMDAKICAgICAgLSAnTUFJTl9VUkw9JHtTRVJWSUNFX0ZRRE5fUE9TVElafScKICAgICAgLSAnRlJPTlRFTkRfVVJMPSR7U0VSVklDRV9GUUROX1BPU1RJWn0nCiAgICAgIC0gJ05FWFRfUFVCTElDX0JBQ0tFTkRfVVJMPSR7U0VSVklDRV9GUUROX1BPU1RJWn0vYXBpJwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1RTRUNSRVR9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovL3Bvc3RncmVzOiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNRTF9EQVRBQkFTRTotcG9zdGl6LWRifScKICAgICAgLSAnUkVESVNfVVJMPXJlZGlzOi8vZGVmYXVsdDoke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9QHJlZGlzOjYzNzknCiAgICAgIC0gJ0JBQ0tFTkRfSU5URVJOQUxfVVJMPWh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCcKICAgICAgLSAnQ0xPVURGTEFSRV9BQ0NPVU5UX0lEPSR7Q0xPVURGTEFSRV9BQ0NPVU5UX0lEfScKICAgICAgLSAnQ0xPVURGTEFSRV9BQ0NFU1NfS0VZPSR7Q0xPVURGTEFSRV9BQ0NFU1NfS0VZfScKICAgICAgLSAnQ0xPVURGTEFSRV9TRUNSRVRfQUNDRVNTX0tFWT0ke0NMT1VERkxBUkVfU0VDUkVUX0FDQ0VTU19LRVl9JwogICAgICAtICdDTE9VREZMQVJFX0JVQ0tFVE5BTUU9JHtDTE9VREZMQVJFX0JVQ0tFVE5BTUV9JwogICAgICAtICdDTE9VREZMQVJFX0JVQ0tFVF9VUkw9JHtDTE9VREZMQVJFX0JVQ0tFVF9VUkx9JwogICAgICAtICdDTE9VREZMQVJFX1JFR0lPTj0ke0NMT1VERkxBUkVfUkVHSU9OfScKICAgICAgLSAnU1RPUkFHRV9QUk9WSURFUj0ke1NUT1JBR0VfUFJPVklERVI6LWxvY2FsfScKICAgICAgLSAnVVBMT0FEX0RJUkVDVE9SWT0ke1VQTE9BRF9ESVJFQ1RPUlk6LS91cGxvYWRzfScKICAgICAgLSAnTkVYVF9QVUJMSUNfVVBMT0FEX0RJUkVDVE9SWT0ke05FWFRfUFVCTElDX1VQTE9BRF9ESVJFQ1RPUlk6LS91cGxvYWRzfScKICAgICAgLSAnTkVYVF9QVUJMSUNfVVBMT0FEX1NUQVRJQ19ESVJFQ1RPUlk9JHtORVhUX1BVQkxJQ19VUExPQURfU1RBVElDX0RJUkVDVE9SWX0nCiAgICAgIC0gJ1JFU0VORF9BUElfS0VZPSR7UkVTRU5EX0FQSV9LRVl9JwogICAgICAtICdFTUFJTF9GUk9NX0FERFJFU1M9JHtFTUFJTF9GUk9NX0FERFJFU1N9JwogICAgICAtICdFTUFJTF9GUk9NX05BTUU9JHtFTUFJTF9GUk9NX05BTUV9JwogICAgICAtICdFTUFJTF9QUk9WSURFUj0ke0VNQUlMX1BST1ZJREVSfScKICAgICAgLSAnWF9BUElfS0VZPSR7U0VSVklDRV9YX0FQSX0nCiAgICAgIC0gJ1hfQVBJX1NFQ1JFVD0ke1NFUlZJQ0VfWF9TRUNSRVR9JwogICAgICAtICdMSU5LRURJTl9DTElFTlRfSUQ9JHtTRVJWSUNFX0xJTktFRElOX0lEfScKICAgICAgLSAnTElOS0VESU5fQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfTElOS0VESU5fU0VDUkVUfScKICAgICAgLSAnUkVERElUX0NMSUVOVF9JRD0ke1NFUlZJQ0VfUkVERElUX0FQSX0nCiAgICAgIC0gJ1JFRERJVF9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9SRURESVRfU0VDUkVUfScKICAgICAgLSAnR0lUSFVCX0NMSUVOVF9JRD0ke1NFUlZJQ0VfR0lUSFVCX0lEfScKICAgICAgLSAnR0lUSFVCX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0dJVEhVQl9TRUNSRVR9JwogICAgICAtICdUSFJFQURTX0FQUF9JRD0ke1NFUlZJQ0VfVEhSRUFEU19JRH0nCiAgICAgIC0gJ1RIUkVBRFNfQVBQX1NFQ1JFVD0ke1NFUlZJQ0VfVEhSRUFEU19TRUNSRVR9JwogICAgICAtICdGQUNFQk9PS19BUFBfSUQ9JHtTRVJWSUNFX0ZBQ0VCT09LX0lEfScKICAgICAgLSAnRkFDRUJPT0tfQVBQX1NFQ1JFVD0ke1NFUlZJQ0VfRkFDRUJPT0tfU0VDUkVUfScKICAgICAgLSAnWU9VVFVCRV9DTElFTlRfSUQ9JHtTRVJWSUNFX1lPVVRVQkVfSUR9JwogICAgICAtICdZT1VUVUJFX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1lPVVRVQkVfU0VDUkVUfScKICAgICAgLSAnVElLVE9LX0NMSUVOVF9JRD0ke1NFUlZJQ0VfVElLVE9LX0lEfScKICAgICAgLSAnVElLVE9LX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1RJS1RPS19TRUNSRVR9JwogICAgICAtICdQSU5URVJFU1RfQ0xJRU5UX0lEPSR7U0VSVklDRV9QSU5URVJFU1RfSUR9JwogICAgICAtICdQSU5URVJFU1RfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfUElOVEVSRVNUX1NFQ1JFVH0nCiAgICAgIC0gJ0RSSUJCQkxFX0NMSUVOVF9JRD0ke1NFUlZJQ0VfRFJJQkJMRV9JRH0nCiAgICAgIC0gJ0RSSUJCQkxFX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0RSSUJCTEVfU0VDUkVUfScKICAgICAgLSAnRElTQ09SRF9DTElFTlRfSUQ9JHtTRVJWSUNFX0RJU0NPUkRfSUR9JwogICAgICAtICdESVNDT1JEX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0RJU0NPUkRfU0VDUkVUfScKICAgICAgLSAnRElTQ09SRF9CT1RfVE9LRU5fSUQ9JHtTRVJWSUNFX0RJU0NPUkRfVE9LRU59JwogICAgICAtICdTTEFDS19JRD0ke1NFUlZJQ0VfU0xBQ0tfSUR9JwogICAgICAtICdTTEFDS19TRUNSRVQ9JHtTRVJWSUNFX1NMQUNLX1NFQ1JFVH0nCiAgICAgIC0gJ1NMQUNLX1NJR05JTkdfU0VDUkVUPSR7U0xBQ0tfU0lHTklOR19TRUNSRVR9JwogICAgICAtICdNQVNUT0RPTl9DTElFTlRfSUQ9JHtTRVJWSUNFX01BU1RPRE9OX0lEfScKICAgICAgLSAnTUFTVE9ET05fQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfTUFTVE9ET05fU0VDUkVUfScKICAgICAgLSAnQkVFSElJVkVfQVBJX0tFWT0ke1NFUlZJQ0VfQkVFSElJVkVfS0VZfScKICAgICAgLSAnQkVFSElJVkVfUFVCTElDQVRJT05fSUQ9JHtTRVJWSUNFX0JFRUhJSVZFX1BVQklEfScKICAgICAgLSAnT1BFTkFJX0FQSV9LRVk9JHtTRVJWSUNFX09QRU5BSV9LRVl9JwogICAgICAtICdORVhUX1BVQkxJQ19ESVNDT1JEX1NVUFBPUlQ9JHtORVhUX1BVQkxJQ19ESVNDT1JEX1NVUFBPUlR9JwogICAgICAtICdORVhUX1BVQkxJQ19QT0xPVE5PPSR7TkVYVF9QVUJMSUNfUE9MT1ROT30nCiAgICAgIC0gSVNfR0VORVJBTD10cnVlCiAgICAgIC0gJ05YX0FERF9QTFVHSU5TPSR7TlhfQUREX1BMVUdJTlM6LWZhbHNlfScKICAgICAgLSAnTk9UX1NFQ1VSRUQ9JHtOT1RfU0VDVVJFRDotZmFsc2V9JwogICAgICAtICdGRUVfQU1PVU5UPSR7RkVFX0FNT1VOVDotMC4wNX0nCiAgICAgIC0gJ1NUUklQRV9QVUJMSVNIQUJMRV9LRVk9JHtTVFJJUEVfUFVCTElTSEFCTEVfS0VZfScKICAgICAgLSAnU1RSSVBFX1NFQ1JFVF9LRVk9JHtTVFJJUEVfU0VDUkVUX0tFWX0nCiAgICAgIC0gJ1NUUklQRV9TSUdOSU5HX0tFWT0ke1NUUklQRV9TSUdOSU5HX0tFWX0nCiAgICAgIC0gJ1NUUklQRV9TSUdOSU5HX0tFWV9DT05ORUNUPSR7U1RSSVBFX1NJR05JTkdfS0VZX0NPTk5FQ1R9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGl6X2NvbmZpZzovY29uZmlnLycKICAgICAgLSAncG9zdGl6X3VwbG9hZHM6L3VwbG9hZHMvJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6NTAwMC8nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTQuNScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3Rpel9wb3N0Z3Jlc3FsX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj1wb3N0Z3JlcwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotcG9zdGl6LWRifScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQjotcG9zdGl6LWRifScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3LjInCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUkVESVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1yZXF1aXJlcGFzcyAke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGl6X3JlZGlzX2RhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSAnLWEnCiAgICAgICAgLSAnJHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgICAgICAtIHBpbmcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAyMAo=", "tags": [ "post everywhere", "social media", @@ -3083,6 +3115,19 @@ "minversion": "0.0.0", "port": "8080" }, + "sequin": { + "documentation": "https://sequinstream.com/docs/?utm_source=coolify.io", + "slogan": "The fastest Postgres change data capture", + "compose": "c2VydmljZXM6CiAgc2VxdWluOgogICAgaW1hZ2U6ICdzZXF1aW4vc2VxdWluOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TRVFVSU5fNzM3NgogICAgICAtICdTRVJWRVJfSE9TVD0ke1NFUlZJQ0VfVVJMX1NFUVVJTn0nCiAgICAgIC0gUEdfSE9TVE5BTUU9cG9zdGdyZXMKICAgICAgLSAnUEdfREFUQUJBU0U9JHtQT1NUR1JFU19EQjotc2VxdWluLWRifScKICAgICAgLSBQR19QT1JUPTU0MzIKICAgICAgLSAnUEdfVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQR19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtIFBHX1BPT0xfU0laRT0yMAogICAgICAtICdTRUNSRVRfS0VZX0JBU0U9JHtTRVJWSUNFX1JFQUxCQVNFNjRfNjRfU0VDUkVUS0VZfScKICAgICAgLSAnVkFVTFRfS0VZPSR7U0VSVklDRV9SRUFMQkFTRTY0X1ZBVUxUS0VZfScKICAgICAgLSAnUkVESVNfVVJMPXJlZGlzOi8vcmVkaXM6NjM3OScKICAgICAgLSBDT05GSUdfRklMRV9QQVRIPS9jb25maWcvcGxheWdyb3VuZC55bWwKICAgICAgLSAnRkVBVFVSRV9BQ0NPVU5UX1NFTEZfU0lHTlVQPSR7RkVBVFVSRV9BQ0NPVU5UX1NFTEZfU0lHTlVQOi1mYWxzZX0nCiAgICAgIC0gJ1NFUVVJTl9URUxFTUVUUllfRElTQUJMRUQ9JHtTRVFVSU5fVEVMRU1FVFJZX0RJU0FCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ0NSQVNIX1JFUE9SVElOR19ESVNBQkxFRD0ke0NSQVNIX1JFUE9SVElOR19ESVNBQkxFRDotZmFsc2V9JwogICAgZGVwZW5kc19vbjoKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo3Mzc2L2hlYWx0aCcKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotc2VxdWluLWRifScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICBjb21tYW5kOgogICAgICAtIHBvc3RncmVzCiAgICAgIC0gJy1jJwogICAgICAtIHdhbF9sZXZlbD1sb2dpY2FsCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9IC1kIHNlcXVpbicKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAycwogICAgICByZXRyaWVzOiA1CiAgICAgIHN0YXJ0X3BlcmlvZDogMnMKICAgICAgc3RhcnRfaW50ZXJ2YWw6IDFzCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc19kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6NycKICAgIGNvbW1hbmQ6CiAgICAgIC0gcmVkaXMtc2VydmVyCiAgICAgIC0gJy0tcG9ydCcKICAgICAgLSAnNjM3OScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSByZWRpcy1jbGkKICAgICAgICAtIHBpbmcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXNfZGF0YTovZGF0YScK", + "tags": [ + "postgres", + "sync", + "data" + ], + "logo": "svgs/sequin.svg", + "minversion": "0.0.0", + "port": "7376" + }, "shlink": { "documentation": "https://shlink.io/?utm_source=coolify.io", "slogan": "The definitive self-hosted URL shortener", From c551be9be8b3bb6daac24543de7a5398157bd14c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:11:29 +0200 Subject: [PATCH 072/202] chore(core): remove unused argument --- app/Actions/Database/StopDatabase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index 6fcdedeeb..5c881e743 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -18,7 +18,7 @@ class StopDatabase { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $isDeleteOperation = false, bool $dockerCleanup = true) + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $dockerCleanup = true) { try { $server = $database->destination->server; From daa96c27a704de6b4628ddacee8974ceb05c96c5 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:12:04 +0200 Subject: [PATCH 073/202] chore(deletion): rename isDeleteOperation to deleteConnectedNetworks --- app/Actions/Service/StopService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index 190b8885d..3f4e96479 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -14,7 +14,7 @@ class StopService public string $jobQueue = 'high'; - public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true) + public function handle(Service $service, bool $deleteConnectedNetworks = false, bool $dockerCleanup = true) { try { $server = $service->destination->server; @@ -36,7 +36,7 @@ class StopService $this->stopContainersInParallel($containersToStop, $server); } - if ($isDeleteOperation) { + if ($deleteConnectedNetworks) { $service->deleteConnectedNetworks(); } if ($dockerCleanup) { From 279edf696ce90b90497933526a1e775b95cceafc Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:12:56 +0200 Subject: [PATCH 074/202] chore(docker): remove unused arguments on StopService --- app/Livewire/Project/CloneMe.php | 4 ++-- app/Livewire/Project/Shared/ResourceOperations.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index a7c44577c..a5d80a11a 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -454,7 +454,7 @@ class CloneMe extends Component if ($this->cloneVolumeData) { try { - StopService::dispatch($application, false, false); + StopService::dispatch($application); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; $sourceServer = $application->service->destination->server; @@ -508,7 +508,7 @@ class CloneMe extends Component if ($this->cloneVolumeData) { try { - StopService::dispatch($database->service, false, false); + StopService::dispatch($database->service); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; $sourceServer = $database->service->destination->server; diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index fb19acb55..c8916bf19 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -412,7 +412,7 @@ class ResourceOperations extends Component if ($this->cloneVolumeData) { try { - StopService::dispatch($application, false, false); + StopService::dispatch($application); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; $sourceServer = $application->service->destination->server; @@ -454,7 +454,7 @@ class ResourceOperations extends Component if ($this->cloneVolumeData) { try { - StopService::dispatch($database->service, false, false); + StopService::dispatch($database->service); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; $sourceServer = $database->service->destination->server; From 36961d8ae840e9e4632bf9fc0aee5b1d10fd1494 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:14:19 +0200 Subject: [PATCH 075/202] fix(docker): cleanup always running on deletion - docker cleanup was always running on deletion instead of using the settings set in the deletion modal --- app/Jobs/DeleteResourceJob.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index a725df52f..b9fbebcc9 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -52,7 +52,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue switch ($this->resource->type()) { case 'application': - StopApplication::run($this->resource, previewDeployments: true); + StopApplication::run($this->resource, previewDeployments: true, dockerCleanup: $this->dockerCleanup); break; case 'standalone-postgresql': case 'standalone-redis': @@ -62,10 +62,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue case 'standalone-keydb': case 'standalone-dragonfly': case 'standalone-clickhouse': - StopDatabase::run($this->resource, true); + StopDatabase::run($this->resource, dockerCleanup: $this->dockerCleanup); break; case 'service': - StopService::run($this->resource, true); + StopService::run($this->resource, $this->deleteConnectedNetworks, $this->dockerCleanup); DeleteService::run($this->resource, $this->deleteVolumes, $this->deleteConnectedNetworks, $this->deleteConfigurations, $this->dockerCleanup); return; @@ -78,7 +78,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue $this->resource->deleteVolumes(); $this->resource->persistentStorages()->delete(); } - $this->resource->fileStorages()->delete(); + $this->resource->fileStorages()->delete(); // these are file mounts which should probably have their own flag $isDatabase = $this->resource instanceof StandalonePostgresql || $this->resource instanceof StandaloneRedis From a40dd8880d49ef691ca4322f86395f363b85ecf4 Mon Sep 17 00:00:00 2001 From: Cynthia Ebert <54354036+Cinzya@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:16:49 +0200 Subject: [PATCH 076/202] fix(proxy): remove hardcoded port 80/443 checks (#6275) --- app/Actions/Proxy/CheckProxy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index d4b03ffc1..a06e547c5 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -66,7 +66,7 @@ class CheckProxy if ($server->id === 0) { $ip = 'host.docker.internal'; } - $portsToCheck = ['80', '443']; + $portsToCheck = []; try { if ($server->proxyType() !== ProxyTypes::NONE->value) { From ed0f2c1c59ab7a98202a796df7d7ab5ae5bd9dd0 Mon Sep 17 00:00:00 2001 From: Gurvan <32047986+gurvancampion@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:31:43 +0200 Subject: [PATCH 077/202] feat(service): add OpenPanel template (#5310) --- public/svgs/openpanel.svg | 1 + templates/compose/openpanel.yaml | 196 +++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 public/svgs/openpanel.svg create mode 100644 templates/compose/openpanel.yaml diff --git a/public/svgs/openpanel.svg b/public/svgs/openpanel.svg new file mode 100644 index 000000000..8508fc69e --- /dev/null +++ b/public/svgs/openpanel.svg @@ -0,0 +1 @@ + diff --git a/templates/compose/openpanel.yaml b/templates/compose/openpanel.yaml new file mode 100644 index 000000000..6d3738dc0 --- /dev/null +++ b/templates/compose/openpanel.yaml @@ -0,0 +1,196 @@ +# documentation: https://openpanel.dev/docs +# slogan: Open source alternative to Mixpanel and Plausible for product analytics +# tags: analytics, insights, privacy, mixpanel, plausible, google, alternative +# logo: svgs/openpanel.svg +# port: 3000 + +services: + opdb: + image: postgres:16-alpine + restart: always + volumes: + - opdb-data:/var/lib/postgresql/data + environment: + - POSTGRES_DB=${OPENPANEL_POSTGRES_DB:-openpanel-db} + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + healthcheck: + test: [CMD-SHELL, "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + + opkv: + image: redis:7.4-alpine + restart: always + volumes: + - opkv-data:/data + command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS} --maxmemory-policy noeviction + healthcheck: + test: [CMD, redis-cli, -a, "${SERVICE_PASSWORD_REDIS}", ping] + interval: 10s + timeout: 5s + retries: 5 + + opch: + image: clickhouse/clickhouse-server:24.3.2-alpine + restart: always + volumes: + - opch-data:/var/lib/clickhouse + - opch-logs:/var/log/clickhouse-server + - type: bind + source: ./clickhouse-config.xml + target: /etc/clickhouse-server/config.d/op-config.xml + read_only: true + content: | + + + warning + true + + 10 + + + + + + + + + + 0.0.0.0 + 0.0.0.0 + opch + + 0 + + + 1 + replica1 + openpanel_cluster + + + - type: bind + source: ./clickhouse-user-config.xml + target: /etc/clickhouse-server/users.d/op-user-config.xml + read_only: true + content: | + + + + 0 + 0 + + + + - type: bind + source: ./init-db.sh + target: /docker-entrypoint-initdb.d/init-db.sh + content: | + #!/bin/sh + set -e + + clickhouse client -n <<-EOSQL + CREATE DATABASE IF NOT EXISTS openpanel; + EOSQL + healthcheck: + test: [CMD-SHELL, 'clickhouse-client --query "SELECT 1"'] + interval: 10s + timeout: 5s + retries: 5 + ulimits: + nofile: + soft: 262144 + hard: 262144 + + opapi: + image: lindesvard/openpanel-api:latest + restart: always + command: > + sh -c " + echo 'Running migrations...' + CI=true pnpm -r run migrate:deploy + + pnpm start + " + depends_on: + opdb: + condition: service_healthy + opch: + condition: service_healthy + opkv: + condition: service_healthy + environment: + # Common + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + # URLs + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 + - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} + - SERVICE_FQDN_OPAPI + # Set coolify FQDN domain + - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI + - NEXT_PUBLIC_DASHBOARD_URL=$SERVICE_FQDN_OPDASHBOARD + # Others + - COOKIE_SECRET=${SERVICE_BASE64_COOKIESECRET} + - ALLOW_REGISTRATION=${OPENPANEL_ALLOW_REGISTRATION:-false} + - ALLOW_INVITATION=${OPENPANEL_ALLOW_INVITATION:-true} + - EMAIL_SENDER=${OPENPANEL_EMAIL_SENDER} + - RESEND_API_KEY=${RESEND_API_KEY} + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + + opdashboard: + image: lindesvard/openpanel-dashboard:latest + restart: always + depends_on: + opapi: + condition: service_healthy + environment: + # Common + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + # URLs + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 + - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} + - SERVICE_FQDN_OPDASHBOARD + # Set coolify FQDN domain + - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI + - NEXT_PUBLIC_DASHBOARD_URL=$SERVICE_FQDN_OPDASHBOARD + healthcheck: + test: + ["CMD-SHELL", "curl -f http://localhost:3000/api/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + + opworker: + image: lindesvard/openpanel-worker:latest + restart: always + depends_on: + opapi: + condition: service_healthy + environment: + # FQDN + - SERVICE_FQDN_OPBULLBOARD + # Common + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + # URLs + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 + - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} + # Set coolify FQDN domain + - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 From b52c414fe481d87aa10f14ad2e7575b500031eb4 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:11:34 +0200 Subject: [PATCH 078/202] refactor(service): improve openpanel template - rename all services - add more depends_on checks to ensure everything works properly and is healthy - reorder and re-format the template, ENVs and the individual sections - remove comments --- templates/compose/openpanel.yaml | 213 ++++++++++++++++--------------- 1 file changed, 107 insertions(+), 106 deletions(-) diff --git a/templates/compose/openpanel.yaml b/templates/compose/openpanel.yaml index 6d3738dc0..7eb2615e4 100644 --- a/templates/compose/openpanel.yaml +++ b/templates/compose/openpanel.yaml @@ -5,11 +5,104 @@ # port: 3000 services: - opdb: + openpanel-dashboard: + image: lindesvard/openpanel-dashboard:latest + environment: + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + - SERVICE_FQDN_OPDASHBOARD_3000 + - NEXT_PUBLIC_API_URL=${SERVICE_FQDN_OPAPI} + - NEXT_PUBLIC_DASHBOARD_URL=${SERVICE_FQDN_OPDASHBOARD} + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 + - CLICKHOUSE_URL=http://clickhouse:8123/openpanel + depends_on: + openpanel-api: + condition: service_healthy + openpanel-worker: + condition: service_healthy + postgres: + condition: service_healthy + redis: + condition: service_healthy + clickhouse: + condition: service_healthy + healthcheck: + test: + ["CMD-SHELL", "curl -f http://localhost:3000/api/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 15s + + openpanel-api: + image: lindesvard/openpanel-api:latest + command: > + sh -c " + echo 'Running migrations...' + CI=true pnpm -r run migrate:deploy + + pnpm start + " + environment: + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + - SERVICE_FQDN_OPAPI + - NEXT_PUBLIC_API_URL=${SERVICE_FQDN_OPAPI} + - NEXT_PUBLIC_DASHBOARD_URL=${SERVICE_FQDN_OPDASHBOARD} + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 + - CLICKHOUSE_URL=http://clickhouse:8123/openpanel + - COOKIE_SECRET=${SERVICE_BASE64_COOKIESECRET} + - ALLOW_REGISTRATION=${OPENPANEL_ALLOW_REGISTRATION:-false} + - ALLOW_INVITATION=${OPENPANEL_ALLOW_INVITATION:-false} + - EMAIL_SENDER=${OPENPANEL_EMAIL_SENDER} + - RESEND_API_KEY=${RESEND_API_KEY} + depends_on: + postgres: + condition: service_healthy + clickhouse: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + + openpanel-worker: + image: lindesvard/openpanel-worker:latest + environment: + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + - SERVICE_FQDN_OPBULLBOARD + - NEXT_PUBLIC_API_URL=${SERVICE_FQDN_OPAPI} + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 + - CLICKHOUSE_URL=http://clickhouse:8123/openpanel + depends_on: + openpanel-api: + condition: service_healthy + postgres: + condition: service_healthy + redis: + condition: service_healthy + clickhouse: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s + + postgres: image: postgres:16-alpine - restart: always volumes: - - opdb-data:/var/lib/postgresql/data + - openpanel_postgres_data:/var/lib/postgresql/data environment: - POSTGRES_DB=${OPENPANEL_POSTGRES_DB:-openpanel-db} - POSTGRES_USER=${SERVICE_USER_POSTGRES} @@ -20,11 +113,12 @@ services: timeout: 5s retries: 5 - opkv: + redis: image: redis:7.4-alpine - restart: always volumes: - - opkv-data:/data + - openpanel_redis_data:/data + environment: + - REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS} command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS} --maxmemory-policy noeviction healthcheck: test: [CMD, redis-cli, -a, "${SERVICE_PASSWORD_REDIS}", ping] @@ -32,12 +126,11 @@ services: timeout: 5s retries: 5 - opch: + clickhouse: image: clickhouse/clickhouse-server:24.3.2-alpine - restart: always volumes: - - opch-data:/var/lib/clickhouse - - opch-logs:/var/log/clickhouse-server + - openpanel_clickhouse_data:/var/lib/clickhouse + - openpanel_clickhouse_logs:/var/log/clickhouse-server - type: bind source: ./clickhouse-config.xml target: /etc/clickhouse-server/config.d/op-config.xml @@ -93,104 +186,12 @@ services: clickhouse client -n <<-EOSQL CREATE DATABASE IF NOT EXISTS openpanel; EOSQL + ulimits: + nofile: + soft: 262144 + hard: 262144 healthcheck: test: [CMD-SHELL, 'clickhouse-client --query "SELECT 1"'] interval: 10s timeout: 5s retries: 5 - ulimits: - nofile: - soft: 262144 - hard: 262144 - - opapi: - image: lindesvard/openpanel-api:latest - restart: always - command: > - sh -c " - echo 'Running migrations...' - CI=true pnpm -r run migrate:deploy - - pnpm start - " - depends_on: - opdb: - condition: service_healthy - opch: - condition: service_healthy - opkv: - condition: service_healthy - environment: - # Common - - NODE_ENV=production - - NEXT_PUBLIC_SELF_HOSTED=true - # URLs - - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 - - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} - - SERVICE_FQDN_OPAPI - # Set coolify FQDN domain - - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI - - NEXT_PUBLIC_DASHBOARD_URL=$SERVICE_FQDN_OPDASHBOARD - # Others - - COOKIE_SECRET=${SERVICE_BASE64_COOKIESECRET} - - ALLOW_REGISTRATION=${OPENPANEL_ALLOW_REGISTRATION:-false} - - ALLOW_INVITATION=${OPENPANEL_ALLOW_INVITATION:-true} - - EMAIL_SENDER=${OPENPANEL_EMAIL_SENDER} - - RESEND_API_KEY=${RESEND_API_KEY} - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] - interval: 10s - timeout: 5s - retries: 5 - - opdashboard: - image: lindesvard/openpanel-dashboard:latest - restart: always - depends_on: - opapi: - condition: service_healthy - environment: - # Common - - NODE_ENV=production - - NEXT_PUBLIC_SELF_HOSTED=true - # URLs - - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 - - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} - - SERVICE_FQDN_OPDASHBOARD - # Set coolify FQDN domain - - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI - - NEXT_PUBLIC_DASHBOARD_URL=$SERVICE_FQDN_OPDASHBOARD - healthcheck: - test: - ["CMD-SHELL", "curl -f http://localhost:3000/api/healthcheck || exit 1"] - interval: 10s - timeout: 5s - retries: 5 - - opworker: - image: lindesvard/openpanel-worker:latest - restart: always - depends_on: - opapi: - condition: service_healthy - environment: - # FQDN - - SERVICE_FQDN_OPBULLBOARD - # Common - - NODE_ENV=production - - NEXT_PUBLIC_SELF_HOSTED=true - # URLs - - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 - - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} - # Set coolify FQDN domain - - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] - interval: 10s - timeout: 5s - retries: 5 From f34225dfd6e3325093a08d902aaf37b0faffdd62 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 6 Aug 2025 03:02:10 +0200 Subject: [PATCH 079/202] Update service-templates.json --- templates/service-templates.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/templates/service-templates.json b/templates/service-templates.json index c037a8889..9f2c0a773 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -2574,6 +2574,23 @@ "minversion": "0.0.0", "port": "8080" }, + "openpanel": { + "documentation": "https://openpanel.dev/docs?utm_source=coolify.io", + "slogan": "Open source alternative to Mixpanel and Plausible for product analytics", + "compose": "c2VydmljZXM6CiAgb3BlbnBhbmVsLWRhc2hib2FyZDoKICAgIGltYWdlOiAnbGluZGVzdmFyZC9vcGVucGFuZWwtZGFzaGJvYXJkOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIE5PREVfRU5WPXByb2R1Y3Rpb24KICAgICAgLSBORVhUX1BVQkxJQ19TRUxGX0hPU1RFRD10cnVlCiAgICAgIC0gU0VSVklDRV9GUUROX09QREFTSEJPQVJEXzMwMDAKICAgICAgLSAnTkVYVF9QVUJMSUNfQVBJX1VSTD0ke1NFUlZJQ0VfRlFETl9PUEFQSX0nCiAgICAgIC0gJ05FWFRfUFVCTElDX0RBU0hCT0FSRF9VUkw9JHtTRVJWSUNFX0ZRRE5fT1BEQVNIQk9BUkR9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7T1BFTlBBTkVMX1BPU1RHUkVTX0RCOi1vcGVucGFuZWwtZGJ9P3NjaGVtYT1wdWJsaWMnCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpczovL2RlZmF1bHQ6JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfUByZWRpczo2Mzc5JwogICAgICAtICdDTElDS0hPVVNFX1VSTD1odHRwOi8vY2xpY2tob3VzZTo4MTIzL29wZW5wYW5lbCcKICAgIGRlcGVuZHNfb246CiAgICAgIG9wZW5wYW5lbC1hcGk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgb3BlbnBhbmVsLXdvcmtlcjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByZWRpczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ2N1cmwgLWYgaHR0cDovL2xvY2FsaG9zdDozMDAwL2FwaS9oZWFsdGhjaGVjayB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDE1cwogIG9wZW5wYW5lbC1hcGk6CiAgICBpbWFnZTogJ2xpbmRlc3ZhcmQvb3BlbnBhbmVsLWFwaTpsYXRlc3QnCiAgICBjb21tYW5kOiAic2ggLWMgXCJcbiAgZWNobyAnUnVubmluZyBtaWdyYXRpb25zLi4uJ1xuICBDST10cnVlIHBucG0gLXIgcnVuIG1pZ3JhdGU6ZGVwbG95XG5cbiAgcG5wbSBzdGFydFxuXCJcbiIKICAgIGVudmlyb25tZW50OgogICAgICAtIE5PREVfRU5WPXByb2R1Y3Rpb24KICAgICAgLSBORVhUX1BVQkxJQ19TRUxGX0hPU1RFRD10cnVlCiAgICAgIC0gU0VSVklDRV9GUUROX09QQVBJCiAgICAgIC0gJ05FWFRfUFVCTElDX0FQSV9VUkw9JHtTRVJWSUNFX0ZRRE5fT1BBUEl9JwogICAgICAtICdORVhUX1BVQkxJQ19EQVNIQk9BUkRfVVJMPSR7U0VSVklDRV9GUUROX09QREFTSEJPQVJEfScKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke09QRU5QQU5FTF9QT1NUR1JFU19EQjotb3BlbnBhbmVsLWRifT9zY2hlbWE9cHVibGljJwogICAgICAtICdEQVRBQkFTRV9VUkxfRElSRUNUPXBvc3RncmVzOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke09QRU5QQU5FTF9QT1NUR1JFU19EQjotb3BlbnBhbmVsLWRifT9zY2hlbWE9cHVibGljJwogICAgICAtICdSRURJU19VUkw9cmVkaXM6Ly9kZWZhdWx0OiR7U0VSVklDRV9QQVNTV09SRF9SRURJU31AcmVkaXM6NjM3OScKICAgICAgLSAnQ0xJQ0tIT1VTRV9VUkw9aHR0cDovL2NsaWNraG91c2U6ODEyMy9vcGVucGFuZWwnCiAgICAgIC0gJ0NPT0tJRV9TRUNSRVQ9JHtTRVJWSUNFX0JBU0U2NF9DT09LSUVTRUNSRVR9JwogICAgICAtICdBTExPV19SRUdJU1RSQVRJT049JHtPUEVOUEFORUxfQUxMT1dfUkVHSVNUUkFUSU9OOi1mYWxzZX0nCiAgICAgIC0gJ0FMTE9XX0lOVklUQVRJT049JHtPUEVOUEFORUxfQUxMT1dfSU5WSVRBVElPTjotZmFsc2V9JwogICAgICAtICdFTUFJTF9TRU5ERVI9JHtPUEVOUEFORUxfRU1BSUxfU0VOREVSfScKICAgICAgLSAnUkVTRU5EX0FQSV9LRVk9JHtSRVNFTkRfQVBJX0tFWX0nCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ2N1cmwgLWYgaHR0cDovL2xvY2FsaG9zdDozMDAwL2hlYWx0aGNoZWNrIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgb3BlbnBhbmVsLXdvcmtlcjoKICAgIGltYWdlOiAnbGluZGVzdmFyZC9vcGVucGFuZWwtd29ya2VyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIE5PREVfRU5WPXByb2R1Y3Rpb24KICAgICAgLSBORVhUX1BVQkxJQ19TRUxGX0hPU1RFRD10cnVlCiAgICAgIC0gU0VSVklDRV9GUUROX09QQlVMTEJPQVJECiAgICAgIC0gJ05FWFRfUFVCTElDX0FQSV9VUkw9JHtTRVJWSUNFX0ZRRE5fT1BBUEl9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7T1BFTlBBTkVMX1BPU1RHUkVTX0RCOi1vcGVucGFuZWwtZGJ9P3NjaGVtYT1wdWJsaWMnCiAgICAgIC0gJ0RBVEFCQVNFX1VSTF9ESVJFQ1Q9cG9zdGdyZXM6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7T1BFTlBBTkVMX1BPU1RHUkVTX0RCOi1vcGVucGFuZWwtZGJ9P3NjaGVtYT1wdWJsaWMnCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpczovL2RlZmF1bHQ6JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfUByZWRpczo2Mzc5JwogICAgICAtICdDTElDS0hPVVNFX1VSTD1odHRwOi8vY2xpY2tob3VzZTo4MTIzL29wZW5wYW5lbCcKICAgIGRlcGVuZHNfb246CiAgICAgIG9wZW5wYW5lbC1hcGk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdjdXJsIC1mIGh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9oZWFsdGhjaGVjayB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDVzCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ29wZW5wYW5lbF9wb3N0Z3Jlc19kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19EQj0ke09QRU5QQU5FTF9QT1NUR1JFU19EQjotb3BlbnBhbmVsLWRifScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6Ny40LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ29wZW5wYW5lbF9yZWRpc19kYXRhOi9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1JFRElTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICBjb21tYW5kOiAncmVkaXMtc2VydmVyIC0tcmVxdWlyZXBhc3MgJHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfSAtLW1heG1lbW9yeS1wb2xpY3kgbm9ldmljdGlvbicKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSByZWRpcy1jbGkKICAgICAgICAtICctYScKICAgICAgICAtICcke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUKICBjbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI0LjMuMi1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdvcGVucGFuZWxfY2xpY2tob3VzZV9kYXRhOi92YXIvbGliL2NsaWNraG91c2UnCiAgICAgIC0gJ29wZW5wYW5lbF9jbGlja2hvdXNlX2xvZ3M6L3Zhci9sb2cvY2xpY2tob3VzZS1zZXJ2ZXInCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2UtY29uZmlnLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9vcC1jb25maWcueG1sCiAgICAgICAgcmVhZF9vbmx5OiB0cnVlCiAgICAgICAgY29udGVudDogIjxjbGlja2hvdXNlPlxuICAgIDxsb2dnZXI+XG4gICAgICAgIDxsZXZlbD53YXJuaW5nPC9sZXZlbD5cbiAgICAgICAgPGNvbnNvbGU+dHJ1ZTwvY29uc29sZT5cbiAgICA8L2xvZ2dlcj5cbiAgICA8a2VlcF9hbGl2ZV90aW1lb3V0PjEwPC9rZWVwX2FsaXZlX3RpbWVvdXQ+XG4gICAgPCEtLSBTdG9wIGFsbCB0aGUgdW5uZWNlc3NhcnkgbG9nZ2luZyAtLT5cbiAgICA8cXVlcnlfdGhyZWFkX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHF1ZXJ5X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHRleHRfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8dHJhY2VfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8bWV0cmljX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8c2Vzc2lvbl9sb2cgcmVtb3ZlPVwicmVtb3ZlXCIvPlxuICAgIDxwYXJ0X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGxpc3Rlbl9ob3N0PjAuMC4wLjA8L2xpc3Rlbl9ob3N0PlxuICAgIDxpbnRlcnNlcnZlcl9saXN0ZW5faG9zdD4wLjAuMC4wPC9pbnRlcnNlcnZlcl9saXN0ZW5faG9zdD5cbiAgICA8aW50ZXJzZXJ2ZXJfaHR0cF9ob3N0Pm9wY2g8L2ludGVyc2VydmVyX2h0dHBfaG9zdD5cbiAgICA8IS0tIERpc2FibGUgY2dyb3VwIG1lbW9yeSBvYnNlcnZlciAtLT5cbiAgICA8Y2dyb3Vwc19tZW1vcnlfdXNhZ2Vfb2JzZXJ2ZXJfd2FpdF90aW1lPjA8L2Nncm91cHNfbWVtb3J5X3VzYWdlX29ic2VydmVyX3dhaXRfdGltZT5cbiAgICA8IS0tIE5vdCB1c2VkIGFueW1vcmUsIGJ1dCBrZXB0IGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eSAtLT5cbiAgICA8bWFjcm9zPlxuICAgICAgICA8c2hhcmQ+MTwvc2hhcmQ+XG4gICAgICAgIDxyZXBsaWNhPnJlcGxpY2ExPC9yZXBsaWNhPlxuICAgICAgICA8Y2x1c3Rlcj5vcGVucGFuZWxfY2x1c3RlcjwvY2x1c3Rlcj5cbiAgICA8L21hY3Jvcz5cbjwvY2xpY2tob3VzZT4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2UtdXNlci1jb25maWcueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL3VzZXJzLmQvb3AtdXNlci1jb25maWcueG1sCiAgICAgICAgcmVhZF9vbmx5OiB0cnVlCiAgICAgICAgY29udGVudDogIjxjbGlja2hvdXNlPlxuICAgIDxwcm9maWxlcz5cbiAgICAgICAgPGRlZmF1bHQ+XG4gICAgICAgICAgICA8bG9nX3F1ZXJpZXM+MDwvbG9nX3F1ZXJpZXM+XG4gICAgICAgICAgICA8bG9nX3F1ZXJ5X3RocmVhZHM+MDwvbG9nX3F1ZXJ5X3RocmVhZHM+XG4gICAgICAgIDwvZGVmYXVsdD5cbiAgICA8L3Byb2ZpbGVzPlxuPC9jbGlja2hvdXNlPlxuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9pbml0LWRiLnNoCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1kYi5zaAogICAgICAgIGNvbnRlbnQ6ICIjIS9iaW4vc2hcbnNldCAtZVxuXG5jbGlja2hvdXNlIGNsaWVudCAtbiA8PC1FT1NRTFxuICBDUkVBVEUgREFUQUJBU0UgSUYgTk9UIEVYSVNUUyBvcGVucGFuZWw7XG5FT1NRTCIKICAgIHVsaW1pdHM6CiAgICAgIG5vZmlsZToKICAgICAgICBzb2Z0OiAyNjIxNDQKICAgICAgICBoYXJkOiAyNjIxNDQKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnY2xpY2tob3VzZS1jbGllbnQgLS1xdWVyeSAiU0VMRUNUIDEiJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUK", + "tags": [ + "analytics", + "insights", + "privacy", + "mixpanel", + "plausible", + "google", + "alternative" + ], + "logo": "svgs/openpanel.svg", + "minversion": "0.0.0", + "port": "3000" + }, "orangehrm": { "documentation": "https://starterhelp.orangehrm.com/hc/en-us?utm_source=coolify.io", "slogan": "OrangeHRM open source HR management software.", From 4f77bd43472335403814efc87579330092f7b022 Mon Sep 17 00:00:00 2001 From: Trung-DV Date: Tue, 5 Aug 2025 15:20:22 +0700 Subject: [PATCH 080/202] Fix volume target, use the last part Signed-off-by: Trung-DV --- bootstrap/helpers/shared.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 7ce511f2c..a6b8fe55b 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -3420,7 +3420,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } if (is_string($volume)) { $source = str($volume)->before(':'); - $target = str($volume)->after(':')->beforeLast(':'); + $target = str($volume)->afterLast(':'); $source = $name; $volume = "$source:$target"; } elseif (is_array($volume)) { From 1d4a19fb6104bf6f8b475e4aaa299f375e98f356 Mon Sep 17 00:00:00 2001 From: Gauthier POGAM--LE MONTAGNER Date: Mon, 11 Aug 2025 12:08:17 +0200 Subject: [PATCH 081/202] fix(service): update healthcheck of penpot backend container (#6272) --- templates/compose/penpot.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/penpot.yaml b/templates/compose/penpot.yaml index fa92abb7f..9a4e2de8d 100644 --- a/templates/compose/penpot.yaml +++ b/templates/compose/penpot.yaml @@ -55,7 +55,7 @@ services: - PENPOT_SMTP_TLS=${PENPOT_SMTP_TLS:-false} - PENPOT_SMTP_SSL=${PENPOT_SMTP_SSL:-false} healthcheck: - test: ['CMD', 'curl', '-f', 'http://127.0.0.1:6060/readyz'] + test: ['CMD', 'node', '-e', "require('http').get({host:'127.0.0.1', port:6060, path:'/readyz'}, res => process.exit(res.statusCode===200 ? 0 : 1)).on('error', () => process.exit(1));"] interval: 10s timeout: 30s retries: 15 From 03040d6bc87c6c9c296c4d7de567ce3dbe3d9634 Mon Sep 17 00:00:00 2001 From: Gauthier POGAM--LE MONTAGNER Date: Mon, 11 Aug 2025 12:13:25 +0200 Subject: [PATCH 082/202] feat(service): add librechat template (#5654) --- public/svgs/librechat.svg | 32 +++++++ templates/compose/librechat.yaml | 157 +++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 public/svgs/librechat.svg create mode 100644 templates/compose/librechat.yaml diff --git a/public/svgs/librechat.svg b/public/svgs/librechat.svg new file mode 100644 index 000000000..36a536d65 --- /dev/null +++ b/public/svgs/librechat.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/compose/librechat.yaml b/templates/compose/librechat.yaml new file mode 100644 index 000000000..e5647bb2d --- /dev/null +++ b/templates/compose/librechat.yaml @@ -0,0 +1,157 @@ +# documentation: https://docs.librechat.ai/install/configuration/dotenv.html +# slogan: Self-hosted, powerful, and privacy-focused chat UI for multiple AI models +# tags: ai,chat,gpt,claude,palm,openai,azure,huggingface,anthropic,ollama,llm +# logo: svgs/librechat.svg +# port: 3080 + +services: + librechat: + image: ghcr.io/danny-avila/librechat-dev-api:latest + depends_on: + mongodb: + condition: service_healthy + rag_api: + condition: service_healthy + environment: + - HOST=0.0.0.0 + - PORT=3080 + - SERVICE_FQDN_LIBRECHAT_3080 + # MongoDB settings + - MONGO_URI=mongodb://${SERVICE_USER_MONGO}:${SERVICE_PASSWORD_MONGO}@mongodb:27017/librechat?authSource=admin + # Meilisearch settings + - MEILI_HOST=http://meilisearch:7700 + - MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI} + # RAG settings + - RAG_PORT=8000 + - RAG_API_URL=http://rag_api:8000 + # Auth settings + - DOMAIN_CLIENT=${SERVICE_FQDN_LIBRECHAT} + - DOMAIN_SERVER=${SERVICE_FQDN_LIBRECHAT} + - JWT_SECRET=${SERVICE_PASSWORD_JWT} + - JWT_REFRESH_SECRET=${SERVICE_PASSWORD_64_JWT} + # App settings + - APP_TITLE=${APP_TITLE:-LibreChat} + - ALLOW_EMAIL_LOGIN=${ALLOW_EMAIL_LOGIN:-true} + - ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-true} + - ALLOW_SOCIAL_LOGIN=${ALLOW_SOCIAL_LOGIN:-false} + - ALLOW_SOCIAL_REGISTRATION=${ALLOW_SOCIAL_REGISTRATION:-false} + - ALLOW_PASSWORD_RESET=${ALLOW_PASSWORD_RESET:-false} + - ALLOW_UNVERIFIED_EMAIL_LOGIN=${ALLOW_UNVERIFIED_EMAIL_LOGIN:-true} + # Encryption settings + - CREDS_KEY=${SERVICE_PASSWORD_64_CREDS} + - CREDS_IV=${SERVICE_PASSWORD_CREDS} + # API Keys + - ANTHROPIC_API_KEY=${SERVICE_ANTHROPIC_API_KEY:-user_provided} + - GOOGLE_KEY=${SERVICE_GOOGLE_API_KEY:-user_provided} + - OPENAI_API_KEY=${SERVICE_OPENAI_API_KEY:-user_provided} + - ASSISTANTS_API_KEY=${SERVICE_ASSISTANTS_API_KEY:-user_provided} + # Debug settings + - DEBUG_LOGGING=${DEBUG_LOGGING:-false} + - DEBUG_OPENAI=${DEBUG_OPENAI:-false} + - DEBUG_PLUGINS=${DEBUG_OPENAI:-false} + - NO_INDEX=${NO_INDEX:-true} + healthcheck: + test: + [ + 'CMD', + 'wget', + '--no-verbose', + '--tries=1', + '--spider', + 'http://127.0.0.1:3080/api/health', + ] + interval: 5s + timeout: 10s + retries: 3 + volumes: + - librechat-images:/app/client/public/images + - librechat-logs:/app/api/logs + - librechat-uploads:/app/uploads + - type: bind + source: ./librechat.yaml + target: /app/librechat.yaml + content: | + # For more information, see the Configuration Guide: + # https://www.librechat.ai/docs/configuration/librechat_yaml + + # Configuration version (required) + version: 1.2.8 + + mongodb: + environment: + - MONGO_INITDB_ROOT_USERNAME=${SERVICE_USER_MONGO} + - MONGO_INITDB_ROOT_PASSWORD=${SERVICE_PASSWORD_MONGO} + image: mongo:8 + volumes: + - mongodb-data:/data/db + healthcheck: + test: + [ + 'CMD', + 'mongosh', + '--eval', + "db.runCommand('ping').ok", + '127.0.0.1:27017/test', + '--quiet', + ] + interval: 5s + timeout: 10s + retries: 3 + + meilisearch: + image: getmeili/meilisearch:v1.12.3 + environment: + - MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI} + - MEILI_NO_ANALYTICS=${MEILI_NO_ANALYTICS:-false} + - MEILI_ENV=production + - MEILI_HOST=http://meilisearch:7700 + volumes: + - meilisearch-data:/meili_data + healthcheck: + test: ['CMD', 'curl', '-f', 'http://127.0.0.1:7700/health'] + interval: 2s + timeout: 10s + retries: 15 + + vectordb: + image: ankane/pgvector:latest + environment: + - POSTGRES_DB=rag + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - POSTGRES_HOST_AUTH_METHOD=trust + volumes: + - vectordb-data:/var/lib/postgresql/data + healthcheck: + test: + - CMD + - pg_isready + - '--username=$SERVICE_USER_POSTGRES' + - '--host=127.0.0.1' + - '--port=5432' + - '--dbname=rag' + interval: 2s + timeout: 1m + retries: 5 + start_period: 10s + + rag_api: + image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:latest + depends_on: + vectordb: + condition: service_healthy + environment: + - POSTGRES_DB=rag + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - DB_HOST=vectordb + - DB_USER=${SERVICE_USER_POSTGRES} + - DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - DB_NAME=rag + - RAG_PORT=8000 + - RAG_OPENAI_API_KEY=${SERVICE_OPENAI_API_KEY:-user_provided} + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"] + interval: 5s + timeout: 10s + retries: 10 From bee98e02bc7e6335f3882162266aaeb2b3579aa7 Mon Sep 17 00:00:00 2001 From: howardshand Date: Mon, 11 Aug 2025 05:29:18 -0500 Subject: [PATCH 083/202] feat(service): add Homebox service (#6116) --- public/svgs/homebox.svg | 11 +++++++++++ templates/compose/homebox.yaml | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 public/svgs/homebox.svg create mode 100644 templates/compose/homebox.yaml diff --git a/public/svgs/homebox.svg b/public/svgs/homebox.svg new file mode 100644 index 000000000..08670bbb9 --- /dev/null +++ b/public/svgs/homebox.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/templates/compose/homebox.yaml b/templates/compose/homebox.yaml new file mode 100644 index 000000000..0f645aa93 --- /dev/null +++ b/templates/compose/homebox.yaml @@ -0,0 +1,27 @@ +# documentation: https://github.com/sysadminsmedia/homebox +# slogan: Homebox is the inventory and organization system built for the Home User. +# tags: inventory, home, organize +# logo: svgs/homebox.svg +# port: 7745 + +services: + homebox: + image: 'ghcr.io/sysadminsmedia/homebox:latest' + environment: + - SERVICE_FQDN_HOMEBOX_7745 + - HBOX_OPTIONS_ALLOW_REGISTRATION=${HBOX_OPTIONS_ALLOW_REGISTRATION:-false} + - HBOX_LOG_LEVEL=${HBOX_LOG_LEVEL:-info} + - HBOX_LOG_FORMAT=${HBOX_LOG_FORMAT:-text} + - HBOX_WEB_MAX_UPLOAD_SIZE=${HBOX_WEB_MAX_UPLOAD_SIZE:-10} + - HBOX_MAILER_HOST=${HBOX_MAILER_HOST} + - HBOX_MAILER_PORT=${HBOX_MAILER_PORT:-587} + - HBOX_MAILER_USERNAME=${HBOX_MAILER_USERNAME} + - HBOX_MAILER_PASSWORD=${HBOX_MAILER_PASSWORD} + - HBOX_MAILER_FROM=${HBOX_MAILER_FROM} + volumes: + - 'homebox-data:/data/' + healthcheck: + test: ["CMD", "sh", "-c", "wget --method=GET -qO- http://localhost:7745/api/v1/status > /dev/null || exit 1"] + interval: 30s + timeout: 5s + retries: 10 From 7dcb5c43ae91aded4d50c60c596a43d84fc10878 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:31:09 +0200 Subject: [PATCH 084/202] chore(service): homebox formatting --- templates/compose/homebox.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/homebox.yaml b/templates/compose/homebox.yaml index 0f645aa93..6d3f3e597 100644 --- a/templates/compose/homebox.yaml +++ b/templates/compose/homebox.yaml @@ -6,7 +6,7 @@ services: homebox: - image: 'ghcr.io/sysadminsmedia/homebox:latest' + image: ghcr.io/sysadminsmedia/homebox:latest environment: - SERVICE_FQDN_HOMEBOX_7745 - HBOX_OPTIONS_ALLOW_REGISTRATION=${HBOX_OPTIONS_ALLOW_REGISTRATION:-false} @@ -19,7 +19,7 @@ services: - HBOX_MAILER_PASSWORD=${HBOX_MAILER_PASSWORD} - HBOX_MAILER_FROM=${HBOX_MAILER_FROM} volumes: - - 'homebox-data:/data/' + - homebox-data:/data healthcheck: test: ["CMD", "sh", "-c", "wget --method=GET -qO- http://localhost:7745/api/v1/status > /dev/null || exit 1"] interval: 30s From 9ec72e8769fc75b82d29bcf9f288215cdd6cdd61 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:32:01 +0200 Subject: [PATCH 085/202] refactor(service): improve librechat - remove comments - format and reorder service --- templates/compose/librechat.yaml | 104 +++++++++++++++---------------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/templates/compose/librechat.yaml b/templates/compose/librechat.yaml index e5647bb2d..fcb8f8c6d 100644 --- a/templates/compose/librechat.yaml +++ b/templates/compose/librechat.yaml @@ -7,29 +7,19 @@ services: librechat: image: ghcr.io/danny-avila/librechat-dev-api:latest - depends_on: - mongodb: - condition: service_healthy - rag_api: - condition: service_healthy environment: - - HOST=0.0.0.0 - - PORT=3080 - SERVICE_FQDN_LIBRECHAT_3080 - # MongoDB settings - - MONGO_URI=mongodb://${SERVICE_USER_MONGO}:${SERVICE_PASSWORD_MONGO}@mongodb:27017/librechat?authSource=admin - # Meilisearch settings - - MEILI_HOST=http://meilisearch:7700 - - MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI} - # RAG settings - - RAG_PORT=8000 - - RAG_API_URL=http://rag_api:8000 - # Auth settings - DOMAIN_CLIENT=${SERVICE_FQDN_LIBRECHAT} - DOMAIN_SERVER=${SERVICE_FQDN_LIBRECHAT} + - HOST=0.0.0.0 + - PORT=3080 + - MONGO_URI=mongodb://${SERVICE_USER_MONGO}:${SERVICE_PASSWORD_MONGO}@mongodb:27017/librechat?authSource=admin + - MEILI_HOST=http://meilisearch:7700 + - MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI} + - RAG_PORT=8000 + - RAG_API_URL=http://rag-api:8000 - JWT_SECRET=${SERVICE_PASSWORD_JWT} - JWT_REFRESH_SECRET=${SERVICE_PASSWORD_64_JWT} - # App settings - APP_TITLE=${APP_TITLE:-LibreChat} - ALLOW_EMAIL_LOGIN=${ALLOW_EMAIL_LOGIN:-true} - ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-true} @@ -37,32 +27,16 @@ services: - ALLOW_SOCIAL_REGISTRATION=${ALLOW_SOCIAL_REGISTRATION:-false} - ALLOW_PASSWORD_RESET=${ALLOW_PASSWORD_RESET:-false} - ALLOW_UNVERIFIED_EMAIL_LOGIN=${ALLOW_UNVERIFIED_EMAIL_LOGIN:-true} - # Encryption settings - CREDS_KEY=${SERVICE_PASSWORD_64_CREDS} - CREDS_IV=${SERVICE_PASSWORD_CREDS} - # API Keys - ANTHROPIC_API_KEY=${SERVICE_ANTHROPIC_API_KEY:-user_provided} - GOOGLE_KEY=${SERVICE_GOOGLE_API_KEY:-user_provided} - OPENAI_API_KEY=${SERVICE_OPENAI_API_KEY:-user_provided} - ASSISTANTS_API_KEY=${SERVICE_ASSISTANTS_API_KEY:-user_provided} - # Debug settings - DEBUG_LOGGING=${DEBUG_LOGGING:-false} - DEBUG_OPENAI=${DEBUG_OPENAI:-false} - DEBUG_PLUGINS=${DEBUG_OPENAI:-false} - NO_INDEX=${NO_INDEX:-true} - healthcheck: - test: - [ - 'CMD', - 'wget', - '--no-verbose', - '--tries=1', - '--spider', - 'http://127.0.0.1:3080/api/health', - ] - interval: 5s - timeout: 10s - retries: 3 volumes: - librechat-images:/app/client/public/images - librechat-logs:/app/api/logs @@ -71,28 +45,46 @@ services: source: ./librechat.yaml target: /app/librechat.yaml content: | - # For more information, see the Configuration Guide: - # https://www.librechat.ai/docs/configuration/librechat_yaml - - # Configuration version (required) version: 1.2.8 + depends_on: + mongodb: + condition: service_healthy + meilisearch: + condition: service_healthy + vectordb: + condition: service_healthy + rag-api: + condition: service_healthy + healthcheck: + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://127.0.0.1:3080/api/health", + ] + interval: 5s + timeout: 10s + retries: 5 mongodb: + image: mongo:8 environment: - MONGO_INITDB_ROOT_USERNAME=${SERVICE_USER_MONGO} - MONGO_INITDB_ROOT_PASSWORD=${SERVICE_PASSWORD_MONGO} - image: mongo:8 volumes: - mongodb-data:/data/db healthcheck: test: [ - 'CMD', - 'mongosh', - '--eval', + "CMD", + "mongosh", + "--eval", "db.runCommand('ping').ok", - '127.0.0.1:27017/test', - '--quiet', + "127.0.0.1:27017/test", + "--quiet", ] interval: 5s timeout: 10s @@ -108,7 +100,7 @@ services: volumes: - meilisearch-data:/meili_data healthcheck: - test: ['CMD', 'curl', '-f', 'http://127.0.0.1:7700/health'] + test: ["CMD", "curl", "-f", "http://127.0.0.1:7700/health"] interval: 2s timeout: 10s retries: 15 @@ -126,20 +118,17 @@ services: test: - CMD - pg_isready - - '--username=$SERVICE_USER_POSTGRES' - - '--host=127.0.0.1' - - '--port=5432' - - '--dbname=rag' + - "--username=$SERVICE_USER_POSTGRES" + - "--host=127.0.0.1" + - "--port=5432" + - "--dbname=rag" interval: 2s timeout: 1m retries: 5 start_period: 10s - rag_api: + rag-api: image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:latest - depends_on: - vectordb: - condition: service_healthy environment: - POSTGRES_DB=rag - POSTGRES_USER=${SERVICE_USER_POSTGRES} @@ -150,8 +139,17 @@ services: - DB_NAME=rag - RAG_PORT=8000 - RAG_OPENAI_API_KEY=${SERVICE_OPENAI_API_KEY:-user_provided} + depends_on: + vectordb: + condition: service_healthy healthcheck: - test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"] + test: + [ + "CMD", + "python", + "-c", + "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')", + ] interval: 5s timeout: 10s retries: 10 From e572017d27be55e56adf19d2351bfbab7cd1f679 Mon Sep 17 00:00:00 2001 From: Yanluis Fermin <32645451+Jacxk@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:03:46 -0400 Subject: [PATCH 086/202] fix(api): duplicated logs in application endpoint (#6292) --- bootstrap/helpers/docker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 944c51e3c..739f98f22 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -1101,7 +1101,7 @@ function getContainerLogs(Server $server, string $container_id, int $lines = 100 ], $server); } - $output .= removeAnsiColors($output); + $output = removeAnsiColors($output); return $output; } From d53e493dcc601988490a210c90781e8c07d7c69e Mon Sep 17 00:00:00 2001 From: Aaryan meena <134821046+aaryan359@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:03:31 +0530 Subject: [PATCH 087/202] chore: clarify usage of custom redis configuration (#6321) --- .../project/database/redis/general.blade.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/project/database/redis/general.blade.php b/resources/views/livewire/project/database/redis/general.blade.php index 577c0d3e9..b4876f325 100644 --- a/resources/views/livewire/project/database/redis/general.blade.php +++ b/resources/views/livewire/project/database/redis/general.blade.php @@ -132,8 +132,16 @@ id="database.public_port" label="Public Port" />
+ placeholder="# maxmemory 256mb +# maxmemory-policy allkeys-lru +# timeout 300" + helper="You only need to provide the Redis directives you want to override — Redis will use default values for everything else.

+⚠️ Important: Coolify automatically applies the requirepass directive using the password shown in the Password field above. If you override requirepass in your custom configuration, make sure it matches the password field to avoid authentication issues.

+🔗 Tip: View the full Redis default configuration to see what options are available." + label="Custom Redis Configuration" rows="10" id="database.redis_conf" /> + + +

Advanced

Date: Mon, 11 Aug 2025 09:03:13 -0400 Subject: [PATCH 088/202] fix(service): documenso signees always pending (#6334) --- templates/compose/documenso.yaml | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index 6a3873799..97ae6f918 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -26,6 +26,16 @@ services: - NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS} - NEXT_PRIVATE_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public + - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/app/apps/remix/certs/certificate.p12 + - NEXT_PRIVATE_SIGNING_PASSPHRASE=${SERVICE_PASSWORD_DOCUMENSO} + - CERT_VALID_DAYS=${CERT_VALID_DAYS:-365} + - CERT_INFO_COUNTRY_NAME=${CERT_INFO_COUNTRY_NAME:-DO} + - CERT_INFO_STATE_OR_PROVIDENCE=${CERT_INFO_STATE_OR_PROVIDENCE:-Santiago} + - CERT_INFO_LOCALITY_NAME=${CERT_INFO_LOCALITY_NAME:-Santiago} + - CERT_INFO_ORGANIZATION_NAME=${CERT_INFO_ORGANIZATION_NAME:-Example INC} + - CERT_INFO_ORGANIZATIONAL_UNIT=${CERT_INFO_ORGANIZATIONAL_UNIT:-IT Department} + - CERT_INFO_EMAIL=${CERT_INFO_EMAIL:-example@gmail.com} + - NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_LOGIN:-false} healthcheck: test: - CMD-SHELL @@ -33,6 +43,55 @@ services: interval: 2s timeout: 10s retries: 20 + entrypoint: + - /bin/sh + - -c + - | + echo "./certs" > /tmp/certs_dir_path + echo "./make-certs.sh" > /tmp/cert_script_path + echo "${SERVICE_PASSWORD_DOCUMENSO}" > /tmp/cert_pass + + touch /tmp/cert_info_path + cat < /tmp/cert_info_path + [ req ] + distinguished_name = req_distinguished_name + prompt = no + [ req_distinguished_name ] + C = ${CERT_INFO_COUNTRY_NAME} + ST = ${CERT_INFO_STATE_OR_PROVIDENCE} + L = ${CERT_INFO_LOCALITY_NAME} + O = ${CERT_INFO_ORGANIZATION_NAME} + OU = ${CERT_INFO_ORGANIZATIONAL_UNIT} + CN = ${SERVICE_FQDN_DOCUMENSO} + emailAddress = ${CERT_INFO_EMAIL} + EOF + + cat < "$(cat /tmp/cert_script_path)" + mkdir -p "$(cat /tmp/certs_dir_path)" && cd "$(cat /tmp/certs_dir_path)" + + openssl genrsa -out private.key 2048 + + openssl req \ + -new \ + -x509 \ + -key private.key \ + -out certificate.crt \ + -days ${CERT_VALID_DAYS} \ + -config /tmp/cert_info_path + + openssl pkcs12 \ + -export \ + -out certificate.p12 \ + -inkey private.key \ + -in certificate.crt \ + -legacy \ + -password file:/tmp/cert_pass + EOF + chmod +x "$(cat /tmp/cert_script_path)" + + sh "$(cat /tmp/cert_script_path)" + + ./start.sh database: image: postgres:17 From c404581b25c8c5319dc9fe1460bdedd9c3c532ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?= <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:22:03 +0200 Subject: [PATCH 089/202] fix(database): custom postgres configs with SSL (#6352) --- app/Actions/Database/StartPostgresql.php | 26 +++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index a40eac17b..4314ccd2f 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -185,6 +185,8 @@ class StartPostgresql } } + $command = ['postgres']; + if (filled($this->database->postgres_conf)) { $docker_compose['services'][$container_name]['volumes'] = array_merge( $docker_compose['services'][$container_name]['volumes'], @@ -195,29 +197,25 @@ class StartPostgresql 'read_only' => true, ]] ); - $docker_compose['services'][$container_name]['command'] = [ - 'postgres', - '-c', - 'config_file=/etc/postgresql/postgresql.conf', - ]; + $command = array_merge($command, ['-c', 'config_file=/etc/postgresql/postgresql.conf']); } if ($this->database->enable_ssl) { - $docker_compose['services'][$container_name]['command'] = [ - 'postgres', - '-c', - 'ssl=on', - '-c', - 'ssl_cert_file=/var/lib/postgresql/certs/server.crt', - '-c', - 'ssl_key_file=/var/lib/postgresql/certs/server.key', - ]; + $command = array_merge($command, [ + '-c', 'ssl=on', + '-c', 'ssl_cert_file=/var/lib/postgresql/certs/server.crt', + '-c', 'ssl_key_file=/var/lib/postgresql/certs/server.key', + ]); } // Add custom docker run options $docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + if (count($command) > 1) { + $docker_compose['services'][$container_name]['command'] = $command; + } + $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; From 9b0fd2073a06a1f470d5b2b36bae9f43f4ab19c7 Mon Sep 17 00:00:00 2001 From: Yanluis Fermin <32645451+Jacxk@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:31:12 -0400 Subject: [PATCH 090/202] fix(api): update service upsert to retain name and description values if not set --- app/Http/Controllers/Api/ServicesController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index f2d6c4dcf..1db2a3663 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -713,8 +713,8 @@ class ServicesController extends Controller } $connectToDockerNetwork = $request->connect_to_docker_network ?? false; - $service->name = $request->name ?? null; - $service->description = $request->description ?? null; + $service->name = $request->name ?? $service->name; + $service->description = $request->description ?? $service->description; $service->environment_id = $environment->id; $service->server_id = $server->id; $service->destination_id = $destination->id; From bf738b2d2d8cb749a35ce7c74298f6b9961ad21b Mon Sep 17 00:00:00 2001 From: Verity <83372423+CallMeVerity@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:55:37 +0100 Subject: [PATCH 091/202] feat(service): add pterodactyl & wings services (#5537) --- templates/compose/pterodactyl-with-wings.yaml | 140 ++++++++++++++++++ templates/compose/pterodactyl.yaml | 93 +++--------- templates/compose/wings.yaml | 44 ++++++ 3 files changed, 208 insertions(+), 69 deletions(-) create mode 100644 templates/compose/pterodactyl-with-wings.yaml create mode 100644 templates/compose/wings.yaml diff --git a/templates/compose/pterodactyl-with-wings.yaml b/templates/compose/pterodactyl-with-wings.yaml new file mode 100644 index 000000000..bd0c07cca --- /dev/null +++ b/templates/compose/pterodactyl-with-wings.yaml @@ -0,0 +1,140 @@ +# documentation: https://pterodactyl.io/ +# slogan: Pterodactyl is a free, open-source game server management panel +# tags: game, game server, management, panel, minecraft +# logo: svgs/pterodactyl.png +# port: 80, 8443 + +services: + mariadb: + image: mariadb:10.5 + healthcheck: + test: + ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized || exit 1"] + start_period: 10s + interval: 10s + timeout: 1s + retries: 3 + environment: + - MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MYSQLROOT + - MYSQL_DATABASE=pterodactyl-db + - MYSQL_USER=$SERVICE_USER_MYSQL + - MYSQL_PASSWORD=$SERVICE_PASSWORD_MYSQL + volumes: + - pterodactyl-db:/var/lib/mysql + + redis: + image: redis:alpine + healthcheck: + test: ["CMD-SHELL", "redis-cli ping || exit 1"] + interval: 10s + timeout: 1s + retries: 3 + + pterodactyl: + image: ghcr.io/pterodactyl/panel:latest + volumes: + - "panel-var:/app/var/" + - "panel-nginx:/etc/nginx/http.d/" + - "panel-certs:/etc/letsencrypt/" + - type: bind + source: ./etc/entrypoint.sh + target: /entrypoint.sh + mode: "0755" + content: | + #!/bin/sh + set -e + + echo "Setting logs permissions..." + chown -R nginx: /app/storage/logs/ + + USER_EXISTS=$(php artisan tinker --no-ansi --execute='echo \Pterodactyl\Models\User::where("email", "'"$ADMIN_EMAIL"'")->exists() ? "1" : "0";') + + if [ "$USER_EXISTS" = "0" ]; then + echo "Admin User does not exist, creating user now." + php artisan p:user:make --no-interaction \ + --admin=1 \ + --email="$ADMIN_EMAIL" \ + --username="$ADMIN_USERNAME" \ + --name-first="$ADMIN_FIRSTNAME" \ + --name-last="$ADMIN_LASTNAME" \ + --password="$ADMIN_PASSWORD" + echo "Admin user created successfully!" + else + echo "Admin User already exists, skipping creation." + fi + + exec supervisord --nodaemon + command: ["/entrypoint.sh"] + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"] + interval: 10s + timeout: 1s + retries: 3 + environment: + - SERVICE_FQDN_PTERODACTYL_80 + - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com} + - ADMIN_USERNAME=${SERVICE_USER_ADMIN} + - ADMIN_FIRSTNAME=${ADMIN_FIRSTNAME:-Admin} + - ADMIN_LASTNAME=${ADMIN_LASTNAME:-User} + - ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN} + - PTERODACTYL_HTTPS=${PTERODACTYL_HTTPS:-false} + - APP_ENV=production + - APP_ENVIRONMENT_ONLY=false + - APP_URL=$SERVICE_FQDN_PTERODACTYL + - APP_TIMEZONE=${TIMEZONE:-UTC} + - APP_SERVICE_AUTHOR=${APP_SERVICE_AUTHOR:-author@example.com} + - LOG_LEVEL=${LOG_LEVEL:-debug} + - CACHE_DRIVER=redis + - SESSION_DRIVER=redis + - QUEUE_DRIVER=redis + - REDIS_HOST=redis + - DB_DATABASE=pterodactyl-db + - DB_USERNAME=$SERVICE_USER_MYSQL + - DB_HOST=mariadb + - DB_PORT=3306 + - DB_PASSWORD=$SERVICE_PASSWORD_MYSQL + - MAIL_FROM=$MAIL_FROM + - MAIL_DRIVER=$MAIL_DRIVER + - MAIL_HOST=$MAIL_HOST + - MAIL_PORT=$MAIL_PORT + - MAIL_USERNAME=$MAIL_USERNAME + - MAIL_PASSWORD=$MAIL_PASSWORD + - MAIL_ENCRYPTION=$MAIL_ENCRYPTION + + wings: + image: "ghcr.io/pterodactyl/wings:latest" + environment: + - SERVICE_FQDN_WINGS_8443 + - "TZ=${TIMEZONE:-UTC}" + - WINGS_USERNAME=$SERVICE_USER_WINGS + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + - "/var/lib/docker/containers/:/var/lib/docker/containers/" + - "/var/lib/pterodactyl/volumes:/var/lib/pterodactyl/volumes" + - "/tmp/pterodactyl:/tmp/pterodactyl" + - wings_lib:/var/lib/pterodactyl/ + - wings_logs:/var/log/pterodactyl/ + - type: bind + source: ./etc/config.yml + target: /etc/pterodactyl/config.yml + content: | + debug: false + uuid: ReplaceConfig + token_id: ReplaceConfig + token: ReplaceConfig + api: + host: 0.0.0.0 + port: 8443 # Warning, panel must have 443 as daemon port, while here it should should be 8443, FQDN in Coolify for this service should be https://*:8443 + ssl: + enabled: false + cert: ReplaceConfig + key: ReplaceConfig + upload_limit: 100 + system: + data: /var/lib/pterodactyl/volumes + sftp: + bind_port: 2022 + allowed_mounts: [] + remote: '' + ports: + - '2022:2022' \ No newline at end of file diff --git a/templates/compose/pterodactyl.yaml b/templates/compose/pterodactyl.yaml index ea64de47a..32939ad7e 100644 --- a/templates/compose/pterodactyl.yaml +++ b/templates/compose/pterodactyl.yaml @@ -1,4 +1,3 @@ -# ignore: true # documentation: https://pterodactyl.io/ # slogan: Pterodactyl is a free, open-source game server management panel # tags: game, game server, management, panel, minecraft @@ -8,8 +7,6 @@ services: mariadb: image: mariadb:10.5 - restart: unless-stopped - command: --default-authentication-plugin=mysql_native_password healthcheck: test: ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized || exit 1"] @@ -18,17 +15,15 @@ services: timeout: 1s retries: 3 environment: - - SERVICE_PASSWORD_MYSQL - MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MYSQLROOT - - MYSQL_DATABASE=panel - - MYSQL_USER=pterodactyl + - MYSQL_DATABASE=pterodactyl-db + - MYSQL_USER=$SERVICE_USER_MYSQL - MYSQL_PASSWORD=$SERVICE_PASSWORD_MYSQL volumes: - pterodactyl-db:/var/lib/mysql redis: image: redis:alpine - restart: unless-stopped healthcheck: test: ["CMD-SHELL", "redis-cli ping || exit 1"] interval: 10s @@ -37,7 +32,6 @@ services: pterodactyl: image: ghcr.io/pterodactyl/panel:latest - restart: unless-stopped volumes: - "panel-var:/app/var/" - "panel-nginx:/etc/nginx/http.d/" @@ -50,28 +44,26 @@ services: #!/bin/sh set -e - echo "Waiting for services to be ready..." - sleep 30 + echo "Setting logs permissions..." + chown -R nginx: /app/storage/logs/ - echo "Setting logs permissions..." - chown -R nginx: /app/storage/logs/ + USER_EXISTS=$(php artisan tinker --no-ansi --execute='echo \Pterodactyl\Models\User::where("email", "'"$ADMIN_EMAIL"'")->exists() ? "1" : "0";') - if ! php artisan p:user:list | grep -q "$ADMIN_EMAIL"; then - echo "Creating admin user..." - php artisan p:user:make --no-interaction \ - --admin=1 \ - --email="$ADMIN_EMAIL" \ - --username="$ADMIN_USERNAME" \ - --name-first="$ADMIN_FIRSTNAME" \ - --name-last="$ADMIN_LASTNAME" \ - --password="$ADMIN_PASSWORD" - echo "Admin user created" - else - echo "Admin user already exists, skipping creation" - fi - - exec supervisord -c --nodaemon + if [ "$USER_EXISTS" = "0" ]; then + echo "Admin User does not exist, creating user now." + php artisan p:user:make --no-interaction \ + --admin=1 \ + --email="$ADMIN_EMAIL" \ + --username="$ADMIN_USERNAME" \ + --name-first="$ADMIN_FIRSTNAME" \ + --name-last="$ADMIN_LASTNAME" \ + --password="$ADMIN_PASSWORD" + echo "Admin user created successfully!" + else + echo "Admin User already exists, skipping creation." + fi + exec supervisord --nodaemon command: ["/entrypoint.sh"] healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"] @@ -79,7 +71,7 @@ services: timeout: 1s retries: 3 environment: - - SERVICE_FQDN_PTERODACTYL + - SERVICE_FQDN_PTERODACTYL_80 - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com} - ADMIN_USERNAME=${SERVICE_USER_ADMIN} - ADMIN_FIRSTNAME=${ADMIN_FIRSTNAME:-Admin} @@ -88,7 +80,7 @@ services: - PTERODACTYL_HTTPS=${PTERODACTYL_HTTPS:-false} - APP_ENV=production - APP_ENVIRONMENT_ONLY=false - - APP_URL=${PTERODACTYL_PUBLIC_FQDN:-$SERVICE_FQDN_PTERODACTYL} + - APP_URL=$SERVICE_FQDN_PTERODACTYL - APP_TIMEZONE=${TIMEZONE:-UTC} - APP_SERVICE_AUTHOR=${APP_SERVICE_AUTHOR:-author@example.com} - LOG_LEVEL=${LOG_LEVEL:-debug} @@ -96,6 +88,8 @@ services: - SESSION_DRIVER=redis - QUEUE_DRIVER=redis - REDIS_HOST=redis + - DB_DATABASE=pterodactyl-db + - DB_USERNAME=$SERVICE_USER_MYSQL - DB_HOST=mariadb - DB_PORT=3306 - DB_PASSWORD=$SERVICE_PASSWORD_MYSQL @@ -105,43 +99,4 @@ services: - MAIL_PORT=$MAIL_PORT - MAIL_USERNAME=$MAIL_USERNAME - MAIL_PASSWORD=$MAIL_PASSWORD - - MAIL_ENCRYPTION=$MAIL_ENCRYPTION - - wings: - image: ghcr.io/pterodactyl/wings:latest - restart: unless-stopped - environment: - - SERVICE_FQDN_WINGS_8080 - - TZ=${TIMEZONE:-UTC} - - WINGS_USERNAME=pterodactyl - volumes: - - "/var/run/docker.sock:/var/run/docker.sock" - - "/var/lib/docker/containers/:/var/lib/docker/containers/" - - "/var/lib/pterodactyl/:/var/lib/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902 - - "/tmp/pterodactyl/:/tmp/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902 - - "wings-logs:/var/log/pterodactyl/" - - - type: bind - source: ./etc/config.yml - target: /etc/pterodactyl/config.yml - content: | - docker: - network: - interface: 172.28.0.1 - dns: - - 1.1.1.1 - - 1.0.0.1 - name: pterodactyl_nw - ispn: false - driver: "" - network_mode: pterodactyl_nw - is_internal: false - enable_icc: true - network_mtu: 1500 - interfaces: - v4: - subnet: 172.28.0.0/16 - gateway: 172.28.0.1 - v6: - subnet: fdba:17c8:6c94::/64 - gateway: fdba:17c8:6c94::1011 + - MAIL_ENCRYPTION=$MAIL_ENCRYPTION \ No newline at end of file diff --git a/templates/compose/wings.yaml b/templates/compose/wings.yaml new file mode 100644 index 000000000..ce4a073f8 --- /dev/null +++ b/templates/compose/wings.yaml @@ -0,0 +1,44 @@ +# documentation: https://pterodactyl.io/ +# slogan: Wings is Pterodactyl's server control plane +# tags: game, game server, management, panel, minecraft +# logo: svgs/pterodactyl.png +# port: 8443 + +services: + wings: + image: "ghcr.io/pterodactyl/wings:latest" + environment: + - SERVICE_FQDN_WINGS_8443 + - "TZ=${TIMEZONE:-UTC}" + - WINGS_USERNAME=$SERVICE_USER_WINGS + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + - "/var/lib/docker/containers/:/var/lib/docker/containers/" + - "/var/lib/pterodactyl/volumes:/var/lib/pterodactyl/volumes" + - "/tmp/pterodactyl:/tmp/pterodactyl" + - wings_lib:/var/lib/pterodactyl/ + - wings_logs:/var/log/pterodactyl/ + - type: bind + source: ./etc/config.yml + target: /etc/pterodactyl/config.yml + content: | + debug: false + uuid: ReplaceConfig + token_id: ReplaceConfig + token: ReplaceConfig + api: + host: 0.0.0.0 + port: 8443 # Warning, panel must have 443 as daemon port, while here it should should be 8443, FQDN in Coolify for this service should be https://*:8443 + ssl: + enabled: false + cert: ReplaceConfig + key: ReplaceConfig + upload_limit: 100 + system: + data: /var/lib/pterodactyl/volumes + sftp: + bind_port: 2022 + allowed_mounts: [] + remote: '' + ports: + - '2022:2022' \ No newline at end of file From 9d7e4286b66076f305dbf1876dc192a9d14a4686 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:25:59 +0200 Subject: [PATCH 092/202] Update service-templates.json --- templates/service-templates.json | 83 +++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/templates/service-templates.json b/templates/service-templates.json index 9f2c0a773..49884462f 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -685,7 +685,7 @@ "documenso": { "documentation": "https://docs.documenso.com/?utm_source=coolify.io", "slogan": "Document signing, finally open source", - "compose": "c2VydmljZXM6CiAgZG9jdW1lbnNvOgogICAgaW1hZ2U6IGRvY3VtZW5zby9kb2N1bWVuc28KICAgIGRlcGVuZHNfb246CiAgICAgIGRhdGFiYXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRE9DVU1FTlNPXzMwMDAKICAgICAgLSAnTkVYVEFVVEhfVVJMPSR7U0VSVklDRV9GUUROX0RPQ1VNRU5TT30nCiAgICAgIC0gJ05FWFRBVVRIX1NFQ1JFVD0ke1NFUlZJQ0VfQkFTRTY0X0FVVEhTRUNSRVR9JwogICAgICAtICdORVhUX1BSSVZBVEVfRU5DUllQVElPTl9LRVk9JHtTRVJWSUNFX0JBU0U2NF9FTkNSWVBUSU9OS0VZfScKICAgICAgLSAnTkVYVF9QUklWQVRFX0VOQ1JZUFRJT05fU0VDT05EQVJZX0tFWT0ke1NFUlZJQ0VfQkFTRTY0X1NFQ09OREFSWUVOQ1JZUFRJT05LRVl9JwogICAgICAtICdORVhUX1BVQkxJQ19XRUJBUFBfVVJMPSR7U0VSVklDRV9GUUROX0RPQ1VNRU5TT30nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX1RSQU5TUE9SVD0ke05FWFRfUFJJVkFURV9TTVRQX1RSQU5TUE9SVH0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0hPU1Q9JHtORVhUX1BSSVZBVEVfU01UUF9IT1NUfScKICAgICAgLSAnTkVYVF9QUklWQVRFX1NNVFBfUE9SVD0ke05FWFRfUFJJVkFURV9TTVRQX1BPUlR9JwogICAgICAtICdORVhUX1BSSVZBVEVfU01UUF9VU0VSTkFNRT0ke05FWFRfUFJJVkFURV9TTVRQX1VTRVJOQU1FfScKICAgICAgLSAnTkVYVF9QUklWQVRFX1NNVFBfUEFTU1dPUkQ9JHtORVhUX1BSSVZBVEVfU01UUF9QQVNTV09SRH0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0ZST01fTkFNRT0ke05FWFRfUFJJVkFURV9TTVRQX0ZST01fTkFNRX0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0ZST01fQUREUkVTUz0ke05FWFRfUFJJVkFURV9TTVRQX0ZST01fQUREUkVTU30nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9EQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRhdGFiYXNlLyR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0/c2NoZW1hPXB1YmxpYycKICAgICAgLSAnTkVYVF9QUklWQVRFX0RJUkVDVF9EQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRhdGFiYXNlLyR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0/c2NoZW1hPXB1YmxpYycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAid2dldCAtcSAtTyAtIGh0dHA6Ly9kb2N1bWVuc286MzAwMC8gfCBncmVwIC1xICdTaWduIGluIHRvIHlvdXIgYWNjb3VudCciCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICBkYXRhYmFzZToKICAgIGltYWdlOiAncG9zdGdyZXM6MTcnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdkb2N1bWVuc29fcG9zdGdyZXNxbF9kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "compose": "c2VydmljZXM6CiAgZG9jdW1lbnNvOgogICAgaW1hZ2U6IGRvY3VtZW5zby9kb2N1bWVuc28KICAgIGRlcGVuZHNfb246CiAgICAgIGRhdGFiYXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRE9DVU1FTlNPXzMwMDAKICAgICAgLSAnTkVYVEFVVEhfVVJMPSR7U0VSVklDRV9GUUROX0RPQ1VNRU5TT30nCiAgICAgIC0gJ05FWFRBVVRIX1NFQ1JFVD0ke1NFUlZJQ0VfQkFTRTY0X0FVVEhTRUNSRVR9JwogICAgICAtICdORVhUX1BSSVZBVEVfRU5DUllQVElPTl9LRVk9JHtTRVJWSUNFX0JBU0U2NF9FTkNSWVBUSU9OS0VZfScKICAgICAgLSAnTkVYVF9QUklWQVRFX0VOQ1JZUFRJT05fU0VDT05EQVJZX0tFWT0ke1NFUlZJQ0VfQkFTRTY0X1NFQ09OREFSWUVOQ1JZUFRJT05LRVl9JwogICAgICAtICdORVhUX1BVQkxJQ19XRUJBUFBfVVJMPSR7U0VSVklDRV9GUUROX0RPQ1VNRU5TT30nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX1RSQU5TUE9SVD0ke05FWFRfUFJJVkFURV9TTVRQX1RSQU5TUE9SVH0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0hPU1Q9JHtORVhUX1BSSVZBVEVfU01UUF9IT1NUfScKICAgICAgLSAnTkVYVF9QUklWQVRFX1NNVFBfUE9SVD0ke05FWFRfUFJJVkFURV9TTVRQX1BPUlR9JwogICAgICAtICdORVhUX1BSSVZBVEVfU01UUF9VU0VSTkFNRT0ke05FWFRfUFJJVkFURV9TTVRQX1VTRVJOQU1FfScKICAgICAgLSAnTkVYVF9QUklWQVRFX1NNVFBfUEFTU1dPUkQ9JHtORVhUX1BSSVZBVEVfU01UUF9QQVNTV09SRH0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0ZST01fTkFNRT0ke05FWFRfUFJJVkFURV9TTVRQX0ZST01fTkFNRX0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0ZST01fQUREUkVTUz0ke05FWFRfUFJJVkFURV9TTVRQX0ZST01fQUREUkVTU30nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9EQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRhdGFiYXNlLyR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0/c2NoZW1hPXB1YmxpYycKICAgICAgLSAnTkVYVF9QUklWQVRFX0RJUkVDVF9EQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRhdGFiYXNlLyR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0/c2NoZW1hPXB1YmxpYycKICAgICAgLSBORVhUX1BSSVZBVEVfU0lHTklOR19MT0NBTF9GSUxFX1BBVEg9L2FwcC9hcHBzL3JlbWl4L2NlcnRzL2NlcnRpZmljYXRlLnAxMgogICAgICAtICdORVhUX1BSSVZBVEVfU0lHTklOR19QQVNTUEhSQVNFPSR7U0VSVklDRV9QQVNTV09SRF9ET0NVTUVOU099JwogICAgICAtICdDRVJUX1ZBTElEX0RBWVM9JHtDRVJUX1ZBTElEX0RBWVM6LTM2NX0nCiAgICAgIC0gJ0NFUlRfSU5GT19DT1VOVFJZX05BTUU9JHtDRVJUX0lORk9fQ09VTlRSWV9OQU1FOi1ET30nCiAgICAgIC0gJ0NFUlRfSU5GT19TVEFURV9PUl9QUk9WSURFTkNFPSR7Q0VSVF9JTkZPX1NUQVRFX09SX1BST1ZJREVOQ0U6LVNhbnRpYWdvfScKICAgICAgLSAnQ0VSVF9JTkZPX0xPQ0FMSVRZX05BTUU9JHtDRVJUX0lORk9fTE9DQUxJVFlfTkFNRTotU2FudGlhZ299JwogICAgICAtICdDRVJUX0lORk9fT1JHQU5JWkFUSU9OX05BTUU9JHtDRVJUX0lORk9fT1JHQU5JWkFUSU9OX05BTUU6LUV4YW1wbGUgSU5DfScKICAgICAgLSAnQ0VSVF9JTkZPX09SR0FOSVpBVElPTkFMX1VOSVQ9JHtDRVJUX0lORk9fT1JHQU5JWkFUSU9OQUxfVU5JVDotSVQgRGVwYXJ0bWVudH0nCiAgICAgIC0gJ0NFUlRfSU5GT19FTUFJTD0ke0NFUlRfSU5GT19FTUFJTDotZXhhbXBsZUBnbWFpbC5jb219JwogICAgICAtICdORVhUX1BVQkxJQ19ESVNBQkxFX1NJR05VUD0ke0RJU0FCTEVfTE9HSU46LWZhbHNlfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAid2dldCAtcSAtTyAtIGh0dHA6Ly9kb2N1bWVuc286MzAwMC8gfCBncmVwIC1xICdTaWduIGluIHRvIHlvdXIgYWNjb3VudCciCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICAgIGVudHJ5cG9pbnQ6CiAgICAgIC0gL2Jpbi9zaAogICAgICAtICctYycKICAgICAgLSAiZWNobyBcIi4vY2VydHNcIiA+IC90bXAvY2VydHNfZGlyX3BhdGhcbmVjaG8gXCIuL21ha2UtY2VydHMuc2hcIiA+IC90bXAvY2VydF9zY3JpcHRfcGF0aFxuZWNobyBcIiR7U0VSVklDRV9QQVNTV09SRF9ET0NVTUVOU099XCIgPiAvdG1wL2NlcnRfcGFzc1xuXG50b3VjaCAvdG1wL2NlcnRfaW5mb19wYXRoXG5jYXQgPDxFT0YgPiAvdG1wL2NlcnRfaW5mb19wYXRoXG5bIHJlcSBdXG5kaXN0aW5ndWlzaGVkX25hbWUgPSByZXFfZGlzdGluZ3Vpc2hlZF9uYW1lXG5wcm9tcHQgPSBub1xuWyByZXFfZGlzdGluZ3Vpc2hlZF9uYW1lIF1cbkMgICAgICAgICAgICA9ICR7Q0VSVF9JTkZPX0NPVU5UUllfTkFNRX1cblNUICAgICAgICAgICA9ICR7Q0VSVF9JTkZPX1NUQVRFX09SX1BST1ZJREVOQ0V9XG5MICAgICAgICAgICAgPSAke0NFUlRfSU5GT19MT0NBTElUWV9OQU1FfVxuTyAgICAgICAgICAgID0gJHtDRVJUX0lORk9fT1JHQU5JWkFUSU9OX05BTUV9XG5PVSAgICAgICAgICAgPSAke0NFUlRfSU5GT19PUkdBTklaQVRJT05BTF9VTklUfVxuQ04gICAgICAgICAgID0gJHtTRVJWSUNFX0ZRRE5fRE9DVU1FTlNPfVxuZW1haWxBZGRyZXNzID0gJHtDRVJUX0lORk9fRU1BSUx9XG5FT0ZcblxuY2F0IDw8RU9GID4gXCIkKGNhdCAvdG1wL2NlcnRfc2NyaXB0X3BhdGgpXCJcbm1rZGlyIC1wIFwiJChjYXQgL3RtcC9jZXJ0c19kaXJfcGF0aClcIiAmJiBjZCBcIiQoY2F0IC90bXAvY2VydHNfZGlyX3BhdGgpXCJcblxub3BlbnNzbCBnZW5yc2EgLW91dCBwcml2YXRlLmtleSAyMDQ4XG5cbm9wZW5zc2wgcmVxIFxcXG4gIC1uZXcgXFxcbiAgLXg1MDkgXFxcbiAgLWtleSBwcml2YXRlLmtleSBcXFxuICAtb3V0IGNlcnRpZmljYXRlLmNydCBcXFxuICAtZGF5cyAke0NFUlRfVkFMSURfREFZU30gXFxcbiAgLWNvbmZpZyAvdG1wL2NlcnRfaW5mb19wYXRoXG5cbm9wZW5zc2wgcGtjczEyIFxcXG4gIC1leHBvcnQgXFxcbiAgLW91dCBjZXJ0aWZpY2F0ZS5wMTIgXFxcbiAgLWlua2V5IHByaXZhdGUua2V5IFxcXG4gIC1pbiBjZXJ0aWZpY2F0ZS5jcnQgXFxcbiAgLWxlZ2FjeSBcXFxuICAtcGFzc3dvcmQgZmlsZTovdG1wL2NlcnRfcGFzc1xuRU9GXG5jaG1vZCAreCBcIiQoY2F0IC90bXAvY2VydF9zY3JpcHRfcGF0aClcIlxuXG5zaCBcIiQoY2F0IC90bXAvY2VydF9zY3JpcHRfcGF0aClcIlxuXG4uL3N0YXJ0LnNoXG4iCiAgZGF0YWJhc2U6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE3JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1kb2N1bWVuc28tZGJ9JwogICAgdm9sdW1lczoKICAgICAgLSAnZG9jdW1lbnNvX3Bvc3RncmVzcWxfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", "tags": [ "signing", "opensource", @@ -1480,6 +1480,19 @@ "minversion": "0.0.0", "port": "7575" }, + "homebox": { + "documentation": "https://github.com/sysadminsmedia/homebox?utm_source=coolify.io", + "slogan": "Homebox is the inventory and organization system built for the Home User.", + "compose": "c2VydmljZXM6CiAgaG9tZWJveDoKICAgIGltYWdlOiAnZ2hjci5pby9zeXNhZG1pbnNtZWRpYS9ob21lYm94OmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9IT01FQk9YXzc3NDUKICAgICAgLSAnSEJPWF9PUFRJT05TX0FMTE9XX1JFR0lTVFJBVElPTj0ke0hCT1hfT1BUSU9OU19BTExPV19SRUdJU1RSQVRJT046LWZhbHNlfScKICAgICAgLSAnSEJPWF9MT0dfTEVWRUw9JHtIQk9YX0xPR19MRVZFTDotaW5mb30nCiAgICAgIC0gJ0hCT1hfTE9HX0ZPUk1BVD0ke0hCT1hfTE9HX0ZPUk1BVDotdGV4dH0nCiAgICAgIC0gJ0hCT1hfV0VCX01BWF9VUExPQURfU0laRT0ke0hCT1hfV0VCX01BWF9VUExPQURfU0laRTotMTB9JwogICAgICAtICdIQk9YX01BSUxFUl9IT1NUPSR7SEJPWF9NQUlMRVJfSE9TVH0nCiAgICAgIC0gJ0hCT1hfTUFJTEVSX1BPUlQ9JHtIQk9YX01BSUxFUl9QT1JUOi01ODd9JwogICAgICAtICdIQk9YX01BSUxFUl9VU0VSTkFNRT0ke0hCT1hfTUFJTEVSX1VTRVJOQU1FfScKICAgICAgLSAnSEJPWF9NQUlMRVJfUEFTU1dPUkQ9JHtIQk9YX01BSUxFUl9QQVNTV09SRH0nCiAgICAgIC0gJ0hCT1hfTUFJTEVSX0ZST009JHtIQk9YX01BSUxFUl9GUk9NfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2hvbWVib3gtZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBzaAogICAgICAgIC0gJy1jJwogICAgICAgIC0gJ3dnZXQgLS1tZXRob2Q9R0VUIC1xTy0gaHR0cDovL2xvY2FsaG9zdDo3NzQ1L2FwaS92MS9zdGF0dXMgPiAvZGV2L251bGwgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCg==", + "tags": [ + "inventory", + "home", + "organize" + ], + "logo": "svgs/homebox.svg", + "minversion": "0.0.0", + "port": "7745" + }, "homepage": { "documentation": "https://gethomepage.dev/latest/?utm_source=coolify.io", "slogan": "A modern, fully static, fast, secure fully proxied, highly customizable application dashboard", @@ -1806,6 +1819,27 @@ "minversion": "0.0.0", "port": "8080" }, + "librechat": { + "documentation": "https://docs.librechat.ai/install/configuration/dotenv.html?utm_source=coolify.io", + "slogan": "Self-hosted, powerful, and privacy-focused chat UI for multiple AI models", + "compose": "c2VydmljZXM6CiAgbGlicmVjaGF0OgogICAgaW1hZ2U6ICdnaGNyLmlvL2Rhbm55LWF2aWxhL2xpYnJlY2hhdC1kZXYtYXBpOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9MSUJSRUNIQVRfMzA4MAogICAgICAtICdET01BSU5fQ0xJRU5UPSR7U0VSVklDRV9GUUROX0xJQlJFQ0hBVH0nCiAgICAgIC0gJ0RPTUFJTl9TRVJWRVI9JHtTRVJWSUNFX0ZRRE5fTElCUkVDSEFUfScKICAgICAgLSBIT1NUPTAuMC4wLjAKICAgICAgLSBQT1JUPTMwODAKICAgICAgLSAnTU9OR09fVVJJPW1vbmdvZGI6Ly8ke1NFUlZJQ0VfVVNFUl9NT05HT306JHtTRVJWSUNFX1BBU1NXT1JEX01PTkdPfUBtb25nb2RiOjI3MDE3L2xpYnJlY2hhdD9hdXRoU291cmNlPWFkbWluJwogICAgICAtICdNRUlMSV9IT1NUPWh0dHA6Ly9tZWlsaXNlYXJjaDo3NzAwJwogICAgICAtICdNRUlMSV9NQVNURVJfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9NRUlMSX0nCiAgICAgIC0gUkFHX1BPUlQ9ODAwMAogICAgICAtICdSQUdfQVBJX1VSTD1odHRwOi8vcmFnLWFwaTo4MDAwJwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdKV1RfUkVGUkVTSF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0pXVH0nCiAgICAgIC0gJ0FQUF9USVRMRT0ke0FQUF9USVRMRTotTGlicmVDaGF0fScKICAgICAgLSAnQUxMT1dfRU1BSUxfTE9HSU49JHtBTExPV19FTUFJTF9MT0dJTjotdHJ1ZX0nCiAgICAgIC0gJ0FMTE9XX1JFR0lTVFJBVElPTj0ke0FMTE9XX1JFR0lTVFJBVElPTjotdHJ1ZX0nCiAgICAgIC0gJ0FMTE9XX1NPQ0lBTF9MT0dJTj0ke0FMTE9XX1NPQ0lBTF9MT0dJTjotZmFsc2V9JwogICAgICAtICdBTExPV19TT0NJQUxfUkVHSVNUUkFUSU9OPSR7QUxMT1dfU09DSUFMX1JFR0lTVFJBVElPTjotZmFsc2V9JwogICAgICAtICdBTExPV19QQVNTV09SRF9SRVNFVD0ke0FMTE9XX1BBU1NXT1JEX1JFU0VUOi1mYWxzZX0nCiAgICAgIC0gJ0FMTE9XX1VOVkVSSUZJRURfRU1BSUxfTE9HSU49JHtBTExPV19VTlZFUklGSUVEX0VNQUlMX0xPR0lOOi10cnVlfScKICAgICAgLSAnQ1JFRFNfS0VZPSR7U0VSVklDRV9QQVNTV09SRF82NF9DUkVEU30nCiAgICAgIC0gJ0NSRURTX0lWPSR7U0VSVklDRV9QQVNTV09SRF9DUkVEU30nCiAgICAgIC0gJ0FOVEhST1BJQ19BUElfS0VZPSR7U0VSVklDRV9BTlRIUk9QSUNfQVBJX0tFWTotdXNlcl9wcm92aWRlZH0nCiAgICAgIC0gJ0dPT0dMRV9LRVk9JHtTRVJWSUNFX0dPT0dMRV9BUElfS0VZOi11c2VyX3Byb3ZpZGVkfScKICAgICAgLSAnT1BFTkFJX0FQSV9LRVk9JHtTRVJWSUNFX09QRU5BSV9BUElfS0VZOi11c2VyX3Byb3ZpZGVkfScKICAgICAgLSAnQVNTSVNUQU5UU19BUElfS0VZPSR7U0VSVklDRV9BU1NJU1RBTlRTX0FQSV9LRVk6LXVzZXJfcHJvdmlkZWR9JwogICAgICAtICdERUJVR19MT0dHSU5HPSR7REVCVUdfTE9HR0lORzotZmFsc2V9JwogICAgICAtICdERUJVR19PUEVOQUk9JHtERUJVR19PUEVOQUk6LWZhbHNlfScKICAgICAgLSAnREVCVUdfUExVR0lOUz0ke0RFQlVHX09QRU5BSTotZmFsc2V9JwogICAgICAtICdOT19JTkRFWD0ke05PX0lOREVYOi10cnVlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xpYnJlY2hhdC1pbWFnZXM6L2FwcC9jbGllbnQvcHVibGljL2ltYWdlcycKICAgICAgLSAnbGlicmVjaGF0LWxvZ3M6L2FwcC9hcGkvbG9ncycKICAgICAgLSAnbGlicmVjaGF0LXVwbG9hZHM6L2FwcC91cGxvYWRzJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9saWJyZWNoYXQueWFtbAogICAgICAgIHRhcmdldDogL2FwcC9saWJyZWNoYXQueWFtbAogICAgICAgIGNvbnRlbnQ6ICJ2ZXJzaW9uOiAxLjIuOFxuIgogICAgZGVwZW5kc19vbjoKICAgICAgbW9uZ29kYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBtZWlsaXNlYXJjaDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICB2ZWN0b3JkYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByYWctYXBpOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDgwL2FwaS9oZWFsdGgnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogNQogIG1vbmdvZGI6CiAgICBpbWFnZTogJ21vbmdvOjgnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTU9OR09fSU5JVERCX1JPT1RfVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfTU9OR099JwogICAgICAtICdNT05HT19JTklUREJfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTU9OR099JwogICAgdm9sdW1lczoKICAgICAgLSAnbW9uZ29kYi1kYXRhOi9kYXRhL2RiJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG1vbmdvc2gKICAgICAgICAtICctLWV2YWwnCiAgICAgICAgLSAiZGIucnVuQ29tbWFuZCgncGluZycpLm9rIgogICAgICAgIC0gJzEyNy4wLjAuMToyNzAxNy90ZXN0JwogICAgICAgIC0gJy0tcXVpZXQnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogIG1laWxpc2VhcmNoOgogICAgaW1hZ2U6ICdnZXRtZWlsaS9tZWlsaXNlYXJjaDp2MS4xMi4zJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01FSUxJX01BU1RFUl9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX01FSUxJfScKICAgICAgLSAnTUVJTElfTk9fQU5BTFlUSUNTPSR7TUVJTElfTk9fQU5BTFlUSUNTOi1mYWxzZX0nCiAgICAgIC0gTUVJTElfRU5WPXByb2R1Y3Rpb24KICAgICAgLSAnTUVJTElfSE9TVD1odHRwOi8vbWVpbGlzZWFyY2g6NzcwMCcKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21laWxpc2VhcmNoLWRhdGE6L21laWxpX2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NzcwMC9oZWFsdGgnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICB2ZWN0b3JkYjoKICAgIGltYWdlOiAnYW5rYW5lL3BndmVjdG9yOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0RCPXJhZwogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSBQT1NUR1JFU19IT1NUX0FVVEhfTUVUSE9EPXRydXN0CiAgICB2b2x1bWVzOgogICAgICAtICd2ZWN0b3JkYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBwZ19pc3JlYWR5CiAgICAgICAgLSAnLS11c2VybmFtZT0kU0VSVklDRV9VU0VSX1BPU1RHUkVTJwogICAgICAgIC0gJy0taG9zdD0xMjcuMC4wLjEnCiAgICAgICAgLSAnLS1wb3J0PTU0MzInCiAgICAgICAgLSAnLS1kYm5hbWU9cmFnJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMW0KICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogIHJhZy1hcGk6CiAgICBpbWFnZTogJ2doY3IuaW8vZGFubnktYXZpbGEvbGlicmVjaGF0LXJhZy1hcGktZGV2LWxpdGU6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfREI9cmFnCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtIERCX0hPU1Q9dmVjdG9yZGIKICAgICAgLSAnREJfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gREJfTkFNRT1yYWcKICAgICAgLSBSQUdfUE9SVD04MDAwCiAgICAgIC0gJ1JBR19PUEVOQUlfQVBJX0tFWT0ke1NFUlZJQ0VfT1BFTkFJX0FQSV9LRVk6LXVzZXJfcHJvdmlkZWR9JwogICAgZGVwZW5kc19vbjoKICAgICAgdmVjdG9yZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBweXRob24KICAgICAgICAtICctYycKICAgICAgICAtICJpbXBvcnQgdXJsbGliLnJlcXVlc3Q7IHVybGxpYi5yZXF1ZXN0LnVybG9wZW4oJ2h0dHA6Ly8xMjcuMC4wLjE6ODAwMC9oZWFsdGgnKSIKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxMAo=", + "tags": [ + "ai", + "chat", + "gpt", + "claude", + "palm", + "openai", + "azure", + "huggingface", + "anthropic", + "ollama", + "llm" + ], + "logo": "svgs/librechat.svg", + "minversion": "0.0.0", + "port": "3080" + }, "libreoffice": { "documentation": "https://docs.linuxserver.io/images/docker-libreoffice/?utm_source=coolify.io", "slogan": "LibreOffice is a free and powerful office suite.", @@ -2715,7 +2749,7 @@ "penpot": { "documentation": "https://help.penpot.app/technical-guide/getting-started/#install-with-docker?utm_source=coolify.io", "slogan": "Penpot is the first Open Source design and prototyping platform for product teams.", - "compose": "c2VydmljZXM6CiAgZnJvbnRlbmQ6CiAgICBpbWFnZTogJ3BlbnBvdGFwcC9mcm9udGVuZDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwZW5wb3QtYXNzZXRzOi9vcHQvZGF0YS9hc3NldHMnCiAgICBkZXBlbmRzX29uOgogICAgICBwZW5wb3QtYmFja2VuZDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBwZW5wb3QtZXhwb3J0ZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9GTEFHUz0ke1BFTlBPVF9GUk9OVEVORF9GTEFHUzotZW5hYmxlLWxvZ2luLXdpdGgtcGFzc3dvcmR9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwZW5wb3QtYmFja2VuZDoKICAgIGltYWdlOiAncGVucG90YXBwL2JhY2tlbmQ6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LWFzc2V0czovb3B0L2RhdGEvYXNzZXRzJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtICdQRU5QT1RfRkxBR1M9JHtQRU5QT1RfQkFDS0VORF9GTEFHUzotZW5hYmxlLWxvZ2luLXdpdGgtcGFzc3dvcmQgZW5hYmxlLXNtdHAgZW5hYmxlLXByZXBsLXNlcnZlcn0nCiAgICAgIC0gUEVOUE9UX0hUVFBfU0VSVkVSX1BPUlQ9NjA2MAogICAgICAtIFBFTlBPVF9TRUNSRVRfS0VZPSRTRVJWSUNFX1JFQUxCQVNFNjRfNjRfUEVOUE9UCiAgICAgIC0gUEVOUE9UX1BVQkxJQ19VUkk9JFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9CQUNLRU5EX1VSST1odHRwOi8vcGVucG90LWJhY2tlbmQnCiAgICAgIC0gJ1BFTlBPVF9FWFBPUlRFUl9VUkk9aHR0cDovL3BlbnBvdC1leHBvcnRlcicKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1VSST1wb3N0Z3Jlc3FsOi8vcG9zdGdyZXMvJHtQT1NUR1JFU19EQjotcGVucG90fScKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BFTlBPVF9SRURJU19VUkk9cmVkaXM6Ly9yZWRpcy8wJwogICAgICAtIFBFTlBPVF9BU1NFVFNfU1RPUkFHRV9CQUNLRU5EPWFzc2V0cy1mcwogICAgICAtIFBFTlBPVF9TVE9SQUdFX0FTU0VUU19GU19ESVJFQ1RPUlk9L29wdC9kYXRhL2Fzc2V0cwogICAgICAtICdQRU5QT1RfVEVMRU1FVFJZX0VOQUJMRUQ9JHtQRU5QT1RfVEVMRU1FVFJZX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnUEVOUE9UX1NNVFBfREVGQVVMVF9GUk9NPSR7UEVOUE9UX1NNVFBfREVGQVVMVF9GUk9NOi1uby1yZXBseUBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX0RFRkFVTFRfUkVQTFlfVE89JHtQRU5QT1RfU01UUF9ERUZBVUxUX1JFUExZX1RPOi1uby1yZXBseUBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX0hPU1Q9JHtQRU5QT1RfU01UUF9IT1NUOi1tYWlscGl0fScKICAgICAgLSAnUEVOUE9UX1NNVFBfUE9SVD0ke1BFTlBPVF9TTVRQX1BPUlQ6LTEwMjV9JwogICAgICAtICdQRU5QT1RfU01UUF9VU0VSTkFNRT0ke1BFTlBPVF9TTVRQX1VTRVJOQU1FOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfU01UUF9QQVNTV09SRD0ke1BFTlBPVF9TTVRQX1BBU1NXT1JEOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfU01UUF9UTFM9JHtQRU5QT1RfU01UUF9UTFM6LWZhbHNlfScKICAgICAgLSAnUEVOUE9UX1NNVFBfU1NMPSR7UEVOUE9UX1NNVFBfU1NMOi1mYWxzZX0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NjA2MC9yZWFkeXonCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogMzBzCiAgICAgIHJldHJpZXM6IDE1CiAgcGVucG90LWV4cG9ydGVyOgogICAgaW1hZ2U6ICdwZW5wb3RhcHAvZXhwb3J0ZXI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEVOUE9UX1BVQkxJQ19VUkk9JFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9SRURJU19VUkk9cmVkaXM6Ly9yZWRpcy8wJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjYwNjEvcmVhZHl6JwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgbWFpbHBpdDoKICAgIGltYWdlOiAnYXhsbGVudC9tYWlscGl0OmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NQUlMUElUXzgwMjUKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSAvbWFpbHBpdAogICAgICAgIC0gcmVhZHl6CiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUnCiAgICB2b2x1bWVzOgogICAgICAtICdwZW5wb3QtcG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0lOSVREQl9BUkdTPS0tZGF0YS1jaGVja3N1bXMKICAgICAgLSBQT1NUR1JFU19VU0VSPSRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1wZW5wb3R9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3LWFscGluZScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1hcHBlbmRvbmx5IHllcycKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BlbnBvdC1yZWRpcy1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "c2VydmljZXM6CiAgZnJvbnRlbmQ6CiAgICBpbWFnZTogJ3BlbnBvdGFwcC9mcm9udGVuZDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwZW5wb3QtYXNzZXRzOi9vcHQvZGF0YS9hc3NldHMnCiAgICBkZXBlbmRzX29uOgogICAgICBwZW5wb3QtYmFja2VuZDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBwZW5wb3QtZXhwb3J0ZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9GTEFHUz0ke1BFTlBPVF9GUk9OVEVORF9GTEFHUzotZW5hYmxlLWxvZ2luLXdpdGgtcGFzc3dvcmR9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwZW5wb3QtYmFja2VuZDoKICAgIGltYWdlOiAncGVucG90YXBwL2JhY2tlbmQ6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LWFzc2V0czovb3B0L2RhdGEvYXNzZXRzJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtICdQRU5QT1RfRkxBR1M9JHtQRU5QT1RfQkFDS0VORF9GTEFHUzotZW5hYmxlLWxvZ2luLXdpdGgtcGFzc3dvcmQgZW5hYmxlLXNtdHAgZW5hYmxlLXByZXBsLXNlcnZlcn0nCiAgICAgIC0gUEVOUE9UX0hUVFBfU0VSVkVSX1BPUlQ9NjA2MAogICAgICAtIFBFTlBPVF9TRUNSRVRfS0VZPSRTRVJWSUNFX1JFQUxCQVNFNjRfNjRfUEVOUE9UCiAgICAgIC0gUEVOUE9UX1BVQkxJQ19VUkk9JFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9CQUNLRU5EX1VSST1odHRwOi8vcGVucG90LWJhY2tlbmQnCiAgICAgIC0gJ1BFTlBPVF9FWFBPUlRFUl9VUkk9aHR0cDovL3BlbnBvdC1leHBvcnRlcicKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1VSST1wb3N0Z3Jlc3FsOi8vcG9zdGdyZXMvJHtQT1NUR1JFU19EQjotcGVucG90fScKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BFTlBPVF9SRURJU19VUkk9cmVkaXM6Ly9yZWRpcy8wJwogICAgICAtIFBFTlBPVF9BU1NFVFNfU1RPUkFHRV9CQUNLRU5EPWFzc2V0cy1mcwogICAgICAtIFBFTlBPVF9TVE9SQUdFX0FTU0VUU19GU19ESVJFQ1RPUlk9L29wdC9kYXRhL2Fzc2V0cwogICAgICAtICdQRU5QT1RfVEVMRU1FVFJZX0VOQUJMRUQ9JHtQRU5QT1RfVEVMRU1FVFJZX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnUEVOUE9UX1NNVFBfREVGQVVMVF9GUk9NPSR7UEVOUE9UX1NNVFBfREVGQVVMVF9GUk9NOi1uby1yZXBseUBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX0RFRkFVTFRfUkVQTFlfVE89JHtQRU5QT1RfU01UUF9ERUZBVUxUX1JFUExZX1RPOi1uby1yZXBseUBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX0hPU1Q9JHtQRU5QT1RfU01UUF9IT1NUOi1tYWlscGl0fScKICAgICAgLSAnUEVOUE9UX1NNVFBfUE9SVD0ke1BFTlBPVF9TTVRQX1BPUlQ6LTEwMjV9JwogICAgICAtICdQRU5QT1RfU01UUF9VU0VSTkFNRT0ke1BFTlBPVF9TTVRQX1VTRVJOQU1FOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfU01UUF9QQVNTV09SRD0ke1BFTlBPVF9TTVRQX1BBU1NXT1JEOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfU01UUF9UTFM9JHtQRU5QT1RfU01UUF9UTFM6LWZhbHNlfScKICAgICAgLSAnUEVOUE9UX1NNVFBfU1NMPSR7UEVOUE9UX1NNVFBfU1NMOi1mYWxzZX0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gbm9kZQogICAgICAgIC0gJy1lJwogICAgICAgIC0gInJlcXVpcmUoJ2h0dHAnKS5nZXQoe2hvc3Q6JzEyNy4wLjAuMScsIHBvcnQ6NjA2MCwgcGF0aDonL3JlYWR5eid9LCByZXMgPT4gcHJvY2Vzcy5leGl0KHJlcy5zdGF0dXNDb2RlPT09MjAwID8gMCA6IDEpKS5vbignZXJyb3InLCAoKSA9PiBwcm9jZXNzLmV4aXQoMSkpOyIKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAzMHMKICAgICAgcmV0cmllczogMTUKICBwZW5wb3QtZXhwb3J0ZXI6CiAgICBpbWFnZTogJ3BlbnBvdGFwcC9leHBvcnRlcjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQRU5QT1RfUFVCTElDX1VSST0kU0VSVklDRV9GUUROX0ZST05URU5EXzgwODAKICAgICAgLSAnUEVOUE9UX1JFRElTX1VSST1yZWRpczovL3JlZGlzLzAnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NjA2MS9yZWFkeXonCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBtYWlscGl0OgogICAgaW1hZ2U6ICdheGxsZW50L21haWxwaXQ6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX01BSUxQSVRfODAyNQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9tYWlscGl0CiAgICAgICAgLSByZWFkeXoKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BlbnBvdC1wb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfSU5JVERCX0FSR1M9LS1kYXRhLWNoZWNrc3VtcwogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXBlbnBvdH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjctYWxwaW5lJwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLWFwcGVuZG9ubHkgeWVzJwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LXJlZGlzLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", "tags": [ "penpot", "design", @@ -2949,6 +2983,36 @@ "minversion": "0.0.0", "port": "9696" }, + "pterodactyl-with-wings": { + "documentation": "https://pterodactyl.io/?utm_source=coolify.io", + "slogan": "Pterodactyl is a free, open-source game server management panel", + "compose": "c2VydmljZXM6CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjoxMC41JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdoZWFsdGhjaGVjay5zaCAtLWNvbm5lY3QgLS1pbm5vZGJfaW5pdGlhbGl6ZWQgfHwgZXhpdCAxJwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UCiAgICAgIC0gTVlTUUxfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBNWVNRTF9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBNWVNRTF9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgdm9sdW1lczoKICAgICAgLSAncHRlcm9kYWN0eWwtZGI6L3Zhci9saWIvbXlzcWwnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncmVkaXMtY2xpIHBpbmcgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICBwdGVyb2RhY3R5bDoKICAgIGltYWdlOiAnZ2hjci5pby9wdGVyb2RhY3R5bC9wYW5lbDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwYW5lbC12YXI6L2FwcC92YXIvJwogICAgICAtICdwYW5lbC1uZ2lueDovZXRjL25naW54L2h0dHAuZC8nCiAgICAgIC0gJ3BhbmVsLWNlcnRzOi9ldGMvbGV0c2VuY3J5cHQvJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9ldGMvZW50cnlwb2ludC5zaAogICAgICAgIHRhcmdldDogL2VudHJ5cG9pbnQuc2gKICAgICAgICBtb2RlOiAnMDc1NScKICAgICAgICBjb250ZW50OiAiIyEvYmluL3NoXG5zZXQgLWVcblxuIGVjaG8gXCJTZXR0aW5nIGxvZ3MgcGVybWlzc2lvbnMuLi5cIlxuIGNob3duIC1SIG5naW54OiAvYXBwL3N0b3JhZ2UvbG9ncy9cblxuIFVTRVJfRVhJU1RTPSQocGhwIGFydGlzYW4gdGlua2VyIC0tbm8tYW5zaSAtLWV4ZWN1dGU9J2VjaG8gXFxQdGVyb2RhY3R5bFxcTW9kZWxzXFxVc2VyOjp3aGVyZShcImVtYWlsXCIsIFwiJ1wiJEFETUlOX0VNQUlMXCInXCIpLT5leGlzdHMoKSA/IFwiMVwiIDogXCIwXCI7JylcblxuIGlmIFsgXCIkVVNFUl9FWElTVFNcIiA9IFwiMFwiIF07IHRoZW5cbiAgIGVjaG8gXCJBZG1pbiBVc2VyIGRvZXMgbm90IGV4aXN0LCBjcmVhdGluZyB1c2VyIG5vdy5cIlxuICAgcGhwIGFydGlzYW4gcDp1c2VyOm1ha2UgLS1uby1pbnRlcmFjdGlvbiBcXFxuICAgICAtLWFkbWluPTEgXFxcbiAgICAgLS1lbWFpbD1cIiRBRE1JTl9FTUFJTFwiIFxcXG4gICAgIC0tdXNlcm5hbWU9XCIkQURNSU5fVVNFUk5BTUVcIiBcXFxuICAgICAtLW5hbWUtZmlyc3Q9XCIkQURNSU5fRklSU1ROQU1FXCIgXFxcbiAgICAgLS1uYW1lLWxhc3Q9XCIkQURNSU5fTEFTVE5BTUVcIiBcXFxuICAgICAtLXBhc3N3b3JkPVwiJEFETUlOX1BBU1NXT1JEXCJcbiAgIGVjaG8gXCJBZG1pbiB1c2VyIGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5IVwiXG4gZWxzZVxuICAgZWNobyBcIkFkbWluIFVzZXIgYWxyZWFkeSBleGlzdHMsIHNraXBwaW5nIGNyZWF0aW9uLlwiXG4gZmlcblxuIGV4ZWMgc3VwZXJ2aXNvcmQgLS1ub2RhZW1vblxuIgogICAgY29tbWFuZDoKICAgICAgLSAvZW50cnlwb2ludC5zaAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdjdXJsIC1zZiBodHRwOi8vbG9jYWxob3N0OjgwIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAxcwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUFRFUk9EQUNUWUxfODAKICAgICAgLSAnQURNSU5fRU1BSUw9JHtBRE1JTl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdBRE1JTl9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9BRE1JTn0nCiAgICAgIC0gJ0FETUlOX0ZJUlNUTkFNRT0ke0FETUlOX0ZJUlNUTkFNRTotQWRtaW59JwogICAgICAtICdBRE1JTl9MQVNUTkFNRT0ke0FETUlOX0xBU1ROQU1FOi1Vc2VyfScKICAgICAgLSAnQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgICAgLSAnUFRFUk9EQUNUWUxfSFRUUFM9JHtQVEVST0RBQ1RZTF9IVFRQUzotZmFsc2V9JwogICAgICAtIEFQUF9FTlY9cHJvZHVjdGlvbgogICAgICAtIEFQUF9FTlZJUk9OTUVOVF9PTkxZPWZhbHNlCiAgICAgIC0gQVBQX1VSTD0kU0VSVklDRV9GUUROX1BURVJPREFDVFlMCiAgICAgIC0gJ0FQUF9USU1FWk9ORT0ke1RJTUVaT05FOi1VVEN9JwogICAgICAtICdBUFBfU0VSVklDRV9BVVRIT1I9JHtBUFBfU0VSVklDRV9BVVRIT1I6LWF1dGhvckBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ0xPR19MRVZFTD0ke0xPR19MRVZFTDotZGVidWd9JwogICAgICAtIENBQ0hFX0RSSVZFUj1yZWRpcwogICAgICAtIFNFU1NJT05fRFJJVkVSPXJlZGlzCiAgICAgIC0gUVVFVUVfRFJJVkVSPXJlZGlzCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIERCX0RBVEFCQVNFPXB0ZXJvZGFjdHlsLWRiCiAgICAgIC0gREJfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIERCX0hPU1Q9bWFyaWFkYgogICAgICAtIERCX1BPUlQ9MzMwNgogICAgICAtIERCX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIC0gTUFJTF9GUk9NPSRNQUlMX0ZST00KICAgICAgLSBNQUlMX0RSSVZFUj0kTUFJTF9EUklWRVIKICAgICAgLSBNQUlMX0hPU1Q9JE1BSUxfSE9TVAogICAgICAtIE1BSUxfUE9SVD0kTUFJTF9QT1JUCiAgICAgIC0gTUFJTF9VU0VSTkFNRT0kTUFJTF9VU0VSTkFNRQogICAgICAtIE1BSUxfUEFTU1dPUkQ9JE1BSUxfUEFTU1dPUkQKICAgICAgLSBNQUlMX0VOQ1JZUFRJT049JE1BSUxfRU5DUllQVElPTgogIHdpbmdzOgogICAgaW1hZ2U6ICdnaGNyLmlvL3B0ZXJvZGFjdHlsL3dpbmdzOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9XSU5HU184NDQzCiAgICAgIC0gJ1RaPSR7VElNRVpPTkU6LVVUQ30nCiAgICAgIC0gV0lOR1NfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9XSU5HUwogICAgdm9sdW1lczoKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICAgIC0gJy92YXIvbGliL2RvY2tlci9jb250YWluZXJzLzovdmFyL2xpYi9kb2NrZXIvY29udGFpbmVycy8nCiAgICAgIC0gJy92YXIvbGliL3B0ZXJvZGFjdHlsL3ZvbHVtZXM6L3Zhci9saWIvcHRlcm9kYWN0eWwvdm9sdW1lcycKICAgICAgLSAnL3RtcC9wdGVyb2RhY3R5bDovdG1wL3B0ZXJvZGFjdHlsJwogICAgICAtICd3aW5nc19saWI6L3Zhci9saWIvcHRlcm9kYWN0eWwvJwogICAgICAtICd3aW5nc19sb2dzOi92YXIvbG9nL3B0ZXJvZGFjdHlsLycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZXRjL2NvbmZpZy55bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvcHRlcm9kYWN0eWwvY29uZmlnLnltbAogICAgICAgIGNvbnRlbnQ6ICJkZWJ1ZzogZmFsc2VcbnV1aWQ6IFJlcGxhY2VDb25maWdcbnRva2VuX2lkOiBSZXBsYWNlQ29uZmlnXG50b2tlbjogUmVwbGFjZUNvbmZpZ1xuYXBpOlxuICBob3N0OiAwLjAuMC4wXG4gIHBvcnQ6IDg0NDMgIyBXYXJuaW5nLCBwYW5lbCBtdXN0IGhhdmUgNDQzIGFzIGRhZW1vbiBwb3J0LCB3aGlsZSBoZXJlIGl0IHNob3VsZCBzaG91bGQgYmUgODQ0MywgRlFETiBpbiBDb29saWZ5IGZvciB0aGlzIHNlcnZpY2Ugc2hvdWxkIGJlIGh0dHBzOi8vKjo4NDQzXG4gIHNzbDpcbiAgICBlbmFibGVkOiBmYWxzZVxuICAgIGNlcnQ6IFJlcGxhY2VDb25maWdcbiAgICBrZXk6IFJlcGxhY2VDb25maWdcbiAgdXBsb2FkX2xpbWl0OiAxMDBcbnN5c3RlbTpcbiAgZGF0YTogL3Zhci9saWIvcHRlcm9kYWN0eWwvdm9sdW1lc1xuICBzZnRwOlxuICAgIGJpbmRfcG9ydDogMjAyMlxuYWxsb3dlZF9tb3VudHM6IFtdXG5yZW1vdGU6ICcnIgogICAgcG9ydHM6CiAgICAgIC0gJzIwMjI6MjAyMicK", + "tags": [ + "game", + "game server", + "management", + "panel", + "minecraft" + ], + "logo": "svgs/pterodactyl.png", + "minversion": "0.0.0", + "port": "80, 8443" + }, + "pterodactyl": { + "documentation": "https://pterodactyl.io/?utm_source=coolify.io", + "slogan": "Pterodactyl is a free, open-source game server management panel", + "compose": "c2VydmljZXM6CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjoxMC41JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdoZWFsdGhjaGVjay5zaCAtLWNvbm5lY3QgLS1pbm5vZGJfaW5pdGlhbGl6ZWQgfHwgZXhpdCAxJwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UCiAgICAgIC0gTVlTUUxfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBNWVNRTF9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBNWVNRTF9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgdm9sdW1lczoKICAgICAgLSAncHRlcm9kYWN0eWwtZGI6L3Zhci9saWIvbXlzcWwnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncmVkaXMtY2xpIHBpbmcgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICBwdGVyb2RhY3R5bDoKICAgIGltYWdlOiAnZ2hjci5pby9wdGVyb2RhY3R5bC9wYW5lbDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwYW5lbC12YXI6L2FwcC92YXIvJwogICAgICAtICdwYW5lbC1uZ2lueDovZXRjL25naW54L2h0dHAuZC8nCiAgICAgIC0gJ3BhbmVsLWNlcnRzOi9ldGMvbGV0c2VuY3J5cHQvJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9ldGMvZW50cnlwb2ludC5zaAogICAgICAgIHRhcmdldDogL2VudHJ5cG9pbnQuc2gKICAgICAgICBtb2RlOiAnMDc1NScKICAgICAgICBjb250ZW50OiAiIyEvYmluL3NoXG5zZXQgLWVcblxuIGVjaG8gXCJTZXR0aW5nIGxvZ3MgcGVybWlzc2lvbnMuLi5cIlxuIGNob3duIC1SIG5naW54OiAvYXBwL3N0b3JhZ2UvbG9ncy9cblxuIFVTRVJfRVhJU1RTPSQocGhwIGFydGlzYW4gdGlua2VyIC0tbm8tYW5zaSAtLWV4ZWN1dGU9J2VjaG8gXFxQdGVyb2RhY3R5bFxcTW9kZWxzXFxVc2VyOjp3aGVyZShcImVtYWlsXCIsIFwiJ1wiJEFETUlOX0VNQUlMXCInXCIpLT5leGlzdHMoKSA/IFwiMVwiIDogXCIwXCI7JylcblxuIGlmIFsgXCIkVVNFUl9FWElTVFNcIiA9IFwiMFwiIF07IHRoZW5cbiAgIGVjaG8gXCJBZG1pbiBVc2VyIGRvZXMgbm90IGV4aXN0LCBjcmVhdGluZyB1c2VyIG5vdy5cIlxuICAgcGhwIGFydGlzYW4gcDp1c2VyOm1ha2UgLS1uby1pbnRlcmFjdGlvbiBcXFxuICAgICAtLWFkbWluPTEgXFxcbiAgICAgLS1lbWFpbD1cIiRBRE1JTl9FTUFJTFwiIFxcXG4gICAgIC0tdXNlcm5hbWU9XCIkQURNSU5fVVNFUk5BTUVcIiBcXFxuICAgICAtLW5hbWUtZmlyc3Q9XCIkQURNSU5fRklSU1ROQU1FXCIgXFxcbiAgICAgLS1uYW1lLWxhc3Q9XCIkQURNSU5fTEFTVE5BTUVcIiBcXFxuICAgICAtLXBhc3N3b3JkPVwiJEFETUlOX1BBU1NXT1JEXCJcbiAgIGVjaG8gXCJBZG1pbiB1c2VyIGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5IVwiXG4gZWxzZVxuICAgZWNobyBcIkFkbWluIFVzZXIgYWxyZWFkeSBleGlzdHMsIHNraXBwaW5nIGNyZWF0aW9uLlwiXG4gZmlcblxuIGV4ZWMgc3VwZXJ2aXNvcmQgLS1ub2RhZW1vblxuIgogICAgY29tbWFuZDoKICAgICAgLSAvZW50cnlwb2ludC5zaAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdjdXJsIC1zZiBodHRwOi8vbG9jYWxob3N0OjgwIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAxcwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUFRFUk9EQUNUWUxfODAKICAgICAgLSAnQURNSU5fRU1BSUw9JHtBRE1JTl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdBRE1JTl9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9BRE1JTn0nCiAgICAgIC0gJ0FETUlOX0ZJUlNUTkFNRT0ke0FETUlOX0ZJUlNUTkFNRTotQWRtaW59JwogICAgICAtICdBRE1JTl9MQVNUTkFNRT0ke0FETUlOX0xBU1ROQU1FOi1Vc2VyfScKICAgICAgLSAnQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgICAgLSAnUFRFUk9EQUNUWUxfSFRUUFM9JHtQVEVST0RBQ1RZTF9IVFRQUzotZmFsc2V9JwogICAgICAtIEFQUF9FTlY9cHJvZHVjdGlvbgogICAgICAtIEFQUF9FTlZJUk9OTUVOVF9PTkxZPWZhbHNlCiAgICAgIC0gQVBQX1VSTD0kU0VSVklDRV9GUUROX1BURVJPREFDVFlMCiAgICAgIC0gJ0FQUF9USU1FWk9ORT0ke1RJTUVaT05FOi1VVEN9JwogICAgICAtICdBUFBfU0VSVklDRV9BVVRIT1I9JHtBUFBfU0VSVklDRV9BVVRIT1I6LWF1dGhvckBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ0xPR19MRVZFTD0ke0xPR19MRVZFTDotZGVidWd9JwogICAgICAtIENBQ0hFX0RSSVZFUj1yZWRpcwogICAgICAtIFNFU1NJT05fRFJJVkVSPXJlZGlzCiAgICAgIC0gUVVFVUVfRFJJVkVSPXJlZGlzCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIERCX0RBVEFCQVNFPXB0ZXJvZGFjdHlsLWRiCiAgICAgIC0gREJfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIERCX0hPU1Q9bWFyaWFkYgogICAgICAtIERCX1BPUlQ9MzMwNgogICAgICAtIERCX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIC0gTUFJTF9GUk9NPSRNQUlMX0ZST00KICAgICAgLSBNQUlMX0RSSVZFUj0kTUFJTF9EUklWRVIKICAgICAgLSBNQUlMX0hPU1Q9JE1BSUxfSE9TVAogICAgICAtIE1BSUxfUE9SVD0kTUFJTF9QT1JUCiAgICAgIC0gTUFJTF9VU0VSTkFNRT0kTUFJTF9VU0VSTkFNRQogICAgICAtIE1BSUxfUEFTU1dPUkQ9JE1BSUxfUEFTU1dPUkQKICAgICAgLSBNQUlMX0VOQ1JZUFRJT049JE1BSUxfRU5DUllQVElPTgo=", + "tags": [ + "game", + "game server", + "management", + "panel", + "minecraft" + ], + "logo": "svgs/pterodactyl.png", + "minversion": "0.0.0", + "port": "80" + }, "qbittorrent": { "documentation": "https://docs.linuxserver.io/images/docker-qbittorrent/?utm_source=coolify.io", "slogan": "The qBittorrent project aims to provide an open-source software alternative to \u03bcTorrent.", @@ -3793,6 +3857,21 @@ "minversion": "0.0.0", "port": "8000" }, + "wings": { + "documentation": "https://pterodactyl.io/?utm_source=coolify.io", + "slogan": "Wings is Pterodactyl's server control plane", + "compose": "c2VydmljZXM6CiAgd2luZ3M6CiAgICBpbWFnZTogJ2doY3IuaW8vcHRlcm9kYWN0eWwvd2luZ3M6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1dJTkdTXzg0NDMKICAgICAgLSAnVFo9JHtUSU1FWk9ORTotVVRDfScKICAgICAgLSBXSU5HU19VU0VSTkFNRT0kU0VSVklDRV9VU0VSX1dJTkdTCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnL3Zhci9saWIvZG9ja2VyL2NvbnRhaW5lcnMvOi92YXIvbGliL2RvY2tlci9jb250YWluZXJzLycKICAgICAgLSAnL3Zhci9saWIvcHRlcm9kYWN0eWwvdm9sdW1lczovdmFyL2xpYi9wdGVyb2RhY3R5bC92b2x1bWVzJwogICAgICAtICcvdG1wL3B0ZXJvZGFjdHlsOi90bXAvcHRlcm9kYWN0eWwnCiAgICAgIC0gJ3dpbmdzX2xpYjovdmFyL2xpYi9wdGVyb2RhY3R5bC8nCiAgICAgIC0gJ3dpbmdzX2xvZ3M6L3Zhci9sb2cvcHRlcm9kYWN0eWwvJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9ldGMvY29uZmlnLnltbAogICAgICAgIHRhcmdldDogL2V0Yy9wdGVyb2RhY3R5bC9jb25maWcueW1sCiAgICAgICAgY29udGVudDogImRlYnVnOiBmYWxzZVxudXVpZDogUmVwbGFjZUNvbmZpZ1xudG9rZW5faWQ6IFJlcGxhY2VDb25maWdcbnRva2VuOiBSZXBsYWNlQ29uZmlnXG5hcGk6XG4gIGhvc3Q6IDAuMC4wLjBcbiAgcG9ydDogODQ0MyAjIFdhcm5pbmcsIHBhbmVsIG11c3QgaGF2ZSA0NDMgYXMgZGFlbW9uIHBvcnQsIHdoaWxlIGhlcmUgaXQgc2hvdWxkIHNob3VsZCBiZSA4NDQzLCBGUUROIGluIENvb2xpZnkgZm9yIHRoaXMgc2VydmljZSBzaG91bGQgYmUgaHR0cHM6Ly8qOjg0NDNcbiAgc3NsOlxuICAgIGVuYWJsZWQ6IGZhbHNlXG4gICAgY2VydDogUmVwbGFjZUNvbmZpZ1xuICAgIGtleTogUmVwbGFjZUNvbmZpZ1xuICB1cGxvYWRfbGltaXQ6IDEwMFxuc3lzdGVtOlxuICBkYXRhOiAvdmFyL2xpYi9wdGVyb2RhY3R5bC92b2x1bWVzXG4gIHNmdHA6XG4gICAgYmluZF9wb3J0OiAyMDIyXG5hbGxvd2VkX21vdW50czogW11cbnJlbW90ZTogJyciCiAgICBwb3J0czoKICAgICAgLSAnMjAyMjoyMDIyJwo=", + "tags": [ + "game", + "game server", + "management", + "panel", + "minecraft" + ], + "logo": "svgs/pterodactyl.png", + "minversion": "0.0.0", + "port": "8443" + }, "wireguard-easy": { "documentation": "https://github.com/wg-easy/wg-easy?utm_source=coolify.io", "slogan": "The easiest way to run WireGuard VPN + Web-based Admin UI.", From 8eb3f94644908c6b80ce0d07fdb34333668ad769 Mon Sep 17 00:00:00 2001 From: Scan <103391616+scanash00@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:55:35 -0800 Subject: [PATCH 093/202] feat(service): add Bluesky PDS template (#6302) --- public/svgs/bluesky.svg | 3 ++ templates/compose/bluesky-pds.yaml | 72 +++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 public/svgs/bluesky.svg diff --git a/public/svgs/bluesky.svg b/public/svgs/bluesky.svg new file mode 100644 index 000000000..77ebea072 --- /dev/null +++ b/public/svgs/bluesky.svg @@ -0,0 +1,3 @@ + + + diff --git a/templates/compose/bluesky-pds.yaml b/templates/compose/bluesky-pds.yaml index 679eb8e79..7d2804421 100644 --- a/templates/compose/bluesky-pds.yaml +++ b/templates/compose/bluesky-pds.yaml @@ -1,34 +1,64 @@ -# ignore: true # documentation: https://github.com/bluesky-social/pds -# slogan: A social network for the decentralized web -# tags: pds, bluesky, social, network, decentralized -# logo: +# slogan: Bluesky PDS (Personal Data Server) +# tags: bluesky, pds, platform +# logo: svgs/bluesky.svg # port: 3000 services: pds: - image: ghcr.io/bluesky-social/pds:0.4 + image: 'ghcr.io/bluesky-social/pds:latest' volumes: - - pds-data:/pds + - ./pds-data:/pds environment: - SERVICE_FQDN_PDS_3000 - - PDS_JWT_SECRET=${SERVICE_BASE64_PDS} - - PDS_ADMIN_PASSWORD=${SERVICE_PASSWORD_PDS} - - PDS_ADMIN_EMAIL=${PDS_ADMIN_EMAIL:-admin@example.com} - - PDS_DATADIR=${PDS_DATADIR:-/pds} - - PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATADIR:-/pds}/blocks - - PDS_BLOB_UPLOAD_LIMIT=${PDS_BLOB_UPLOAD_LIMIT:-52428800} - PDS_HOSTNAME=${SERVICE_URL_PDS} - - PDS_DID_PLC_URL=https://plc.directory - - PDS_BSKY_APP_VIEW_URL=https://api.bsky.app - - PDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app - - PDS_REPORT_SERVICE_URL=https://mod.bsky.app - - PDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac - - PDS_CRAWLERS=https://bsky.network + - PDS_JWT_SECRET=${SERVICE_PASSWORD_JWT_SECRET} + - PDS_ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN} + - PDS_ADMIN_EMAIL=${SERVICE_EMAIL_ADMIN} + - PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=${PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX} + - PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds} + - PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATA_DIRECTORY:-/pds}/blocks + - PDS_BLOB_UPLOAD_LIMIT=${PDS_BLOB_UPLOAD_LIMIT:-52428800} + - PDS_DID_PLC_URL=${PDS_DID_PLC_URL:-https://plc.directory} + - PDS_BSKY_APP_VIEW_URL=${PDS_BSKY_APP_VIEW_URL:-https://api.bsky.app} + - PDS_BSKY_APP_VIEW_DID=${PDS_BSKY_APP_VIEW_DID:-did:web:api.bsky.app} + - PDS_REPORT_SERVICE_URL=${PDS_REPORT_SERVICE_URL:-https://mod.bsky.app/xrpc/com.atproto.moderation.createReport} + - PDS_REPORT_SERVICE_DID=${PDS_REPORT_SERVICE_DID:-did:plc:ar7c4by46qjdydhdevvrndac} + - PDS_CRAWLERS=${PDS_CRAWLERS:-https://bsky.network} - LOG_ENABLED=${LOG_ENABLED:-true} - - PDS_EMAIL_SMTP_URL=${PDS_EMAIL_SMTP_URL:-smtp://localhost:8025} - - PDS_EMAIL_FROM_ADDRESS=${PDS_EMAIL_FROM_ADDRESS:-admin@example.com} - - PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=${SERVICE_HEX_32_ROTATIONKEY} + + command: > + sh -c ' + echo "Installing curl, bash, and pdsadmin..." + apk add --no-cache curl bash && \ + curl -o /usr/local/bin/pdsadmin.sh https://raw.githubusercontent.com/bluesky-social/pds/main/pdsadmin.sh && \ + chmod +x /usr/local/bin/pdsadmin.sh && \ + ln -sf /usr/local/bin/pdsadmin.sh /usr/local/bin/pdsadmin + + echo "Generating /pds/pds.env..." + printf "%s\n" \ + "SERVICE_FQDN_PDS_3000=$${SERVICE_FQDN_PDS_3000}" \ + "PDS_HOSTNAME=$${PDS_HOSTNAME}" \ + "PDS_JWT_SECRET=$${PDS_JWT_SECRET}" \ + "PDS_ADMIN_PASSWORD=$${PDS_ADMIN_PASSWORD}" \ + "PDS_ADMIN_EMAIL=$${PDS_ADMIN_EMAIL}" \ + "PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$${PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX}" \ + "PDS_DATA_DIRECTORY=$${PDS_DATA_DIRECTORY}" \ + "PDS_BLOBSTORE_DISK_LOCATION=$${PDS_DATA_DIRECTORY}/blocks" \ + "PDS_BLOB_UPLOAD_LIMIT=$${PDS_BLOB_UPLOAD_LIMIT}" \ + "PDS_DID_PLC_URL=$${PDS_DID_PLC_URL}" \ + "PDS_BSKY_APP_VIEW_URL=$${PDS_BSKY_APP_VIEW_URL}" \ + "PDS_BSKY_APP_VIEW_DID=$${PDS_BSKY_APP_VIEW_DID}" \ + "PDS_REPORT_SERVICE_URL=$${PDS_REPORT_SERVICE_URL}" \ + "PDS_REPORT_SERVICE_DID=$${PDS_REPORT_SERVICE_DID}" \ + "PDS_CRAWLERS=$${PDS_CRAWLERS}" \ + "LOG_ENABLED=$${LOG_ENABLED}" \ + > /pds/pds.env + + echo "Launching PDS..." + exec node --enable-source-maps index.js + ' + healthcheck: test: ["CMD", "wget", "--spider", "http://127.0.0.1:3000/xrpc/_health"] interval: 2s From 158711a1658e9ca000711f33852d73f6a76b838c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 22:03:52 +0200 Subject: [PATCH 094/202] Update service-templates.json --- templates/service-templates.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/templates/service-templates.json b/templates/service-templates.json index 49884462f..1dbfac94b 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -203,6 +203,19 @@ "logo": "svgs/bitcoin.svg", "minversion": "0.0.0" }, + "bluesky-pds": { + "documentation": "https://github.com/bluesky-social/pds?utm_source=coolify.io", + "slogan": "Bluesky PDS (Personal Data Server)", + "compose": "c2VydmljZXM6CiAgcGRzOgogICAgaW1hZ2U6ICdnaGNyLmlvL2JsdWVza3ktc29jaWFsL3BkczpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICcuL3Bkcy1kYXRhOi9wZHMnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUERTXzMwMDAKICAgICAgLSAnUERTX0hPU1ROQU1FPSR7U0VSVklDRV9VUkxfUERTfScKICAgICAgLSAnUERTX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVF9TRUNSRVR9JwogICAgICAtICdQRFNfQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgICAgLSAnUERTX0FETUlOX0VNQUlMPSR7U0VSVklDRV9FTUFJTF9BRE1JTn0nCiAgICAgIC0gJ1BEU19QTENfUk9UQVRJT05fS0VZX0syNTZfUFJJVkFURV9LRVlfSEVYPSR7UERTX1BMQ19ST1RBVElPTl9LRVlfSzI1Nl9QUklWQVRFX0tFWV9IRVh9JwogICAgICAtICdQRFNfREFUQV9ESVJFQ1RPUlk9JHtQRFNfREFUQV9ESVJFQ1RPUlk6LS9wZHN9JwogICAgICAtICdQRFNfQkxPQlNUT1JFX0RJU0tfTE9DQVRJT049JHtQRFNfREFUQV9ESVJFQ1RPUlk6LS9wZHN9L2Jsb2NrcycKICAgICAgLSAnUERTX0JMT0JfVVBMT0FEX0xJTUlUPSR7UERTX0JMT0JfVVBMT0FEX0xJTUlUOi01MjQyODgwMH0nCiAgICAgIC0gJ1BEU19ESURfUExDX1VSTD0ke1BEU19ESURfUExDX1VSTDotaHR0cHM6Ly9wbGMuZGlyZWN0b3J5fScKICAgICAgLSAnUERTX0JTS1lfQVBQX1ZJRVdfVVJMPSR7UERTX0JTS1lfQVBQX1ZJRVdfVVJMOi1odHRwczovL2FwaS5ic2t5LmFwcH0nCiAgICAgIC0gJ1BEU19CU0tZX0FQUF9WSUVXX0RJRD0ke1BEU19CU0tZX0FQUF9WSUVXX0RJRDotZGlkOndlYjphcGkuYnNreS5hcHB9JwogICAgICAtICdQRFNfUkVQT1JUX1NFUlZJQ0VfVVJMPSR7UERTX1JFUE9SVF9TRVJWSUNFX1VSTDotaHR0cHM6Ly9tb2QuYnNreS5hcHAveHJwYy9jb20uYXRwcm90by5tb2RlcmF0aW9uLmNyZWF0ZVJlcG9ydH0nCiAgICAgIC0gJ1BEU19SRVBPUlRfU0VSVklDRV9ESUQ9JHtQRFNfUkVQT1JUX1NFUlZJQ0VfRElEOi1kaWQ6cGxjOmFyN2M0Ynk0NnFqZHlkaGRldnZybmRhY30nCiAgICAgIC0gJ1BEU19DUkFXTEVSUz0ke1BEU19DUkFXTEVSUzotaHR0cHM6Ly9ic2t5Lm5ldHdvcmt9JwogICAgICAtICdMT0dfRU5BQkxFRD0ke0xPR19FTkFCTEVEOi10cnVlfScKICAgIGNvbW1hbmQ6ICJzaCAtYyAnXG4gIGVjaG8gXCJJbnN0YWxsaW5nIGN1cmwsIGJhc2gsIGFuZCBwZHNhZG1pbi4uLlwiXG4gIGFwayBhZGQgLS1uby1jYWNoZSBjdXJsIGJhc2ggJiYgXFxcbiAgY3VybCAtbyAvdXNyL2xvY2FsL2Jpbi9wZHNhZG1pbi5zaCBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYmx1ZXNreS1zb2NpYWwvcGRzL21haW4vcGRzYWRtaW4uc2ggJiYgXFxcbiAgY2htb2QgK3ggL3Vzci9sb2NhbC9iaW4vcGRzYWRtaW4uc2ggJiYgXFxcbiAgbG4gLXNmIC91c3IvbG9jYWwvYmluL3Bkc2FkbWluLnNoIC91c3IvbG9jYWwvYmluL3Bkc2FkbWluXG5cbiAgZWNobyBcIkdlbmVyYXRpbmcgL3Bkcy9wZHMuZW52Li4uXCJcbiAgcHJpbnRmIFwiJXNcXG5cIiBcXFxuICBcIlNFUlZJQ0VfRlFETl9QRFNfMzAwMD0kJHtTRVJWSUNFX0ZRRE5fUERTXzMwMDB9XCIgXFxcbiAgXCJQRFNfSE9TVE5BTUU9JCR7UERTX0hPU1ROQU1FfVwiIFxcXG4gIFwiUERTX0pXVF9TRUNSRVQ9JCR7UERTX0pXVF9TRUNSRVR9XCIgXFxcbiAgXCJQRFNfQURNSU5fUEFTU1dPUkQ9JCR7UERTX0FETUlOX1BBU1NXT1JEfVwiIFxcXG4gIFwiUERTX0FETUlOX0VNQUlMPSQke1BEU19BRE1JTl9FTUFJTH1cIiBcXFxuICBcIlBEU19QTENfUk9UQVRJT05fS0VZX0syNTZfUFJJVkFURV9LRVlfSEVYPSQke1BEU19QTENfUk9UQVRJT05fS0VZX0syNTZfUFJJVkFURV9LRVlfSEVYfVwiIFxcXG4gIFwiUERTX0RBVEFfRElSRUNUT1JZPSQke1BEU19EQVRBX0RJUkVDVE9SWX1cIiBcXFxuICBcIlBEU19CTE9CU1RPUkVfRElTS19MT0NBVElPTj0kJHtQRFNfREFUQV9ESVJFQ1RPUll9L2Jsb2Nrc1wiIFxcXG4gIFwiUERTX0JMT0JfVVBMT0FEX0xJTUlUPSQke1BEU19CTE9CX1VQTE9BRF9MSU1JVH1cIiBcXFxuICBcIlBEU19ESURfUExDX1VSTD0kJHtQRFNfRElEX1BMQ19VUkx9XCIgXFxcbiAgXCJQRFNfQlNLWV9BUFBfVklFV19VUkw9JCR7UERTX0JTS1lfQVBQX1ZJRVdfVVJMfVwiIFxcXG4gIFwiUERTX0JTS1lfQVBQX1ZJRVdfRElEPSQke1BEU19CU0tZX0FQUF9WSUVXX0RJRH1cIiBcXFxuICBcIlBEU19SRVBPUlRfU0VSVklDRV9VUkw9JCR7UERTX1JFUE9SVF9TRVJWSUNFX1VSTH1cIiBcXFxuICBcIlBEU19SRVBPUlRfU0VSVklDRV9ESUQ9JCR7UERTX1JFUE9SVF9TRVJWSUNFX0RJRH1cIiBcXFxuICBcIlBEU19DUkFXTEVSUz0kJHtQRFNfQ1JBV0xFUlN9XCIgXFxcbiAgXCJMT0dfRU5BQkxFRD0kJHtMT0dfRU5BQkxFRH1cIiBcXFxuICA+IC9wZHMvcGRzLmVudlxuXG4gIGVjaG8gXCJMYXVuY2hpbmcgUERTLi4uXCJcbiAgZXhlYyBub2RlIC0tZW5hYmxlLXNvdXJjZS1tYXBzIGluZGV4LmpzXG4nXG4iCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6MzAwMC94cnBjL19oZWFsdGgnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTAK", + "tags": [ + "bluesky", + "pds", + "platform" + ], + "logo": "svgs/bluesky.svg", + "minversion": "0.0.0", + "port": "3000" + }, "bookstack": { "documentation": "https://www.bookstackapp.com/docs/?utm_source=coolify.io", "slogan": "BookStack is a simple, self-hosted, easy-to-use platform for organising and storing information", From 103a9c2df29c0c47983ea5faec8d75fe373884c0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:57:05 +0200 Subject: [PATCH 095/202] fix(policy): update delete method to check for admin status in S3StoragePolicy --- app/Policies/S3StoragePolicy.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Policies/S3StoragePolicy.php b/app/Policies/S3StoragePolicy.php index 28f5f8426..4f837a3dd 100644 --- a/app/Policies/S3StoragePolicy.php +++ b/app/Policies/S3StoragePolicy.php @@ -21,7 +21,7 @@ class S3StoragePolicy */ public function view(User $user, S3Storage $storage): bool { - return $user->teams()->where('id', $storage->team_id)->exists(); + return $user->teams()->get()->firstWhere('id', $storage->team_id)->exists(); } /** @@ -37,7 +37,7 @@ class S3StoragePolicy */ public function update(User $user, Server $server): bool { - return $user->teams()->get()->firstWhere('id', $server->team_id) !== null; + return $user->teams()->get()->firstWhere('id', $server->team_id)->exists() && $user->isAdmin(); } /** @@ -45,7 +45,7 @@ class S3StoragePolicy */ public function delete(User $user, S3Storage $storage): bool { - return $user->teams()->where('id', $storage->team_id)->exists(); + return $user->teams()->get()->firstWhere('id', $storage->team_id)->exists() && $user->isAdmin(); } /** From cc5abc093d35be0b25bb6a69fefa34575df1eba3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Jul 2025 20:49:12 +0200 Subject: [PATCH 096/202] fix(container): sort containers alphabetically by name in ExecuteContainerCommand and update filtering in Terminal Index --- app/Livewire/Project/Shared/ExecuteContainerCommand.php | 6 ++++++ app/Livewire/Terminal/Index.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php index 2d55807c7..6833492a6 100644 --- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php +++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php @@ -132,6 +132,12 @@ class ExecuteContainerCommand extends Component }); } } + + // Sort containers alphabetically by name + $this->containers = $this->containers->sortBy(function ($container) { + return data_get($container, 'container.Names'); + }); + if ($this->containers->count() === 1) { $this->selected_container = data_get($this->containers->first(), 'container.Names'); } diff --git a/app/Livewire/Terminal/Index.php b/app/Livewire/Terminal/Index.php index 10084a991..f4008cb1c 100644 --- a/app/Livewire/Terminal/Index.php +++ b/app/Livewire/Terminal/Index.php @@ -59,7 +59,7 @@ class Index extends Component return null; })->filter(); - }); + })->sortBy('name'); } public function updatedSelectedUuid() From a0bc4dac55e5f9255376dc017874035eb8642fa7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 24 Jul 2025 09:39:27 +0200 Subject: [PATCH 097/202] fix(application): streamline environment variable updates for Docker Compose services and enhance FQDN generation logic --- app/Livewire/Project/Application/General.php | 63 +- app/Livewire/Project/Resource/Create.php | 2 +- app/Models/Application.php | 2 +- app/Models/Service.php | 4 +- bootstrap/helpers/parsers.php | 1657 ++++++++++++++++++ bootstrap/helpers/services.php | 201 +-- bootstrap/helpers/shared.php | 113 +- 7 files changed, 1794 insertions(+), 248 deletions(-) create mode 100644 bootstrap/helpers/parsers.php diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 58a35caa0..2eadbaa0e 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -4,7 +4,6 @@ namespace App\Livewire\Project\Application; use App\Actions\Application\GenerateConfig; use App\Models\Application; -use App\Models\EnvironmentVariable; use Illuminate\Support\Collection; use Livewire\Component; use Spatie\Url\Url; @@ -270,7 +269,6 @@ class General extends Component $this->application->save(); $this->dispatch('success', 'Domain generated.'); if ($this->application->build_pack === 'dockercompose') { - $this->updateServiceEnvironmentVariables(); $this->loadComposeFile(showToast: false); } @@ -344,7 +342,7 @@ class General extends Component $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); if ($this->application->build_pack === 'dockercompose') { - $this->loadComposeFile(); + $this->loadComposeFile(showToast: false); } $this->dispatch('configurationChanged'); } catch (\Throwable $e) { @@ -421,7 +419,7 @@ class General extends Component } if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) { - $compose_return = $this->loadComposeFile(); + $compose_return = $this->loadComposeFile(showToast: false); if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) { return; } @@ -470,7 +468,6 @@ class General extends Component } $this->application->docker_compose_domains = json_encode($originalDomains); - foreach ($originalDomains as $serviceName => $service) { $domain = data_get($service, 'domain'); if ($domain) { @@ -486,12 +483,6 @@ class General extends Component } $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); - - // Update SERVICE_FQDN_ and SERVICE_URL_ environment variables for Docker Compose applications - if ($this->application->build_pack === 'dockercompose') { - $this->updateServiceEnvironmentVariables(); - } - $showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!'); } catch (\Throwable $e) { $originalFqdn = $this->application->getOriginal('fqdn'); @@ -525,25 +516,33 @@ class General extends Component foreach ($domains as $serviceName => $service) { $serviceNameFormatted = str($serviceName)->upper()->replace('-', '_'); $domain = data_get($service, 'domain'); + // Delete SERVICE_FQDN_ and SERVICE_URL_ variables if domain is removed + $this->application->environment_variables()->where('resourceable_type', Application::class) + ->where('resourceable_id', $this->application->id) + ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%") + ->delete(); + + $this->application->environment_variables()->where('resourceable_type', Application::class) + ->where('resourceable_id', $this->application->id) + ->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%") + ->delete(); if ($domain) { // Create or update SERVICE_FQDN_ and SERVICE_URL_ variables $fqdn = Url::fromString($domain); $port = $fqdn->getPort(); $path = $fqdn->getPath(); - $fqdnValue = $fqdn->getScheme().'://'.$fqdn->getHost(); - if ($path !== '/') { - $fqdnValue = $fqdnValue.$path; - } - $urlValue = str($domain)->after('://'); + $urlValue = $fqdn->getScheme().'://'.$fqdn->getHost(); if ($path !== '/') { $urlValue = $urlValue.$path; } + $fqdnValue = str($domain)->after('://'); + if ($path !== '/') { + $fqdnValue = $fqdnValue.$path; + } // Create/update SERVICE_FQDN_ - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Application::class, - 'resourceable_id' => $this->application->id, + $this->application->environment_variables()->updateOrCreate([ 'key' => "SERVICE_FQDN_{$serviceNameFormatted}", ], [ 'value' => $fqdnValue, @@ -552,21 +551,16 @@ class General extends Component ]); // Create/update SERVICE_URL_ - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Application::class, - 'resourceable_id' => $this->application->id, + $this->application->environment_variables()->updateOrCreate([ 'key' => "SERVICE_URL_{$serviceNameFormatted}", ], [ 'value' => $urlValue, 'is_build_time' => false, 'is_preview' => false, ]); - // Create/update port-specific variables if port exists - if ($port) { - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Application::class, - 'resourceable_id' => $this->application->id, + if (filled($port)) { + $this->application->environment_variables()->updateOrCreate([ 'key' => "SERVICE_FQDN_{$serviceNameFormatted}_{$port}", ], [ 'value' => $fqdnValue, @@ -574,9 +568,7 @@ class General extends Component 'is_preview' => false, ]); - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Application::class, - 'resourceable_id' => $this->application->id, + $this->application->environment_variables()->updateOrCreate([ 'key' => "SERVICE_URL_{$serviceNameFormatted}_{$port}", ], [ 'value' => $urlValue, @@ -584,17 +576,6 @@ class General extends Component 'is_preview' => false, ]); } - } else { - // Delete SERVICE_FQDN_ and SERVICE_URL_ variables if domain is removed - EnvironmentVariable::where('resourceable_type', Application::class) - ->where('resourceable_id', $this->application->id) - ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%") - ->delete(); - - EnvironmentVariable::where('resourceable_type', Application::class) - ->where('resourceable_id', $this->application->id) - ->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%") - ->delete(); } } } diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index e7cff4f29..d8e397e59 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -102,7 +102,7 @@ class Create extends Component } }); } - $service->parse(isNew: true); + $service->parse(isNew: true, isOneClick: true); return redirect()->route('project.service.configuration', [ 'service_uuid' => $service->uuid, diff --git a/app/Models/Application.php b/app/Models/Application.php index 86eea1de8..ed97454d2 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1353,7 +1353,7 @@ class Application extends BaseModel public function parse(int $pull_request_id = 0, ?int $preview_id = null) { if ((int) $this->compose_parsing_version >= 3) { - return newParser($this, $pull_request_id, $preview_id); + return applicationParser($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); } else { diff --git a/app/Models/Service.php b/app/Models/Service.php index da6c34fbb..4bef46642 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -1274,10 +1274,10 @@ class Service extends BaseModel instant_remote_process($commands, $this->server); } - public function parse(bool $isNew = false): Collection + public function parse(bool $isNew = false, bool $isOneClick = false): Collection { if ((int) $this->compose_parsing_version >= 3) { - return newParser($this); + return serviceParser($this, isOneClick: $isOneClick); } elseif ($this->docker_compose_raw) { return parseDockerComposeFile($this, $isNew); } else { diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php new file mode 100644 index 000000000..649de731d --- /dev/null +++ b/bootstrap/helpers/parsers.php @@ -0,0 +1,1657 @@ +fileStorages(); + + try { + $yaml = Yaml::parse($compose); + } catch (\Exception) { + return collect([]); + } + $services = data_get($yaml, 'services', collect([])); + $topLevel = collect([ + 'volumes' => collect(data_get($yaml, 'volumes', [])), + 'networks' => collect(data_get($yaml, 'networks', [])), + 'configs' => collect(data_get($yaml, 'configs', [])), + 'secrets' => collect(data_get($yaml, 'secrets', [])), + ]); + // If there are predefined volumes, make sure they are not null + if ($topLevel->get('volumes')->count() > 0) { + $temp = collect([]); + foreach ($topLevel['volumes'] as $volumeName => $volume) { + if (is_null($volume)) { + continue; + } + $temp->put($volumeName, $volume); + } + $topLevel['volumes'] = $temp; + } + // Get the base docker network + $baseNetwork = collect([$uuid]); + if ($isPullRequest) { + $baseNetwork = collect(["{$uuid}-{$pullRequestId}"]); + } + + $parsedServices = collect([]); + + $allMagicEnvironments = collect([]); + foreach ($services as $serviceName => $service) { + $magicEnvironments = collect([]); + $image = data_get_str($service, 'image'); + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + // convert environment variables to one format + $environment = convertToKeyValueCollection($environment); + + // Add Coolify defined environments + $allEnvironments = $resource->environment_variables()->get(['key', 'value']); + + $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { + return [$item['key'] => $item['value']]; + }); + // filter and add magic environments + foreach ($environment as $key => $value) { + // Get all SERVICE_ variables from keys and values + $key = str($key); + $value = str($value); + $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; + preg_match_all($regex, $value, $valueMatches); + if (count($valueMatches[1]) > 0) { + foreach ($valueMatches[1] as $match) { + $match = replaceVariables($match); + if ($match->startsWith('SERVICE_')) { + if ($magicEnvironments->has($match->value())) { + continue; + } + $magicEnvironments->put($match->value(), ''); + } + } + } + // Get magic environments where we need to preset the FQDN + if ($key->startsWith('SERVICE_FQDN_')) { + // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 + 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; + } + $fqdn = $resource->fqdn; + if (blank($resource->fqdn)) { + $fqdn = generateFqdn($server, "$uuid"); + } + + if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { + $path = $value->value(); + if ($path !== '/') { + $fqdn = "$fqdn$path"; + } + } + $fqdnWithPort = $fqdn; + if ($port) { + $fqdnWithPort = "$fqdn:$port"; + } + if (is_null($resource->fqdn)) { + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->fqdn = $fqdnWithPort; + $resource->save(); + } + + if (substr_count(str($key)->value(), '_') === 2) { + $resource->environment_variables()->updateOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + if (substr_count(str($key)->value(), '_') === 3) { + + $newKey = str($key)->beforeLast('_'); + $resource->environment_variables()->updateOrCreate([ + 'key' => $newKey->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + + $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); + if ($magicEnvironments->count() > 0) { + foreach ($magicEnvironments as $key => $value) { + $key = str($key); + $value = replaceVariables($value); + $command = parseCommandFromMagicEnvVariable($key); + if ($command->value() === 'FQDN') { + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } elseif ($command->value() === 'URL') { + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } else { + $value = generateEnvValue($command, $resource); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + } + + // Parse the rest of the services + foreach ($services as $serviceName => $service) { + $image = data_get_str($service, 'image'); + $restart = data_get_str($service, 'restart', RESTART_MODE); + $logging = data_get($service, 'logging'); + + if ($server->isLogDrainEnabled()) { + if ($resource->isLogDrainEnabled()) { + $logging = generate_fluentd_configuration(); + } + } + $volumes = collect(data_get($service, 'volumes', [])); + $networks = collect(data_get($service, 'networks', [])); + $use_network_mode = data_get($service, 'network_mode') !== null; + $depends_on = collect(data_get($service, 'depends_on', [])); + $labels = collect(data_get($service, 'labels', [])); + if ($labels->count() > 0) { + if (isAssociativeArray($labels)) { + $newLabels = collect([]); + $labels->each(function ($value, $key) use ($newLabels) { + $newLabels->push("$key=$value"); + }); + $labels = $newLabels; + } + } + $environment = collect(data_get($service, 'environment', [])); + $ports = collect(data_get($service, 'ports', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + $environment = convertToKeyValueCollection($environment); + $coolifyEnvironments = collect([]); + + $isDatabase = isDatabaseImage($image, $service); + $volumesParsed = collect([]); + + $baseName = generateApplicationContainerName( + application: $resource, + pull_request_id: $pullRequestId + ); + $containerName = "$serviceName-$baseName"; + $predefinedPort = null; + + $originalResource = $resource; + + if ($volumes->count() > 0) { + foreach ($volumes as $index => $volume) { + $type = null; + $source = null; + $target = null; + $content = null; + $isDirectory = false; + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if (sourceIsLocal($source)) { + $type = str('bind'); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // By default, we cannot determine if the bind is a directory or not, so we set it to directory + $isDirectory = true; + } + } else { + $type = str('volume'); + } + } elseif (is_array($volume)) { + $type = data_get_str($volume, 'type'); + $source = data_get_str($volume, 'source'); + $target = data_get_str($volume, 'target'); + $content = data_get($volume, 'content'); + $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); + + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // if isDirectory is not set (or false) & content is also not set, we assume it is a directory + if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { + $isDirectory = true; + } + } + } + if ($type->value() === 'bind') { + if ($source->value() === '/var/run/docker.sock') { + $volume = $source->value().':'.$target->value(); + } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { + $volume = $source->value().':'.$target->value(); + } else { + if ((int) $resource->compose_parsing_version >= 4) { + $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + } else { + $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + } + $source = replaceLocalSource($source, $mainDirectory); + if ($isPullRequest) { + $source = $source."-pr-$pullRequestId"; + } + LocalFileVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'fs_path' => $source, + 'mount_path' => $target, + 'content' => $content, + 'is_directory' => $isDirectory, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + if (isDev()) { + if ((int) $resource->compose_parsing_version >= 4) { + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); + } else { + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); + } + } + $volume = "$source:$target"; + } + } elseif ($type->value() === 'volume') { + if ($topLevel->get('volumes')->has($source->value())) { + $temp = $topLevel->get('volumes')->get($source->value()); + if (data_get($temp, 'driver_opts.type') === 'cifs') { + continue; + } + if (data_get($temp, 'driver_opts.type') === 'nfs') { + continue; + } + } + $slugWithoutUuid = Str::slug($source, '-'); + $name = "{$uuid}_{$slugWithoutUuid}"; + + if ($isPullRequest) { + $name = "{$name}-pr-$pullRequestId"; + } + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $source = $name; + $volume = "$source:$target"; + } elseif (is_array($volume)) { + data_set($volume, 'source', $name); + } + $topLevel->get('volumes')->put($name, [ + 'name' => $name, + ]); + LocalPersistentVolume::updateOrCreate( + [ + 'name' => $name, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'name' => $name, + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + } + dispatch(new ServerFilesFromServerJob($originalResource)); + $volumesParsed->put($index, $volume); + } + } + + if ($depends_on?->count() > 0) { + if ($isPullRequest) { + $newDependsOn = collect([]); + $depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) { + if (is_numeric($condition)) { + $dependency = "$dependency-pr-$pullRequestId"; + + $newDependsOn->put($condition, $dependency); + } else { + $condition = "$condition-pr-$pullRequestId"; + $newDependsOn->put($condition, $dependency); + } + }); + $depends_on = $newDependsOn; + } + } + if (! $use_network_mode) { + if ($topLevel->get('networks')?->count() > 0) { + foreach ($topLevel->get('networks') as $networkName => $network) { + if ($networkName === 'default') { + continue; + } + // ignore aliases + if ($network['aliases'] ?? false) { + continue; + } + $networkExists = $networks->contains(function ($value, $key) use ($networkName) { + return $value == $networkName || $key == $networkName; + }); + if (! $networkExists) { + $networks->put($networkName, null); + } + } + } + $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { + return $value == $baseNetwork; + }); + if (! $baseNetworkExists) { + foreach ($baseNetwork as $network) { + $topLevel->get('networks')->put($network, [ + 'name' => $network, + 'external' => true, + ]); + } + } + } + + // Collect/create/update ports + $collectedPorts = collect([]); + if ($ports->count() > 0) { + foreach ($ports as $sport) { + if (is_string($sport) || is_numeric($sport)) { + $collectedPorts->push($sport); + } + if (is_array($sport)) { + $target = data_get($sport, 'target'); + $published = data_get($sport, 'published'); + $protocol = data_get($sport, 'protocol'); + $collectedPorts->push("$target:$published/$protocol"); + } + } + } + + $networks_temp = collect(); + + if (! $use_network_mode) { + foreach ($networks as $key => $network) { + if (gettype($network) === 'string') { + // networks: + // - appwrite + $networks_temp->put($network, null); + } elseif (gettype($network) === 'array') { + // networks: + // default: + // ipv4_address: 192.168.203.254 + $networks_temp->put($key, $network); + } + } + foreach ($baseNetwork as $key => $network) { + $networks_temp->put($network, null); + } + + if (data_get($resource, 'settings.connect_to_docker_network')) { + $network = $resource->destination->network; + $networks_temp->put($network, null); + $topLevel->get('networks')->put($network, [ + 'name' => $network, + 'external' => true, + ]); + } + } + + $normalEnvironments = $environment->diffKeys($allMagicEnvironments); + $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { + return ! str($value)->startsWith('SERVICE_'); + }); + foreach ($normalEnvironments as $key => $value) { + $key = str($key); + $value = str($value); + $originalValue = $value; + $parsedValue = replaceVariables($value); + if ($value->startsWith('$SERVICE_')) { + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + + continue; + } + if (! $value->startsWith('$')) { + continue; + } + if ($key->value() === $parsedValue->value()) { + $value = null; + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } else { + if ($value->startsWith('$')) { + $isRequired = false; + if ($value->contains(':-')) { + $value = replaceVariables($value); + $key = $value->before(':'); + $value = $value->after(':-'); + } elseif ($value->contains('-')) { + $value = replaceVariables($value); + + $key = $value->before('-'); + $value = $value->after('-'); + } elseif ($value->contains(':?')) { + $value = replaceVariables($value); + + $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()->firstOrCreate([ + 'key' => $parsedKeyValue, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $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()] = $value; + + continue; + } + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + 'is_required' => $isRequired, + ]); + } + } + } + $branch = $originalResource->git_branch; + if ($pullRequestId !== 0) { + $branch = "pull/{$pullRequestId}/head"; + } + if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\""); + } + + // Add COOLIFY_RESOURCE_UUID to environment + if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}"); + } + + // Add COOLIFY_CONTAINER_NAME to environment + if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}"); + } + + if ($isPullRequest) { + $preview = $resource->previews()->find($preview_id); + $domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]); + } else { + $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); + } + ray($domains); + $fqdns = data_get($domains, "$serviceName.domain"); + // Generate SERVICE_FQDN & SERVICE_URL for dockercompose + if ($resource->build_pack === 'dockercompose') { + foreach ($domains as $forServiceName => $domain) { + $parsedDomain = data_get($domain, 'domain'); + $serviceNameFormatted = str($serviceName)->upper()->replace('-', '_'); + + if (filled($parsedDomain)) { + $parsedDomain = str($parsedDomain)->explode(',')->first(); + $coolifyUrl = Url::fromString($parsedDomain); + $coolifyScheme = $coolifyUrl->getScheme(); + $coolifyFqdn = $coolifyUrl->getHost(); + $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); + $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyUrl->__toString()); + $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyFqdn); + $resource->environment_variables()->updateOrCreate([ + 'resourceable_type' => Application::class, + 'resourceable_id' => $resource->id, + 'key' => 'SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), + ], [ + 'value' => $coolifyUrl->__toString(), + 'is_build_time' => false, + 'is_preview' => false, + ]); + $resource->environment_variables()->updateOrCreate([ + 'resourceable_type' => Application::class, + 'resourceable_id' => $resource->id, + 'key' => 'SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), + ], [ + 'value' => $coolifyFqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } else { + $resource->environment_variables()->where('resourceable_type', Application::class) + ->where('resourceable_id', $resource->id) + ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%") + ->delete(); + $resource->environment_variables()->where('resourceable_type', Application::class) + ->where('resourceable_id', $resource->id) + ->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%") + ->delete(); + } + } + } + // If the domain is set, we need to generate the FQDNs for the preview + if (filled($fqdns)) { + $fqdns = str($fqdns)->explode(','); + if ($isPullRequest) { + $preview = $resource->previews()->find($preview_id); + $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))); + if ($docker_compose_domains->count() > 0) { + $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain"); + if ($found_fqdn) { + $fqdns = collect($found_fqdn); + } else { + $fqdns = collect([]); + } + } else { + $fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId, $resource) { + $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pullRequestId); + $url = Url::fromString($fqdn); + $template = $resource->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2; + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn"; + $preview->fqdn = $preview_fqdn; + $preview->save(); + + return $preview_fqdn; + }); + } + } + } + $defaultLabels = defaultLabels( + id: $resource->id, + name: $containerName, + projectName: $resource->project()->name, + resourceName: $resource->name, + pull_request_id: $pullRequestId, + type: 'application', + environment: $resource->environment->name, + ); + + $isDatabase = isDatabaseImage($image, $service); + // Add COOLIFY_FQDN & COOLIFY_URL to environment + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + $fqdnsWithoutPort = $fqdns->map(function ($fqdn) { + return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://')); + }); + $coolifyEnvironments->put('COOLIFY_URL', $fqdnsWithoutPort->implode(',')); + + $urls = $fqdns->map(function ($fqdn) { + return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':'); + }); + $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(',')); + } + add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); + 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) { + $isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled'); + if ($isContainerLabelEscapeEnabled) { + $serviceLabels = $serviceLabels->map(function ($value, $key) { + return escapeDollarSign($value); + }); + } + } + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + $shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels; + $uuid = $resource->uuid; + $network = data_get($resource, 'destination.network'); + if ($isPullRequest) { + $uuid = "{$resource->uuid}-{$pullRequestId}"; + } + if ($isPullRequest) { + $network = "{$resource->destination->network}-{$pullRequestId}"; + } + if ($shouldGenerateLabelsExactly) { + switch ($server->proxyType()) { + case ProxyTypes::TRAEFIK->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + break; + case ProxyTypes::CADDY->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + )); + break; + } + } else { + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + )); + } + } + data_forget($service, 'volumes.*.content'); + data_forget($service, 'volumes.*.isDirectory'); + 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(), + 'labels' => $serviceLabels, + ]); + if (! $use_network_mode) { + $payload['networks'] = $networks_temp; + } + if ($ports->count() > 0) { + $payload['ports'] = $ports; + } + if ($volumesParsed->count() > 0) { + $payload['volumes'] = $volumesParsed; + } + if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) { + $payload['environment'] = $environment->merge($coolifyEnvironments); + } + if ($logging) { + $payload['logging'] = $logging; + } + if ($depends_on->count() > 0) { + $payload['depends_on'] = $depends_on; + } + if ($isPullRequest) { + $serviceName = "{$serviceName}-pr-{$pullRequestId}"; + } + + $parsedServices->put($serviceName, $payload); + } + $topLevel->put('services', $parsedServices); + + $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; + + $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) { + return array_search($key, $customOrder); + }); + + $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2); + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->save(); + + return $topLevel; +} + +function serviceParser(Service $resource, bool $isOneClick = false): Collection +{ + $uuid = data_get($resource, 'uuid'); + $compose = data_get($resource, 'docker_compose_raw'); + if (! $compose) { + return collect([]); + } + + $server = data_get($resource, 'server'); + $allServices = get_service_templates(); + + try { + $yaml = Yaml::parse($compose); + } catch (\Exception) { + return collect([]); + } + $services = data_get($yaml, 'services', collect([])); + $topLevel = collect([ + 'volumes' => collect(data_get($yaml, 'volumes', [])), + 'networks' => collect(data_get($yaml, 'networks', [])), + 'configs' => collect(data_get($yaml, 'configs', [])), + 'secrets' => collect(data_get($yaml, 'secrets', [])), + ]); + // If there are predefined volumes, make sure they are not null + if ($topLevel->get('volumes')->count() > 0) { + $temp = collect([]); + foreach ($topLevel['volumes'] as $volumeName => $volume) { + if (is_null($volume)) { + continue; + } + $temp->put($volumeName, $volume); + } + $topLevel['volumes'] = $temp; + } + // Get the base docker network + $baseNetwork = collect([$uuid]); + + $parsedServices = collect([]); + + $allMagicEnvironments = collect([]); + foreach ($services as $serviceName => $service) { + $predefinedPort = null; + $magicEnvironments = collect([]); + $image = data_get_str($service, 'image'); + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + $isDatabase = isDatabaseImage($image, $service); + + $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) { + $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); + if ($applicationFound) { + $savedService = $applicationFound; + } else { + $savedService = ServiceDatabase::firstOrCreate([ + 'name' => $serviceName, + 'service_id' => $resource->id, + ]); + } + } else { + $savedService = ServiceApplication::firstOrCreate([ + 'name' => $serviceName, + 'service_id' => $resource->id, + ], [ + 'is_gzip_enabled' => true, + ]); + } + // Check if image changed + if ($savedService->image !== $image) { + $savedService->image = $image; + $savedService->save(); + } + // Pocketbase does not need gzip for SSE. + if (str($savedService->image)->contains('pocketbase') && $savedService->is_gzip_enabled) { + $savedService->is_gzip_enabled = false; + $savedService->save(); + } + + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + // convert environment variables to one format + $environment = convertToKeyValueCollection($environment); + + // Add Coolify defined environments + $allEnvironments = $resource->environment_variables()->get(['key', 'value']); + + $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { + return [$item['key'] => $item['value']]; + }); + // filter and add magic environments + foreach ($environment as $key => $value) { + // Get all SERVICE_ variables from keys and values + $key = str($key); + $value = str($value); + $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; + preg_match_all($regex, $value, $valueMatches); + if (count($valueMatches[1]) > 0) { + foreach ($valueMatches[1] as $match) { + $match = replaceVariables($match); + if ($match->startsWith('SERVICE_')) { + if ($magicEnvironments->has($match->value())) { + continue; + } + $magicEnvironments->put($match->value(), ''); + } + } + } + // Get magic environments where we need to preset the FQDN + if ($key->startsWith('SERVICE_FQDN_')) { + // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 + 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 ($isOneClick) { + if (blank($savedService->fqdn)) { + if ($fqdnFor) { + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + } else { + $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); + } + } else { + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + } + + } else { + // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty + if (blank($savedService->fqdn)) { + $fqdn = ''; + } else { + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + } + } + if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { + $path = $value->value(); + if ($path !== '/') { + $fqdn = "$fqdn$path"; + } + } + $fqdnWithPort = $fqdn; + if ($port) { + $fqdnWithPort = "$fqdn:$port"; + } + if (is_null($savedService->fqdn)) { + $savedService->fqdn = $fqdnWithPort; + $savedService->save(); + } + + if (substr_count(str($key)->value(), '_') === 2) { + $resource->environment_variables()->updateOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + if (substr_count(str($key)->value(), '_') === 3) { + $newKey = str($key)->beforeLast('_'); + $resource->environment_variables()->updateOrCreate([ + 'key' => $newKey->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + + $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); + if ($magicEnvironments->count() > 0) { + foreach ($magicEnvironments as $key => $value) { + $key = str($key); + $value = replaceVariables($value); + $command = parseCommandFromMagicEnvVariable($key); + if ($command->value() === 'FQDN') { + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } elseif ($command->value() === 'URL') { + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } else { + $value = generateEnvValue($command, $resource); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + } + + $serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) { + return $app->isLogDrainEnabled(); + }); + + // Parse the rest of the services + foreach ($services as $serviceName => $service) { + $image = data_get_str($service, 'image'); + $restart = data_get_str($service, 'restart', RESTART_MODE); + $logging = data_get($service, 'logging'); + + if ($server->isLogDrainEnabled()) { + if ($serviceAppsLogDrainEnabledMap->get($serviceName)) { + $logging = generate_fluentd_configuration(); + } + } + $volumes = collect(data_get($service, 'volumes', [])); + $networks = collect(data_get($service, 'networks', [])); + $use_network_mode = data_get($service, 'network_mode') !== null; + $depends_on = collect(data_get($service, 'depends_on', [])); + $labels = collect(data_get($service, 'labels', [])); + if ($labels->count() > 0) { + if (isAssociativeArray($labels)) { + $newLabels = collect([]); + $labels->each(function ($value, $key) use ($newLabels) { + $newLabels->push("$key=$value"); + }); + $labels = $newLabels; + } + } + $environment = collect(data_get($service, 'environment', [])); + $ports = collect(data_get($service, 'ports', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + $environment = convertToKeyValueCollection($environment); + $coolifyEnvironments = collect([]); + + $isDatabase = isDatabaseImage($image, $service); + $volumesParsed = collect([]); + + $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) { + $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); + if ($applicationFound) { + $savedService = $applicationFound; + } else { + $savedService = ServiceDatabase::firstOrCreate([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); + } + } else { + $savedService = ServiceApplication::firstOrCreate([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); + } + $fileStorages = $savedService->fileStorages(); + if ($savedService->image !== $image) { + $savedService->image = $image; + $savedService->save(); + } + + $originalResource = $savedService; + + if ($volumes->count() > 0) { + foreach ($volumes as $index => $volume) { + $type = null; + $source = null; + $target = null; + $content = null; + $isDirectory = false; + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if (sourceIsLocal($source)) { + $type = str('bind'); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // By default, we cannot determine if the bind is a directory or not, so we set it to directory + $isDirectory = true; + } + } else { + $type = str('volume'); + } + } elseif (is_array($volume)) { + $type = data_get_str($volume, 'type'); + $source = data_get_str($volume, 'source'); + $target = data_get_str($volume, 'target'); + $content = data_get($volume, 'content'); + $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); + + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // if isDirectory is not set (or false) & content is also not set, we assume it is a directory + if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { + $isDirectory = true; + } + } + } + if ($type->value() === 'bind') { + if ($source->value() === '/var/run/docker.sock') { + $volume = $source->value().':'.$target->value(); + } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { + $volume = $source->value().':'.$target->value(); + } else { + if ((int) $resource->compose_parsing_version >= 4) { + $mainDirectory = str(base_configuration_dir().'/services/'.$uuid); + } else { + $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + } + $source = replaceLocalSource($source, $mainDirectory); + LocalFileVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'fs_path' => $source, + 'mount_path' => $target, + 'content' => $content, + 'is_directory' => $isDirectory, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + if (isDev()) { + if ((int) $resource->compose_parsing_version >= 4) { + $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') { + if ($topLevel->get('volumes')->has($source->value())) { + $temp = $topLevel->get('volumes')->get($source->value()); + if (data_get($temp, 'driver_opts.type') === 'cifs') { + continue; + } + if (data_get($temp, 'driver_opts.type') === 'nfs') { + continue; + } + } + $slugWithoutUuid = Str::slug($source, '-'); + $name = "{$uuid}_{$slugWithoutUuid}"; + + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $source = $name; + $volume = "$source:$target"; + } elseif (is_array($volume)) { + data_set($volume, 'source', $name); + } + $topLevel->get('volumes')->put($name, [ + 'name' => $name, + ]); + LocalPersistentVolume::updateOrCreate( + [ + 'name' => $name, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'name' => $name, + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + } + dispatch(new ServerFilesFromServerJob($originalResource)); + $volumesParsed->put($index, $volume); + } + } + + if (! $use_network_mode) { + if ($topLevel->get('networks')?->count() > 0) { + foreach ($topLevel->get('networks') as $networkName => $network) { + if ($networkName === 'default') { + continue; + } + // ignore aliases + if ($network['aliases'] ?? false) { + continue; + } + $networkExists = $networks->contains(function ($value, $key) use ($networkName) { + return $value == $networkName || $key == $networkName; + }); + if (! $networkExists) { + $networks->put($networkName, null); + } + } + } + $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { + return $value == $baseNetwork; + }); + if (! $baseNetworkExists) { + foreach ($baseNetwork as $network) { + $topLevel->get('networks')->put($network, [ + 'name' => $network, + 'external' => true, + ]); + } + } + } + + // Collect/create/update ports + $collectedPorts = collect([]); + if ($ports->count() > 0) { + foreach ($ports as $sport) { + if (is_string($sport) || is_numeric($sport)) { + $collectedPorts->push($sport); + } + if (is_array($sport)) { + $target = data_get($sport, 'target'); + $published = data_get($sport, 'published'); + $protocol = data_get($sport, 'protocol'); + $collectedPorts->push("$target:$published/$protocol"); + } + } + } + $originalResource->ports = $collectedPorts->implode(','); + $originalResource->save(); + + $networks_temp = collect(); + + if (! $use_network_mode) { + foreach ($networks as $key => $network) { + if (gettype($network) === 'string') { + // networks: + // - appwrite + $networks_temp->put($network, null); + } elseif (gettype($network) === 'array') { + // networks: + // default: + // ipv4_address: 192.168.203.254 + $networks_temp->put($key, $network); + } + } + foreach ($baseNetwork as $key => $network) { + $networks_temp->put($network, null); + } + } + + $normalEnvironments = $environment->diffKeys($allMagicEnvironments); + $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { + return ! str($value)->startsWith('SERVICE_'); + }); + foreach ($normalEnvironments as $key => $value) { + $key = str($key); + $value = str($value); + $originalValue = $value; + $parsedValue = replaceVariables($value); + if ($value->startsWith('$SERVICE_')) { + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + + continue; + } + if (! $value->startsWith('$')) { + continue; + } + if ($key->value() === $parsedValue->value()) { + $value = null; + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } else { + if ($value->startsWith('$')) { + $isRequired = false; + if ($value->contains(':-')) { + $value = replaceVariables($value); + $key = $value->before(':'); + $value = $value->after(':-'); + } elseif ($value->contains('-')) { + $value = replaceVariables($value); + + $key = $value->before('-'); + $value = $value->after('-'); + } elseif ($value->contains(':?')) { + $value = replaceVariables($value); + + $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()->firstOrCreate([ + 'key' => $parsedKeyValue, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $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()] = $value; + + continue; + } + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + 'is_required' => $isRequired, + ]); + } + } + } + + // Add COOLIFY_RESOURCE_UUID to environment + if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}"); + } + + // Add COOLIFY_CONTAINER_NAME to environment + if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}"); + } + + if ($savedService->serviceType()) { + $fqdns = generateServiceSpecificFqdns($savedService); + } else { + $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); + } + + $defaultLabels = defaultLabels( + id: $resource->id, + name: $containerName, + projectName: $resource->project()->name, + resourceName: $resource->name, + type: 'service', + subType: $isDatabase ? 'database' : 'application', + subId: $savedService->id, + subName: $savedService->human_name ?? $savedService->name, + environment: $resource->environment->name, + ); + + // Add COOLIFY_FQDN & COOLIFY_URL to environment + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + $fqdnsWithoutPort = $fqdns->map(function ($fqdn) { + return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':'); + }); + $coolifyEnvironments->put('COOLIFY_FQDN', $fqdnsWithoutPort->implode(',')); + $urls = $fqdns->map(function ($fqdn) { + return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://')); + }); + $coolifyEnvironments->put('COOLIFY_URL', $urls->implode(',')); + } + add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); + 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) { + $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) { + $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels; + $uuid = $resource->uuid; + $network = data_get($resource, 'destination.network'); + if ($shouldGenerateLabelsExactly) { + switch ($server->proxyType()) { + case ProxyTypes::TRAEFIK->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + break; + case ProxyTypes::CADDY->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + )); + break; + } + } else { + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + )); + } + } + if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { + $savedService->update(['exclude_from_status' => true]); + } + data_forget($service, 'volumes.*.content'); + data_forget($service, 'volumes.*.isDirectory'); + 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(), + 'labels' => $serviceLabels, + ]); + if (! $use_network_mode) { + $payload['networks'] = $networks_temp; + } + if ($ports->count() > 0) { + $payload['ports'] = $ports; + } + if ($volumesParsed->count() > 0) { + $payload['volumes'] = $volumesParsed; + } + if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) { + $payload['environment'] = $environment->merge($coolifyEnvironments); + } + if ($logging) { + $payload['logging'] = $logging; + } + if ($depends_on->count() > 0) { + $payload['depends_on'] = $depends_on; + } + + $parsedServices->put($serviceName, $payload); + } + $topLevel->put('services', $parsedServices); + + $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; + + $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) { + return array_search($key, $customOrder); + }); + + $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2); + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->save(); + + return $topLevel; +} diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 1e1d2a073..cf12a28a5 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -1,7 +1,6 @@ image = $updatedImage; $resource->save(); } + + $serviceName = str($resource->name)->upper()->replace('-', '_'); + $resource->service->environment_variables()->where('key', 'LIKE', "SERVICE_FQDN_{$serviceName}%")->delete(); + $resource->service->environment_variables()->where('key', 'LIKE', "SERVICE_URL_{$serviceName}%")->delete(); + if ($resource->fqdn) { $resourceFqdns = str($resource->fqdn)->explode(','); - if ($resourceFqdns->count() === 1) { - $resourceFqdns = $resourceFqdns->first(); - $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_'); - $fqdn = Url::fromString($resourceFqdns); - $port = $fqdn->getPort(); - $path = $fqdn->getPath(); - $fqdn = $fqdn->getScheme().'://'.$fqdn->getHost(); - $fqdnValue = ($path === '/') ? $fqdn : $fqdn.$path; - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Service::class, - 'resourceable_id' => $resource->service_id, - 'key' => $variableName, - ], [ - 'value' => $fqdnValue, - 'is_build_time' => false, - 'is_preview' => false, - ]); - if ($port) { - $variableName = $variableName."_$port"; - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Service::class, - 'resourceable_id' => $resource->service_id, - 'key' => $variableName, - ], [ - 'value' => $fqdnValue, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); - $url = Url::fromString($fqdn); - $port = $url->getPort(); - $path = $url->getPath(); - $url = $url->getHost(); - $urlValue = str($fqdn)->after('://'); - if ($path !== '/') { - $urlValue = $urlValue.$path; - } - EnvironmentVariable::updateOrCreate([ + $resourceFqdns = $resourceFqdns->first(); + $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); + $url = Url::fromString($resourceFqdns); + $port = $url->getPort(); + $path = $url->getPath(); + $urlValue = $url->getScheme().'://'.$url->getHost(); + $urlValue = ($path === '/') ? $urlValue : $urlValue.$path; + $resource->service->environment_variables()->updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $urlValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + if ($port) { + $variableName = $variableName."_$port"; + $resource->service->environment_variables()->updateOrCreate([ 'resourceable_type' => Service::class, 'resourceable_id' => $resource->service_id, 'key' => $variableName, @@ -164,114 +148,37 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) 'is_build_time' => false, 'is_preview' => false, ]); - if ($port) { - $variableName = $variableName."_$port"; - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Service::class, - 'resourceable_id' => $resource->service_id, - 'key' => $variableName, - ], [ - 'value' => $urlValue, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } elseif ($resourceFqdns->count() > 1) { - foreach ($resourceFqdns as $fqdn) { - $host = Url::fromString($fqdn); - $port = $host->getPort(); - $url = $host->getHost(); - $path = $host->getPath(); - $host = $host->getScheme().'://'.$host->getHost(); - if ($port) { - $port_envs = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'like', "SERVICE_FQDN_%_$port") - ->get(); - foreach ($port_envs as $port_env) { - $service_fqdn = str($port_env->key)->beforeLast('_')->after('SERVICE_FQDN_'); - $env = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'SERVICE_FQDN_'.$service_fqdn) - ->first(); - if ($env) { - if ($path === '/') { - $env->value = $host; - } else { - $env->value = $host.$path; - } - $env->save(); - } - if ($path === '/') { - $port_env->value = $host; - } else { - $port_env->value = $host.$path; - } - $port_env->save(); - } - $port_envs_url = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'like', "SERVICE_URL_%_$port") - ->get(); - foreach ($port_envs_url as $port_env_url) { - $service_url = str($port_env_url->key)->beforeLast('_')->after('SERVICE_URL_'); - $env = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'SERVICE_URL_'.$service_url) - ->first(); - if ($env) { - if ($path === '/') { - $env->value = $url; - } else { - $env->value = $url.$path; - } - $env->save(); - } - if ($path === '/') { - $port_env_url->value = $url; - } else { - $port_env_url->value = $url.$path; - } - $port_env_url->save(); - } - } else { - $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_'); - $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', $variableName) - ->first(); - $fqdn = Url::fromString($fqdn); - $fqdn = $fqdn->getScheme().'://'.$fqdn->getHost().$fqdn->getPath(); - if ($generatedEnv) { - $generatedEnv->value = $fqdn; - $generatedEnv->save(); - } - $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); - $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', $variableName) - ->first(); - $url = Url::fromString($fqdn); - $url = $url->getHost().$url->getPath(); - if ($generatedEnv) { - $url = str($fqdn)->after('://'); - $generatedEnv->value = $url; - $generatedEnv->save(); - } - } - } } - } else { - // If FQDN is removed, delete the corresponding environment variables - $serviceName = str($resource->name)->upper()->replace('-', '_'); - EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceName}%") - ->delete(); - EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'LIKE', "SERVICE_URL_{$serviceName}%") - ->delete(); + $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_'); + $fqdn = Url::fromString($resourceFqdns); + $port = $fqdn->getPort(); + $path = $fqdn->getPath(); + $fqdn = $fqdn->getHost(); + $fqdnValue = str($fqdn)->after('://'); + if ($path !== '/') { + $fqdnValue = $fqdnValue.$path; + } + $resource->service->environment_variables()->updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $fqdnValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + if ($port) { + $variableName = $variableName."_$port"; + $resource->service->environment_variables()->updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $fqdnValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } } } catch (\Throwable $e) { return handleError($e); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 7ce511f2c..1e9b95288 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -2918,10 +2918,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } -function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection +function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null, bool $isOneClick = false): Collection { $isApplication = $resource instanceof Application; $isService = $resource instanceof Service; + if ($isApplication) { + return applicationParser($resource, $pull_request_id, $preview_id); + } + if ($isService) { + return serviceParser($resource, $isOneClick); + } + + return collect([]); $uuid = data_get($resource, 'uuid'); $compose = data_get($resource, 'docker_compose_raw'); @@ -3078,14 +3086,25 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $fqdn = generateFqdn($server, "$uuid"); } } elseif ($isService) { - if (blank($savedService->fqdn)) { - if ($fqdnFor) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + if ($isOneClick) { + if (blank($savedService->fqdn)) { + if ($fqdnFor) { + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + } else { + $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); + } } else { - $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); } + } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty + if (blank($savedService->fqdn)) { + $fqdn = ''; + } else { + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + } + ray($fqdn); } } @@ -3141,51 +3160,41 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $key = str($key); $value = replaceVariables($value); $command = parseCommandFromMagicEnvVariable($key); - $found = $resource->environment_variables()->where('key', $key->value())->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first(); - if ($found) { - continue; - } if ($command->value() === 'FQDN') { - if ($isApplication && $resource->build_pack === 'dockercompose') { - continue; + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); } - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); } elseif ($command->value() === 'URL') { - if ($isApplication && $resource->build_pack === 'dockercompose') { - continue; + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); } - // For services, only generate URL if explicit FQDN is set - if ($isService && blank($savedService->fqdn)) { - continue; - } - $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); } else { $value = generateEnvValue($command, $resource); $resource->environment_variables()->firstOrCreate([ @@ -3279,12 +3288,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $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, @@ -3550,7 +3553,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { return ! str($value)->startsWith('SERVICE_'); }); - foreach ($normalEnvironments as $key => $value) { $key = str($key); $value = str($value); @@ -3669,6 +3671,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $fqdns = data_get($domains, "$serviceName.domain"); // Generate SERVICE_FQDN & SERVICE_URL for dockercompose if ($resource->build_pack === 'dockercompose') { + foreach ($domains as $forServiceName => $domain) { $parsedDomain = data_get($domain, 'domain'); if (filled($parsedDomain)) { @@ -3731,7 +3734,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } else { $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); } - $defaultLabels = defaultLabels( id: $resource->id, name: $containerName, @@ -3757,7 +3759,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(',')); } add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); - if ($environment->count() > 0) { $environment = $environment->filter(function ($value, $key) { return ! str($key)->startsWith('SERVICE_FQDN_'); From 1ddec358a55793bd12a2d4f99d6b866c756dbb37 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:45:21 +0200 Subject: [PATCH 098/202] feat(input): add autofocus attribute to input component for improved accessibility --- app/View/Components/Forms/Input.php | 1 + resources/views/components/forms/input.blade.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php index 7283ef20f..a7bd87949 100644 --- a/app/View/Components/Forms/Input.php +++ b/app/View/Components/Forms/Input.php @@ -25,6 +25,7 @@ class Input extends Component public string $autocomplete = 'off', public ?int $minlength = null, public ?int $maxlength = null, + public bool $autofocus = false, ) {} public function render(): View|Closure|string diff --git a/resources/views/components/forms/input.blade.php b/resources/views/components/forms/input.blade.php index 20e6485bf..799a5f110 100644 --- a/resources/views/components/forms/input.blade.php +++ b/resources/views/components/forms/input.blade.php @@ -32,7 +32,7 @@ wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled" type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}" name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}" - aria-placeholder="{{ $attributes->get('placeholder') }}"> + aria-placeholder="{{ $attributes->get('placeholder') }}" @if ($autofocus) x-ref="autofocusInput" @endif>
@else @@ -45,7 +45,7 @@ max="{{ $attributes->get('max') }}" minlength="{{ $attributes->get('minlength') }}" maxlength="{{ $attributes->get('maxlength') }}" @if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}" - placeholder="{{ $attributes->get('placeholder') }}"> + placeholder="{{ $attributes->get('placeholder') }}" @if ($autofocus) x-ref="autofocusInput" @endif> @endif @if (!$label && $helper) From a2c5f4b9d1f9afa68db054ad9f392533a9c29977 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:50:09 +0200 Subject: [PATCH 099/202] refactor(public-git-repository): enhance form structure and add autofocus to repository URL input --- .../new/public-git-repository.blade.php | 162 ++++++++++-------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/resources/views/livewire/project/new/public-git-repository.blade.php b/resources/views/livewire/project/new/public-git-repository.blade.php index ad48b6d4d..e07cfaaae 100644 --- a/resources/views/livewire/project/new/public-git-repository.blade.php +++ b/resources/views/livewire/project/new/public-git-repository.blade.php @@ -1,84 +1,94 @@ -
+

Create a new Application

Deploy any public Git repositories.
+ +
-
-
- - - Check repository - -
-
- For example application deployments, checkout Coolify - Examples. -
- @if ($branchFound) - @if ($rate_limit_remaining && $rate_limit_reset) -
-
Rate Limit
- -
- @endif -
-
- @if ($git_source === 'other') - - @else - - @endif - - - - - - - @if ($isStatic) - - @endif -
- @if ($build_pack === 'dockercompose') - - - Compose file location in your repository:{{ Str::start($base_directory . $docker_compose_location, '/') }} - @else - - @endif - @if ($show_is_static) - -
- -
- @endif - {{--
- -
--}} - {{-- @if ($build_pack === 'dockercompose' && isDev()) -
If you choose Docker Compose based deployments, you cannot - change it afterwards.
- - @endif --}} -
- - Continue - - @endif +
+ + + Check repository + +
+
+ For example application deployments, checkout Coolify + Examples.
+ + @if ($branchFound) + @if ($rate_limit_remaining && $rate_limit_reset) +
+
Rate Limit
+ +
+ @endif + + +
+
+
+ @if ($git_source === 'other') + + @else + + @endif + + + + + + + @if ($isStatic) + + @endif +
+ @if ($build_pack === 'dockercompose') +
+ + +
+ + Compose file location in your repository: +
+
+ @else + + @endif + @if ($show_is_static) + +
+ +
+ @endif + {{--
+ +
--}} + {{-- @if ($build_pack === 'dockercompose' && isDev()) +
If you choose Docker Compose based deployments, you cannot + change it afterwards.
+ + @endif --}} +
+ + Continue + +
+ @endif
From e2518e53d9d5842265b0dd95b9102af1680e41df Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:21:15 +0200 Subject: [PATCH 100/202] refactor(public-git-repository): remove commented-out code for cleaner template --- .../livewire/project/new/public-git-repository.blade.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/resources/views/livewire/project/new/public-git-repository.blade.php b/resources/views/livewire/project/new/public-git-repository.blade.php index e07cfaaae..ba9e37784 100644 --- a/resources/views/livewire/project/new/public-git-repository.blade.php +++ b/resources/views/livewire/project/new/public-git-repository.blade.php @@ -76,15 +76,6 @@ helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
@endif - {{--
- -
--}} - {{-- @if ($build_pack === 'dockercompose' && isDev()) -
If you choose Docker Compose based deployments, you cannot - change it afterwards.
- - @endif --}}
Continue From e8892b3d29290b69b796a6f2bddeab84d323b392 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:46:59 +0200 Subject: [PATCH 101/202] feat(core): finally fqdn is fqdn and url is url. haha --- app/Livewire/Project/Application/General.php | 18 +- .../Project/Application/PreviewsCompose.php | 2 +- app/Livewire/Project/CloneMe.php | 2 +- app/Livewire/Project/Resource/Create.php | 2 +- .../Project/Shared/ResourceOperations.php | 2 +- app/Models/Application.php | 18 +- app/Models/Service.php | 6 +- bootstrap/helpers/docker.php | 8 +- bootstrap/helpers/parsers.php | 235 +- bootstrap/helpers/shared.php | 1914 +++++++++-------- 10 files changed, 1159 insertions(+), 1048 deletions(-) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 2eadbaa0e..e7515b23f 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -227,7 +227,19 @@ class General extends Component return; } - $this->application->parse(); + + // Refresh parsedServiceDomains to reflect any changes in docker_compose_domains + $this->application->refresh(); + $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; + ray($this->parsedServiceDomains); + // Convert service names with dots to use underscores for HTML form binding + $sanitizedDomains = []; + foreach ($this->parsedServiceDomains as $serviceName => $domain) { + $sanitizedKey = str($serviceName)->slug('_')->toString(); + $sanitizedDomains[$sanitizedKey] = $domain; + } + $this->parsedServiceDomains = $sanitizedDomains; + $showToast && $this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('compose_loaded'); $this->dispatch('refreshStorages'); @@ -245,7 +257,7 @@ class General extends Component public function generateDomain(string $serviceName) { $uuid = new Cuid2; - $domain = generateFqdn($this->application->destination->server, $uuid); + $domain = generateUrl(server: $this->application->destination->server, random: $uuid); $sanitizedKey = str($serviceName)->slug('_')->toString(); $this->parsedServiceDomains[$sanitizedKey]['domain'] = $domain; @@ -315,7 +327,7 @@ class General extends Component { $server = data_get($this->application, 'destination.server'); if ($server) { - $fqdn = generateFqdn($server, $this->application->uuid); + $fqdn = generateFqdn(server: $server, random: $this->application->uuid, parserVersion: $this->application->compose_parsing_version); $this->application->fqdn = $fqdn; $this->application->save(); $this->resetDefaultLabels(); diff --git a/app/Livewire/Project/Application/PreviewsCompose.php b/app/Livewire/Project/Application/PreviewsCompose.php index 334d96cad..5938b2944 100644 --- a/app/Livewire/Project/Application/PreviewsCompose.php +++ b/app/Livewire/Project/Application/PreviewsCompose.php @@ -48,7 +48,7 @@ class PreviewsCompose extends Component $random = new Cuid2; // Generate a unique domain like main app services do - $generated_fqdn = generateFqdn($server, $random); + $generated_fqdn = generateFqdn(server: $server, random: $random, parserVersion: $this->preview->application->compose_parsing_version); $preview_fqdn = str_replace('{{random}}', $random, $template); $preview_fqdn = str_replace('{{domain}}', str($generated_fqdn)->after('://'), $preview_fqdn); diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index a5d80a11a..57f4a0c68 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -129,7 +129,7 @@ class CloneMe extends Component $uuid = (string) new Cuid2; $url = $application->fqdn; if ($this->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { - $url = generateFqdn($this->server, $uuid); + $url = generateFqdn(server: $this->server, random: $uuid, parserVersion: $application->compose_parsing_version); } $newApplication = $application->replicate([ diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index d8e397e59..e7cff4f29 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -102,7 +102,7 @@ class Create extends Component } }); } - $service->parse(isNew: true, isOneClick: true); + $service->parse(isNew: true); return redirect()->route('project.service.configuration', [ 'service_uuid' => $service->uuid, diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index c8916bf19..853dbe57a 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -61,7 +61,7 @@ class ResourceOperations extends Component $url = $this->resource->fqdn; if ($server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { - $url = generateFqdn($server, $uuid); + $url = generateFqdn(server: $server, random: $uuid, parserVersion: $this->resource->compose_parsing_version); } $new_resource = $this->resource->replicate([ diff --git a/app/Models/Application.php b/app/Models/Application.php index ed97454d2..f74ed89d1 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -111,7 +111,7 @@ class Application extends BaseModel { use HasConfiguration, HasFactory, SoftDeletes; - private static $parserVersion = '4'; + private static $parserVersion = '5'; protected $guarded = []; @@ -1442,7 +1442,21 @@ class Application extends BaseModel $parsedServices = $this->parse(); if ($this->docker_compose_domains) { $json = collect(json_decode($this->docker_compose_domains)); - $names = collect(data_get($parsedServices, 'services'))->keys()->toArray(); + foreach ($json as $key => $value) { + if (str($key)->contains('-')) { + $key = str($key)->replace('-', '_'); + } + $json->put((string) $key, $value); + } + $services = collect(data_get($parsedServices, 'services', [])); + foreach ($services as $name => $service) { + if (str($name)->contains('-')) { + $replacedName = str($name)->replace('-', '_'); + $services->put((string) $replacedName, $service); + $services->forget((string) $name); + } + } + $names = collect($services)->keys()->toArray(); $jsonNames = $json->keys()->toArray(); $diff = array_diff($jsonNames, $names); $json = $json->filter(function ($value, $key) use ($diff) { diff --git a/app/Models/Service.php b/app/Models/Service.php index 4bef46642..ed3241f46 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -42,7 +42,7 @@ class Service extends BaseModel { use HasFactory, SoftDeletes; - private static $parserVersion = '4'; + private static $parserVersion = '5'; protected $guarded = []; @@ -1274,10 +1274,10 @@ class Service extends BaseModel instant_remote_process($commands, $this->server); } - public function parse(bool $isNew = false, bool $isOneClick = false): Collection + public function parse(bool $isNew = false): Collection { if ((int) $this->compose_parsing_version >= 3) { - return serviceParser($this, isOneClick: $isOneClick); + return serviceParser($this); } elseif ($this->docker_compose_raw) { return parseDockerComposeFile($this, $isNew); } else { diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 739f98f22..1737ca714 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -256,12 +256,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) if (str($MINIO_BROWSER_REDIRECT_URL->value ?? '')->isEmpty()) { $MINIO_BROWSER_REDIRECT_URL->update([ - 'value' => generateFqdn($server, 'console-'.$uuid, true), + 'value' => generateFqdn(server: $server, random: 'console-'.$uuid, parserVersion: $resource->compose_parsing_version, forceHttps: true), ]); } if (str($MINIO_SERVER_URL->value ?? '')->isEmpty()) { $MINIO_SERVER_URL->update([ - 'value' => generateFqdn($server, 'minio-'.$uuid, true), + 'value' => generateFqdn(server: $server, random: 'minio-'.$uuid, parserVersion: $resource->compose_parsing_version, forceHttps: true), ]); } $payload = collect([ @@ -279,12 +279,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) if (str($LOGTO_ENDPOINT->value ?? '')->isEmpty()) { $LOGTO_ENDPOINT->update([ - 'value' => generateFqdn($server, 'logto-'.$uuid), + 'value' => generateFqdn(server: $server, random: 'logto-'.$uuid, parserVersion: $resource->compose_parsing_version), ]); } if (str($LOGTO_ADMIN_ENDPOINT->value ?? '')->isEmpty()) { $LOGTO_ADMIN_ENDPOINT->update([ - 'value' => generateFqdn($server, 'logto-admin-'.$uuid), + 'value' => generateFqdn(server: $server, random: 'logto-admin-'.$uuid, parserVersion: $resource->compose_parsing_version), ]); } $payload = collect([ diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index 649de731d..e5e0cbe86 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -16,7 +16,7 @@ use Spatie\Url\Url; use Symfony\Component\Yaml\Yaml; use Visus\Cuid2\Cuid2; -function applicationParser(Application $resource, int $pull_request_id = 0, ?int $preview_id = null, bool $isOneClick = false): Collection +function applicationParser(Application $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection { $uuid = data_get($resource, 'uuid'); $compose = data_get($resource, 'docker_compose_raw'); @@ -100,6 +100,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int } } // Get magic environments where we need to preset the FQDN + // for example SERVICE_FQDN_APP_3000 (without a value) if ($key->startsWith('SERVICE_FQDN_')) { // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 if (substr_count(str($key)->value(), '_') === 3) { @@ -111,7 +112,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int } $fqdn = $resource->fqdn; if (blank($resource->fqdn)) { - $fqdn = generateFqdn($server, "$uuid"); + $fqdn = generateFqdn(server: $server, random: "$uuid", parserVersion: $resource->compose_parsing_version); } if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { @@ -160,44 +161,58 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); if ($magicEnvironments->count() > 0) { + // Generate Coolify environment variables foreach ($magicEnvironments as $key => $value) { $key = str($key); $value = replaceVariables($value); $command = parseCommandFromMagicEnvVariable($key); if ($command->value() === 'FQDN') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('-')) { + $fqdnFor = str($fqdnFor)->replace('-', '_'); + } + $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); + $url = generateUrl(server: $server, random: "$fqdnFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + if ($resource->build_pack === 'dockercompose') { + $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]); + // Put URL in the domains array instead of FQDN + $domains->put((string) $fqdnFor, [ + 'domain' => $url, ]); + $resource->docker_compose_domains = $domains->toJson(); + $resource->save(); } } elseif ($command->value() === 'URL') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, + $urlFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($urlFor)->contains('-')) { + $urlFor = str($urlFor)->replace('-', '_'); + } + $url = generateUrl(server: $server, random: "$urlFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $url, + 'is_build_time' => false, + 'is_preview' => false, + ]); + if ($resource->build_pack === 'dockercompose') { + $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]); + $domains->put((string) $urlFor, [ + 'domain' => $url, ]); + $resource->docker_compose_domains = $domains->toJson(); + $resource->save(); } } else { $value = generateEnvValue($command, $resource); @@ -599,7 +614,6 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int } else { $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); } - ray($domains); $fqdns = data_get($domains, "$serviceName.domain"); // Generate SERVICE_FQDN & SERVICE_URL for dockercompose if ($resource->build_pack === 'dockercompose') { @@ -849,7 +863,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int return $topLevel; } -function serviceParser(Service $resource, bool $isOneClick = false): Collection +function serviceParser(Service $resource): Collection { $uuid = data_get($resource, 'uuid'); $compose = data_get($resource, 'docker_compose_raw'); @@ -977,49 +991,86 @@ function serviceParser(Service $resource, bool $isOneClick = false): Collection } } // Get magic environments where we need to preset the FQDN - if ($key->startsWith('SERVICE_FQDN_')) { + if ($key->startsWith('SERVICE_FQDN_') || $key->startsWith('SERVICE_URL_')) { // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 if (substr_count(str($key)->value(), '_') === 3) { - $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); + if ($key->startsWith('SERVICE_FQDN_')) { + $urlFor = null; + $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); + } + if ($key->startsWith('SERVICE_URL_')) { + $fqdnFor = null; + $urlFor = $key->after('SERVICE_URL_')->beforeLast('_')->lower()->value(); + } $port = $key->afterLast('_')->value(); } else { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if ($key->startsWith('SERVICE_FQDN_')) { + $urlFor = null; + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + } + if ($key->startsWith('SERVICE_URL_')) { + $fqdnFor = null; + $urlFor = $key->after('SERVICE_URL_')->lower()->value(); + } $port = null; } - if ($isOneClick) { - if (blank($savedService->fqdn)) { - if ($fqdnFor) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - } else { - $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); - } + // if ($isOneClick) { + if (blank($savedService->fqdn)) { + if ($fqdnFor) { + $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + $fqdn = generateFqdn(server: $server, random: "{$savedService->name}-$uuid", parserVersion: $resource->compose_parsing_version); + } + if ($urlFor) { + $url = generateUrl($server, "$urlFor-$uuid"); + } else { + $url = generateUrl($server, "{$savedService->name}-$uuid"); } - } else { - // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty - if (blank($savedService->fqdn)) { - $fqdn = ''; - } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); - } + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + $url = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); } + + // } else { + // // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty + // if (blank($savedService->fqdn)) { + // $fqdn = ''; + // } else { + // $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + // } + // } if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { $path = $value->value(); if ($path !== '/') { $fqdn = "$fqdn$path"; + $url = "$url$path"; } } $fqdnWithPort = $fqdn; - if ($port) { + $urlWithPort = $url; + if ($fqdn && $port) { $fqdnWithPort = "$fqdn:$port"; } + if ($url && $port) { + $urlWithPort = "$url:$port"; + } + ray("urlWithPort: $urlWithPort, fqdnWithPort: $fqdnWithPort", "parserVersion: $resource->compose_parsing_version", 'isVersionGreaterThan4207: '.version_compare('4.0.0-beta.420.7', config('constants.coolify.version'), '>=')); if (is_null($savedService->fqdn)) { - $savedService->fqdn = $fqdnWithPort; + if ((int) $resource->compose_parsing_version >= 5 && version_compare(config('constants.coolify.version'), '4.0.0-beta.420.7', '>=')) { + if ($fqdnFor) { + ray("setting fqdn(fqdnWithPort) to $fqdnWithPort"); + $savedService->fqdn = $fqdnWithPort; + } + if ($urlFor) { + ray("setting fqdn(urlWithPort) to $urlWithPort"); + $savedService->fqdn = $urlWithPort; + } + } else { + ray("setting fqdn(fqdnWithPort) old parser version to $fqdnWithPort"); + $savedService->fqdn = $fqdnWithPort; + } $savedService->save(); } - if (substr_count(str($key)->value(), '_') === 2) { $resource->environment_variables()->updateOrCreate([ 'key' => $key->value(), @@ -1030,6 +1081,15 @@ function serviceParser(Service $resource, bool $isOneClick = false): Collection 'is_build_time' => false, 'is_preview' => false, ]); + $resource->environment_variables()->updateOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $url, + 'is_build_time' => false, + 'is_preview' => false, + ]); } if (substr_count(str($key)->value(), '_') === 3) { $newKey = str($key)->beforeLast('_'); @@ -1042,6 +1102,15 @@ function serviceParser(Service $resource, bool $isOneClick = false): Collection 'is_build_time' => false, 'is_preview' => false, ]); + $resource->environment_variables()->updateOrCreate([ + 'key' => $newKey->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $url, + 'is_build_time' => false, + 'is_preview' => false, + ]); } } } @@ -1053,40 +1122,36 @@ function serviceParser(Service $resource, bool $isOneClick = false): Collection $value = replaceVariables($value); $command = parseCommandFromMagicEnvVariable($key); if ($command->value() === 'FQDN') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); } + $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); } elseif ($command->value() === 'URL') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); } + $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); + $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); } else { $value = generateEnvValue($command, $resource); $resource->environment_variables()->firstOrCreate([ diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 1e9b95288..624e0b0d1 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -402,7 +402,7 @@ function data_get_str($data, $key, $default = null): Stringable return str($str); } -function generateFqdn(Server $server, string $random, bool $forceHttps = false): string +function generateUrl(Server $server, string $random, bool $forceHttps = false): string { $wildcard = data_get($server, 'settings.wildcard_domain'); if (is_null($wildcard) || $wildcard === '') { @@ -418,6 +418,26 @@ function generateFqdn(Server $server, string $random, bool $forceHttps = false): return "$scheme://{$random}.$host$path"; } +function generateFqdn(Server $server, string $random, bool $forceHttps = false, int $parserVersion = 4): string +{ + $wildcard = data_get($server, 'settings.wildcard_domain'); + if (is_null($wildcard) || $wildcard === '') { + $wildcard = sslip($server); + } + $url = Url::fromString($wildcard); + $host = $url->getHost(); + $path = $url->getPath() === '/' ? '' : $url->getPath(); + $scheme = $url->getScheme(); + if ($forceHttps) { + $scheme = 'https'; + } + + if ($parserVersion >= 5 && version_compare(config('constants.coolify.version'), '4.0.0-beta.420.7', '>=')) { + return "{$random}.$host$path"; + } + + return "$scheme://{$random}.$host$path"; +} function sslip(Server $server) { if (isDev() && $server->id === 0) { @@ -2918,1008 +2938,1008 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } -function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null, bool $isOneClick = false): Collection -{ - $isApplication = $resource instanceof Application; - $isService = $resource instanceof Service; - if ($isApplication) { - return applicationParser($resource, $pull_request_id, $preview_id); - } - if ($isService) { - return serviceParser($resource, $isOneClick); - } +// function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection +// { +// $isApplication = $resource instanceof Application; +// $isService = $resource instanceof Service; +// if ($isApplication) { +// return applicationParser($resource, $pull_request_id, $preview_id); +// } +// if ($isService) { +// return serviceParser($resource); +// } - return collect([]); +// return collect([]); - $uuid = data_get($resource, 'uuid'); - $compose = data_get($resource, 'docker_compose_raw'); - if (! $compose) { - return collect([]); - } +// $uuid = data_get($resource, 'uuid'); +// $compose = data_get($resource, 'docker_compose_raw'); +// if (! $compose) { +// return collect([]); +// } - if ($isApplication) { - $pullRequestId = $pull_request_id; - $isPullRequest = $pullRequestId == 0 ? false : true; - $server = data_get($resource, 'destination.server'); - $fileStorages = $resource->fileStorages(); - } elseif ($isService) { - $server = data_get($resource, 'server'); - $allServices = get_service_templates(); - } else { - return collect([]); - } +// if ($isApplication) { +// $pullRequestId = $pull_request_id; +// $isPullRequest = $pullRequestId == 0 ? false : true; +// $server = data_get($resource, 'destination.server'); +// $fileStorages = $resource->fileStorages(); +// } elseif ($isService) { +// $server = data_get($resource, 'server'); +// $allServices = get_service_templates(); +// } else { +// return collect([]); +// } - try { - $yaml = Yaml::parse($compose); - } catch (\Exception) { - return collect([]); - } - $services = data_get($yaml, 'services', collect([])); - $topLevel = collect([ - 'volumes' => collect(data_get($yaml, 'volumes', [])), - 'networks' => collect(data_get($yaml, 'networks', [])), - 'configs' => collect(data_get($yaml, 'configs', [])), - 'secrets' => collect(data_get($yaml, 'secrets', [])), - ]); - // If there are predefined volumes, make sure they are not null - if ($topLevel->get('volumes')->count() > 0) { - $temp = collect([]); - foreach ($topLevel['volumes'] as $volumeName => $volume) { - if (is_null($volume)) { - continue; - } - $temp->put($volumeName, $volume); - } - $topLevel['volumes'] = $temp; - } - // Get the base docker network - $baseNetwork = collect([$uuid]); - if ($isApplication && $isPullRequest) { - $baseNetwork = collect(["{$uuid}-{$pullRequestId}"]); - } +// try { +// $yaml = Yaml::parse($compose); +// } catch (\Exception) { +// return collect([]); +// } +// $services = data_get($yaml, 'services', collect([])); +// $topLevel = collect([ +// 'volumes' => collect(data_get($yaml, 'volumes', [])), +// 'networks' => collect(data_get($yaml, 'networks', [])), +// 'configs' => collect(data_get($yaml, 'configs', [])), +// 'secrets' => collect(data_get($yaml, 'secrets', [])), +// ]); +// // If there are predefined volumes, make sure they are not null +// if ($topLevel->get('volumes')->count() > 0) { +// $temp = collect([]); +// foreach ($topLevel['volumes'] as $volumeName => $volume) { +// if (is_null($volume)) { +// continue; +// } +// $temp->put($volumeName, $volume); +// } +// $topLevel['volumes'] = $temp; +// } +// // Get the base docker network +// $baseNetwork = collect([$uuid]); +// if ($isApplication && $isPullRequest) { +// $baseNetwork = collect(["{$uuid}-{$pullRequestId}"]); +// } - $parsedServices = collect([]); +// $parsedServices = collect([]); - $allMagicEnvironments = collect([]); - foreach ($services as $serviceName => $service) { - $predefinedPort = null; - $magicEnvironments = collect([]); - $image = data_get_str($service, 'image'); - $environment = collect(data_get($service, 'environment', [])); - $buildArgs = collect(data_get($service, 'build.args', [])); - $environment = $environment->merge($buildArgs); - $isDatabase = isDatabaseImage($image, $service); +// $allMagicEnvironments = collect([]); +// foreach ($services as $serviceName => $service) { +// $predefinedPort = null; +// $magicEnvironments = collect([]); +// $image = data_get_str($service, 'image'); +// $environment = collect(data_get($service, 'environment', [])); +// $buildArgs = collect(data_get($service, 'build.args', [])); +// $environment = $environment->merge($buildArgs); +// $isDatabase = isDatabaseImage($image, $service); - if ($isService) { - $containerName = "$serviceName-{$resource->uuid}"; +// 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) { - $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); - if ($applicationFound) { - $savedService = $applicationFound; - } else { - $savedService = ServiceDatabase::firstOrCreate([ - 'name' => $serviceName, - 'service_id' => $resource->id, - ]); - } - } else { - $savedService = ServiceApplication::firstOrCreate([ - 'name' => $serviceName, - 'service_id' => $resource->id, - ], [ - 'is_gzip_enabled' => true, - ]); - } - // Check if image changed - if ($savedService->image !== $image) { - $savedService->image = $image; - $savedService->save(); - } - // Pocketbase does not need gzip for SSE. - if (str($savedService->image)->contains('pocketbase') && $savedService->is_gzip_enabled) { - $savedService->is_gzip_enabled = false; - $savedService->save(); - } - } +// 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) { +// $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); +// if ($applicationFound) { +// $savedService = $applicationFound; +// } else { +// $savedService = ServiceDatabase::firstOrCreate([ +// 'name' => $serviceName, +// 'service_id' => $resource->id, +// ]); +// } +// } else { +// $savedService = ServiceApplication::firstOrCreate([ +// 'name' => $serviceName, +// 'service_id' => $resource->id, +// ], [ +// 'is_gzip_enabled' => true, +// ]); +// } +// // Check if image changed +// if ($savedService->image !== $image) { +// $savedService->image = $image; +// $savedService->save(); +// } +// // Pocketbase does not need gzip for SSE. +// if (str($savedService->image)->contains('pocketbase') && $savedService->is_gzip_enabled) { +// $savedService->is_gzip_enabled = false; +// $savedService->save(); +// } +// } - $environment = collect(data_get($service, 'environment', [])); - $buildArgs = collect(data_get($service, 'build.args', [])); - $environment = $environment->merge($buildArgs); +// $environment = collect(data_get($service, 'environment', [])); +// $buildArgs = collect(data_get($service, 'build.args', [])); +// $environment = $environment->merge($buildArgs); - // convert environment variables to one format - $environment = convertToKeyValueCollection($environment); +// // convert environment variables to one format +// $environment = convertToKeyValueCollection($environment); - // Add Coolify defined environments - $allEnvironments = $resource->environment_variables()->get(['key', 'value']); +// // Add Coolify defined environments +// $allEnvironments = $resource->environment_variables()->get(['key', 'value']); - $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { - return [$item['key'] => $item['value']]; - }); - // filter and add magic environments - foreach ($environment as $key => $value) { - // Get all SERVICE_ variables from keys and values - $key = str($key); - $value = str($value); - $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; - preg_match_all($regex, $value, $valueMatches); - if (count($valueMatches[1]) > 0) { - foreach ($valueMatches[1] as $match) { - $match = replaceVariables($match); - if ($match->startsWith('SERVICE_')) { - if ($magicEnvironments->has($match->value())) { - continue; - } - $magicEnvironments->put($match->value(), ''); - } - } - } - // Get magic environments where we need to preset the FQDN - if ($key->startsWith('SERVICE_FQDN_')) { - // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 - 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 = $resource->fqdn; - if (blank($resource->fqdn)) { - $fqdn = generateFqdn($server, "$uuid"); - } - } elseif ($isService) { - if ($isOneClick) { - if (blank($savedService->fqdn)) { - if ($fqdnFor) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - } else { - $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); - } - } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); - } +// $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { +// return [$item['key'] => $item['value']]; +// }); +// // filter and add magic environments +// foreach ($environment as $key => $value) { +// // Get all SERVICE_ variables from keys and values +// $key = str($key); +// $value = str($value); +// $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; +// preg_match_all($regex, $value, $valueMatches); +// if (count($valueMatches[1]) > 0) { +// foreach ($valueMatches[1] as $match) { +// $match = replaceVariables($match); +// if ($match->startsWith('SERVICE_')) { +// if ($magicEnvironments->has($match->value())) { +// continue; +// } +// $magicEnvironments->put($match->value(), ''); +// } +// } +// } +// // Get magic environments where we need to preset the FQDN +// if ($key->startsWith('SERVICE_FQDN_')) { +// // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 +// 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 = $resource->fqdn; +// if (blank($resource->fqdn)) { +// $fqdn = generateFqdn($server, "$uuid"); +// } +// } elseif ($isService) { +// if ($isOneClick) { +// if (blank($savedService->fqdn)) { +// if ($fqdnFor) { +// $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); +// } else { +// $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); +// } +// } else { +// $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); +// } - } else { - // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty - if (blank($savedService->fqdn)) { - $fqdn = ''; - } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); - } - ray($fqdn); - } - } +// } else { +// // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty +// if (blank($savedService->fqdn)) { +// $fqdn = ''; +// } else { +// $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); +// } +// ray($fqdn); +// } +// } - if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $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 = $fqdnWithPort; - $resource->save(); - } elseif ($isService && is_null($savedService->fqdn)) { - $savedService->fqdn = $fqdnWithPort; - $savedService->save(); - } +// if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $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 = $fqdnWithPort; +// $resource->save(); +// } elseif ($isService && is_null($savedService->fqdn)) { +// $savedService->fqdn = $fqdnWithPort; +// $savedService->save(); +// } - if (substr_count(str($key)->value(), '_') === 2) { - $resource->environment_variables()->updateOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - if (substr_count(str($key)->value(), '_') === 3) { - $newKey = str($key)->beforeLast('_'); - $resource->environment_variables()->updateOrCreate([ - 'key' => $newKey->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } - } +// if (substr_count(str($key)->value(), '_') === 2) { +// $resource->environment_variables()->updateOrCreate([ +// 'key' => $key->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $fqdn, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// if (substr_count(str($key)->value(), '_') === 3) { +// $newKey = str($key)->beforeLast('_'); +// $resource->environment_variables()->updateOrCreate([ +// 'key' => $newKey->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $fqdn, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// } +// } - $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); - if ($magicEnvironments->count() > 0) { - foreach ($magicEnvironments as $key => $value) { - $key = str($key); - $value = replaceVariables($value); - $command = parseCommandFromMagicEnvVariable($key); - if ($command->value() === 'FQDN') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } elseif ($command->value() === 'URL') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } else { - $value = generateEnvValue($command, $resource); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } - } - } +// $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); +// if ($magicEnvironments->count() > 0) { +// foreach ($magicEnvironments as $key => $value) { +// $key = str($key); +// $value = replaceVariables($value); +// $command = parseCommandFromMagicEnvVariable($key); +// if ($command->value() === 'FQDN') { +// if ($isOneClick) { +// $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); +// if (str($fqdnFor)->contains('_')) { +// $fqdnFor = str($fqdnFor)->before('_'); +// } +// $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $fqdn, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// } elseif ($command->value() === 'URL') { +// if ($isOneClick) { +// $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); +// if (str($fqdnFor)->contains('_')) { +// $fqdnFor = str($fqdnFor)->before('_'); +// } +// $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); +// $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $fqdn, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// } else { +// $value = generateEnvValue($command, $resource); +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $value, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// } +// } +// } - $serviceAppsLogDrainEnabledMap = collect([]); - if ($resource instanceof Service) { - $serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) { - return $app->isLogDrainEnabled(); - }); - } +// $serviceAppsLogDrainEnabledMap = collect([]); +// if ($resource instanceof Service) { +// $serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) { +// return $app->isLogDrainEnabled(); +// }); +// } - // Parse the rest of the services - foreach ($services as $serviceName => $service) { - $image = data_get_str($service, 'image'); - $restart = data_get_str($service, 'restart', RESTART_MODE); - $logging = data_get($service, 'logging'); +// // Parse the rest of the services +// foreach ($services as $serviceName => $service) { +// $image = data_get_str($service, 'image'); +// $restart = data_get_str($service, 'restart', RESTART_MODE); +// $logging = data_get($service, 'logging'); - if ($server->isLogDrainEnabled()) { - if ($resource instanceof Application && $resource->isLogDrainEnabled()) { - $logging = generate_fluentd_configuration(); - } - if ($resource instanceof Service && $serviceAppsLogDrainEnabledMap->get($serviceName)) { - $logging = generate_fluentd_configuration(); - } - } - $volumes = collect(data_get($service, 'volumes', [])); - $networks = collect(data_get($service, 'networks', [])); - $use_network_mode = data_get($service, 'network_mode') !== null; - $depends_on = collect(data_get($service, 'depends_on', [])); - $labels = collect(data_get($service, 'labels', [])); - if ($labels->count() > 0) { - if (isAssociativeArray($labels)) { - $newLabels = collect([]); - $labels->each(function ($value, $key) use ($newLabels) { - $newLabels->push("$key=$value"); - }); - $labels = $newLabels; - } - } - $environment = collect(data_get($service, 'environment', [])); - $ports = collect(data_get($service, 'ports', [])); - $buildArgs = collect(data_get($service, 'build.args', [])); - $environment = $environment->merge($buildArgs); +// if ($server->isLogDrainEnabled()) { +// if ($resource instanceof Application && $resource->isLogDrainEnabled()) { +// $logging = generate_fluentd_configuration(); +// } +// if ($resource instanceof Service && $serviceAppsLogDrainEnabledMap->get($serviceName)) { +// $logging = generate_fluentd_configuration(); +// } +// } +// $volumes = collect(data_get($service, 'volumes', [])); +// $networks = collect(data_get($service, 'networks', [])); +// $use_network_mode = data_get($service, 'network_mode') !== null; +// $depends_on = collect(data_get($service, 'depends_on', [])); +// $labels = collect(data_get($service, 'labels', [])); +// if ($labels->count() > 0) { +// if (isAssociativeArray($labels)) { +// $newLabels = collect([]); +// $labels->each(function ($value, $key) use ($newLabels) { +// $newLabels->push("$key=$value"); +// }); +// $labels = $newLabels; +// } +// } +// $environment = collect(data_get($service, 'environment', [])); +// $ports = collect(data_get($service, 'ports', [])); +// $buildArgs = collect(data_get($service, 'build.args', [])); +// $environment = $environment->merge($buildArgs); - $environment = convertToKeyValueCollection($environment); - $coolifyEnvironments = collect([]); +// $environment = convertToKeyValueCollection($environment); +// $coolifyEnvironments = collect([]); - $isDatabase = isDatabaseImage($image, $service); - $volumesParsed = collect([]); +// $isDatabase = isDatabaseImage($image, $service); +// $volumesParsed = collect([]); - if ($isApplication) { - $baseName = generateApplicationContainerName( - application: $resource, - pull_request_id: $pullRequestId - ); - $containerName = "$serviceName-$baseName"; - $predefinedPort = null; - } elseif ($isService) { - $containerName = "$serviceName-{$resource->uuid}"; +// if ($isApplication) { +// $baseName = generateApplicationContainerName( +// application: $resource, +// pull_request_id: $pullRequestId +// ); +// $containerName = "$serviceName-$baseName"; +// $predefinedPort = null; +// } elseif ($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 ($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) { - $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); - if ($applicationFound) { - $savedService = $applicationFound; - } else { - $savedService = ServiceDatabase::firstOrCreate([ - 'name' => $serviceName, - 'image' => $image, - 'service_id' => $resource->id, - ]); - } - } else { - $savedService = ServiceApplication::firstOrCreate([ - 'name' => $serviceName, - 'image' => $image, - 'service_id' => $resource->id, - ]); - } - $fileStorages = $savedService->fileStorages(); - if ($savedService->image !== $image) { - $savedService->image = $image; - $savedService->save(); - } - } +// if ($isDatabase) { +// $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); +// if ($applicationFound) { +// $savedService = $applicationFound; +// } else { +// $savedService = ServiceDatabase::firstOrCreate([ +// 'name' => $serviceName, +// 'image' => $image, +// 'service_id' => $resource->id, +// ]); +// } +// } else { +// $savedService = ServiceApplication::firstOrCreate([ +// 'name' => $serviceName, +// 'image' => $image, +// 'service_id' => $resource->id, +// ]); +// } +// $fileStorages = $savedService->fileStorages(); +// if ($savedService->image !== $image) { +// $savedService->image = $image; +// $savedService->save(); +// } +// } - $originalResource = $isApplication ? $resource : $savedService; +// $originalResource = $isApplication ? $resource : $savedService; - if ($volumes->count() > 0) { - foreach ($volumes as $index => $volume) { - $type = null; - $source = null; - $target = null; - $content = null; - $isDirectory = false; - if (is_string($volume)) { - $source = str($volume)->before(':'); - $target = str($volume)->after(':')->beforeLast(':'); - $foundConfig = $fileStorages->whereMountPath($target)->first(); - if (sourceIsLocal($source)) { - $type = str('bind'); - if ($foundConfig) { - $contentNotNull_temp = data_get($foundConfig, 'content'); - if ($contentNotNull_temp) { - $content = $contentNotNull_temp; - } - $isDirectory = data_get($foundConfig, 'is_directory'); - } else { - // By default, we cannot determine if the bind is a directory or not, so we set it to directory - $isDirectory = true; - } - } else { - $type = str('volume'); - } - } elseif (is_array($volume)) { - $type = data_get_str($volume, 'type'); - $source = data_get_str($volume, 'source'); - $target = data_get_str($volume, 'target'); - $content = data_get($volume, 'content'); - $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); +// if ($volumes->count() > 0) { +// foreach ($volumes as $index => $volume) { +// $type = null; +// $source = null; +// $target = null; +// $content = null; +// $isDirectory = false; +// if (is_string($volume)) { +// $source = str($volume)->before(':'); +// $target = str($volume)->after(':')->beforeLast(':'); +// $foundConfig = $fileStorages->whereMountPath($target)->first(); +// if (sourceIsLocal($source)) { +// $type = str('bind'); +// if ($foundConfig) { +// $contentNotNull_temp = data_get($foundConfig, 'content'); +// if ($contentNotNull_temp) { +// $content = $contentNotNull_temp; +// } +// $isDirectory = data_get($foundConfig, 'is_directory'); +// } else { +// // By default, we cannot determine if the bind is a directory or not, so we set it to directory +// $isDirectory = true; +// } +// } else { +// $type = str('volume'); +// } +// } elseif (is_array($volume)) { +// $type = data_get_str($volume, 'type'); +// $source = data_get_str($volume, 'source'); +// $target = data_get_str($volume, 'target'); +// $content = data_get($volume, 'content'); +// $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); - $foundConfig = $fileStorages->whereMountPath($target)->first(); - if ($foundConfig) { - $contentNotNull_temp = data_get($foundConfig, 'content'); - if ($contentNotNull_temp) { - $content = $contentNotNull_temp; - } - $isDirectory = data_get($foundConfig, 'is_directory'); - } else { - // if isDirectory is not set (or false) & content is also not set, we assume it is a directory - if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { - $isDirectory = true; - } - } - } - if ($type->value() === 'bind') { - if ($source->value() === '/var/run/docker.sock') { - $volume = $source->value().':'.$target->value(); - } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { - $volume = $source->value().':'.$target->value(); - } else { - 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, - 'resource_id' => $originalResource->id, - 'resource_type' => get_class($originalResource), - ], - [ - 'fs_path' => $source, - 'mount_path' => $target, - 'content' => $content, - 'is_directory' => $isDirectory, - 'resource_id' => $originalResource->id, - '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') { - if ($topLevel->get('volumes')->has($source->value())) { - $temp = $topLevel->get('volumes')->get($source->value()); - if (data_get($temp, 'driver_opts.type') === 'cifs') { - continue; - } - if (data_get($temp, 'driver_opts.type') === 'nfs') { - continue; - } - } - $slugWithoutUuid = Str::slug($source, '-'); - $name = "{$uuid}_{$slugWithoutUuid}"; +// $foundConfig = $fileStorages->whereMountPath($target)->first(); +// if ($foundConfig) { +// $contentNotNull_temp = data_get($foundConfig, 'content'); +// if ($contentNotNull_temp) { +// $content = $contentNotNull_temp; +// } +// $isDirectory = data_get($foundConfig, 'is_directory'); +// } else { +// // if isDirectory is not set (or false) & content is also not set, we assume it is a directory +// if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { +// $isDirectory = true; +// } +// } +// } +// if ($type->value() === 'bind') { +// if ($source->value() === '/var/run/docker.sock') { +// $volume = $source->value().':'.$target->value(); +// } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { +// $volume = $source->value().':'.$target->value(); +// } else { +// 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, +// 'resource_id' => $originalResource->id, +// 'resource_type' => get_class($originalResource), +// ], +// [ +// 'fs_path' => $source, +// 'mount_path' => $target, +// 'content' => $content, +// 'is_directory' => $isDirectory, +// 'resource_id' => $originalResource->id, +// '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') { +// if ($topLevel->get('volumes')->has($source->value())) { +// $temp = $topLevel->get('volumes')->get($source->value()); +// if (data_get($temp, 'driver_opts.type') === 'cifs') { +// continue; +// } +// if (data_get($temp, 'driver_opts.type') === 'nfs') { +// continue; +// } +// } +// $slugWithoutUuid = Str::slug($source, '-'); +// $name = "{$uuid}_{$slugWithoutUuid}"; - if ($isApplication && $isPullRequest) { - $name = "{$name}-pr-$pullRequestId"; - } - if (is_string($volume)) { - $source = str($volume)->before(':'); - $target = str($volume)->after(':')->beforeLast(':'); - $source = $name; - $volume = "$source:$target"; - } elseif (is_array($volume)) { - data_set($volume, 'source', $name); - } - $topLevel->get('volumes')->put($name, [ - 'name' => $name, - ]); - LocalPersistentVolume::updateOrCreate( - [ - 'name' => $name, - 'resource_id' => $originalResource->id, - 'resource_type' => get_class($originalResource), - ], - [ - 'name' => $name, - 'mount_path' => $target, - 'resource_id' => $originalResource->id, - 'resource_type' => get_class($originalResource), - ] - ); - } - dispatch(new ServerFilesFromServerJob($originalResource)); - $volumesParsed->put($index, $volume); - } - } +// if ($isApplication && $isPullRequest) { +// $name = "{$name}-pr-$pullRequestId"; +// } +// if (is_string($volume)) { +// $source = str($volume)->before(':'); +// $target = str($volume)->after(':')->beforeLast(':'); +// $source = $name; +// $volume = "$source:$target"; +// } elseif (is_array($volume)) { +// data_set($volume, 'source', $name); +// } +// $topLevel->get('volumes')->put($name, [ +// 'name' => $name, +// ]); +// LocalPersistentVolume::updateOrCreate( +// [ +// 'name' => $name, +// 'resource_id' => $originalResource->id, +// 'resource_type' => get_class($originalResource), +// ], +// [ +// 'name' => $name, +// 'mount_path' => $target, +// 'resource_id' => $originalResource->id, +// 'resource_type' => get_class($originalResource), +// ] +// ); +// } +// dispatch(new ServerFilesFromServerJob($originalResource)); +// $volumesParsed->put($index, $volume); +// } +// } - if ($depends_on?->count() > 0) { - if ($isApplication && $isPullRequest) { - $newDependsOn = collect([]); - $depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) { - if (is_numeric($condition)) { - $dependency = "$dependency-pr-$pullRequestId"; +// if ($depends_on?->count() > 0) { +// if ($isApplication && $isPullRequest) { +// $newDependsOn = collect([]); +// $depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) { +// if (is_numeric($condition)) { +// $dependency = "$dependency-pr-$pullRequestId"; - $newDependsOn->put($condition, $dependency); - } else { - $condition = "$condition-pr-$pullRequestId"; - $newDependsOn->put($condition, $dependency); - } - }); - $depends_on = $newDependsOn; - } - } - if (! $use_network_mode) { - if ($topLevel->get('networks')?->count() > 0) { - foreach ($topLevel->get('networks') as $networkName => $network) { - if ($networkName === 'default') { - continue; - } - // ignore aliases - if ($network['aliases'] ?? false) { - continue; - } - $networkExists = $networks->contains(function ($value, $key) use ($networkName) { - return $value == $networkName || $key == $networkName; - }); - if (! $networkExists) { - $networks->put($networkName, null); - } - } - } - $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { - return $value == $baseNetwork; - }); - if (! $baseNetworkExists) { - foreach ($baseNetwork as $network) { - $topLevel->get('networks')->put($network, [ - 'name' => $network, - 'external' => true, - ]); - } - } - } +// $newDependsOn->put($condition, $dependency); +// } else { +// $condition = "$condition-pr-$pullRequestId"; +// $newDependsOn->put($condition, $dependency); +// } +// }); +// $depends_on = $newDependsOn; +// } +// } +// if (! $use_network_mode) { +// if ($topLevel->get('networks')?->count() > 0) { +// foreach ($topLevel->get('networks') as $networkName => $network) { +// if ($networkName === 'default') { +// continue; +// } +// // ignore aliases +// if ($network['aliases'] ?? false) { +// continue; +// } +// $networkExists = $networks->contains(function ($value, $key) use ($networkName) { +// return $value == $networkName || $key == $networkName; +// }); +// if (! $networkExists) { +// $networks->put($networkName, null); +// } +// } +// } +// $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { +// return $value == $baseNetwork; +// }); +// if (! $baseNetworkExists) { +// foreach ($baseNetwork as $network) { +// $topLevel->get('networks')->put($network, [ +// 'name' => $network, +// 'external' => true, +// ]); +// } +// } +// } - // Collect/create/update ports - $collectedPorts = collect([]); - if ($ports->count() > 0) { - foreach ($ports as $sport) { - if (is_string($sport) || is_numeric($sport)) { - $collectedPorts->push($sport); - } - if (is_array($sport)) { - $target = data_get($sport, 'target'); - $published = data_get($sport, 'published'); - $protocol = data_get($sport, 'protocol'); - $collectedPorts->push("$target:$published/$protocol"); - } - } - } - if ($isService) { - $originalResource->ports = $collectedPorts->implode(','); - $originalResource->save(); - } +// // Collect/create/update ports +// $collectedPorts = collect([]); +// if ($ports->count() > 0) { +// foreach ($ports as $sport) { +// if (is_string($sport) || is_numeric($sport)) { +// $collectedPorts->push($sport); +// } +// if (is_array($sport)) { +// $target = data_get($sport, 'target'); +// $published = data_get($sport, 'published'); +// $protocol = data_get($sport, 'protocol'); +// $collectedPorts->push("$target:$published/$protocol"); +// } +// } +// } +// if ($isService) { +// $originalResource->ports = $collectedPorts->implode(','); +// $originalResource->save(); +// } - $networks_temp = collect(); +// $networks_temp = collect(); - if (! $use_network_mode) { - foreach ($networks as $key => $network) { - if (gettype($network) === 'string') { - // networks: - // - appwrite - $networks_temp->put($network, null); - } elseif (gettype($network) === 'array') { - // networks: - // default: - // ipv4_address: 192.168.203.254 - $networks_temp->put($key, $network); - } - } - foreach ($baseNetwork as $key => $network) { - $networks_temp->put($network, null); - } +// if (! $use_network_mode) { +// foreach ($networks as $key => $network) { +// if (gettype($network) === 'string') { +// // networks: +// // - appwrite +// $networks_temp->put($network, null); +// } elseif (gettype($network) === 'array') { +// // networks: +// // default: +// // ipv4_address: 192.168.203.254 +// $networks_temp->put($key, $network); +// } +// } +// foreach ($baseNetwork as $key => $network) { +// $networks_temp->put($network, null); +// } - if ($isApplication) { - if (data_get($resource, 'settings.connect_to_docker_network')) { - $network = $resource->destination->network; - $networks_temp->put($network, null); - $topLevel->get('networks')->put($network, [ - 'name' => $network, - 'external' => true, - ]); - } - } - } +// if ($isApplication) { +// if (data_get($resource, 'settings.connect_to_docker_network')) { +// $network = $resource->destination->network; +// $networks_temp->put($network, null); +// $topLevel->get('networks')->put($network, [ +// 'name' => $network, +// 'external' => true, +// ]); +// } +// } +// } - $normalEnvironments = $environment->diffKeys($allMagicEnvironments); - $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { - return ! str($value)->startsWith('SERVICE_'); - }); - foreach ($normalEnvironments as $key => $value) { - $key = str($key); - $value = str($value); - $originalValue = $value; - $parsedValue = replaceVariables($value); - if ($value->startsWith('$SERVICE_')) { - $resource->environment_variables()->firstOrCreate([ - 'key' => $key, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_build_time' => false, - 'is_preview' => false, - ]); +// $normalEnvironments = $environment->diffKeys($allMagicEnvironments); +// $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { +// return ! str($value)->startsWith('SERVICE_'); +// }); +// foreach ($normalEnvironments as $key => $value) { +// $key = str($key); +// $value = str($value); +// $originalValue = $value; +// $parsedValue = replaceVariables($value); +// if ($value->startsWith('$SERVICE_')) { +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key, +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $value, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); - continue; - } - if (! $value->startsWith('$')) { - continue; - } - if ($key->value() === $parsedValue->value()) { - $value = null; - $resource->environment_variables()->firstOrCreate([ - 'key' => $key, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } else { - if ($value->startsWith('$')) { - $isRequired = false; - if ($value->contains(':-')) { - $value = replaceVariables($value); - $key = $value->before(':'); - $value = $value->after(':-'); - } elseif ($value->contains('-')) { - $value = replaceVariables($value); +// continue; +// } +// if (! $value->startsWith('$')) { +// continue; +// } +// if ($key->value() === $parsedValue->value()) { +// $value = null; +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key, +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $value, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } else { +// if ($value->startsWith('$')) { +// $isRequired = false; +// if ($value->contains(':-')) { +// $value = replaceVariables($value); +// $key = $value->before(':'); +// $value = $value->after(':-'); +// } elseif ($value->contains('-')) { +// $value = replaceVariables($value); - $key = $value->before('-'); - $value = $value->after('-'); - } elseif ($value->contains(':?')) { - $value = replaceVariables($value); +// $key = $value->before('-'); +// $value = $value->after('-'); +// } elseif ($value->contains(':?')) { +// $value = replaceVariables($value); - $key = $value->before(':'); - $value = $value->after(':?'); - $isRequired = true; - } elseif ($value->contains('?')) { - $value = replaceVariables($value); +// $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()->firstOrCreate([ - 'key' => $parsedKeyValue, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $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('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first()->real_value; - $environment[$parsedKeyValue->value()] = $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()->firstOrCreate([ +// 'key' => $parsedKeyValue, +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $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('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first()->real_value; +// $environment[$parsedKeyValue->value()] = $value; - continue; - } - $resource->environment_variables()->firstOrCreate([ - 'key' => $key, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_build_time' => false, - 'is_preview' => false, - 'is_required' => $isRequired, - ]); - } - } - } - if ($isApplication) { - $branch = $originalResource->git_branch; - if ($pullRequestId !== 0) { - $branch = "pull/{$pullRequestId}/head"; - } - if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { - $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\""); - } - } +// continue; +// } +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key, +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $value, +// 'is_build_time' => false, +// 'is_preview' => false, +// 'is_required' => $isRequired, +// ]); +// } +// } +// } +// if ($isApplication) { +// $branch = $originalResource->git_branch; +// if ($pullRequestId !== 0) { +// $branch = "pull/{$pullRequestId}/head"; +// } +// if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { +// $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\""); +// } +// } - // Add COOLIFY_RESOURCE_UUID to environment - if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { - $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}"); - } +// // Add COOLIFY_RESOURCE_UUID to environment +// if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { +// $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}"); +// } - // Add COOLIFY_CONTAINER_NAME to environment - if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { - $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}"); - } +// // Add COOLIFY_CONTAINER_NAME to environment +// if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { +// $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}"); +// } - if ($isApplication) { - if ($isPullRequest) { - $preview = $resource->previews()->find($preview_id); - $domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]); - } else { - $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); - } - $fqdns = data_get($domains, "$serviceName.domain"); - // Generate SERVICE_FQDN & SERVICE_URL for dockercompose - if ($resource->build_pack === 'dockercompose') { +// if ($isApplication) { +// if ($isPullRequest) { +// $preview = $resource->previews()->find($preview_id); +// $domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]); +// } else { +// $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); +// } +// $fqdns = data_get($domains, "$serviceName.domain"); +// // Generate SERVICE_FQDN & SERVICE_URL for dockercompose +// if ($resource->build_pack === 'dockercompose') { - foreach ($domains as $forServiceName => $domain) { - $parsedDomain = data_get($domain, 'domain'); - if (filled($parsedDomain)) { - $parsedDomain = str($parsedDomain)->explode(',')->first(); - $coolifyUrl = Url::fromString($parsedDomain); - $coolifyScheme = $coolifyUrl->getScheme(); - $coolifyFqdn = $coolifyUrl->getHost(); - $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); - $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyUrl->__toString()); - $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyFqdn); - } - } - } - // If the domain is set, we need to generate the FQDNs for the preview - if (filled($fqdns)) { - $fqdns = str($fqdns)->explode(','); - if ($isPullRequest) { - $preview = $resource->previews()->find($preview_id); - $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))); - if ($docker_compose_domains->count() > 0) { - $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain"); - if ($found_fqdn) { - $fqdns = collect($found_fqdn); - } else { - $fqdns = collect([]); - } - } else { - $fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId, $resource) { - $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pullRequestId); - $url = Url::fromString($fqdn); - $template = $resource->preview_url_template; - $host = $url->getHost(); - $schema = $url->getScheme(); - $random = new Cuid2; - $preview_fqdn = str_replace('{{random}}', $random, $template); - $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); - $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); - $preview_fqdn = "$schema://$preview_fqdn"; - $preview->fqdn = $preview_fqdn; - $preview->save(); +// foreach ($domains as $forServiceName => $domain) { +// $parsedDomain = data_get($domain, 'domain'); +// if (filled($parsedDomain)) { +// $parsedDomain = str($parsedDomain)->explode(',')->first(); +// $coolifyUrl = Url::fromString($parsedDomain); +// $coolifyScheme = $coolifyUrl->getScheme(); +// $coolifyFqdn = $coolifyUrl->getHost(); +// $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); +// $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyUrl->__toString()); +// $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyFqdn); +// } +// } +// } +// // If the domain is set, we need to generate the FQDNs for the preview +// if (filled($fqdns)) { +// $fqdns = str($fqdns)->explode(','); +// if ($isPullRequest) { +// $preview = $resource->previews()->find($preview_id); +// $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))); +// if ($docker_compose_domains->count() > 0) { +// $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain"); +// if ($found_fqdn) { +// $fqdns = collect($found_fqdn); +// } else { +// $fqdns = collect([]); +// } +// } else { +// $fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId, $resource) { +// $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pullRequestId); +// $url = Url::fromString($fqdn); +// $template = $resource->preview_url_template; +// $host = $url->getHost(); +// $schema = $url->getScheme(); +// $random = new Cuid2; +// $preview_fqdn = str_replace('{{random}}', $random, $template); +// $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); +// $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); +// $preview_fqdn = "$schema://$preview_fqdn"; +// $preview->fqdn = $preview_fqdn; +// $preview->save(); - return $preview_fqdn; - }); - } - } - } - $defaultLabels = defaultLabels( - id: $resource->id, - name: $containerName, - projectName: $resource->project()->name, - resourceName: $resource->name, - pull_request_id: $pullRequestId, - type: 'application', - environment: $resource->environment->name, - ); +// return $preview_fqdn; +// }); +// } +// } +// } +// $defaultLabels = defaultLabels( +// id: $resource->id, +// name: $containerName, +// projectName: $resource->project()->name, +// resourceName: $resource->name, +// pull_request_id: $pullRequestId, +// type: 'application', +// environment: $resource->environment->name, +// ); - } elseif ($isService) { - if ($savedService->serviceType()) { - $fqdns = generateServiceSpecificFqdns($savedService); - } else { - $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); - } - $defaultLabels = defaultLabels( - id: $resource->id, - name: $containerName, - projectName: $resource->project()->name, - resourceName: $resource->name, - type: 'service', - subType: $isDatabase ? 'database' : 'application', - subId: $savedService->id, - subName: $savedService->human_name ?? $savedService->name, - environment: $resource->environment->name, - ); - } - // Add COOLIFY_FQDN & COOLIFY_URL to environment - if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { - $fqdnsWithoutPort = $fqdns->map(function ($fqdn) { - return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://')); - }); - $coolifyEnvironments->put('COOLIFY_URL', $fqdnsWithoutPort->implode(',')); +// } elseif ($isService) { +// if ($savedService->serviceType()) { +// $fqdns = generateServiceSpecificFqdns($savedService); +// } else { +// $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); +// } +// $defaultLabels = defaultLabels( +// id: $resource->id, +// name: $containerName, +// projectName: $resource->project()->name, +// resourceName: $resource->name, +// type: 'service', +// subType: $isDatabase ? 'database' : 'application', +// subId: $savedService->id, +// subName: $savedService->human_name ?? $savedService->name, +// environment: $resource->environment->name, +// ); +// } +// // Add COOLIFY_FQDN & COOLIFY_URL to environment +// if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { +// $fqdnsWithoutPort = $fqdns->map(function ($fqdn) { +// return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://')); +// }); +// $coolifyEnvironments->put('COOLIFY_URL', $fqdnsWithoutPort->implode(',')); - $urls = $fqdns->map(function ($fqdn) { - return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':'); - }); - $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(',')); - } - add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); - 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; - } - } +// $urls = $fqdns->map(function ($fqdn) { +// return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':'); +// }); +// $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(',')); +// } +// add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); +// 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 = data_get($resource, 'destination.network'); - if ($isPullRequest) { - $uuid = "{$resource->uuid}-{$pullRequestId}"; - } - if ($isPullRequest) { - $network = "{$resource->destination->network}-{$pullRequestId}"; - } - } else { - $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels; - $uuid = $resource->uuid; - $network = data_get($resource, 'destination.network'); - } - if ($shouldGenerateLabelsExactly) { - switch ($server->proxyType()) { - case ProxyTypes::TRAEFIK->value: - $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( - uuid: $uuid, - domains: $fqdns, - is_force_https_enabled: true, - serviceLabels: $serviceLabels, - is_gzip_enabled: $originalResource->isGzipEnabled(), - is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), - service_name: $serviceName, - image: $image - )); - break; - case ProxyTypes::CADDY->value: - $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( - network: $network, - uuid: $uuid, - domains: $fqdns, - is_force_https_enabled: true, - serviceLabels: $serviceLabels, - is_gzip_enabled: $originalResource->isGzipEnabled(), - is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), - service_name: $serviceName, - image: $image, - predefinedPort: $predefinedPort - )); - break; - } - } else { - $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( - uuid: $uuid, - domains: $fqdns, - is_force_https_enabled: true, - serviceLabels: $serviceLabels, - is_gzip_enabled: $originalResource->isGzipEnabled(), - is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), - service_name: $serviceName, - image: $image - )); - $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( - network: $network, - uuid: $uuid, - domains: $fqdns, - is_force_https_enabled: true, - serviceLabels: $serviceLabels, - is_gzip_enabled: $originalResource->isGzipEnabled(), - is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), - service_name: $serviceName, - image: $image, - predefinedPort: $predefinedPort - )); - } - } - if ($isService) { - if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { - $savedService->update(['exclude_from_status' => true]); - } - } - data_forget($service, 'volumes.*.content'); - data_forget($service, 'volumes.*.isDirectory'); - data_forget($service, 'volumes.*.is_directory'); - data_forget($service, 'exclude_from_hc'); +// 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 = data_get($resource, 'destination.network'); +// if ($isPullRequest) { +// $uuid = "{$resource->uuid}-{$pullRequestId}"; +// } +// if ($isPullRequest) { +// $network = "{$resource->destination->network}-{$pullRequestId}"; +// } +// } else { +// $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels; +// $uuid = $resource->uuid; +// $network = data_get($resource, 'destination.network'); +// } +// if ($shouldGenerateLabelsExactly) { +// switch ($server->proxyType()) { +// case ProxyTypes::TRAEFIK->value: +// $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( +// uuid: $uuid, +// domains: $fqdns, +// is_force_https_enabled: true, +// serviceLabels: $serviceLabels, +// is_gzip_enabled: $originalResource->isGzipEnabled(), +// is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), +// service_name: $serviceName, +// image: $image +// )); +// break; +// case ProxyTypes::CADDY->value: +// $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( +// network: $network, +// uuid: $uuid, +// domains: $fqdns, +// is_force_https_enabled: true, +// serviceLabels: $serviceLabels, +// is_gzip_enabled: $originalResource->isGzipEnabled(), +// is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), +// service_name: $serviceName, +// image: $image, +// predefinedPort: $predefinedPort +// )); +// break; +// } +// } else { +// $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( +// uuid: $uuid, +// domains: $fqdns, +// is_force_https_enabled: true, +// serviceLabels: $serviceLabels, +// is_gzip_enabled: $originalResource->isGzipEnabled(), +// is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), +// service_name: $serviceName, +// image: $image +// )); +// $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( +// network: $network, +// uuid: $uuid, +// domains: $fqdns, +// is_force_https_enabled: true, +// serviceLabels: $serviceLabels, +// is_gzip_enabled: $originalResource->isGzipEnabled(), +// is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), +// service_name: $serviceName, +// image: $image, +// predefinedPort: $predefinedPort +// )); +// } +// } +// if ($isService) { +// if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { +// $savedService->update(['exclude_from_status' => true]); +// } +// } +// data_forget($service, 'volumes.*.content'); +// data_forget($service, 'volumes.*.isDirectory'); +// 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'); +// $volumesParsed = $volumesParsed->map(function ($volume) { +// data_forget($volume, 'content'); +// data_forget($volume, 'is_directory'); +// data_forget($volume, 'isDirectory'); - return $volume; - }); +// return $volume; +// }); - $payload = collect($service)->merge([ - 'container_name' => $containerName, - 'restart' => $restart->value(), - 'labels' => $serviceLabels, - ]); - if (! $use_network_mode) { - $payload['networks'] = $networks_temp; - } - if ($ports->count() > 0) { - $payload['ports'] = $ports; - } - if ($volumesParsed->count() > 0) { - $payload['volumes'] = $volumesParsed; - } - if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) { - $payload['environment'] = $environment->merge($coolifyEnvironments); - } - if ($logging) { - $payload['logging'] = $logging; - } - if ($depends_on->count() > 0) { - $payload['depends_on'] = $depends_on; - } - if ($isApplication && $isPullRequest) { - $serviceName = "{$serviceName}-pr-{$pullRequestId}"; - } +// $payload = collect($service)->merge([ +// 'container_name' => $containerName, +// 'restart' => $restart->value(), +// 'labels' => $serviceLabels, +// ]); +// if (! $use_network_mode) { +// $payload['networks'] = $networks_temp; +// } +// if ($ports->count() > 0) { +// $payload['ports'] = $ports; +// } +// if ($volumesParsed->count() > 0) { +// $payload['volumes'] = $volumesParsed; +// } +// if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) { +// $payload['environment'] = $environment->merge($coolifyEnvironments); +// } +// if ($logging) { +// $payload['logging'] = $logging; +// } +// if ($depends_on->count() > 0) { +// $payload['depends_on'] = $depends_on; +// } +// if ($isApplication && $isPullRequest) { +// $serviceName = "{$serviceName}-pr-{$pullRequestId}"; +// } - $parsedServices->put($serviceName, $payload); - } - $topLevel->put('services', $parsedServices); +// $parsedServices->put($serviceName, $payload); +// } +// $topLevel->put('services', $parsedServices); - $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; +// $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; - $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) { - return array_search($key, $customOrder); - }); +// $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) { +// return array_search($key, $customOrder); +// }); - $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2); - data_forget($resource, 'environment_variables'); - data_forget($resource, 'environment_variables_preview'); - $resource->save(); +// $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2); +// data_forget($resource, 'environment_variables'); +// data_forget($resource, 'environment_variables_preview'); +// $resource->save(); - return $topLevel; -} +// return $topLevel; +// } function generate_fluentd_configuration(): array { From 0e7cc988a6d7b091be0c903b700c245b120cce43 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:57:00 +0200 Subject: [PATCH 102/202] feat(user): add changelog read tracking and unread count method --- app/Console/Commands/InitChangelog.php | 98 ++++++ app/Livewire/SettingsDropdown.php | 52 ++++ app/Models/User.php | 10 + app/Models/UserChangelogRead.php | 48 +++ app/Services/ChangelogService.php | 294 ++++++++++++++++++ config/services.php | 2 +- ...2403_create_user_changelog_reads_table.php | 34 ++ docker/production/Dockerfile | 1 + resources/views/components/navbar.blade.php | 187 +++++------ .../livewire/settings-dropdown.blade.php | 271 ++++++++++++++++ 10 files changed, 887 insertions(+), 110 deletions(-) create mode 100644 app/Console/Commands/InitChangelog.php create mode 100644 app/Livewire/SettingsDropdown.php create mode 100644 app/Models/UserChangelogRead.php create mode 100644 app/Services/ChangelogService.php create mode 100644 database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php create mode 100644 resources/views/livewire/settings-dropdown.blade.php diff --git a/app/Console/Commands/InitChangelog.php b/app/Console/Commands/InitChangelog.php new file mode 100644 index 000000000..f9eb12f04 --- /dev/null +++ b/app/Console/Commands/InitChangelog.php @@ -0,0 +1,98 @@ +argument('month') ?: Carbon::now()->format('Y-m'); + + // Validate month format + if (! preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $month)) { + $this->error('Invalid month format. Use YYYY-MM format with valid months 01-12 (e.g., 2025-08)'); + + return self::FAILURE; + } + + $changelogsDir = base_path('changelogs'); + $filePath = $changelogsDir."/{$month}.json"; + + // Create changelogs directory if it doesn't exist + if (! is_dir($changelogsDir)) { + mkdir($changelogsDir, 0755, true); + $this->info("Created changelogs directory: {$changelogsDir}"); + } + + // Check if file already exists + if (file_exists($filePath)) { + if (! $this->confirm("File {$month}.json already exists. Overwrite?")) { + $this->info('Operation cancelled'); + + return self::SUCCESS; + } + } + + // Parse the month for example data + $carbonMonth = Carbon::createFromFormat('Y-m', $month); + $monthName = $carbonMonth->format('F Y'); + $sampleDate = $carbonMonth->addDays(14)->toISOString(); // Mid-month + + // Get version from config + $version = 'v'.config('constants.coolify.version'); + + // Create example changelog structure + $exampleData = [ + 'entries' => [ + [ + 'version' => $version, + 'title' => 'Example Feature Release', + 'content' => "This is an example changelog entry for {$monthName}. Replace this with your actual release notes. Include details about new features, improvements, bug fixes, and any breaking changes.", + 'published_at' => $sampleDate, + ], + ], + ]; + + // Write the file + $jsonContent = json_encode($exampleData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + + if (file_put_contents($filePath, $jsonContent) === false) { + $this->error("Failed to create changelog file: {$filePath}"); + + return self::FAILURE; + } + + $this->info("✅ Created changelog file: changelogs/{$month}.json"); + $this->line(" Example entry created for {$monthName}"); + $this->line(' Edit the file to add your actual changelog entries'); + + // Show the file contents + if ($this->option('verbose')) { + $this->newLine(); + $this->line('File contents:'); + $this->line($jsonContent); + } + + return self::SUCCESS; + } +} diff --git a/app/Livewire/SettingsDropdown.php b/app/Livewire/SettingsDropdown.php new file mode 100644 index 000000000..5bb3e37a4 --- /dev/null +++ b/app/Livewire/SettingsDropdown.php @@ -0,0 +1,52 @@ +getUnreadChangelogCount(); + } + + public function getEntriesProperty() + { + $user = Auth::user(); + + return app(ChangelogService::class)->getEntriesForUser($user); + } + + public function openWhatsNewModal() + { + $this->showWhatsNewModal = true; + } + + public function closeWhatsNewModal() + { + $this->showWhatsNewModal = false; + } + + public function markAsRead($identifier) + { + app(ChangelogService::class)->markAsReadForUser($identifier, Auth::user()); + } + + public function markAllAsRead() + { + app(ChangelogService::class)->markAllAsReadForUser(Auth::user()); + } + + public function render() + { + return view('livewire.settings-dropdown', [ + 'entries' => $this->entries, + 'unreadCount' => $this->unreadCount, + ]); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 6cd1b66db..3c5a220f8 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -203,6 +203,16 @@ class User extends Authenticatable implements SendsEmail return $this->belongsToMany(Team::class)->withPivot('role'); } + public function changelogReads() + { + return $this->hasMany(UserChangelogRead::class); + } + + public function getUnreadChangelogCount(): int + { + return app(\App\Services\ChangelogService::class)->getUnreadCountForUser($this); + } + public function getRecipients(): array { return [$this->email]; diff --git a/app/Models/UserChangelogRead.php b/app/Models/UserChangelogRead.php new file mode 100644 index 000000000..28c384cb5 --- /dev/null +++ b/app/Models/UserChangelogRead.php @@ -0,0 +1,48 @@ + 'datetime', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public static function markAsRead(int $userId, string $identifier): void + { + self::firstOrCreate([ + 'user_id' => $userId, + 'changelog_identifier' => $identifier, + ], [ + 'read_at' => now(), + ]); + } + + public static function isReadByUser(int $userId, string $identifier): bool + { + return self::where('user_id', $userId) + ->where('changelog_identifier', $identifier) + ->exists(); + } + + public static function getReadIdentifiersForUser(int $userId): array + { + return self::where('user_id', $userId) + ->pluck('changelog_identifier') + ->toArray(); + } +} diff --git a/app/Services/ChangelogService.php b/app/Services/ChangelogService.php new file mode 100644 index 000000000..c6f4c8752 --- /dev/null +++ b/app/Services/ChangelogService.php @@ -0,0 +1,294 @@ +fetchChangelogData(); + + if (! $data || ! isset($data['entries'])) { + return collect(); + } + + return collect($data['entries']) + ->filter(fn ($entry) => $this->validateEntryData($entry)) + ->map(function ($entry) { + $entry['published_at'] = Carbon::parse($entry['published_at']); + $entry['content_html'] = $this->parseMarkdown($entry['content']); + + return (object) $entry; + }) + ->filter(fn ($entry) => $entry->published_at <= now()) + ->sortBy('published_at') + ->reverse() + ->values(); + } + + // Load entries from recent months for performance + $availableMonths = $this->getAvailableMonths(); + $monthsToLoad = $availableMonths->take($recentMonths); + + return $monthsToLoad + ->flatMap(fn ($month) => $this->getEntriesForMonth($month)) + ->sortBy('published_at') + ->reverse() + ->values(); + } + + public function getAllEntries(): Collection + { + $availableMonths = $this->getAvailableMonths(); + + return $availableMonths + ->flatMap(fn ($month) => $this->getEntriesForMonth($month)) + ->sortBy('published_at') + ->reverse() + ->values(); + } + + public function getEntriesForUser(User $user): Collection + { + $entries = $this->getEntries(); + $readIdentifiers = UserChangelogRead::getReadIdentifiersForUser($user->id); + + return $entries->map(function ($entry) use ($readIdentifiers) { + $entry->is_read = in_array($entry->version, $readIdentifiers); + + return $entry; + })->sortBy([ + ['is_read', 'asc'], // unread first + ['published_at', 'desc'], // then by date + ])->values(); + } + + public function getUnreadCountForUser(User $user): int + { + if (isDev()) { + $entries = $this->getEntries(); + $readIdentifiers = UserChangelogRead::getReadIdentifiersForUser($user->id); + + return $entries->reject(fn ($entry) => in_array($entry->version, $readIdentifiers))->count(); + } else { + return Cache::remember( + 'user_unread_changelog_count_'.$user->id, + now()->addHour(), + function () use ($user) { + $entries = $this->getEntries(); + $readIdentifiers = UserChangelogRead::getReadIdentifiersForUser($user->id); + + return $entries->reject(fn ($entry) => in_array($entry->version, $readIdentifiers))->count(); + } + ); + } + } + + public function getAvailableMonths(): Collection + { + $pattern = base_path('changelogs/*.json'); + $files = glob($pattern); + + if ($files === false) { + return collect(); + } + + return collect($files) + ->map(fn ($file) => basename($file, '.json')) + ->filter(fn ($name) => preg_match('/^\d{4}-\d{2}$/', $name)) + ->sort() + ->reverse() + ->values(); + } + + public function getEntriesForMonth(string $month): Collection + { + $path = base_path("changelogs/{$month}.json"); + + if (! file_exists($path)) { + return collect(); + } + + $content = file_get_contents($path); + + if ($content === false) { + Log::error("Failed to read changelog file: {$month}.json"); + + return collect(); + } + + $data = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + Log::error("Invalid JSON in {$month}.json: ".json_last_error_msg()); + + return collect(); + } + + if (! isset($data['entries']) || ! is_array($data['entries'])) { + return collect(); + } + + return collect($data['entries']) + ->filter(fn ($entry) => $this->validateEntryData($entry)) + ->map(function ($entry) { + $entry['published_at'] = Carbon::parse($entry['published_at']); + $entry['content_html'] = $this->parseMarkdown($entry['content']); + + return (object) $entry; + }) + ->filter(fn ($entry) => $entry->published_at <= now()) + ->sortBy('published_at') + ->reverse() + ->values(); + } + + private function fetchChangelogData(): ?array + { + // Legacy support for old changelog.json + $path = base_path('changelog.json'); + + if (file_exists($path)) { + $content = file_get_contents($path); + + if ($content === false) { + Log::error('Failed to read changelog.json file'); + + return null; + } + + $data = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + Log::error('Invalid JSON in changelog.json: '.json_last_error_msg()); + + return null; + } + + return $data; + } + + // New monthly structure - combine all months + $allEntries = []; + foreach ($this->getAvailableMonths() as $month) { + $monthEntries = $this->getEntriesForMonth($month); + foreach ($monthEntries as $entry) { + $allEntries[] = (array) $entry; + } + } + + return ['entries' => $allEntries]; + } + + public function markAsReadForUser(string $version, User $user): void + { + UserChangelogRead::markAsRead($user->id, $version); + Cache::forget('user_unread_changelog_count_'.$user->id); + } + + public function markAllAsReadForUser(User $user): void + { + $entries = $this->getEntries(); + + foreach ($entries as $entry) { + UserChangelogRead::markAsRead($user->id, $entry->version); + } + + Cache::forget('user_unread_changelog_count_'.$user->id); + } + + private function validateEntryData(array $data): bool + { + $required = ['version', 'title', 'content', 'published_at']; + + foreach ($required as $field) { + if (! isset($data[$field]) || empty($data[$field])) { + return false; + } + } + + return true; + } + + public function clearAllReadStatus(): array + { + try { + $count = UserChangelogRead::count(); + UserChangelogRead::truncate(); + + // Clear all user caches + $this->clearAllUserCaches(); + + return [ + 'success' => true, + 'message' => "Successfully cleared {$count} read status records", + ]; + } catch (\Exception $e) { + Log::error('Failed to clear read status: '.$e->getMessage()); + + return [ + 'success' => false, + 'message' => 'Failed to clear read status: '.$e->getMessage(), + ]; + } + } + + private function clearAllUserCaches(): void + { + $users = User::select('id')->get(); + + foreach ($users as $user) { + Cache::forget('user_unread_changelog_count_'.$user->id); + } + } + + private function parseMarkdown(string $content): string + { + // Convert markdown to HTML using simple regex patterns + $html = $content; + + // Headers + $html = preg_replace('/^### (.*?)$/m', '

$1

', $html); + $html = preg_replace('/^## (.*?)$/m', '

$1

', $html); + $html = preg_replace('/^# (.*?)$/m', '

$1

', $html); + + // Bold text + $html = preg_replace('/\*\*(.*?)\*\*/', '$1', $html); + $html = preg_replace('/__(.*?)__/', '$1', $html); + + // Italic text + $html = preg_replace('/\*(.*?)\*/', '$1', $html); + $html = preg_replace('/_(.*?)_/', '$1', $html); + + // Code blocks + $html = preg_replace('/```(.*?)```/s', '
$1
', $html); + + // Inline code + $html = preg_replace('/`([^`]+)`/', '$1', $html); + + // Links + $html = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '$1', $html); + + // Line breaks (convert double newlines to paragraphs) + $paragraphs = preg_split('/\n\s*\n/', trim($html)); + $html = '

'.implode('

', $paragraphs).'

'; + + // Single line breaks + $html = preg_replace('/\n/', '
', $html); + + // Unordered lists + $html = preg_replace('/^\- (.*)$/m', '
  • • $1
  • ', $html); + $html = preg_replace('/(
  • .*<\/li>)/s', '
      $1
    ', $html); + + return $html; + } +} diff --git a/config/services.php b/config/services.php index 7add50a5c..6a21cda18 100644 --- a/config/services.php +++ b/config/services.php @@ -65,6 +65,6 @@ return [ 'client_secret' => env('ZITADEL_CLIENT_SECRET'), 'redirect' => env('ZITADEL_REDIRECT_URI'), 'base_url' => env('ZITADEL_BASE_URL'), - ] + ], ]; diff --git a/database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php b/database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php new file mode 100644 index 000000000..4c340d106 --- /dev/null +++ b/database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('changelog_identifier'); + $table->timestamp('read_at'); + $table->timestamps(); + + $table->unique(['user_id', 'changelog_identifier']); + $table->index('user_id'); + $table->index('changelog_identifier'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_changelog_reads'); + } +}; diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index a2a4b5fa3..6c9628a81 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -120,6 +120,7 @@ COPY --chown=www-data:www-data templates ./templates COPY --chown=www-data:www-data resources/views ./resources/views COPY --chown=www-data:www-data artisan artisan COPY --chown=www-data:www-data openapi.yaml ./openapi.yaml +COPY --chown=www-data:www-data changelogs/ ./changelogs/ RUN composer dump-autoload diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index be26d55ca..1a145aa4b 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -1,119 +1,88 @@ -