From b997b7393b40f300ea1064cc84d304aeb8ae9911 Mon Sep 17 00:00:00 2001 From: Kael Date: Fri, 11 Oct 2024 02:44:52 +1100 Subject: [PATCH 01/27] feat: allow disabling default redirect, set status to 503 --- app/Listeners/ProxyStartedNotification.php | 2 +- app/Livewire/Server/Proxy.php | 16 +- app/Models/Server.php | 139 +++++++++--------- .../views/livewire/server/proxy.blade.php | 9 +- .../proxy/dynamic-configurations.blade.php | 2 +- 5 files changed, 91 insertions(+), 77 deletions(-) diff --git a/app/Listeners/ProxyStartedNotification.php b/app/Listeners/ProxyStartedNotification.php index d0541b162..9045b1e5c 100644 --- a/app/Listeners/ProxyStartedNotification.php +++ b/app/Listeners/ProxyStartedNotification.php @@ -14,7 +14,7 @@ class ProxyStartedNotification public function handle(ProxyStarted $event): void { $this->server = data_get($event, 'data'); - $this->server->setupDefault404Redirect(); + $this->server->setupDefaultRedirect(); $this->server->setupDynamicProxyConfiguration(); $this->server->proxy->force_stop = false; $this->server->save(); diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php index 55d0c4966..fbdba53c1 100644 --- a/app/Livewire/Server/Proxy.php +++ b/app/Livewire/Server/Proxy.php @@ -16,6 +16,7 @@ class Proxy extends Component public $proxy_settings = null; + public bool $redirect_enabled = true; public ?string $redirect_url = null; protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit']; @@ -27,6 +28,7 @@ class Proxy extends Component public function mount() { $this->selectedProxy = $this->server->proxyType(); + $this->redirect_enabled = data_get($this->server, 'proxy.redirect_enabled', true); $this->redirect_url = data_get($this->server, 'proxy.redirect_url'); } @@ -65,13 +67,25 @@ class Proxy extends Component } } + public function instantSaveRedirect() + { + try { + $this->server->proxy->redirect_enabled = $this->redirect_enabled; + $this->server->save(); + $this->server->setupDefaultRedirect(); + $this->dispatch('success', 'Proxy configuration saved.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function submit() { try { SaveConfiguration::run($this->server, $this->proxy_settings); $this->server->proxy->redirect_url = $this->redirect_url; $this->server->save(); - $this->server->setupDefault404Redirect(); + $this->server->setupDefaultRedirect(); $this->dispatch('success', 'Proxy configuration saved.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Models/Server.php b/app/Models/Server.php index 8864deef1..0a92caa60 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -94,6 +94,14 @@ class Server extends BaseModel ]); } } + if (!isset($server->proxy->redirect_enabled)) { + $server->proxy->redirect_enabled = true; + } + }); + static::retrieved(function ($server) { + if (!isset($server->proxy->redirect_enabled)) { + $server->proxy->redirect_enabled = true; + } }); static::deleting(function ($server) { $server->destinations()->each(function ($destination) { @@ -164,70 +172,72 @@ class Server extends BaseModel return $this->proxyType() && $this->proxyType() !== 'NONE' && $this->isFunctional() && ! $this->isSwarmWorker() && ! $this->settings->is_build_server; } - public function setupDefault404Redirect() + public function setupDefaultRedirect() { + $banner = + "# This file is generated by Coolify, do not edit it manually.\n" . + "# Disable the default redirect to customize (only if you know what are you doing).\n\n"; $dynamic_conf_path = $this->proxyPath().'/dynamic'; $proxy_type = $this->proxyType(); + $redirect_enabled = $this->proxy->redirect_enabled ?? true; $redirect_url = $this->proxy->redirect_url; + if ($proxy_type === ProxyTypes::TRAEFIK->value) { - $default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml"; + $default_redirect_file = "$dynamic_conf_path/default_redirect_503.yaml"; } elseif ($proxy_type === ProxyTypes::CADDY->value) { - $default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy"; + $default_redirect_file = "$dynamic_conf_path/default_redirect_503.caddy"; } - if (empty($redirect_url)) { + + instant_remote_process([ + "mkdir -p $dynamic_conf_path", + "rm -f $dynamic_conf_path/default_redirect_404.yaml", + "rm -f $dynamic_conf_path/default_redirect_404.caddy", + ], $this); + + if (!$redirect_enabled) { + instant_remote_process(["rm -f $default_redirect_file"], $this); + } else { if ($proxy_type === ProxyTypes::CADDY->value) { - $conf = ':80, :443 { -respond 404 + if (empty($redirect_url)) { + $conf = ':80, :443 { + respond 503 }'; - $conf = - "# This file is automatically generated by Coolify.\n". - "# Do not edit it manually (only if you know what are you doing).\n\n". - $conf; - $base64 = base64_encode($conf); - instant_remote_process([ - "mkdir -p $dynamic_conf_path", - "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", - ], $this); - $this->reloadCaddy(); - - return; - } - instant_remote_process([ - "mkdir -p $dynamic_conf_path", - "rm -f $default_redirect_file", - ], $this); - - return; - } - if ($proxy_type === ProxyTypes::TRAEFIK->value) { - $dynamic_conf = [ - 'http' => [ - 'routers' => [ - 'catchall' => [ - 'entryPoints' => [ - 0 => 'http', - 1 => 'https', - ], - 'service' => 'noop', - 'rule' => 'HostRegexp(`{catchall:.*}`)', - 'priority' => 1, - 'middlewares' => [ - 0 => 'redirect-regexp@file', + } else { + $conf = ":80, :443 { + redir $redirect_url +}"; + } + } elseif ($proxy_type === ProxyTypes::TRAEFIK->value) { + $dynamic_conf = [ + 'http' => [ + 'routers' => [ + 'catchall' => [ + 'entryPoints' => [ + 0 => 'http', + 1 => 'https', + ], + 'service' => 'noop', + 'rule' => 'HostRegexp(`{catchall:.*}`)', + 'priority' => 1, ], ], - ], - 'services' => [ - 'noop' => [ - 'loadBalancer' => [ - 'servers' => [ - 0 => [ - 'url' => '', - ], + 'services' => [ + 'noop' => [ + 'loadBalancer' => [ + 'servers' => [], ], ], ], ], - 'middlewares' => [ + ]; + if (!empty($redirect_url)) { + $dynamic_conf['http']['routers']['catchall']['middlewares'] = [ + 0 => 'redirect-regexp@file', + ]; + $dynamic_conf['http']['services']['noop']['loadBalancer']['servers'][0] = [ + 'url' => '', + ]; + $dynamic_conf['http']['middlewares'] = [ 'redirect-regexp' => [ 'redirectRegex' => [ 'regex' => '(.*)', @@ -235,32 +245,17 @@ respond 404 'permanent' => false, ], ], - ], - ], - ]; - $conf = Yaml::dump($dynamic_conf, 12, 2); - $conf = - "# This file is automatically generated by Coolify.\n". - "# Do not edit it manually (only if you know what are you doing).\n\n". - $conf; - - $base64 = base64_encode($conf); - } elseif ($proxy_type === ProxyTypes::CADDY->value) { - $conf = ":80, :443 { - redir $redirect_url -}"; - $conf = - "# This file is automatically generated by Coolify.\n". - "# Do not edit it manually (only if you know what are you doing).\n\n". - $conf; + ]; + } + $conf = Yaml::dump($dynamic_conf, 12, 2); + } + $conf = $banner . $conf; $base64 = base64_encode($conf); + instant_remote_process([ + "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", + ], $this); } - instant_remote_process([ - "mkdir -p $dynamic_conf_path", - "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", - ], $this); - if ($proxy_type === 'CADDY') { $this->reloadCaddy(); } diff --git a/resources/views/livewire/server/proxy.blade.php b/resources/views/livewire/server/proxy.blade.php index 25c3f7acd..5748a5876 100644 --- a/resources/views/livewire/server/proxy.blade.php +++ b/resources/views/livewire/server/proxy.blade.php @@ -28,6 +28,13 @@ id="server.settings.generate_exact_labels" label="Generate labels only for {{ str($server->proxyType())->title() }}" instantSave /> +
Default request handler
+
+ + @if ($redirect_enabled) + + @endif +
@if ($server->proxyType() === ProxyTypes::TRAEFIK->value)

Traefik

@elseif ($server->proxyType() === 'CADDY') @@ -40,8 +47,6 @@ configurations. @endif -
diff --git a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php index a8192cdb1..dd1fa59f0 100644 --- a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php +++ b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php @@ -29,7 +29,7 @@ @if (str_replace('|', '.', $fileName) === 'coolify.yaml' || str_replace('|', '.', $fileName) === 'Caddyfile' || str_replace('|', '.', $fileName) === 'coolify.caddy' || - str_replace('|', '.', $fileName) === 'default_redirect_404.caddy') + str_replace('|', '.', $fileName) === 'default_redirect_503.caddy')

File: {{ str_replace('|', '.', $fileName) }}

From 2f3503d8b8509ddc9904a2fc3ffc12c3cd98e7b4 Mon Sep 17 00:00:00 2001 From: Kael Date: Fri, 11 Oct 2024 03:24:46 +1100 Subject: [PATCH 02/27] fix: don't allow editing traefik config --- .../views/livewire/server/proxy/dynamic-configurations.blade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php index dd1fa59f0..5499ea95b 100644 --- a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php +++ b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php @@ -29,6 +29,7 @@ @if (str_replace('|', '.', $fileName) === 'coolify.yaml' || str_replace('|', '.', $fileName) === 'Caddyfile' || str_replace('|', '.', $fileName) === 'coolify.caddy' || + str_replace('|', '.', $fileName) === 'default_redirect_503.yaml' || str_replace('|', '.', $fileName) === 'default_redirect_503.caddy')

File: {{ str_replace('|', '.', $fileName) }}

From eb0686fe2035ce4624e04037c66620f6cb348376 Mon Sep 17 00:00:00 2001 From: Marvin von Rappard Date: Tue, 12 Nov 2024 22:37:55 +0100 Subject: [PATCH 03/27] feat: slack notifications --- app/Jobs/SendMessageToSlackJob.php | 58 ++++ app/Livewire/Notifications/Slack.php | 130 +++++++++ app/Models/Team.php | 10 +- .../Application/DeploymentFailed.php | 46 ++- .../Application/DeploymentSuccess.php | 46 ++- .../Application/StatusChanged.php | 24 +- app/Notifications/Channels/SendsSlack.php | 8 + app/Notifications/Channels/SlackChannel.php | 22 ++ .../Container/ContainerRestarted.php | 24 +- .../Container/ContainerStopped.php | 24 +- app/Notifications/Database/BackupFailed.php | 17 +- app/Notifications/Database/BackupSuccess.php | 16 +- app/Notifications/Dto/SlackMessage.php | 28 ++ .../Internal/GeneralNotification.php | 19 +- .../ScheduledTask/TaskFailed.php | 24 +- app/Notifications/Server/DockerCleanup.php | 19 +- app/Notifications/Server/ForceDisabled.php | 26 +- app/Notifications/Server/ForceEnabled.php | 22 +- app/Notifications/Server/HighDiskUsage.php | 25 +- app/Notifications/Server/Reachable.php | 21 +- app/Notifications/Server/Unreachable.php | 22 +- app/Notifications/Test.php | 15 +- bootstrap/helpers/shared.php | 270 +++++++++--------- ...add_slack_notifications_to_teams_table.php | 37 +++ .../components/notification/navbar.blade.php | 6 +- .../livewire/notifications/slack.blade.php | 43 +++ routes/web.php | 1 + 27 files changed, 822 insertions(+), 181 deletions(-) create mode 100644 app/Jobs/SendMessageToSlackJob.php create mode 100644 app/Livewire/Notifications/Slack.php create mode 100644 app/Notifications/Channels/SendsSlack.php create mode 100644 app/Notifications/Channels/SlackChannel.php create mode 100644 app/Notifications/Dto/SlackMessage.php create mode 100644 database/migrations/2024_11_12_213200_add_slack_notifications_to_teams_table.php create mode 100644 resources/views/livewire/notifications/slack.blade.php diff --git a/app/Jobs/SendMessageToSlackJob.php b/app/Jobs/SendMessageToSlackJob.php new file mode 100644 index 000000000..b78088f50 --- /dev/null +++ b/app/Jobs/SendMessageToSlackJob.php @@ -0,0 +1,58 @@ +webhookUrl, [ + 'blocks' => [ + [ + 'type' => 'section', + 'text' => [ + 'type' => 'plain_text', + 'text' => "Coolify Notification", + ], + ], + ], + 'attachments' => [ + [ + 'color' => $this->message->color, + 'blocks' => [ + [ + 'type' => 'header', + 'text' => [ + 'type' => 'plain_text', + 'text' => $this->message->title, + ], + ], + [ + 'type' => 'section', + 'text' => [ + 'type' => 'mrkdwn', + 'text' => $this->message->description + ] + ] + ] + ] + ] + ]); + } +} \ No newline at end of file diff --git a/app/Livewire/Notifications/Slack.php b/app/Livewire/Notifications/Slack.php new file mode 100644 index 000000000..edd32a071 --- /dev/null +++ b/app/Livewire/Notifications/Slack.php @@ -0,0 +1,130 @@ +team = auth()->user()->currentTeam(); + $this->syncData(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->team->slack_enabled = $this->slackEnabled; + $this->team->slack_webhook_url = $this->slackWebhookUrl; + $this->team->slack_notifications_test = $this->slackNotificationsTest; + $this->team->slack_notifications_deployments = $this->slackNotificationsDeployments; + $this->team->slack_notifications_status_changes = $this->slackNotificationsStatusChanges; + $this->team->slack_notifications_database_backups = $this->slackNotificationsDatabaseBackups; + $this->team->slack_notifications_scheduled_tasks = $this->slackNotificationsScheduledTasks; + $this->team->slack_notifications_server_disk_usage = $this->slackNotificationsServerDiskUsage; + $this->team->save(); + refreshSession(); + } else { + $this->slackEnabled = $this->team->slack_enabled; + $this->slackWebhookUrl = $this->team->slack_webhook_url; + $this->slackNotificationsTest = $this->team->slack_notifications_test; + $this->slackNotificationsDeployments = $this->team->slack_notifications_deployments; + $this->slackNotificationsStatusChanges = $this->team->slack_notifications_status_changes; + $this->slackNotificationsDatabaseBackups = $this->team->slack_notifications_database_backups; + $this->slackNotificationsScheduledTasks = $this->team->slack_notifications_scheduled_tasks; + $this->slackNotificationsServerDiskUsage = $this->team->slack_notifications_server_disk_usage; + } + } + + public function instantSaveSlackEnabled() + { + try { + $this->validate([ + 'slackWebhookUrl' => 'required', + ], [ + 'slackWebhookUrl.required' => 'Slack Webhook URL is required.', + ]); + $this->saveModel(); + } catch (\Throwable $e) { + $this->slackEnabled = false; + return handleError($e, $this); + } + } + + public function instantSave() + { + try { + $this->syncData(true); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function submit() + { + try { + $this->resetErrorBag(); + $this->syncData(true); + $this->saveModel(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function saveModel() + { + $this->syncData(true); + refreshSession(); + $this->dispatch('success', 'Settings saved.'); + } + + public function sendTestNotification() + { + try { + $this->team->notify(new Test); + $this->dispatch('success', 'Test notification sent.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.notifications.slack'); + } +} \ No newline at end of file diff --git a/app/Models/Team.php b/app/Models/Team.php index db485054b..2035abdee 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Notifications\Channels\SendsDiscord; use App\Notifications\Channels\SendsEmail; +use App\Notifications\Channels\SendsSlack; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; @@ -70,7 +71,7 @@ use OpenApi\Attributes as OA; ), ] )] -class Team extends Model implements SendsDiscord, SendsEmail +class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack { use Notifiable; @@ -127,6 +128,11 @@ class Team extends Model implements SendsDiscord, SendsEmail ]; } + public function routeNotificationForSlack() + { + return data_get($this, 'slack_webhook_url', null); + } + public function getRecepients($notification) { $recipients = data_get($notification, 'emails', null); @@ -161,7 +167,7 @@ class Team extends Model implements SendsDiscord, SendsEmail return 9999999; } $team = Team::find(currentTeam()->id); - if (! $team) { + if (!$team) { return 0; } diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index 242980e00..dc6b93aad 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -5,6 +5,7 @@ namespace App\Notifications\Application; use App\Models\Application; use App\Models\ApplicationPreview; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -44,7 +45,7 @@ class DeploymentFailed extends Notification implements ShouldQueue if (str($this->fqdn)->explode(',')->count() > 1) { $this->fqdn = str($this->fqdn)->explode(',')->first(); } - $this->deployment_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; + $this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; } public function via(object $notifiable): array @@ -58,10 +59,10 @@ class DeploymentFailed extends Notification implements ShouldQueue $pull_request_id = data_get($this->preview, 'pull_request_id', 0); $fqdn = $this->fqdn; if ($pull_request_id === 0) { - $mail->subject('Coolify: Deployment failed of '.$this->application_name.'.'); + $mail->subject('Coolify: Deployment failed of ' . $this->application_name . '.'); } else { $fqdn = $this->preview->fqdn; - $mail->subject('Coolify: Deployment failed of pull request #'.$this->preview->pull_request_id.' of '.$this->application_name.'.'); + $mail->subject('Coolify: Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.'); } $mail->view('emails.application-deployment-failed', [ 'name' => $this->application_name, @@ -78,7 +79,7 @@ class DeploymentFailed extends Notification implements ShouldQueue if ($this->preview) { $message = new DiscordMessage( title: ':cross_mark: Deployment failed', - description: 'Pull request: '.$this->preview->pull_request_id, + description: 'Pull request: ' . $this->preview->pull_request_id, color: DiscordMessage::errorColor(), isCritical: true, ); @@ -87,13 +88,13 @@ class DeploymentFailed extends Notification implements ShouldQueue $message->addField('Environment', $this->environment_name, true); $message->addField('Name', $this->application_name, true); - $message->addField('Deployment Logs', '[Link]('.$this->deployment_url.')'); + $message->addField('Deployment Logs', '[Link](' . $this->deployment_url . ')'); if ($this->fqdn) { $message->addField('Domain', $this->fqdn, true); } } else { if ($this->fqdn) { - $description = '[Open application]('.$this->fqdn.')'; + $description = '[Open application](' . $this->fqdn . ')'; } else { $description = ''; } @@ -108,7 +109,7 @@ class DeploymentFailed extends Notification implements ShouldQueue $message->addField('Environment', $this->environment_name, true); $message->addField('Name', $this->application_name, true); - $message->addField('Deployment Logs', '[Link]('.$this->deployment_url.')'); + $message->addField('Deployment Logs', '[Link](' . $this->deployment_url . ')'); } return $message; @@ -117,9 +118,9 @@ class DeploymentFailed extends Notification implements ShouldQueue public function toTelegram(): array { if ($this->preview) { - $message = 'Coolify: Pull request #'.$this->preview->pull_request_id.' of '.$this->application_name.' ('.$this->preview->fqdn.') deployment failed: '; + $message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . ' (' . $this->preview->fqdn . ') deployment failed: '; } else { - $message = 'Coolify: Deployment failed of '.$this->application_name.' ('.$this->fqdn.'): '; + $message = 'Coolify: Deployment failed of ' . $this->application_name . ' (' . $this->fqdn . '): '; } $buttons[] = [ 'text' => 'Deployment logs', @@ -133,4 +134,31 @@ class DeploymentFailed extends Notification implements ShouldQueue ], ]; } + + public function toSlack(): SlackMessage + { + if ($this->preview) { + $title = "Pull request #{$this->preview->pull_request_id} deployment failed"; + $description = "Pull request deployment failed for {$this->application_name}"; + if ($this->preview->fqdn) { + $description .= "\nPreview URL: {$this->preview->fqdn}"; + } + } else { + $title = "Deployment failed"; + $description = "Deployment failed for {$this->application_name}"; + if ($this->fqdn) { + $description .= "\nApplication URL: {$this->fqdn}"; + } + } + + $description .= "\n\n**Project:** " . data_get($this->application, 'environment.project.name'); + $description .= "\n**Environment:** {$this->environment_name}"; + $description .= "\n**Deployment Logs:** {$this->deployment_url}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 946a622ca..e11d6db1c 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -9,7 +9,7 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; - +use App\Notifications\Dto\SlackMessage; class DeploymentSuccess extends Notification implements ShouldQueue { use Queueable; @@ -44,7 +44,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue if (str($this->fqdn)->explode(',')->count() > 1) { $this->fqdn = str($this->fqdn)->explode(',')->first(); } - $this->deployment_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; + $this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; } public function via(object $notifiable): array @@ -84,21 +84,21 @@ class DeploymentSuccess extends Notification implements ShouldQueue if ($this->preview) { $message = new DiscordMessage( title: ':white_check_mark: Preview deployment successful', - description: 'Pull request: '.$this->preview->pull_request_id, + description: 'Pull request: ' . $this->preview->pull_request_id, color: DiscordMessage::successColor(), ); if ($this->preview->fqdn) { - $message->addField('Application', '[Link]('.$this->preview->fqdn.')'); + $message->addField('Application', '[Link](' . $this->preview->fqdn . ')'); } $message->addField('Project', data_get($this->application, 'environment.project.name'), true); $message->addField('Environment', $this->environment_name, true); $message->addField('Name', $this->application_name, true); - $message->addField('Deployment logs', '[Link]('.$this->deployment_url.')'); + $message->addField('Deployment logs', '[Link](' . $this->deployment_url . ')'); } else { if ($this->fqdn) { - $description = '[Open application]('.$this->fqdn.')'; + $description = '[Open application](' . $this->fqdn . ')'; } else { $description = ''; } @@ -111,7 +111,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue $message->addField('Environment', $this->environment_name, true); $message->addField('Name', $this->application_name, true); - $message->addField('Deployment logs', '[Link]('.$this->deployment_url.')'); + $message->addField('Deployment logs', '[Link](' . $this->deployment_url . ')'); } return $message; @@ -120,7 +120,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue public function toTelegram(): array { if ($this->preview) { - $message = 'Coolify: New PR'.$this->preview->pull_request_id.' version successfully deployed of '.$this->application_name.''; + $message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ''; if ($this->preview->fqdn) { $buttons[] = [ 'text' => 'Open Application', @@ -128,7 +128,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue ]; } } else { - $message = '✅ New version successfully deployed of '.$this->application_name.''; + $message = '✅ New version successfully deployed of ' . $this->application_name . ''; if ($this->fqdn) { $buttons[] = [ 'text' => 'Open Application', @@ -148,4 +148,32 @@ class DeploymentSuccess extends Notification implements ShouldQueue ], ]; } + + + public function toSlack(): SlackMessage + { + if ($this->preview) { + $title = "Pull request #{$this->preview->pull_request_id} successfully deployed"; + $description = "New version successfully deployed for {$this->application_name}"; + if ($this->preview->fqdn) { + $description .= "\nPreview URL: {$this->preview->fqdn}"; + } + } else { + $title = "New version successfully deployed"; + $description = "New version successfully deployed for {$this->application_name}"; + if ($this->fqdn) { + $description .= "\nApplication URL: {$this->fqdn}"; + } + } + + $description .= "\n\n**Project:** " . data_get($this->application, 'environment.project.name'); + $description .= "\n**Environment:** {$this->environment_name}"; + $description .= "\n**Deployment Logs:** {$this->deployment_url}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::successColor() + ); + } } diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index 852c6b526..c7445cb70 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -8,7 +8,7 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; - +use App\Notifications\Dto\SlackMessage; class StatusChanged extends Notification implements ShouldQueue { use Queueable; @@ -34,7 +34,7 @@ class StatusChanged extends Notification implements ShouldQueue if (str($this->fqdn)->explode(',')->count() > 1) { $this->fqdn = str($this->fqdn)->explode(',')->first(); } - $this->resource_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->resource->uuid}"; + $this->resource_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->resource->uuid}"; } public function via(object $notifiable): array @@ -60,7 +60,7 @@ class StatusChanged extends Notification implements ShouldQueue { return new DiscordMessage( title: ':cross_mark: Application stopped', - description: '[Open Application in Coolify]('.$this->resource_url.')', + description: '[Open Application in Coolify](' . $this->resource_url . ')', color: DiscordMessage::errorColor(), isCritical: true, ); @@ -68,7 +68,7 @@ class StatusChanged extends Notification implements ShouldQueue public function toTelegram(): array { - $message = 'Coolify: '.$this->resource_name.' has been stopped.'; + $message = 'Coolify: ' . $this->resource_name . ' has been stopped.'; return [ 'message' => $message, @@ -80,4 +80,20 @@ class StatusChanged extends Notification implements ShouldQueue ], ]; } + + public function toSlack(): SlackMessage + { + $title = "Application stopped"; + $description = "{$this->resource_name} has been stopped"; + + $description .= "\n\n**Project:** " . data_get($this->resource, 'environment.project.name'); + $description .= "\n**Environment:** {$this->environment_name}"; + $description .= "\n**Application URL:** {$this->resource_url}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Channels/SendsSlack.php b/app/Notifications/Channels/SendsSlack.php new file mode 100644 index 000000000..417d4adda --- /dev/null +++ b/app/Notifications/Channels/SendsSlack.php @@ -0,0 +1,8 @@ +toSlack(); + $webhookUrl = $notifiable->routeNotificationForSlack(); + if (!$webhookUrl) { + return; + } + dispatch(new SendMessageToSlackJob($message, $webhookUrl))->onQueue('high'); + } +} \ No newline at end of file diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index 182a1f5fc..40bd9b7ee 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -8,14 +8,16 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; - +use App\Notifications\Dto\SlackMessage; class ContainerRestarted extends Notification implements ShouldQueue { use Queueable; public $tries = 1; - public function __construct(public string $name, public Server $server, public ?string $url = null) {} + public function __construct(public string $name, public Server $server, public ?string $url = null) + { + } public function via(object $notifiable): array { @@ -44,7 +46,7 @@ class ContainerRestarted extends Notification implements ShouldQueue ); if ($this->url) { - $message->addField('Resource', '[Link]('.$this->url.')'); + $message->addField('Resource', '[Link](' . $this->url . ')'); } return $message; @@ -69,4 +71,20 @@ class ContainerRestarted extends Notification implements ShouldQueue return $payload; } + + public function toSlack(): SlackMessage + { + $title = "Resource restarted"; + $description = "A resource ({$this->name}) has been restarted automatically on {$this->server->name}"; + + if ($this->url) { + $description .= "\n**Resource URL:** {$this->url}"; + } + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::warningColor() + ); + } } diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index 33a55c65a..9b3824624 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -8,14 +8,16 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; - +use App\Notifications\Dto\SlackMessage; class ContainerStopped extends Notification implements ShouldQueue { use Queueable; public $tries = 1; - public function __construct(public string $name, public Server $server, public ?string $url = null) {} + public function __construct(public string $name, public Server $server, public ?string $url = null) + { + } public function via(object $notifiable): array { @@ -44,7 +46,7 @@ class ContainerStopped extends Notification implements ShouldQueue ); if ($this->url) { - $message->addField('Resource', '[Link]('.$this->url.')'); + $message->addField('Resource', '[Link](' . $this->url . ')'); } return $message; @@ -69,4 +71,20 @@ class ContainerStopped extends Notification implements ShouldQueue return $payload; } + + public function toSlack(): SlackMessage + { + $title = "Resource stopped"; + $description = "A resource ({$this->name}) has been stopped unexpectedly on {$this->server->name}"; + + if ($this->url) { + $description .= "\n**Resource URL:** {$this->url}"; + } + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index 8e2733339..137a6d730 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -8,7 +8,7 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; - +use App\Notifications\Dto\SlackMessage; class BackupFailed extends Notification implements ShouldQueue { use Queueable; @@ -69,4 +69,19 @@ class BackupFailed extends Notification implements ShouldQueue 'message' => $message, ]; } + + public function toSlack(): SlackMessage + { + $title = "Database backup failed"; + $description = "Database backup for {$this->name} (db:{$this->database_name}) has FAILED."; + + $description .= "\n\n**Frequency:** {$this->frequency}"; + $description .= "\n\n**Error Output:**\n{$this->output}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index 5128c8ed6..76ddc9221 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -8,7 +8,7 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; - +use App\Notifications\Dto\SlackMessage; class BackupSuccess extends Notification implements ShouldQueue { use Queueable; @@ -66,4 +66,18 @@ class BackupSuccess extends Notification implements ShouldQueue 'message' => $message, ]; } + + public function toSlack(): SlackMessage + { + $title = "Database backup successful"; + $description = "Database backup for {$this->name} (db:{$this->database_name}) was successful."; + + $description .= "\n\n**Frequency:** {$this->frequency}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::successColor() + ); + } } diff --git a/app/Notifications/Dto/SlackMessage.php b/app/Notifications/Dto/SlackMessage.php new file mode 100644 index 000000000..efd0cf5e6 --- /dev/null +++ b/app/Notifications/Dto/SlackMessage.php @@ -0,0 +1,28 @@ + $this->message, ]; } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Coolify: General Notification', + description: $this->message, + color: SlackMessage::infoColor(), + ); + } } diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php index c3501a8eb..99d83fdf6 100644 --- a/app/Notifications/ScheduledTask/TaskFailed.php +++ b/app/Notifications/ScheduledTask/TaskFailed.php @@ -8,7 +8,7 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; - +use App\Notifications\Dto\SlackMessage; class TaskFailed extends Notification implements ShouldQueue { use Queueable; @@ -55,7 +55,7 @@ class TaskFailed extends Notification implements ShouldQueue ); if ($this->url) { - $message->addField('Scheduled task', '[Link]('.$this->url.')'); + $message->addField('Scheduled task', '[Link](' . $this->url . ')'); } return $message; @@ -75,4 +75,24 @@ class TaskFailed extends Notification implements ShouldQueue 'message' => $message, ]; } + + public function toSlack(): SlackMessage + { + $title = "Scheduled task failed"; + $description = "Scheduled task ({$this->task->name}) failed."; + + if ($this->output) { + $description .= "\n\n**Error Output:**\n{$this->output}"; + } + + if ($this->url) { + $description .= "\n\n**Task URL:** {$this->url}"; + } + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php index 7ea1b84c2..eb4498835 100644 --- a/app/Notifications/Server/DockerCleanup.php +++ b/app/Notifications/Server/DockerCleanup.php @@ -6,6 +6,7 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\TelegramChannel; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Notification; @@ -16,7 +17,9 @@ class DockerCleanup extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server, public string $message) {} + public function __construct(public Server $server, public string $message) + { + } public function via(object $notifiable): array { @@ -24,7 +27,7 @@ class DockerCleanup extends Notification implements ShouldQueue // $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - + $isSlackEnabled = data_get($notifiable, 'slack_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; } @@ -34,6 +37,9 @@ class DockerCleanup extends Notification implements ShouldQueue if ($isTelegramEnabled) { $channels[] = TelegramChannel::class; } + if ($isSlackEnabled) { + $channels[] = SlackChannel::class; + } return $channels; } @@ -65,4 +71,13 @@ class DockerCleanup extends Notification implements ShouldQueue 'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}", ]; } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Server cleanup job done', + description: "Server '{$this->server->name}' cleanup job done!\n\n{$this->message}", + color: SlackMessage::successColor() + ); + } } diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php index a26c803ee..969f60d79 100644 --- a/app/Notifications/Server/ForceDisabled.php +++ b/app/Notifications/Server/ForceDisabled.php @@ -6,7 +6,9 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\SlackChannel; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -18,7 +20,9 @@ class ForceDisabled extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + } public function via(object $notifiable): array { @@ -26,7 +30,7 @@ class ForceDisabled extends Notification implements ShouldQueue $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - + $isSlackEnabled = data_get($notifiable, 'slack_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; } @@ -36,6 +40,9 @@ class ForceDisabled extends Notification implements ShouldQueue if ($isTelegramEnabled) { $channels[] = TelegramChannel::class; } + if ($isSlackEnabled) { + $channels[] = SlackChannel::class; + } return $channels; } @@ -70,4 +77,19 @@ class ForceDisabled extends Notification implements ShouldQueue 'message' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).", ]; } + + + public function toSlack(): SlackMessage + { + $title = "Server disabled"; + $description = "Server ({$this->server->name}) disabled because it is not paid!\n"; + $description .= "All automations and integrations are stopped.\n\n"; + $description .= "Please update your subscription to enable the server again: https://app.coolify.io/subscriptions"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php index 65b65a10c..e24136c81 100644 --- a/app/Notifications/Server/ForceEnabled.php +++ b/app/Notifications/Server/ForceEnabled.php @@ -6,7 +6,9 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\SlackChannel; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -18,7 +20,9 @@ class ForceEnabled extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + } public function via(object $notifiable): array { @@ -26,7 +30,7 @@ class ForceEnabled extends Notification implements ShouldQueue $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - + $isSlackEnabled = data_get($notifiable, 'slack_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; } @@ -36,6 +40,9 @@ class ForceEnabled extends Notification implements ShouldQueue if ($isTelegramEnabled) { $channels[] = TelegramChannel::class; } + if ($isSlackEnabled) { + $channels[] = SlackChannel::class; + } return $channels; } @@ -66,4 +73,15 @@ class ForceEnabled extends Notification implements ShouldQueue 'message' => "Coolify: Server ({$this->server->name}) enabled again!", ]; } + + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Server enabled', + description: "Server '{$this->server->name}' enabled again!", + color: SlackMessage::successColor() + ); + } + } diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index e373abc03..a780d9d15 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -4,6 +4,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -15,7 +16,9 @@ class HighDiskUsage extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold) {} + public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold) + { + } public function via(object $notifiable): array { @@ -47,7 +50,7 @@ class HighDiskUsage extends Notification implements ShouldQueue $message->addField('Disk usage', "{$this->disk_usage}%", true); $message->addField('Threshold', "{$this->server_disk_usage_notification_threshold}%", true); $message->addField('What to do?', '[Link](https://coolify.io/docs/knowledge-base/server/automated-cleanup)', true); - $message->addField('Change Settings', '[Threshold]('.base_url().'/server/'.$this->server->uuid.'#advanced) | [Notification]('.base_url().'/notifications/discord)'); + $message->addField('Change Settings', '[Threshold](' . base_url() . '/server/' . $this->server->uuid . '#advanced) | [Notification](' . base_url() . '/notifications/discord)'); return $message; } @@ -58,4 +61,22 @@ class HighDiskUsage extends Notification implements ShouldQueue 'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->server_disk_usage_notification_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.", ]; } + + public function toSlack(): SlackMessage + { + $description = "Server '{$this->server->name}' high disk usage detected!\n"; + $description .= "Disk usage: {$this->disk_usage}%\n"; + $description .= "Threshold: {$this->server_disk_usage_notification_threshold}%\n\n"; + $description .= "Please cleanup your disk to prevent data-loss.\n"; + $description .= "Tips for cleanup: https://coolify.io/docs/knowledge-base/server/automated-cleanup\n"; + $description .= "Change settings:\n"; + $description .= "- Threshold: " . base_url() . "/server/" . $this->server->uuid . "#advanced\n"; + $description .= "- Notifications: " . base_url() . "/notifications/discord"; + + return new SlackMessage( + title: 'High disk usage detected', + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Server/Reachable.php b/app/Notifications/Server/Reachable.php index 9b54501d9..6ca4170f5 100644 --- a/app/Notifications/Server/Reachable.php +++ b/app/Notifications/Server/Reachable.php @@ -6,7 +6,9 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\SlackChannel; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -23,7 +25,7 @@ class Reachable extends Notification implements ShouldQueue public function __construct(public Server $server) { $this->isRateLimited = isEmailRateLimited( - limiterKey: 'server-reachable:'.$this->server->id, + limiterKey: 'server-reachable:' . $this->server->id, ); } @@ -37,7 +39,7 @@ class Reachable extends Notification implements ShouldQueue $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - + $isSlackEnabled = data_get($notifiable, 'slack_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; } @@ -47,6 +49,9 @@ class Reachable extends Notification implements ShouldQueue if ($isTelegramEnabled) { $channels[] = TelegramChannel::class; } + if ($isSlackEnabled) { + $channels[] = SlackChannel::class; + } return $channels; } @@ -77,4 +82,16 @@ class Reachable extends Notification implements ShouldQueue 'message' => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!", ]; } + + + + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: "Server revived", + description: "Server '{$this->server->name}' revived.\nAll automations & integrations are turned on again!", + color: SlackMessage::successColor() + ); + } } diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index 5bc568e82..cdb18b1bd 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -6,7 +6,9 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\SlackChannel; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -23,7 +25,7 @@ class Unreachable extends Notification implements ShouldQueue public function __construct(public Server $server) { $this->isRateLimited = isEmailRateLimited( - limiterKey: 'server-unreachable:'.$this->server->id, + limiterKey: 'server-unreachable:' . $this->server->id, ); } @@ -37,6 +39,7 @@ class Unreachable extends Notification implements ShouldQueue $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isSlackEnabled = data_get($notifiable, 'slack_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; @@ -47,6 +50,9 @@ class Unreachable extends Notification implements ShouldQueue if ($isTelegramEnabled) { $channels[] = TelegramChannel::class; } + if ($isSlackEnabled) { + $channels[] = SlackChannel::class; + } return $channels; } @@ -81,4 +87,18 @@ class Unreachable extends Notification implements ShouldQueue 'message' => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.", ]; } + + + public function toSlack(): SlackMessage + { + $description = "Your server '{$this->server->name}' is unreachable.\n"; + $description .= "All automations & integrations are turned off!\n\n"; + $description .= "*IMPORTANT:* We automatically try to revive your server and turn on all automations & integrations."; + + return new SlackMessage( + title: 'Server unreachable', + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index a43b1e153..6aae641c4 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -3,6 +3,7 @@ namespace App\Notifications; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -15,7 +16,9 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public ?string $emails = null) {} + public function __construct(public ?string $emails = null) + { + } public function via(object $notifiable): array { @@ -47,7 +50,7 @@ class Test extends Notification implements ShouldQueue color: DiscordMessage::successColor(), ); - $message->addField(name: 'Dashboard', value: '[Link]('.base_url().')', inline: true); + $message->addField(name: 'Dashboard', value: '[Link](' . base_url() . ')', inline: true); return $message; } @@ -64,4 +67,12 @@ class Test extends Notification implements ShouldQueue ], ]; } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Test Slack Notification', + description: 'This is a test Slack notification from Coolify.' + ); + } } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 2f0a3ac2a..e8bfbbd64 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -26,6 +26,7 @@ use App\Models\Team; use App\Models\User; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; +use App\Notifications\Channels\SlackChannel; use App\Notifications\Channels\TelegramChannel; use App\Notifications\Internal\GeneralNotification; use Carbon\CarbonImmutable; @@ -66,27 +67,27 @@ function base_configuration_dir(): string } function application_configuration_dir(): string { - return base_configuration_dir().'/applications'; + return base_configuration_dir() . '/applications'; } function service_configuration_dir(): string { - return base_configuration_dir().'/services'; + return base_configuration_dir() . '/services'; } function database_configuration_dir(): string { - return base_configuration_dir().'/databases'; + return base_configuration_dir() . '/databases'; } function database_proxy_dir($uuid): string { - return base_configuration_dir()."/databases/$uuid/proxy"; + return base_configuration_dir() . "/databases/$uuid/proxy"; } function backup_dir(): string { - return base_configuration_dir().'/backups'; + return base_configuration_dir() . '/backups'; } function metrics_dir(): string { - return base_configuration_dir().'/metrics'; + return base_configuration_dir() . '/metrics'; } function generate_readme_file(string $name, string $updated_at): string @@ -114,15 +115,15 @@ function showBoarding(): bool } function refreshSession(?Team $team = null): void { - if (! $team) { + if (!$team) { if (Auth::user()->currentTeam()) { $team = Team::find(Auth::user()->currentTeam()->id); } else { $team = User::find(Auth::id())->teams->first(); } } - Cache::forget('team:'.Auth::id()); - Cache::remember('team:'.Auth::id(), 3600, function () use ($team) { + Cache::forget('team:' . Auth::id()); + Cache::remember('team:' . Auth::id(), 3600, function () use ($team) { return $team; }); session(['currentTeam' => $team]); @@ -154,7 +155,7 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n $message = null; } if ($customErrorMessage) { - $message = $customErrorMessage.' '.$message; + $message = $customErrorMessage . ' ' . $message; } if (isset($livewire)) { @@ -227,7 +228,7 @@ function generateSSHKey(string $type = 'rsa') function formatPrivateKey(string $privateKey) { $privateKey = trim($privateKey); - if (! str_ends_with($privateKey, "\n")) { + if (!str_ends_with($privateKey, "\n")) { $privateKey .= "\n"; } @@ -249,7 +250,7 @@ function is_transactional_emails_active(): bool function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string { - if (! $settings) { + if (!$settings) { $settings = instanceSettings(); } config()->set('mail.from.address', data_get($settings, 'smtp_from_address')); @@ -349,7 +350,7 @@ function isSubscribed() function isProduction(): bool { - return ! isDev(); + return !isDev(); } function isDev(): bool { @@ -358,7 +359,7 @@ function isDev(): bool function isCloud(): bool { - return ! config('coolify.self_hosted'); + return !config('coolify.self_hosted'); } function translate_cron_expression($expression_to_validate): string @@ -397,14 +398,14 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null { $settings = instanceSettings(); $type = set_transanctional_email_settings($settings); - if (! $type) { + if (!$type) { throw new Exception('No email settings found.'); } if ($cc) { Mail::send( [], [], - fn (Message $message) => $message + fn(Message $message) => $message ->to($email) ->replyTo($email) ->cc($cc) @@ -415,7 +416,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null Mail::send( [], [], - fn (Message $message) => $message + fn(Message $message) => $message ->to($email) ->subject($mail->subject) ->html((string) $mail->render()) @@ -440,11 +441,13 @@ function setNotificationChannels($notifiable, $event) { $channels = []; $isEmailEnabled = isEmailEnabled($notifiable); + $isSlackEnabled = data_get($notifiable, 'slack_enabled'); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); $isSubscribedToEmailEvent = data_get($notifiable, "smtp_notifications_$event"); $isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event"); $isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event"); + $isSubscribedToSlackEvent = data_get($notifiable, "slack_notifications_$event"); if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { $channels[] = DiscordChannel::class; @@ -455,6 +458,9 @@ function setNotificationChannels($notifiable, $event) if ($isTelegramEnabled && $isSubscribedToTelegramEvent) { $channels[] = TelegramChannel::class; } + if ($isSlackEnabled && $isSubscribedToSlackEvent) { + $channels[] = SlackChannel::class; + } return $channels; } @@ -559,7 +565,7 @@ function getResourceByUuid(string $uuid, ?int $teamId = null) return null; } $resource = queryResourcesByUuid($uuid); - if (! is_null($resource) && $resource->environment->project->team_id === $teamId) { + if (!is_null($resource) && $resource->environment->project->team_id === $teamId) { return $resource; } @@ -651,29 +657,29 @@ function queryResourcesByUuid(string $uuid) function generateTagDeployWebhook($tag_name) { $baseUrl = base_url(); - $api = Url::fromString($baseUrl).'/api/v1'; + $api = Url::fromString($baseUrl) . '/api/v1'; $endpoint = "/deploy?tag=$tag_name"; - return $api.$endpoint; + return $api . $endpoint; } function generateDeployWebhook($resource) { $baseUrl = base_url(); - $api = Url::fromString($baseUrl).'/api/v1'; + $api = Url::fromString($baseUrl) . '/api/v1'; $endpoint = '/deploy'; $uuid = data_get($resource, 'uuid'); - return $api.$endpoint."?uuid=$uuid&force=false"; + return $api . $endpoint . "?uuid=$uuid&force=false"; } function generateGitManualWebhook($resource, $type) { - if ($resource->source_id !== 0 && ! is_null($resource->source_id)) { + if ($resource->source_id !== 0 && !is_null($resource->source_id)) { return null; } if ($resource->getMorphClass() === \App\Models\Application::class) { $baseUrl = base_url(); - return Url::fromString($baseUrl)."/webhooks/source/$type/events/manual"; + return Url::fromString($baseUrl) . "/webhooks/source/$type/events/manual"; } return null; @@ -700,7 +706,7 @@ function getTopLevelNetworks(Service|Application $resource) $hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false; // Only add 'networks' key if 'network_mode' is not 'host' - if (! $hasHostNetworkMode) { + if (!$hasHostNetworkMode) { // Collect/create/update networks if ($serviceNetworks->count() > 0) { foreach ($serviceNetworks as $networkName => $networkDetails) { @@ -714,7 +720,7 @@ function getTopLevelNetworks(Service|Application $resource) $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); - if (! $networkExists) { + if (!$networkExists) { if (is_string($networkDetails) || is_int($networkDetails)) { $topLevelNetworks->put($networkDetails, null); } @@ -725,7 +731,7 @@ function getTopLevelNetworks(Service|Application $resource) $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { return $value == $definedNetwork; }); - if (! $definedNetworkExists) { + if (!$definedNetworkExists) { foreach ($definedNetwork as $network) { $topLevelNetworks->put($network, [ 'name' => $network, @@ -766,7 +772,7 @@ function getTopLevelNetworks(Service|Application $resource) $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); - if (! $networkExists) { + if (!$networkExists) { if (is_string($networkDetails) || is_int($networkDetails)) { $topLevelNetworks->put($networkDetails, null); } @@ -776,7 +782,7 @@ function getTopLevelNetworks(Service|Application $resource) $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { return $value == $definedNetwork; }); - if (! $definedNetworkExists) { + if (!$definedNetworkExists) { foreach ($definedNetwork as $network) { $topLevelNetworks->put($network, [ 'name' => $network, @@ -912,7 +918,7 @@ function generateEnvValue(string $command, Service|Application|null $service = n case 'PASSWORD_64': $generatedValue = Str::password(length: 64, symbols: false); break; - // This is not base64, it's just a random string + // This is not base64, it's just a random string case 'BASE64_64': $generatedValue = Str::random(64); break; @@ -923,7 +929,7 @@ function generateEnvValue(string $command, Service|Application|null $service = n case 'BASE64_32': $generatedValue = Str::random(32); break; - // This is base64, + // This is base64, case 'REALBASE64_64': $generatedValue = base64_encode(Str::random(64)); break; @@ -1014,7 +1020,7 @@ function validate_dns_entry(string $fqdn, Server $server) } $settings = instanceSettings(); $is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled'); - if (! $is_dns_validation_enabled) { + if (!$is_dns_validation_enabled) { return true; } $dns_servers = data_get($settings, 'custom_dns_servers'); @@ -1032,7 +1038,7 @@ function validate_dns_entry(string $fqdn, Server $server) $query = new DNSQuery($dns_server); $results = $query->query($host, $type); if ($results === false || $query->hasError()) { - ray('Error: '.$query->getLasterror()); + ray('Error: ' . $query->getLasterror()); } else { foreach ($results as $result) { if ($result->getType() == $type) { @@ -1042,7 +1048,7 @@ function validate_dns_entry(string $fqdn, Server $server) break; } if ($result->getData() === $ip) { - ray($host.' has IP address '.$result->getData()); + ray($host . ' has IP address ' . $result->getData()); ray($result->getString()); $found_matching_ip = true; break; @@ -1090,15 +1096,15 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = $applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); if ($uuid) { - $applications = $applications->filter(fn ($app) => $app->uuid !== $uuid); - $serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid); + $applications = $applications->filter(fn($app) => $app->uuid !== $uuid); + $serviceApplications = $serviceApplications->filter(fn($app) => $app->uuid !== $uuid); } $domainFound = false; foreach ($applications as $app) { if (is_null($app->fqdn)) { continue; } - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1117,7 +1123,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = if (str($app->fqdn)->isEmpty()) { continue; } - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1167,7 +1173,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null }); $apps = Application::all(); foreach ($apps as $app) { - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1186,7 +1192,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null } $apps = ServiceApplication::all(); foreach ($apps as $app) { - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1222,7 +1228,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array { $commands = $commands->map(function ($line) { if ( - ! str(trim($line))->startsWith([ + !str(trim($line))->startsWith([ 'cd', 'command', 'echo', @@ -1243,7 +1249,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array $commands = $commands->map(function ($line) use ($server) { if (Str::startsWith($line, 'sudo mkdir -p')) { - return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p'); + return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p'); } return $line; @@ -1271,11 +1277,11 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array } function parseLineForSudo(string $command, Server $server): string { - if (! str($command)->startSwith('cd') && ! str($command)->startSwith('command')) { + if (!str($command)->startSwith('cd') && !str($command)->startSwith('command')) { $command = "sudo $command"; } if (Str::startsWith($command, 'sudo mkdir -p')) { - $command = "$command && sudo chown -R $server->user:$server->user ".Str::after($command, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($command, 'sudo mkdir -p'); + $command = "$command && sudo chown -R $server->user:$server->user " . Str::after($command, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($command, 'sudo mkdir -p'); } if (str($command)->contains('$(') || str($command)->contains('`')) { $command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value(); @@ -1397,7 +1403,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull $isDirectory = data_get($foundConfig, 'is_directory'); } else { $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); - if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { + if ((is_null($isDirectory) || !$isDirectory) && is_null($content)) { // if isDirectory is not set (or false) & content is also not set, we assume it is a directory ray('setting isDirectory to true'); $isDirectory = true; @@ -1412,9 +1418,9 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull return $volume; } if (get_class($resource) === \App\Models\Application::class) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; } else { - $dir = base_configuration_dir().'/services/'.$resource->service->uuid; + $dir = base_configuration_dir() . '/services/' . $resource->service->uuid; } if ($source->startsWith('.')) { @@ -1424,9 +1430,9 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull $source = $source->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $source = $source."-pr-$pull_request_id"; + $source = $source . "-pr-$pull_request_id"; } - if (! $resource?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git) { + if (!$resource?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git) { LocalFileVolume::updateOrCreate( [ 'mount_path' => $target, @@ -1555,7 +1561,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($serviceLabels->count() > 0) { $removedLabels = collect([]); $serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) { - if (! str($serviceLabel)->contains('=')) { + if (!str($serviceLabel)->contains('=')) { $removedLabels->put($serviceLabelName, $serviceLabel); return false; @@ -1637,7 +1643,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); - if (! $networkExists) { + if (!$networkExists) { if (is_string($networkDetails) || is_int($networkDetails)) { $topLevelNetworks->put($networkDetails, null); } @@ -1663,12 +1669,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $savedService->ports = $collectedPorts->implode(','); $savedService->save(); - if (! $hasHostNetworkMode) { + if (!$hasHostNetworkMode) { // Add Coolify specific networks $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { return $value == $definedNetwork; }); - if (! $definedNetworkExists) { + if (!$definedNetworkExists) { foreach ($definedNetwork as $network) { $topLevelNetworks->put($network, [ 'name' => $network, @@ -1880,9 +1886,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $fqdn = "$fqdn$path"; } - if (! $isDatabase) { + if (!$isDatabase) { if ($savedService->fqdn) { - data_set($savedService, 'fqdn', $savedService->fqdn.','.$fqdn); + data_set($savedService, 'fqdn', $savedService->fqdn . ',' . $fqdn); } else { data_set($savedService, 'fqdn', $fqdn); } @@ -1897,7 +1903,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal ]); } // Caddy needs exact port in some cases. - if ($predefinedPort && ! $key->endsWith("_{$predefinedPort}")) { + if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) { $fqdns_exploded = str($savedService->fqdn)->explode(','); if ($fqdns_exploded->count() > 1) { continue; @@ -1937,12 +1943,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'service_id' => $resource->id, ])->first(); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); - if (! is_null($command)) { + if (!is_null($command)) { if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if (Str::lower($forService) === $serviceName) { $fqdn = generateFqdn($resource->server, $containerName); } else { - $fqdn = generateFqdn($resource->server, Str::lower($forService).'-'.$resource->uuid); + $fqdn = generateFqdn($resource->server, Str::lower($forService) . '-' . $resource->uuid); } if ($port) { $fqdn = "$fqdn:$port"; @@ -1972,13 +1978,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'is_preview' => false, ]); } - if (! $isDatabase) { - if ($command->value() === 'FQDN' && is_null($savedService->fqdn) && ! $foundEnv) { + if (!$isDatabase) { + if ($command->value() === 'FQDN' && is_null($savedService->fqdn) && !$foundEnv) { $savedService->fqdn = $fqdn; $savedService->save(); } // Caddy needs exact port in some cases. - if ($predefinedPort && ! $key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') { + if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') { $fqdns_exploded = str($savedService->fqdn)->explode(','); if ($fqdns_exploded->count() > 1) { continue; @@ -2000,7 +2006,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } else { $generatedValue = generateEnvValue($command, $resource); - if (! $foundEnv) { + if (!$foundEnv) { EnvironmentVariable::create([ 'key' => $key, 'value' => $generatedValue, @@ -2055,7 +2061,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } $defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); $serviceLabels = $serviceLabels->merge($defaultLabels); - if (! $isDatabase && $fqdns->count() > 0) { + if (!$isDatabase && $fqdns->count() > 0) { if ($fqdns) { $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels; if ($shouldGenerateLabelsExactly) { @@ -2123,7 +2129,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } data_set($service, 'labels', $serviceLabels->toArray()); data_forget($service, 'is_database'); - if (! data_get($service, 'restart')) { + if (!data_get($service, 'restart')) { data_set($service, 'restart', RESTART_MODE); } if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { @@ -2162,21 +2168,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}"); // TODO: move this in a shared function - if (! $parsedServiceVariables->has('COOLIFY_APP_NAME')) { + if (!$parsedServiceVariables->has('COOLIFY_APP_NAME')) { $parsedServiceVariables->put('COOLIFY_APP_NAME', "\"{$resource->name}\""); } - if (! $parsedServiceVariables->has('COOLIFY_SERVER_IP')) { + if (!$parsedServiceVariables->has('COOLIFY_SERVER_IP')) { $parsedServiceVariables->put('COOLIFY_SERVER_IP', "\"{$resource->destination->server->ip}\""); } - if (! $parsedServiceVariables->has('COOLIFY_ENVIRONMENT_NAME')) { + if (!$parsedServiceVariables->has('COOLIFY_ENVIRONMENT_NAME')) { $parsedServiceVariables->put('COOLIFY_ENVIRONMENT_NAME', "\"{$resource->environment->name}\""); } - if (! $parsedServiceVariables->has('COOLIFY_PROJECT_NAME')) { + if (!$parsedServiceVariables->has('COOLIFY_PROJECT_NAME')) { $parsedServiceVariables->put('COOLIFY_PROJECT_NAME', "\"{$resource->project()->name}\""); } $parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) { - if (! str($value)->startsWith('$')) { + if (!str($value)->startsWith('$')) { $found_env = $envs_from_coolify->where('key', $key)->first(); if ($found_env) { return $found_env->value; @@ -2260,7 +2266,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($serviceLabels->count() > 0) { $removedLabels = collect([]); $serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) { - if (! str($serviceLabel)->contains('=')) { + if (!str($serviceLabel)->contains('=')) { $removedLabels->put($serviceLabelName, $serviceLabel); return false; @@ -2280,11 +2286,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) { if (is_string($volume)) { $volume = str($volume); - if ($volume->contains(':') && ! $volume->startsWith('/')) { + if ($volume->contains(':') && !$volume->startsWith('/')) { $name = $volume->before(':'); $mount = $volume->after(':'); if ($name->startsWith('.') || $name->startsWith('~')) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; if ($name->startsWith('.')) { $name = $name->replaceFirst('.', $dir); } @@ -2292,12 +2298,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $name->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; } $volume = str("$name:$mount"); } else { if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; $volume = str("$name:$mount"); if ($topLevelVolumes->has($name)) { $v = $topLevelVolumes->get($name); @@ -2336,7 +2342,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; } $volume = str("$name:$mount"); } @@ -2347,7 +2353,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $read_only = data_get($volume, 'read_only'); if ($source && $target) { if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; if (str($source, '.')) { $source = str($source)->replaceFirst('.', $dir); } @@ -2355,23 +2361,23 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $source = str($source)->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $source = $source."-pr-$pull_request_id"; + $source = $source . "-pr-$pull_request_id"; } if ($read_only) { - data_set($volume, 'source', $source.':'.$target.':ro'); + data_set($volume, 'source', $source . ':' . $target . ':ro'); } else { - data_set($volume, 'source', $source.':'.$target); + data_set($volume, 'source', $source . ':' . $target); } } else { if ($pull_request_id !== 0) { - $source = $source."-pr-$pull_request_id"; + $source = $source . "-pr-$pull_request_id"; } if ($read_only) { - data_set($volume, 'source', $source.':'.$target.':ro'); + data_set($volume, 'source', $source . ':' . $target . ':ro'); } else { - data_set($volume, 'source', $source.':'.$target); + data_set($volume, 'source', $source . ':' . $target); } - if (! str($source)->startsWith('/')) { + if (!str($source)->startsWith('/')) { if ($topLevelVolumes->has($source)) { $v = $topLevelVolumes->get($source); if (data_get($v, 'driver_opts.type') === 'cifs') { @@ -2404,11 +2410,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) { if (is_string($volume)) { $volume = str($volume); - if ($volume->contains(':') && ! $volume->startsWith('/')) { + if ($volume->contains(':') && !$volume->startsWith('/')) { $name = $volume->before(':'); $mount = $volume->after(':'); if ($name->startsWith('.') || $name->startsWith('~')) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; if ($name->startsWith('.')) { $name = $name->replaceFirst('.', $dir); } @@ -2416,13 +2422,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $name->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; } $volume = str("$name:$mount"); } else { if ($pull_request_id !== 0) { $uuid = $resource->uuid; - $name = $uuid."-$name-pr-$pull_request_id"; + $name = $uuid . "-$name-pr-$pull_request_id"; $volume = str("$name:$mount"); if ($topLevelVolumes->has($name)) { $v = $topLevelVolumes->get($name); @@ -2441,7 +2447,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } else { $uuid = $resource->uuid; - $name = str($uuid."-$name"); + $name = str($uuid . "-$name"); $volume = str("$name:$mount"); if ($topLevelVolumes->has($name->value())) { $v = $topLevelVolumes->get($name->value()); @@ -2464,7 +2470,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; } $volume = str("$name:$mount"); } @@ -2476,7 +2482,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($source && $target) { $uuid = $resource->uuid; if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; if (str($source, '.')) { $source = str($source)->replaceFirst('.', $dir); } @@ -2484,22 +2490,22 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $source = str($source)->replaceFirst('~', $dir); } if ($read_only) { - data_set($volume, 'source', $source.':'.$target.':ro'); + data_set($volume, 'source', $source . ':' . $target . ':ro'); } else { - data_set($volume, 'source', $source.':'.$target); + data_set($volume, 'source', $source . ':' . $target); } } else { if ($pull_request_id === 0) { - $source = $uuid."-$source"; + $source = $uuid . "-$source"; } else { - $source = $uuid."-$source-pr-$pull_request_id"; + $source = $uuid . "-$source-pr-$pull_request_id"; } if ($read_only) { - data_set($volume, 'source', $source.':'.$target.':ro'); + data_set($volume, 'source', $source . ':' . $target . ':ro'); } else { - data_set($volume, 'source', $source.':'.$target); + data_set($volume, 'source', $source . ':' . $target); } - if (! str($source)->startsWith('/')) { + if (!str($source)->startsWith('/')) { if ($topLevelVolumes->has($source)) { $v = $topLevelVolumes->get($source); if (data_get($v, 'driver_opts.type') === 'cifs') { @@ -2532,7 +2538,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($pull_request_id !== 0 && count($serviceDependencies) > 0) { $serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) { - return $dependency."-pr-$pull_request_id"; + return $dependency . "-pr-$pull_request_id"; }); data_set($service, 'depends_on', $serviceDependencies->toArray()); } @@ -2554,7 +2560,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); - if (! $networkExists) { + if (!$networkExists) { if (is_string($networkDetails) || is_int($networkDetails)) { $topLevelNetworks->put($networkDetails, null); } @@ -2582,7 +2588,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { return $value == $definedNetwork; }); - if (! $definedNetworkExists) { + if (!$definedNetworkExists) { foreach ($definedNetwork as $network) { if ($pull_request_id !== 0) { $topLevelNetworks->put($network, [ @@ -2700,12 +2706,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'application_id' => $resource->id, ])->first(); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); - if (! is_null($command)) { + if (!is_null($command)) { if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if (Str::lower($forService) === $serviceName) { $fqdn = generateFqdn($server, $containerName); } else { - $fqdn = generateFqdn($server, Str::lower($forService).'-'.$resource->uuid); + $fqdn = generateFqdn($server, Str::lower($forService) . '-' . $resource->uuid); } if ($port) { $fqdn = "$fqdn:$port"; @@ -2726,7 +2732,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } else { $generatedValue = generateEnvValue($command); - if (! $foundEnv) { + if (!$foundEnv) { EnvironmentVariable::create([ 'key' => $key, 'value' => $generatedValue, @@ -2898,7 +2904,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } data_set($service, 'labels', $serviceLabels->toArray()); data_forget($service, 'is_database'); - if (! data_get($service, 'restart')) { + if (!data_get($service, 'restart')) { data_set($service, 'restart', RESTART_MODE); } data_set($service, 'container_name', $containerName); @@ -2909,7 +2915,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal }); if ($pull_request_id !== 0) { $services->each(function ($service, $serviceName) use ($pull_request_id, $services) { - $services[$serviceName."-pr-$pull_request_id"] = $service; + $services[$serviceName . "-pr-$pull_request_id"] = $service; data_forget($services, $serviceName); }); } @@ -2937,7 +2943,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $uuid = data_get($resource, 'uuid'); $compose = data_get($resource, 'docker_compose_raw'); - if (! $compose) { + if (!$compose) { return collect([]); } @@ -3330,29 +3336,29 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $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)) { + 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(); + $volume = $source->value() . ':' . $target->value(); } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { - $volume = $source->value().':'.$target->value(); + $volume = $source->value() . ':' . $target->value(); } else { if ((int) $resource->compose_parsing_version >= 4) { if ($isApplication) { - $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + $mainDirectory = str(base_configuration_dir() . '/applications/' . $uuid); } elseif ($isService) { - $mainDirectory = str(base_configuration_dir().'/services/'.$uuid); + $mainDirectory = str(base_configuration_dir() . '/services/' . $uuid); } } else { - $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + $mainDirectory = str(base_configuration_dir() . '/applications/' . $uuid); } $source = replaceLocalSource($source, $mainDirectory); if ($isApplication && $isPullRequest) { - $source = $source."-pr-$pullRequestId"; + $source = $source . "-pr-$pullRequestId"; } LocalFileVolume::updateOrCreate( [ @@ -3372,12 +3378,12 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int 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); + $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); + $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); + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/' . $uuid); } } $volume = "$source:$target"; @@ -3444,7 +3450,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $depends_on = $newDependsOn; } } - if (! $use_network_mode) { + if (!$use_network_mode) { if ($topLevel->get('networks')?->count() > 0) { foreach ($topLevel->get('networks') as $networkName => $network) { if ($networkName === 'default') { @@ -3457,7 +3463,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $networkExists = $networks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); - if (! $networkExists) { + if (!$networkExists) { $networks->put($networkName, null); } } @@ -3465,7 +3471,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { return $value == $baseNetwork; }); - if (! $baseNetworkExists) { + if (!$baseNetworkExists) { foreach ($baseNetwork as $network) { $topLevel->get('networks')->put($network, [ 'name' => $network, @@ -3497,7 +3503,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $networks_temp = collect(); - if (! $use_network_mode) { + if (!$use_network_mode) { foreach ($networks as $key => $network) { if (gettype($network) === 'string') { // networks: @@ -3528,7 +3534,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $normalEnvironments = $environment->diffKeys($allMagicEnvironments); $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { - return ! str($value)->startsWith('SERVICE_'); + return !str($value)->startsWith('SERVICE_'); }); foreach ($normalEnvironments as $key => $value) { @@ -3548,7 +3554,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int continue; } - if (! $value->startsWith('$')) { + if (!$value->startsWith('$')) { continue; } if ($key->value() === $parsedValue->value()) { @@ -3679,7 +3685,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); } // Add COOLIFY_FQDN & COOLIFY_URL to environment - if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + if (!$isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { $coolifyEnvironments->put('COOLIFY_URL', $fqdns->implode(',')); $urls = $fqdns->map(function ($fqdn) { @@ -3691,7 +3697,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int if ($environment->count() > 0) { $environment = $environment->filter(function ($value, $key) { - return ! str($key)->startsWith('SERVICE_FQDN_'); + 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()) { @@ -3718,7 +3724,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int }); } } - if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + if (!$isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { if ($isApplication) { $shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels; $uuid = $resource->uuid; @@ -3811,7 +3817,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int 'restart' => $restart->value(), 'labels' => $serviceLabels, ]); - if (! $use_network_mode) { + if (!$use_network_mode) { $payload['networks'] = $networks_temp; } if ($ports->count() > 0) { @@ -3871,7 +3877,7 @@ function isAssociativeArray($array) $array = $array->toArray(); } - if (! is_array($array)) { + if (!is_array($array)) { throw new \InvalidArgumentException('Input must be an array or a Collection.'); } @@ -3984,7 +3990,7 @@ function instanceSettings() function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id) { $server = Server::find($server_id)->where('team_id', $team_id)->first(); - if (! $server) { + if (!$server) { return; } $uuid = new Cuid2; @@ -4011,7 +4017,7 @@ function loadConfigFromGit(string $repository, string $branch, string $base_dire function loggy($message = null, array $context = []) { - if (! isDev()) { + if (!isDev()) { return; } if (function_exists('ray') && config('app.debug')) { @@ -4046,7 +4052,7 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla $limiterKey, $maxAttempts = 0, function () use (&$rateLimited, &$limiterKey, $callbackOnSuccess) { - isDev() && loggy('Rate limit not reached for '.$limiterKey); + isDev() && loggy('Rate limit not reached for ' . $limiterKey); $rateLimited = false; if ($callbackOnSuccess) { @@ -4055,8 +4061,8 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla }, $decaySeconds, ); - if (! $executed) { - isDev() && loggy('Rate limit reached for '.$limiterKey.'. Rate limiter will be disabled for '.$decaySeconds.' seconds.'); + if (!$executed) { + isDev() && loggy('Rate limit reached for ' . $limiterKey . '. Rate limiter will be disabled for ' . $decaySeconds . ' seconds.'); $rateLimited = true; } diff --git a/database/migrations/2024_11_12_213200_add_slack_notifications_to_teams_table.php b/database/migrations/2024_11_12_213200_add_slack_notifications_to_teams_table.php new file mode 100644 index 000000000..c3896a053 --- /dev/null +++ b/database/migrations/2024_11_12_213200_add_slack_notifications_to_teams_table.php @@ -0,0 +1,37 @@ +boolean('slack_enabled')->default(false); + $table->string('slack_webhook_url')->nullable(); + $table->boolean('slack_notifications_test')->default(false); + $table->boolean('slack_notifications_deployments')->default(false); + $table->boolean('slack_notifications_status_changes')->default(false); + $table->boolean('slack_notifications_database_backups')->default(false); + $table->boolean('slack_notifications_scheduled_tasks')->default(false); + $table->boolean('slack_notifications_server_disk_usage')->default(false); + }); + } + + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn([ + 'slack_enabled', + 'slack_webhook_url', + 'slack_notifications_test', + 'slack_notifications_deployments', + 'slack_notifications_status_changes', + 'slack_notifications_database_backups', + 'slack_notifications_scheduled_tasks', + 'slack_notifications_server_disk_usage', + ]); + }); + } +}; \ No newline at end of file diff --git a/resources/views/components/notification/navbar.blade.php b/resources/views/components/notification/navbar.blade.php index 0fbbc69a2..c4dbd25af 100644 --- a/resources/views/components/notification/navbar.blade.php +++ b/resources/views/components/notification/navbar.blade.php @@ -15,6 +15,10 @@ href="{{ route('notifications.discord') }}"> + + +
- + \ No newline at end of file diff --git a/resources/views/livewire/notifications/slack.blade.php b/resources/views/livewire/notifications/slack.blade.php new file mode 100644 index 000000000..b3685173c --- /dev/null +++ b/resources/views/livewire/notifications/slack.blade.php @@ -0,0 +1,43 @@ +
+ + Notifications | Coolify + + +
+
+

Slack

+ + Save + + @if ($slackEnabled) + + Send Test Notifications + + @endif +
+
+ +
+ + + @if ($slackEnabled) +

Subscribe to events

+
+ @if (isDev()) + + @endif + + + + + +
+ @endif +
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index afe392052..7aa23ac95 100644 --- a/routes/web.php +++ b/routes/web.php @@ -133,6 +133,7 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/email', NotificationEmail::class)->name('notifications.email'); Route::get('/telegram', NotificationTelegram::class)->name('notifications.telegram'); Route::get('/discord', NotificationDiscord::class)->name('notifications.discord'); + Route::get('/slack', App\Livewire\Notifications\Slack::class)->name('notifications.slack'); }); Route::prefix('storages')->group(function () { From 80ed561374438076f50684453d65531bd63d09b2 Mon Sep 17 00:00:00 2001 From: Marvin von Rappard Date: Tue, 12 Nov 2024 22:45:07 +0100 Subject: [PATCH 04/27] fix: add warning color --- app/Notifications/Dto/SlackMessage.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Notifications/Dto/SlackMessage.php b/app/Notifications/Dto/SlackMessage.php index efd0cf5e6..86532c65b 100644 --- a/app/Notifications/Dto/SlackMessage.php +++ b/app/Notifications/Dto/SlackMessage.php @@ -25,4 +25,9 @@ class SlackMessage { return '#00ff00'; } + + public static function warningColor(): string + { + return '#ffa500'; + } } \ No newline at end of file From 40fb73ee8e06300b304dc7736111d8fd8fc64fc5 Mon Sep 17 00:00:00 2001 From: Marvin von Rappard Date: Wed, 20 Nov 2024 13:01:56 +0100 Subject: [PATCH 05/27] fix: import NotificationSlack correctly --- routes/web.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/routes/web.php b/routes/web.php index 2fc5046e5..d8ba925f2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,6 +13,7 @@ use App\Livewire\ForcePasswordReset; use App\Livewire\Notifications\Discord as NotificationDiscord; use App\Livewire\Notifications\Email as NotificationEmail; use App\Livewire\Notifications\Telegram as NotificationTelegram; +use App\Livewire\Notifications\Slack as NotificationSlack; use App\Livewire\Profile\Index as ProfileIndex; use App\Livewire\Project\Application\Configuration as ApplicationConfiguration; use App\Livewire\Project\Application\Deployment\Index as DeploymentIndex; @@ -132,7 +133,7 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/email', NotificationEmail::class)->name('notifications.email'); Route::get('/telegram', NotificationTelegram::class)->name('notifications.telegram'); Route::get('/discord', NotificationDiscord::class)->name('notifications.discord'); - Route::get('/slack', App\Livewire\Notifications\Slack::class)->name('notifications.slack'); + Route::get('/slack', NotificationSlack::class)->name('notifications.slack'); }); Route::prefix('storages')->group(function () { @@ -286,7 +287,7 @@ Route::middleware(['auth'])->group(function () { 'privateKey' => $privateKeyLocation, 'root' => '/', ]); - if (! $disk->exists($filename)) { + if (!$disk->exists($filename)) { return response()->json(['message' => 'Backup not found.'], 404); } @@ -298,7 +299,7 @@ Route::middleware(['auth'])->group(function () { if ($stream === false || is_null($stream)) { abort(500, 'Failed to open stream for the requested file.'); } - while (! feof($stream)) { + while (!feof($stream)) { echo fread($stream, 2048); flush(); } @@ -306,7 +307,7 @@ Route::middleware(['auth'])->group(function () { fclose($stream); }, 200, [ 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'attachment; filename="'.basename($filename).'"', + 'Content-Disposition' => 'attachment; filename="' . basename($filename) . '"', ]); } catch (\Throwable $e) { return response()->json(['message' => $e->getMessage()], 500); From ba808b14a465bd05b409643e9b0fba15f6f7b9e7 Mon Sep 17 00:00:00 2001 From: mohanlokesh <120879877+mohanlokesh@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:25:41 +0530 Subject: [PATCH 06/27] fix: display actual values for disk space checks in installer script --- scripts/install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 1a039f64f..3f289438f 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -30,7 +30,7 @@ WARNING_SPACE=false if [ "$TOTAL_SPACE" -lt "$REQUIRED_TOTAL_SPACE" ]; then WARNING_SPACE=true - cat << 'EOF' + cat << EOF WARNING: Insufficient total disk space! Total disk space: ${TOTAL_SPACE}GB @@ -41,7 +41,7 @@ EOF fi if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_AVAILABLE_SPACE" ]; then - cat << 'EOF' + cat << EOF WARNING: Insufficient available disk space! Available disk space: ${AVAILABLE_SPACE}GB @@ -49,7 +49,7 @@ Required available space: ${REQUIRED_AVAILABLE_SPACE}GB ================== EOF - WARNING_SPACE=true +WARNING_SPACE=true fi if [ "$WARNING_SPACE" = true ]; then From eeb8d97cfd185aa783baa1c07237ff178d7e1f7b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 11:22:54 +0100 Subject: [PATCH 07/27] fix: old git versions does not have --cone implemented properly --- app/Models/Application.php | 46 +++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index a68c1d54a..d1efd3f33 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1321,17 +1321,43 @@ class Application extends BaseModel if (! $gitRemoteStatus['is_accessible']) { throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}"); } + $getGitVersion = instant_remote_process(['git --version'], $this->destination->server, false); + $gitVersion = str($getGitVersion)->explode(' ')->last(); - $commands = collect([ - "rm -rf /tmp/{$uuid}", - "mkdir -p /tmp/{$uuid}", - "cd /tmp/{$uuid}", - $cloneCommand, - 'git sparse-checkout init --cone', - "git sparse-checkout set {$fileList->implode(' ')}", - 'git read-tree -mu HEAD', - "cat .$workdir$composeFile", - ]); + if (version_compare($gitVersion, '2.35.1', '<')) { + $fileList = $fileList->map(function ($file) { + $parts = explode('/', trim($file, '.')); + $paths = collect(); + $currentPath = ''; + foreach ($parts as $part) { + $currentPath .= ($currentPath ? '/' : '').$part; + $paths->push($currentPath); + } + + return $paths; + })->flatten()->unique()->values(); + $commands = collect([ + "rm -rf /tmp/{$uuid}", + "mkdir -p /tmp/{$uuid}", + "cd /tmp/{$uuid}", + $cloneCommand, + 'git sparse-checkout init --cone', + "git sparse-checkout set {$fileList->implode(' ')}", + 'git read-tree -mu HEAD', + "cat .$workdir$composeFile", + ]); + } else { + $commands = collect([ + "rm -rf /tmp/{$uuid}", + "mkdir -p /tmp/{$uuid}", + "cd /tmp/{$uuid}", + $cloneCommand, + 'git sparse-checkout init --cone', + "git sparse-checkout set {$fileList->implode(' ')}", + 'git read-tree -mu HEAD', + "cat .$workdir$composeFile", + ]); + } try { $composeFileContent = instant_remote_process($commands, $this->destination->server); } catch (\Exception $e) { From 0c10b1e5b6f1bba02ca04134aa03f5081765cf88 Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:20:30 +0100 Subject: [PATCH 08/27] Added Heimdall SVG --- public/svgs/heimdall.svg | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 public/svgs/heimdall.svg diff --git a/public/svgs/heimdall.svg b/public/svgs/heimdall.svg new file mode 100644 index 000000000..6ecfa8457 --- /dev/null +++ b/public/svgs/heimdall.svg @@ -0,0 +1,11 @@ + + + + background + + + + Layer 1 + + + \ No newline at end of file From 0886046b304ca77a7a05011df6e92bfe728335c9 Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:21:40 +0100 Subject: [PATCH 09/27] Update heimdall.yaml with SVG Logo --- templates/compose/heimdall.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/heimdall.yaml b/templates/compose/heimdall.yaml index 7ae07b296..851f981b5 100644 --- a/templates/compose/heimdall.yaml +++ b/templates/compose/heimdall.yaml @@ -1,6 +1,7 @@ # documentation: https://github.com/linuxserver/Heimdall # slogan: Heimdall is a dashboard for managing and organizing your server applications. # tags: dashboard, server, applications, interface +# logo: svgs/heimdall.svg services: heimdall: From d69ea22c0258af3328b67ea868e9796e2497de3c Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:36:25 +0100 Subject: [PATCH 10/27] Upload Logos for InvoiceNinja, Pairdrop and Kuzzle --- public/svgs/invoiceninja.png | Bin 0 -> 36038 bytes public/svgs/kuzzle.png | Bin 0 -> 16439 bytes public/svgs/pairdrop.png | Bin 0 -> 61165 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/svgs/invoiceninja.png create mode 100644 public/svgs/kuzzle.png create mode 100644 public/svgs/pairdrop.png diff --git a/public/svgs/invoiceninja.png b/public/svgs/invoiceninja.png new file mode 100644 index 0000000000000000000000000000000000000000..5141cd4d1e38c783c8c40662618464df3f029219 GIT binary patch literal 36038 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 literal 0 HcmV?d00001 diff --git a/public/svgs/kuzzle.png b/public/svgs/kuzzle.png new file mode 100644 index 0000000000000000000000000000000000000000..a7bf37029fd23e28d0e132928cad6ddc68d89984 GIT binary patch literal 16439 zcmeIZby$>J_b?0tD4~R;pbmmG2uR0}BGM>Q(%lT*;YcZ|f~e#G(y4SKDIy^t-60K< zL-XD zGZQ;=q4$mDu5JEC1Xp^QdOB_p>tWxqd|E#>!CEz6X>DwkU47TY(VwKPfTbzd%_*Wc zRh)Y9Q%n#}+9IySfw|I-Kdr5zZr}X-uKV|KaYVmof6t6fH{aYiwy{OuJ~ez$iMp^_ zD^i!oel-~~QXu?;Va@{UoU-k!!3$jJlkWt>riTQrV)jY&b2nPbv9xJYNdA89{q*+1 zDpsvUv))k zTMKsVygIDWrghrk@0s=KX11eqtjcG$ z=ik?sfAPpOb%WK}U)?}=^7T|52@7_mX>BKn5tH7=BF6i(UL+k?2&AS=&?^rrD z%p=Wzi7FBcbi^~bn(tQs8uZ3ye3~j0Q5Ui7DcycE-H2^1YZCtBTV2%H$ z7NLI)T(4Dr7p7`1k;$cLZ&I3(t}CE*41soG`B!_@;#*zOJ6%9^O1`t}M7@JD2hL9| zNX+)Qrn;m$PrvwXFmvC<$i>JR>Y6HVJLtW|Z?{>T@rCe@&4(uwsWQ(;xWrk_LtxD< z&E`sK%aozP1SZ@*9zyx#QPc*tS2{}I=K1~wFQ{`}*BuK=hh`<8J&M1=;+HkQ#fPKn zI3LWYAbFD^#G%$Fcw;}yBR~l@R*YzO-L@CUGxam_z#p)QhFnbQ&W3JE-NGwi<13K6Gtg z&&!c%3Two#-tgk&%*THhXK)&;3VDHZ!Q@5MW;WCXI0$_U9R*7j6)bk(8y^c7`wA8g z@P&;90seL4@*5lYX8reim<<*#W)BvY1n^%hEC>?v&kD{m66c?9Y(w;qG=zR%fGtEe z&ve{$RFp-{;12gq%;BaM_q`mP&>OJCy+nae2Mae7dM^iiM^{lV7z1X9DDaJ5=4PPB zY;m)LG3cnM)62qLEa(OA^W5iQkRYU|rx$lIw-kN)@X_UQ;4c`1wVRuhC^xsKr{{f7 zzWZ<&D{fv95fN^l2iy-HZ~;5GT)iFLOuV=pT^T_j|G;@@;cDh$vd9}rJB8_R#+woV5&$a)#U)>t+1_yWz zgaidgHwzcw@a1j)Ao%~k;}VuQH=14l!9Gwnm{mYH5`^O1{}QwWVSJ8a9~PE~g~CJW zXI|JFlSFaJ+6JtD>wA?GDiBn-pQW_*-pVr%1j{^TPUCv?ypPKJ{gsDx)N-8k!};3k zX>|hu=rgOFt~3piCnaOpY7t=O-b}EUZ`bF1N86GY6v1W^6xoa)$)p z(l#u>s!)(`=_Jti;-b-i=dic3*Hh*%z0?aVYzQts>3=`|%Y^@G!v9@XAeDh@nvti; zd1=5%R@09kee7RtE8O*9mLa=yTSojYMO({&YX>v({mR;AF_#XY0R6<3GqzUWpr(ghH7unxCdyC5gbNbqSbYFgk z-@_m+K)&o~vGub-tiIS!sheGc7|lF>9jTzHnKo@v3@3Avng$1XO#-)j29qvKe@u2D zH65{;l+kw)5m@(8_d7>|mckm-w3=)6S*-M8E3S`VQ9+RzLo_lFAo^UF_BFVRCb%h{ zOFop2lv`~}$nzpenuabG{j1_#q%?QigU2tytsgavcgo0{g^2ycCvhFmk~&}t z7z%Cg&D84&JXg*H$$w6uY%j`JMq-#IQv5rQJrsSIinI^O{bHUJLjgSx142MUl1}Di zz~&$50(6FN5_YNgeG8S5O^sM`%;hwhFRu8<9sdE%Lxp0wRZ>~I7uw(2{~Mz{xKuX) zA$^{wxkS|n@j9wkx%D6C)%< zIH7ePM~zcIsCb#ACH3_bUGxO#vpHceuGHUI%OS)7;-N*1Y@hG``VQKuEfCm>XVcli zfN)K2W+D63nzcDL^uY%OjHIP-&|0Ts&ID>50>8}400zY`kVa#P%mq-+Dypl&pxoqw zS9jr8{j!GzV@)W)nx?~AgfJMtkcw}riITh_v%OHDBmK`3;2k3rF5`j|6!FQ}_rzby zPZndy@!Wt1Zb%~ovDn28H?`Xh*W$$(3QYlECnXJ!z>D_Dh3o2JBU6Tr?={KLzefTw zQ(_K}2Zq@U#LVRJakIspcpl8(tknQN7KrY_K*owBh6l@sA$HOulsLh>G+vXG=H{vKvd;yAE(8s`OZp2NRHaSIG@s%kWGQ4Cz~#{UbzdXqQO zA0N8uV=G72l8{>&mVVfs)@u+>FhAT9zO6F5s?;a~7uC?{2tI3*{G8RJ$M#3=hs2w) z`JtO8sJ*4qUYdt93Aw2x$vs!E@Rsos45~Cyp=t_qd@#IkLgI>sn!Vx_XF<^>D=%{Pcq#fzvOwUIg*J=pLo-O78yy?t zg;qgEp^957fZt5~V2$BvpEAN-qa)j4G?CrNFTdP>L4aa5im_K3^P{>VU?prHCa`iP^OBOl)0Wb4OKW4mJ!W5_|tTfq5-RG0Jo@6Yw3cwz#;CVzy;Qom4SP6sS2LV zfilxWZBm$>V!+Og_-CNPBYPqHBU1sak&?>;{_&mA&4N;`P}S}DQ}Ty5a^6aLqFw>% ztS%&=)SQ<9t&P}_>r||wlc-eflPAHHCI>?uQ3bE;8c)LA_D|&>2{yhahP1VH6&Ihc(w9Oh9* zd>UQKUa|c;UnKlsQu<6`Me$$<&+OEZ#%Nne7iV(J+dbS@N2`Dyl0nKUD*xFA3VV}# z@^{>U-uq^plmedZ4z;6#d!`)5#`Es0x30qB(4VyWq#pj;S#n_#-m9}KOW2cFCWA0B zlan3r0&$Nq-=x+#O|2_2kD){!wzBy~IqQKWEU*QCBD@Lzf=O>P+dl2dvwCh&XCZ*e z;5pp_*%(E|N<3uk>kHL>5S(t)VSV%{SxSowwKdGFOfYu$?Mq5lsFBSET#EM>QeAhBv($Kuo;nK(r$e~L5t#q zLA)pss%uHl@&Xwr>Zi8j0WGWCJoiZBW){k#TccDnWNqaIbs+Ucqu;;L%abwz+;+S~ zsAp(C_t_u+l*F~f{iQ;DxjN$_*-L^%AoC*9rWny+Awuz!#P|R5=!uu#{cRa{6OJ!0 zdFsgrkrj4Y?CRIUxswPmS?Uw6=Qh zMY&ATtE5-fK^0}ED89#8LEQxi$Vv@T{UG0EDOq3ER!-wIa44tz4ct8PKona#9viKM&pH!5 z6n=;lfz&p7`5(XemY=9mqDZi(21r#>i4l{~B5aAoxA(#_iJJ8C3?4#Dp+t>j6>vDW z=-e?EiOLr2Vqb0^7nGG8Agc&05io(EWpw&G1B8%^bK8!;qa`Ohz2=2y2%M$yABjv% z7-gdZ0x36S0gPVOkuNqjJh|Li{J-l@QEtD^PWo6;anWzUat!9(oIpC#a0|8shU*%9 zz~S^NwjX{2q?zZNqO+VoTKPGHgF4&^d?+ped4p?aD@cJT(L})a$#IfmcpWVA_ECF5 zreUqOLygaQsqUAT7~x5U0wEhm0Ux87IrUQO_J#gYB6tkUgTEx5zLAv;@6_x3 zNPEP<{fq+fmFUuisiBy+P+Bk8J^6~X*SlHSo9BnqE72|S~S)Tl#h?e7^XlbNmw=w>@i z`8_)}X|MPvgZdv=$RKO$VN`1Qa$HJW4DB%?^#dO)k^@{%J`9XwNB917MLa#=Q6UK$ zpgg&76B_Dap|U3Sr|!CT)dyu)v#W+J+Xz9&sRW?IP1a)r%54XdoW9}-4-BmO(;wLN zykk_h@za#=ltVuZ^tnxdTH|C0TxMBcq)_2boIR}0p+Tw+fx&=}NX>+tz>cBe)=O;7+) zWqJuV6^ApK=;9AwCv%M#i1x(_D z=Y@MyyVVzU-)W`5^z=Jl9QvsV&W5z2H&ZVHBhg)+?Z=Zz4~O~qw|lRuij20 zA5u;xKTy8df4sYlT99*gYz!;->BXxLy%Vsv6;glmCiL9IaEa;WTNzL?Ux8aI!fHFeH1w>;^{y7-3Xp~^kq*r@)rQ8x$@~9$tU>&!WLaY{i&2zLwQ*# z;WqVm^<6q|>t>gU!By6_UOu9Vr$@&QogyG{N52qwilvqiyLW1~5L&`ztFKNZ%7eF< zgF*b% z#rv;F#kSTN2ge5grcQQjuZj)oRIyL8EHpPz(#$i)p79NMEgpR{nhsxUkt+N<$**`( zx0U=fax!3keZeU7_7hak&tY6eHsIB~`1NtP6bVpMf|8GwrYc%-Hdq|@m^4y8H?s=i zX#87v$C6ADSq&-to3*~tr>Xut@XwdoiZiuhnLPhq-gdPxnApW#-wFQ@{^D&O7i+y4 zzCk|(=mz{Lo5@f3jHBd7{g14kx=&slqjX^V*>~LniUM(ltUq&a9R}tmd>|=E@5~iQ zGg!Yfa(IPVoe^OPPc8T*!=9N2Q4>EiS^06)mU|p#LD1?%FR;{0`#pKS^akleq$z}V zlfX{(*tqShq44Cm`VYQR`?U;6tW1Z@08XJpAwO9DtlJW_%87y0VnZFiez8+2v zxQ_+#D=N4ONdckqg`AeD3f_?9+Oku}H?>SIvzX3&p0oKffrIptrLQY>UQclG?;(T+ zH)Dxw#>W#6U(Kquq;wg4vzr^s{{*;1N+9@aTYveiIOB*Y)az(=m_Br}?aAH@aQwn; zy4_`hvPjkE{OJtIBwH?fbVBxmcN5>&^IX$L{P^u-Pha0B{BU32Yn7QQIPlf0X}q9m z(Btth2Mr1cBBTlQT@6r<8@)Yu8h-o89~bPStU8G9sS_e7FDOjwoXvA7%!=toCxQ|V zn+hi-g%T=(7m;U3z0qN@^cUI>7>~WD)TA(XT=1T2#m<2RlaTA*bhqD9d0InX zF$Jtggd=lP-z~J=nDCf|B)I(fd#^s-tp3pK8KT*dMD78XeAem1q=HO^=v72!C$qqN z=^g~!Tc0{wtQ#Se1CoEO9~Iu?9^u<+5{!9PjqIsy;SD!9kHO-ci|>)hPJ&`Pt&;^;aGPQRVGG!pO~i zHH;HK4tNO|o(sG)8NeG-xeX;QmrSOK?*2Ri)rw5MT{Pt^Ygxww*{AwAvGbx&q=YN~ zI}P`>VSUkw_PZQkEUqN`tm3qIS_Zg$%6PW7XUchIJQt(*)-RhvH&XcZ9AZV~Uf?f( zmQHo(_0!QoSVP;9iC~iCS7={(SltkcVv_^%o}#3!{#_UIN;|%r`>`eSzg?I>+|C+)j%7ct@Wh`^C4t8jZF)o^Zi>9!wZyS zKAu&U$L)0^;c{&y<)$~4K;7nl3Md^~Ggk1z8A1=-p^vreQif!Umcn2k0&~K=(r{O~ z`(DEVq@Zt}ELXPhrlmmJeosyr2A4j#7JQk!F;ev*W4y4H)r?gUohmCCgW=S@-u68# zdX3NngY#$+67P3X8hENxgY|6se^k=u##{FGxXM3&t*?*o%40=GkQ|^yTA#9(m&cAS z>su_l873OZoT`ubju!vvh`85Bitt!+SXn)q6OxVEu5p_9wUu9}$k{(R4kjsB2zH>v zlV76fVjtbKkYyFd&>++}b6CyeF{dPZ>z91?0t@@QCMUqI#9@H<>rN=sv?uzY?;@{d z|NCW)X9;)u#)hVz&=ESlmNEl;2RbSIplaNmmD^_%DD2>%1|^8wAEDH`?W#Ibf2OWg zMh{UTTb^o3!{7SJF4PV)=~jUL3@(>Zb}Y-4;}_lxE=_OZ<= z!#4L=K8%OZYNFh0zhB?n#iZ`Xu((_DGwC3V(#$F>KO6o%lKK(wl3DB31FG}ciJX?= zrW0g9GU5KnTtIdRW~O=kN@RRIzuM<38ZSgBP;sB?3(@u%#SNb?eGopYZr$eETy(qk z>l_}LbwbmIcEbaKWgB_L_6!Q(b|<7H%@Gg8(kw>0F*0Q42#;XIFxPKkKEhAs-^8P{@ zRdd#E#T=ta*=;z6fr0!2|JAE46Q4+-R=8i#7 z^M;^1K%rflh%ObhyoZ40k!l6qSbl;$(fxT8lS*PISSKo}e-DvBR4`d5sCoFVfZeQ) zN56h|4UphYm^yXDkNS+!-* zA6@x0L~`3{PD}4w>NFmb{s1I0fk!Jj}^lx)N{c4 zmlw7Da&ZsVPi1~AY5RU(8Q~c}_wt%s5%DbCG|@@JWoIaH(Bm**+cbprdi#lqm+Xs) zAl^rh)*a=P(RFMlC~~JSV9Lgw085#R8^*d=fV>$MMsp!7_9fj*x#}!Brl!=l;$xF2 zf!|&_b>@{b1zo1L!wMdlzmk^y`yfa5{)2-#Y|Z=9o1kp|1&FOrbXzl5VfvS>i=NEV zvE=eyL+i*=Mmg5zFS@(bZPvR#6s8#k8GblaFlttJKWXm`%A!pR#_G{i=tHmr zDyjke;cYICdUU(`m`k^9W?6iihWDpjW99I`>?S+Dm%_ACG+yDBa8v2DAMGY|MoCym#2=UFj;CFBjgp-Kep&Lt93;j*!TB(lWaZ8rb zGwLq*(`ho<4(*KWCKCI*n4L|R2xH}_`aSxAa`-b+Lok^_W+~A8{Y3qTmd!4SPn3H5 z$jyTT(iF}82Md^ca1-tWJ*Q(1sk23D9rqvk8t*=svy9c3`r>b{H_+?w*3ZoP61Vec zoc@4KAI#>bFBe%&W$3(3w3_mAe^ry=D$QowHa zK%qe2c~)6?^*ePiO;N8}x!3b$eU$If_Voz++h;Mkb9ITKl(Up38-ITLlRd9rN!q(J zn{5$Y&b?m0SMzhkGPp41dFp8ZbN&e6w0u1R?s8NROUpT-4nzwwXwi8kavSY58ga3n z$;|xV_Ty;XUdm!Vlk#Yg+YT@L4TEzy9Uuh=Cj2{5{Xn+PT$=FSBc)7$IBkS!2rx`jZk!M4%&_!7-Lu$#< z6O%tPa-&-?HTYLzGqaU*oI9RG%F4;fj@LZfC#SjhT#wb`PPH# zmPUR81=y&e1xmE=Xi0H{S~-kAzm(eKx!2 zo?ejm5KasKZ7bOv>_FY)=iT3PvOd^xQ#}_vqLIAmvGu)nz)!978Nb)s(4OdK9V5pH zknB-ztLT1lCs=g@B-!;m4^tYZ1^PqqQza~^Mckp3y|F=^a|04@XrdsV5ryjLJI|%R zcdit8AmIxu0$k`heLc7TM%|L*_zfy<{V*-oX^~(x`yEAtO(q^VU{sXOJ<6c|k}8;krz94bu~6 z_OO-s$nq}j62U~MjFKuoo*w}WlN7ApwvwqmRM$$*kM>uunkzjiHLaDlQ}cED2rJ_| zYdPf|T?#&|E;(qm;{U^!=4uRNR|0gBskF<`E`r8Dl;V%SrQ8my!ELHSMu7s{W#K^6-6G$m}5}f=2S~dgCX`L~{P26L7 z#@D*_+u85BvmZUERKPf4PlW%6wMHLpyTP0DQF&Tla90^h)*^&5IjWX|M-MV{Z+c4^ z>aJ|g4(p?)C{C2Tx39kn|MFOG$mouxWkcMS7yQHA=ewsP|1Eb^?=7GcGEYz zqdlM|JDDO=ZbQQq^L7n`W}I>E(!dGjCAP)<^Ku$nlpSFVX9t0*fma1+(SeMVo(E_W z9kcFts~A^9PVS$0y(%Q~p<_M|_&9M`Hr12sy_d3bk(jVo8C~>a^Q33KH=C|y@P5I&|sf;9h0zW$h{3BB~V2u83|Tfz^3@1IDLLyALu@B6yi z-oIcLOG~Fw(`J&SVirREJ>Osdl}}gYxkM*peb)1rNO&SCb@i!&FbwOU{3q zmG;`m%rvYFe};OvRX&?OE80QC-=0VH-#(}eg$+Pymc=!1)xl1p-Q2y}9&5yGi<$f; zmyqng^(S%S;NZwy9Q3^GKwrOjZ$}}e357>IjV<9Z9gz^oa+vO=FI*DnyByG7)&Ya~ zdN%MymiH(}9V6j{6&#T<4(O-$Z&>WfYDrV;VAkH62vZ;H*17X(2kBi6Z3$5Wfs2h2-;*?pDDACS`M}{lnv3$X@$uo*N{0IQ;}d6>{Qy`l zI4$w)QPUDq+cNjwa}mVk2P4WM<$6xH@WNc1lqJ;jI)TWL$`GadpEmUTeLWfkCNs|t z_A(HB{U0Oh!h{TOK4&>~#vkxcZ1BL@NIoBaHo*NEwRZ7b*hvYMw^-UAsHT!yc{bB# z7bWz0t!Wa)6DSVkFA*(m)KLA?TVy7`ouPzR9g`G`XEliJfWpmLfgO!U<$?>hKok;W8?c?3&U~LySkD2F~D_E1<&a@$4Xh9#Zq4=lHm~Fl9QtC8HVVW%G zwG_(#WLW=Vd5~Y?zQ9aJw&jX+u1QSV?*x^`ski`oR|gunKa)PpUsi=(>(M7%X2Ej> zpXB;7WwuUC&HDk~fkDy5kI%NYBjVco2|v;t0rNA*x1XN!PYp!<@IgtEKw5y2H@Kve zCMxs|PzFXTy3}<*K?L-lF`-)$QYv_kCp$6O$#f*SHts(zyLr@3u~q2UirH>~3uMSY z7T7N*wUFpZs9tkb&}0}%0q`W>CdcKa;EJH}a{>*YFeZmPn7>MH0iC!H8B1V0U!4G3 zyN~0G@;mf&2BMv)b?lC2-`zM`AL`y=E{qHX|0QIG_Y#*T_W|( zP7{#D$cF%nZ*YY^1MMgMvUN}_c}v^vBK6#JxHx&wA^Sw-crN-AsTLeR_qCdekL{T3 z7smDaQ@Ke#am+a^gbPsKHSBSJONn97M?ECp$$%^Ly_mcZwB#85yYX@ z@mJi zgpPv#lN9Z2SVH9wl*w8egX;$_#&*3ggAMK@U`!H9P>ac~UjXeL=hx%o5knn5ALUpt zkun94dPUu|2Wqu60{#3RAMZ8bv1|yMH`|<&NR3(o5h4I?Wzwf$18=Rx+XSNb-oEVC zxx`6?mmH=6OH2(ChaagAaAk+-&82Puj^o3@=+dvracXsUa9Snqsc~JLu0q~1WJ8#8 z$UZ4I;4XUZv*{I{=&ZNW593pOPq@*Iq*ze9mT~8nvjkl&aJAs9?6LU|(HPMav~q{| ziwPk?YsSrEGLG;qA5y9SNpVKdOx$vGW&&d&(hbnoey-QSAiS@*;U!-!d{4gOCxaso zj{xI_M%C(KNCn2r8Yqft*2ko;JA$!M%Ge1w|811^vVv`dq&zo0j7pUPu)KhYj0W!GgKyD|Z!&@2%RD$y!F z1yJTnZhOaukx^CSiz{d6%qe#8tIpB_|QX z!)X)(U>wW>#wikOc2+T!(;9-q_1%BQ=#b6;TJ2obFVRFJLm_fHWCB2*qXXtSr9jl^ zeh&YQDJ#S9?qAndn^y1be+G_8B{1?#B|X)wF-K5x1X_14iDEc$h1;n$h=e%c1|Q~b zP{l4C;eS^^S%bmkzufpQH~vr41f(kD0vhX8ri&G0@(cZc+bYPZJ}iHt)0tBS@CIkfOy@ei--n;Z3s`TDNk)}u$lpoSVgJC{?-p+3Kkg$MIA0!MJ;^qthA##*l1Z`b@iulhmhC zH}C|?#F5O!ZT3#RO#We5Il~t};g)dPm*zfOTU#SsUrS#kTl56q+E(r7)|ae0<>&L- zJiPHWMwE|K%rJlGs-v;FJ2F|mKLSdC_rR3|wgpuuj)^swWp-}Zt*!cw>#d`Ghr47`?J!owpX^4fLfR;9v%GcRAt^~PXB<*6_S+;-6 z{ayemm3lwCMwyAutU@ChZzEw!D)!}3cvSt+n!ODm!S|zK=16)5Z6TO4YAFamy!3r zj#8qpiL8ElMpuA7L}l7|dt}_Qvtw*;_wsnp?9+imBuC%EEt6UFJbfHz+vPyV+-`>k zTG!FtcI0~olvmgOAolTrKBVRf|F-YCH0IlT+C)Vmh+_2VVrlT$G z=R*gJY417HqF7dJ0Cw(ZxwY%n(`IkvmvIzl==caW}b z>S2CJqcB}#$1o2^DJM1sc|zF`X{-QGWPm*^#Pgn)zjTNk+h1~}vDbfY3$nrfA_?%2 zV>8pyhu!h^L&C%a!~}%+RYKf?MA+mBVX}Ts&eDd;s{asyeJ97}8W7+kEhrcq94rtl zD&XzsA}A~+B_${%A}AulkEP)E5A_PL58?Onzx7AN-!hbu{*HcbJ^^mtUa&th?H#-W z1LWA)u;Z|Q&d<|FN9SMSz5M@y0u~R!5PKg%VF4jQPfx-B=Y~DFAl6O(s%rj_5t30#@^odHe>ALoI|{=MYtZvNQkhyHczZ;vuU`u_dy z@3;5d{&FQ4>@TN~ws-tn1%LY>q|;yX#LD`6$kEl_%LR#z9{(`cf8KZdKbV4;xU;>Z zh?6M4sIa&sznG|)5Wkd|gCo``gdM~kg+#>d?f=&5U()@(odbgH{g8?-SRAofVa@X| ztYBP!Efn|voJ+7P^3N(@0pk}I=KnWfqW=I)@E-~b{)rj?)L2&V|AmR{Uj+XVWUzdH z-@^tkY%CP~S1|ksX4v`u-~9T=TKwO91Pu1yhy1Vb{V!bqh3kKX!2gQ)zt;6%xc*lN z{I7`rYhC}p!A1D*ati5%&4Pll#ghEJ$#HCObXecWhhs^zo3@%_&dwOK_O)k{gU-whP z5|2eDawNye=Pg}<=-}Wix{&nuqh%6#5;z96+>YMJ1fJV+eGz$3J46myB_xJxcXJia83Sp8SDwtY3_RbQqD5W zNmEbcQnTgu_|vN%DsdoxC~%5TEQ+Oz9blSek${J;3gzwr$$>1A=*ekXmN z@Vj%uj77N(?_uPmxvft4X>n`)zAX#UIsQ9g)%6C-twq$!uy;>e`_2q%Ia4pyF9(^n zl|-*h(hT?|4f#onrJ_ou3_me_(LY@K*wL$nan8yBM=>;phO?-L@O$8OUOY}~zwr2o zf5pf17C4}+7tBuH(YJO&p|m+FJ6P@zeS8RxG7V0za&ik?J^=SAd^`jj;zd-sV z%ZV%=h2NmIF$RWah+FEM6m7w!(o^P{5_qWq#s1S?u-Yh zo|2;|aVq_xOQmWQv?i3?za%)`N?qp69>}(0^zJoC_QOp38ieQBo2ln7<#wNX{hpK= z&A;6x&L4U+bLiOT^F^=J>EiHf#=|Rfl)^hBh3V&bPi}$cOn_SFr|n=g>yzC2klAFU zBqin0#=Z5$t56r*G!>(lcbeu`HnzTc&! z-fB>n-@a@GN0*_ze%QQz?{p_Wqm%;}SU)1aOH~B#kR#Zx9kh7>Z=#v6CrXdVPHVbw zFo%paKe_>%D;jYSj}xFq&m{zKf-rmhuS&OI5pER~+sb95?k zW=%Y0D+P6xpz?cC)U<;%9U|!3a-frf8`X)CVd`AI^+Ku9u{~7xWx4VAs4)lebg0UR zfAtpsJ4fJTZPcS6_Zg#+-pp#w3%{$PrfSO4{fAWF1WS;bwlgn!ExOa0f<+8LbwmLi z=G1`vhA*;sR`U*FdyNK2EIXeKl!PS3@+o+iR5>e-r61_DNu9adba?8@j8KA{7x<~> z5&fzDcwYCvxi(mxZKPL2c<)Bg?OvtY+-f}seKf}_-8FUGRwE);>~C^=u`*Bzz?XJK zUYC4`pE0yQGRYj88Q+h>efz7qmxu&MB(k5?*S6aqj23=0BmAn=6_!s5V#EDN@)l&R zI2w0bB4>uc#m#J=B=)rok0c3Q*N3AnW?PG&!@+HVU)b)PF76n1&P!50R>(;QWZrwJ zq;Z7m1B8qM(C8bwp!QwWz||2-P7@~KCj~zsQT$)X-mt^cR$TJEIgw`Ze#G^XvHU``_lHr9hZv=0@0p_Qmu;4?Xuzf{RES+Et_6`Otb(6=1N5 z`zS+Ux$62*;SSf}crK$P39{=$*N0Oc9L9GAbYn)T5UfDS24E*9hEdY%V*gbQ+1q{> zR|@hZnnYVp0$}n^`T^3>Ht{c(u62`aRg*Pri zP?q*Y306+u2Ng(dRBlVq@To5UVUnNQw^-ZS?rQl-9Chod6UzLz!pGBe@ZD}W2s4=a zM`&MkPwdJbZTXs0-VgyG=`}C2!m(zu`n#V@X0zUh=NTp7HQ(;LJXjA+{d39Wl$~2( zX<$mqD9PCT(6%zFS zo}EggbG}}bTWKC6KcWfHSwC05(Qr)|(azQN$#*dyYl99R#(6_~Utmv-0x1yU&e&iw z74yerkQ%^={;-;k&1!$N-0z<7oWJ4r2oH-2it7?wh0jizT;5Dos09aq!wq>W|jc2k?m?DwfXpw2QN~ zUyO^prYu(zDZokb2nrw#2#J5!iyr}I0})iPl9TrWjfrSp@(^WkBRiTmWhPn~^B-J~y^h~|0LN;zAb|WfB-;|81hr3T=%?uSaOVqg$njj}!{q=ju zsfdB*#CrnC*>@9?-KTmz& zB<=s?$XmqYS9$AAJLzDa9XX#$ghdHm8aLg83Ss8QB{t7%yRK^Pl(zk-gdVYYb|3?sQA$nb}|S%>=*Tf*@}usJ?V|jh$_DQQubepCQoUR z_5{oZz%aWRC73K-3O)K>cU2}|$cY~ZdSL@HsrbairU=s47_Sw@Rcq~@s{MAR*SzbuJ&=BP9zl{So#HDPkBxcIA7b18H!q24iz5z>7Nwz zeq=PN;4XVo2!Tb-CA^*=RiUG3oP9oFwx!0-r3gd8cCNxi9)SS%jNqFQ<)2!qpYq-r`icn&4(2FTteTE~S zCP~9e;BB>ZZIXIbk~$C@vY4kaVCTx0TGp)*aD01bwGXl&m-E(6Taj(l9rdE5)u7de z|BcApo@X-*#5V=JwCN4qc?F(0R7k7qJe8Mzb>)IbGr%a=ZAq|}$mrB;2@vw$HN#aNWraRQq%y(vfUyfhgXMh1;a#0(c0FgXNX<6AO z{d#K@oh}S8-z+7AqB0am-5_?Kw) z;S}&N*_auUx6RA06n7>GKp(*+VMFi-H*cEs*G1lo9}`wQKWWT>kOgKIpVx}0;2mtQ zR=MTNmdP->XI@u>wiUiI(`Hiw=TKC4v5SuN4_7&SO29Kvm_F(524z#16l&izx3rD> z;pw4bh!o~^78yI2bU4L2oMFiSwZrU8R)5IG4-w^V2s(){9cSVoLhvO8Aj*y|Ahe6c zbEW(Gfh9?~%}e9M#Q8Wq0s0{@%a_)1;{f`av?QGXh~p*?-*9j~WsfNq;j(*^36&5K z94HMXH_knU5n6o`q*FqG%uC?HPx@oZe7j!ABuJ8Q2r*TrSBF=Erh4i3+0nnN0X#7@ zg;KIC>iUDNS={xHw9G@3wH5>8ecpYT+^d}L?CahZrm;G_BTX;bARMg8;itIn@!Aa% zt~E!c)pHLut8)goi?Z74<> zh1axyHjTEO;c$L2=zt4I#V#^!9J)8$kPPjf`ypTN>1kq3w@EuUXF&n2i3Ll%%(dHp zzo#)l>OUbEltq2UOdk^ELgme*?En`R*FnQbP+MZCL2s-5CoYc(i&Y3F%J63OAXU%e z5F~bdh9(Y>tJvVYi*UyW@w2vE|nmL`k2T%J=9Kbj;l3(a}qa z>;yTcgAODI4o;bolbsrochGI2g7Q{}&H`y-JEsqmznS1M5HlUTs*)r`ZC?=xGz`9? z>O2NvS|kPI-i#n(igWD({mEDs^zVynjTq2;FCN$VrW{lJTw7_>jr^Vkz3R0qgZEbI zQif#N>`cC@(sO~!N8(;1`Hnc{AEbWF!PGsZ4)lt&@!cK51l8QTG`|5??oOK64`5iM zq*mDNO~-G~0Vn(Ou?1SzT*Yz^Mb6B8DO52+M5lS% zl}YgT6ZGWkCAx=5nTg>gH{i`;DhV~>6$L^Jeqqt8Wa>-BKEeQ)y2u&HC7VP^FdTYM zofm0@w~zrjIFa^bfXlQ>!yfgFGlI1d)wUmdvSU6C_tcQmRH9gnNz3PW&V|O9F?>Bz z-QCGI`1Moc*0bk%Juw&C1jUZB!qU{}6v(=U7bE4=nmHpVTC3}tX|H!5in38H$O7OW zH>WLWrJ)&Lp@^Kb2_*mm7RwIt`AJU=_b}n05let+fYrFXUfL`2s5Ce7KB5g)-klnQ z<2ev=aY!Zb=b>>L)qGv`$NcJ=ttWxA=j;I%KGRP$$0JGKyl82dxPR_TfuT*?oyMJdtHvEKkgDH3ktX zQxXORyxkq%Tn^dtasPaPGBO4@(}A)iFJXEiqBEZ3S>h{(?r3yIB`EI!s!fh$VZxcN zf$mefO#jT>20QQ`hfToRHs*%pRB;9^4oR z_BD2-08b@yShZFYZYy--9h!i8;?weLKind(b3H8U9!RVWGYna+_r2IHq)S+?>mBcy znZlS04%bS-S2a)ux$;?++7G{Z;O?{n(YN-mq#8aJK+yCJts_DiI`qG)30{wbB!LTZ z0?D36ytRhLJ0?$a$G83c+mhM(V(|nmHhFyBJ>`}?9E3AR@FtYN15t#2~;QrKlff5Z{#$7xg# zGS%Zn2X3=a-PZ;7ejd{7*#Lu78R=?hKBp^(h!}q9D(hvM9jC3Haq1l)prYDoarske#;+=T0oIdqdVA9xa30qt;s0G zn|52WI|?h8IH(^D;*k-TCHWgBF!U0Gg4em-Es46Cghk%Hn$^E9wY*z7dL`_S27Pntb1HI{NE*+z#>pa!I4=6+%nQ~*NK zSEtSh2Aip+nOcJ6_LYk;s{m6u%{Mm8n4#2s)f@Pbh?6?#m=z8BPUFL6QtyF1EwnQGpUjO7=k; zqKnngE1SGNqqw84bp(5Wg4ewElRDt$5XH%8(cq4u8U?qC=e>7Ph2M-(IkGRh62~sK zV34Wy{$g=38d@#1ed%Ju^TY+5UVCe|8<^TuvW%Qn#0If~FEss}0)=TuFGYGh1tqK)ggX=6#1X0DQMb5BTAT>^!F2y&7KkPu#n3Z6y!i^}qO!rMoomG~m@g}} zE~=nsX873BE##()9;Y#$%KTLfUWAY1EhP{>vtVCp{>bPiE8IhcOv@r71XCeAf9y>3 zG5bn5&RP~7&~A`V@g(20=zY;GasTOpJZ+7@dBmYYGV94W2XF+dyTk1EOVvZmRp+085xEjwXA%c?+Qhd8XWdCe*wSr;_UIENv4n@8aB~{Ff=!1sytiRVmTm_CX zf&w0WQXZR_LudqKb#Rnbg4t+i_OtK&sUOuXf|}3^ed1Bka>x&cW1( zV3w$pb*`6zm3jksu7rdiN+2H))^fg}iS!}{eTq^2nn1HplKbO>lb;Kd6D~f)_J-mj zXz%>c0*!J5s~c~s!1^~Sc4>3R-rQ&0_`WQ2>btxe!kEhQ#{e#lrs+!E-m4N!$Og5T zLVoRB>YyYjG2^5sUu%J#3Y0_*=V~h%w8_^1Qq&U$vdCwX?Jr`K%r(={N9)jLJ0NRm z7q6G;NJwi3G1+lYMUx__}Rt~wrc`XjuLX1m4?#lv*ezF$T>uwc07Mx#;%|pUg zsev8Gn467B+E-+|gza1XcvikPH~J&2v3fVO^^nu|Mmj@CJ@pFaRP>Sl!qah)#6!62 z?L*chz(ew6*|=U6Le!gM^xAg(JzfwLRS_5!@w*e(tfL*wrqF=hF1u?PSe+yzz&n(3 zYH+brEqtJ>KRr57MZ*C&PXWZoRf*sq^gCNXltO21-dBRq*4(kVJ?Fol z1ex)y>epd$zZ3YhD6K$1Qd<1>M76q5jeh|fCoYdj_4qD5>^M)24IZomy}{6R@^1N7 z$f>@&Uq`Vs1yH3&KzFlSYz$x5pK#wrxM5n`rDRXV7Iw9b^IDU11Fn8wB@ zX%8eNlqgR3Lym433f8>NI8_6^5B+;wH3^rcUZe;A;t$Wvj*&f^H@aTI%QhyCWe z?I`;kVVd4*`v9ysD_G1fbjnFVWk5q^z(9uCB}``rMHMCewM9_M&fd38ewzRX1W|&U z`!uBvQO}>x(?#uDP-)OhKgTg1WVt;FJ`FgnheSHs^iE?Gv_MUL;X%uWh{`U?I8l(u zx9+OJ#NO+52R>VUk{GS4P|5q?6eU((syB{qM2{+jB%UdW<*APQP^g_5tuv^X1jFwq zx5t(%Wf-Dfh-i>xA9qC_ovRi=LJeT)Pz*V=i%oG@u=wSNxIsb!{>RKyIa*LW*`#<5 zV`cX2`zlFb5IHo2cBJwCCD^N{3bOCc5pkU?Of*?XC#tAx zW3Y}3X&$j^c|a#vs6O*XB)8q8gRu85g=`LykEnu%2OczKJ<`K!gR(?|DIz~d z(^MwmpJ|6MJ`{6XVm<~4%V9)P1N{_Ama6WAQq)#F$tjPLVw^9igdLo2SE!<)KXFlXi z&RV#DPS>7IB_mtdpZD(LAj!{qejsUsn83otu!L>wK7k%PG!!!RSi{x(ptSp4;v5f4 zp2sB@aWYxEp}XRqjF@{PZQ4H0)fWOpPE6lNON_II_jw6| zXd{jQG^_-!iDi=y)?plzDFbB=tC{nH6!?t-jyWd+9l$aqI+*%6g8))?;b-8?lt<#D z2~b3o3BbSj=AfvXO>y?lgyBk`TYVIt2SIViSYjukpmGcXQ^K8N&R2rzSsU_M%i*F( zktmkXgAgVY>02vo;zxO)v6YVyHkjUkOiAo~(E|7yNV8&4TJD!8d;D7167iw;uQy@P zVpN^}K^nX7&N}?%O}aOqBe9u4!Jx9{)0HojmvIiJrB&9K%|*bEE9REFpcr*jabj_^ zRsYi)IA&iM(6P^f_qRSd$%3|fv~^6mZ04BfV%P3x+(Zam}Bkcoolo%v=4Wy;-r=_x40)|{J8aCZ&?Eu|25}?uM<=BoK%Y3 z&=Q@_vxhIoiT%!?C~|7_(@8+JC)5DY>;mUVo;8JcZUVJ{sJcg3mAk`g?nRPHl z(F(C+tmcjdR32;?ClLg(wFutD=eTG*u-)5DWE&2%4BAl*@S90&gP@1e@~`BTV=!l} zHNNN653t)adj?~5mFz}XcWY|&r}F-F^9^tK;DqRHY{eukXWf1I77Fr-B`l6)K=3NE z0T&dC*2zh&xsWDjMKY~bTzv|P(^V6qx>~{e*AaGf-b^}i?(86`^Av88bUh`6j`diM z*-UjDO>FNQD8PuG)CLY}00AP%K{)j4V(GS3xIM4J@DTg@F$qW0+633ti%5uf&_hQq zc@Ld#uWFgaNkCU0^pFhIF`fx2?ia*7REfH0K{J?ns%WeXrx$X(<0uk!Gz-0I=M`>q zOE+Lc=MkBy5OPgA4a!Ezp}mjij-nYJ4hOI9Y?|oBvlmQuKs_<6lj%|Go5+iqAs{+16(GVy;wM7pOM0I&1>9+Rc zp!mwn%C2Y!0^DE)nS|FbRXNAWCltZjlra%9*9pKP(T?cRnK(*eR0C)M*lh`lrGK$4 z#-LIwm?I8zILsz(2G-O9pD1xV_|m^7O-lsk>T+xB(t<7ead_|iAytkh=UFMrd$UG^p}^MH$Y=+rbsIY54x z)u_zLg~=_xmjC3JhuZX=zDXssUcQH`?t*s*#q#Qa#@{JRi!Aho^HTbE zuUnoFxrWlU)i{4Z_1IusNUtU;fvPGXf&+znSfaNw`+_TWe6fXzIKo9iXHi|2+wr`| zUiQR~#b)aXLah3Ueu^KCV^k$5X?&Tg2Sg@)_2}+(TKCv%T_d4AOIX zZXb}!gV#e`-~IL{zao7SzVTY>Jyf*Bi3=;RLDXw!3SK|VWMY7-Lw(-Wh+M3sj_42Q z?TxslC<(*!-qySSzjlI zeRjNZLtZs%sN^?$&*5wP0HXJKXQ@&m7>E*Lw7-P_^NQx;3kBAgkR^?fVG!iGNru}Y zD;Z`^w05`JBWDL0CB?^Y#3V&NucbAvu*7{obnM;e4W?w_xt&n?KEYH4*r+mx` zFU-sAJQYKfQ~|Bgf;XV;t}0|}jSuV$vWmQO3kSjyKAkiw5G_9*qpEuDENqiT@(mXa zo|wsh;%-R*wLS-`NF!c`cZK1p zlq`1t3iT>N4`+RUy?%ib7JcYw%xz!U8<&%=?9x* z+;3Vw1F}}+PwcV5qURV*_UvoXCp=r4jF^i&`Uy!N%3C}5(>j2$F))JQOy@nfiNUH3ea?-+c+<;`+L`XtTZq zc6xSARjMqa!Lu93(-4t>)ty1yqH{1yCbo+z7*<0v0srm3+;Q7Te2xu|^UitjqYMB;Vi3aXW@o&6vOE#N>k`<0P?$ zQ@=**^bH>+j@d;ZXdnoR+j7_Wg4Pg?ADBiis>DuCpK&AW5ci?sI_vj*DIw#QFJ5cK zg6J~=2fKM8HE@!qsc55G7bLfl`1_|+WL4wN&Pp7RFo=xUzG9UPh7q{abnOS-YkKM! zMIYD0VRQWJAP_JctAYlyFF=rlDl|wP)5x$ z>pa_%(C=O)s3rO~9|0<;CF?GBE6~s(9iXdjWD3m5j@h!-=xT~&ve6ea4gG#dsM1}c zkLI;Q){+iA;`DVgb0(b)G7$(wI2&XC{N=3AHXmI46lgjO(TP66ZDCDUN2PW5GQYBI zqq<^CJ1R=5*sYWd-+jQ@=^FsA(7J49!4$|6W3bx_?~Fi`IxuvmI{}MKBhlv9H#=b| zmL%(p8q15nZ}xw_#eGh0wM9Plh(TzV>@)h>h63v%uII&}u;R9wBm^p7VtQ%3!BY)= zw`=oa(hb*po3VeKWvl;X*~jBY-vb8+C9&p$p`eCw`U>5_VjA>?WrG|l!GG}m`TqN+ zrgyeeyC6=y$&=M@akHUpg)YGh0%fb)m0?BBA2y4=+c_73o8Bx>OW~U-`vUR6Oo73D zh29`~C6Jy030pSUT8{E$@tkz2mt|`(qhyLHR-38&M`|tb$OvStk!4Yc9Jee%tRWc2 zz0%m|b;>F}KS1@uD{=clpxU+Y=4Cr@oB{vv>Prg;b`Jepl~Dbgjkz-QX0OlY8ZEc~ zxTAgft-gYe;j^?4?Osj@oJQ|Ej&NK`F#c!XJ21mZV^-jX;W*zmjhWeBD)cP-qgx z`&0YK??{#aO$Q-2BJaSq0!4a1C3;_f;SrlrNg3N*jCLZr_FJ==e-%j9To=}RpK*(Wf|M3cDT~egrxd;FrJk z#;jgiR;i9YyXi{3Gp=2$1ek?&jV0sQDFH3nAGttP1X?FYm8K599-#)f#45{fvL}@S z7n9zk_R-!;Kdy|4x~&qmab`>Z#85i~IAD9n`97f?h+v|v^72REeeikMWC-#u)wDe* zb$Wo35dobrURE(H4cXVHAmm3Y8PcrbZ`cqPf88wdGJ>EghCGBp6C+2~WcXx9geOCm zUz@$m#nm&~akIgRk5a!cZs!$jMtgHAtU7&6a&U?lVmM9sWdNFr`M}SE!j*x{X@dbf zKE>U=1$aUth1mq=4wpXDc{mheHnoncGd#N>-c~xawR}^2+^G)#>X%y^C5rE6PcayD zSOX9(0kW~Eahs1RH^#jZ^w}2>JoT8LGmn;8q9cLVkA_-34f-ZV0$T{}oHmkb0WVIF z2yRf3`to77!3$HZDuH7J7kEz)3emERp!=ow9Z_@-6F$m2*J^o*j|209p7@tn=8yq_ zX?H!s!ok?cy?zhR-`+j9H!Z@|_aoD9QK3w<-t|Nbi`+u$$N19T##yY}L#R=KLd9)? zZ=XN=kO{=qg7*+u1>vf@{jY^XsBt}0z!=;zk=_kp^(rFD5{K@oF zTU(Z(i%Ef}Fo1`-aajT)B2sM0i0`vo;}>z3Kn*>+5uK}W-|*KKQ=epXGwt{6yz@RHQ)K1HfvjCi1k@^;B`5X98qhdR{iN< z)l=aI4B;-;afkDpDPY;xf-&RQUHIO=o}#SM4g5swvB62u${?XB7xcBnXsh98n%s#} z<(13uawYwB$+7ceEo@C*+^5yzMjD}`y%^{VRLY{?C0(05Su``;VF|tV0W0)GD@0u! z5NMZkm380pJM45I{PU>^ySu1A4f>UaFI0{7CfC@dfnY~6B<_Pe!f;I#S@FyHI77jm zv%dJvyLfa*2lYoM{y*x|0H>cASrSBeA*2g@^}S5X=WH)8_R&ct)%rTafMiq$J{Iu8 zv-T~sJ!kC&{w|IfcYP~5BhaG<_*geGqzc*e0Y^p>@VN>6G6BZ}o`*K7blxX$ zPllo`G&WJ%-L*+x;r-H@>kU0--zQeC!}2g+mqUh~*xT?8P3`(C>RDDuIVoKG=<3i8+&6O7xl( z@49N`?GqDMkhdtB)Dx{B_tfn5#$02*<=JW`9L4u@%Z$Wqm3FsYuR+<;->omIEAozS z#P+Lj-$f4`Bdv=h_ioosUa5^ma!yRMEk)IJ_QlnZI~S86S~gY9)l?inl=yG1ZO$5WFk z$E%ogU>=^nFqSPG*`PhuNB|+j1t$ZUKnMh!rVfKbGSy)zPJ5l|K7QNPFTlflPQO$w zo~I)TpBk1w*1%}+BJy-?#%q+uL)wh4pPUcg-WZvhsrlUN_oIGq)~aIEM6&a}2ItqM z8OuRoS%KvvQj-!stB}5!Qot%K2y*^9D^)3#mBT91{O`th$^n#;?55@8r1;Ste;yJ zW~KTlLqbi?IQjDgBRVD>`vAV=H{BY)wsebIC1bu5q&(5iNEgKoUG?Ar27?KWZ+h1~ zAsJps%bnId**lzetv)Ov^Za^6*K?4ss9nAfr$r?jrv^&L8Mnd8*u0za^vB-=Un2FD8SU;v=0C1AX6k&^n4o^c;k*) zE9_wnp@KVaBp0bx79gmtc`R6|J=V4x%77 z+_fc)K5w;QV!3N&Mfe`pk+CoACzw2T3naAkQ7XdH4$xV#xxjeOlywLg>a*BenC_fc zk}jX8F{!ZW+zcUVz_txAn4qe!Pn2U<{YV8Yj1s7CXf_pd$NL~+VH8xfKh~Jrsh&FU z-7YKRO}Ke;ImG=O-l>LUM-pl+gNl(W1UD$sYqwiiK_-eI+m4qM9yE#i>JgcSnA2B7 zyQBCC>6za?x026X-Z2K@2U7I(?<=`3K@In$@@IV}IgC;iVsqZnNqNwfc z+gPqIgI!9Wi^+SfaV$S(>b~gz?dxP)nOKIvzxVJ--pfjA_JG>K}!nEva#jb{8szF z<*8A2pT1sW=0plwpA%}WsbL;NeC{tk|NaD=JXOxKGyl>+7Tf4vMu2NfnPjOA7CYd+ zWJlAHR}n!^&WKISx|@8!IFlj+Y0M1hZts=?!loo`A$Vf~Lx(N?Mgf?2^Gv{F8a0=Witg-S(6S(O5p7v0p}JbF?1oD( zp_34*iBrwESHEZn=1gWH3J+P~<|e>MF1GH*pRoEpuo!(ImaRhQC@FB?u&c9-cCph# zVKB?M+l#Ld4|QdSSlu9p5rA{1&wYnD4%?UChr%PxaW(HSGf%Anlu$)Cv;OzE_Et^L zA^Vyt2Sm?>Def94e7(p5WTsHbR6DwAOq(<(1~dW>v()3F$fsFjX^#gpW+*ecTa_6M zTX3)3AegyE4_q}*oUmPOoDk($!G$KO+FWDW__wOuO^%DOp0fwvjASmpv>ozqQtr%F z0hx%VPHt3hM+<#XdjRvCr54%21FMbTVsa4ztOLci`&bjum}tR27QNLMqa`1{4nR3m z*eMayCG%q4>Cq%DE*VJ@?#Ium(>4dkx0@R!w_r^(_$PCQmN*v-1lZ$-Vni;Ev-)+3 z_~Z7|PaNrQ`_r#TgHkbC3Jdp_9o>lAzW=a8TgmBPGHbxhK4OMk8eIoHvB$o#t3pM~ z!Q}7|Iq>QXzRy)2#S0V7_1}69Tsf)GSrxEfrKki)aUt{|l@}hx7BfkU|NXc#x{Cr5 z8`{R))pRSxH6?}!vGw}nym9E#LCO*Djvay9c_-?lqY*> z!Ao}0p8~$Mi__`DAX|s>7t_eplOM?aX4eH{AC@7L8H$*f3BZ_4J!GF(nQ2qY0p9Q< z5dudL!=>3B9oUm6Y4E;h%rDm7QY2T*aM+AdPC%A~W&w4e$U$tpfA#@_p!5)@!!OPq zkoLKYhykKQ%^An!>p|tfe1A*^q-Vd@8k^8h1$?991dgfzb}jA=E%u1jOuUG*8dJVP zTj|jE%tIezVPN@2)cw>f<>EO#A4~{a+|0T%1=@yqb}=7M z{!KGE%~#0R?go^Ix{^+JAp!Q}CSQ~c-)`h5ib3vMb#FsH%V1@rEvR?=Qc?V42*b7i zxR-3Rqq5OAGdW_rSA>5Kx!wG!uZ|LMu{O#PVIx0eTcwQNrk;>jh#wv4hjG&vc5|0g zeP&109_t4DPzsQL^n>XvIFQp|qr~jfQg9@!R*lWICVOZ8k?W&EwtC6;(iH<(Jc=0p zI)spq$IPQS7=am}6d?aL_aONIP0XG+mlw!6Lvs~-5 zC#*L8BYPXUGSX9??~Z-U#sL*^tmhkWyAJlIh}Sk;cJ9jU6kDngozq%NpAn9IeaoL~Bc~}7kQi(d{$P|>Yr>e# z!S*hgQo~)4V7QDS-4Nx8?+C9Sc(CD@_WElknb>ql^U60a)g|3o<|Gka%`cA~J}V5& z-Gd-RwfM7FP#ZjmJJtBMU4dqb6!c8Xa^B(J~ZO3X=f9|5j2#u2GZtB5H~T$*iSN2yPI8^MW_ z2O*;{72UgapqS4(WcoKUjb3hNbI7Fr>A}@En*`WcPkx!)Xo^y)-^95lxrvSWQD*{p zr6T_K-}?5UfFwjy-B_C7x7iflg~hikQC|K&UF_`NKO8}6OtseGIZx))lc))XvlxnF zphQF5Vc}WuGC63Gnol631oj`H?5Hy(^2_ig&H3bGkI!)aWMo!tOk`#<3J-hRy>KIJ zf<;$iGT=Rj`-QWUlr!d%zl}i`6n*^pwYwc@eEp@XP=&%Y%cDMh7&Z}g+)oku^ik~S zxEyAbn*M9 z1#xbLbG2>VK9mj66@;F0%OQhqw0%EaYpg@hA=?_5XQ}QleP1J8?y0{#g1lPqP@5^Z zX(G3!Nd=XE(Mh#P}BGQf#YpL zvkl6finMwqPm|#`7F^t&v1d^c=M9%hg+ynL%v?W-sEglnE;R~z_74eqsHh~TjUW1p zR^Vl;$-+HCAr%w@V`T*8)Txs0!TV3EK?zhbZts@dQo7Za;2v&dth7b+WF#qMNcu(= z%^yR;F8LW6-YouHO97NqS!QMgQyVFwEliH9ax?eb(WR%<4a~#32P(UT*IyrG#4i<$ zd2Eb*;6Zp)&@Nt1Iaq~yQC`>_vxN1JvZ5%+e7~JepA2n!=u^^&!7YpvsYY~(h038H zG+#CfrrOGVOmouo^$aI~v||n^#B!6K${krnKheOcWU2{aSSIhIe4qI9 zwA}oy0YjSmmrYnTEj&YjAoZ#S6toY)Dm`f)IEN5qz&v1X5)}jiwq0_@H`%H3hlQ8~KyeXZ>#ybRrx2Ws^MSePL zdrbeE;57W`ZCRwpisR_4YqPHxgoryHa%;jyNkf1;a^A}W3nNDtQvFw&cr&R!?#HF) zwg1>oy-3%i66qtWjktfAR)K9>b-FQp=AvYIZ7-OZ<-8B=2galgjMsV`YHJ6}_&B00 zM=Jc@l|fzxKqO{>`;V)&M@NxDI^KTlfnCQf8$`FnP5wawLNm2YO_Cv}AlAjTR zBxsoXZzykh4ZNe}ccOan(e>rOryOimO;vAOaDF)yoeIm<@BKLUV`CDj;k>N<&EK%L z%_XjYjd#ydE@MQrzv~ENiOGio4keeBrOtYjprqk}TsUZwoTtho))UPO1lnMOb>ygC zQ-xCDi@D#$fa6OnvV#rcoKK_E|0~*(;q#;k#zymsq-{9VEYc-?_VReS+bGg&Uq9eu zp6PcrBy3!~hWIu>RoZ0uar{+=z{QObMF>x~)=Di7Ry|-k)qp~9ERR?jM8`rzTu=KB zaSUl>q2wlT`#B48$<=%B3vNy#YAHrGy8)Vib@3r8^5{2vr#6$*ULG;@5`C)+%ZX7#W2vfx%PmJozTQ}Oz3>x&8;gvVC0&&Qi~1aJ~rAl?IG za)3#%ldRU?1Z%>~e@Uvs8FV?!@$Y4aCl@vYZiUjCe980lmGpp?%-!cL9`-CaGGk{> zYputZZb|aA`@JVjHn@-oUUGrTedgR5@L#KXbnNMT{JH85)ArWoxu<7KY6NS*<_^AD zy96X;dy>Vg0)!8f%<+qq)3@SLI4*iZz`r@I|KCU475QaMZiS)CFrdIUOt9IO3vL#1;3Y2-=W26jO$QN9&!HKr(oE6#~Z?t}d#XWOi{&%jVY+^*lLHFt;2K4Fefh0EuVbyoSzjNQ9 zGG+$MCrh-Q1>C%4E9c&fkR{RDq4rq)%3Euuo2$az_Iveun^tqToN=oXU`qAWnZC8pIdFvFL3&%ryEH0mzWZC$JVO$!vgu>$hW~iF-X$-hPv>o31Y!T z0jbWHSPd;J=tZU*l83;RS`9Bd*o10RG_B(W#@Ex3k0H>15`DeUE?p-=g+^i3vnr44 zRjFTm%`@#W5<{&`Jb2%yupdIpg8tEK_u&heO)rGD?IHCru7v(}h~(#g_o@V5=i~+1 z0Lf{xZiD^VFm9;)38l^~db=DclKg#!ZkCn4tSvc;WdgAxO69Ac1*9y~@Xp10TYsy+ z#4u2;co9Xbd3`j&l?S&NH;MAusQ%xDCai4P?d~_woIP`r0XSm4fi|xG zzpofvd7tofKE@Wm%(1~frUkT;qUxM+L1~lY65F!$dxt}$CN{sO=dkh>0ut&upSBXx z%Eq5u=zC@IRYF@)O@M51Zl$9+Q}g?MLdblsD5FK6HEKlxeAkib-O z-sgYjvq-Gvk2XljQ@%DQZ-`w4bB=*~0Ds8G5V4CW3Nd%7gak@L9M8v}rj=v>7=vup z6g~2m?ypIEQR`xZUbtt2{gTxVy!Z$qPrES#DtvclF6!#OM-9J_ea017UUk%aAC7?Z zw%W}(dQn+iUH|nuTjNVCaDF^4mZZClGRFCZU#{@a`P}!2?aaG}-EwopJb7VAV6?D3 zNLwQ#)jCm2MZyEhlT*~aRFf zH~oEq79Y0WHl_j1uw3mobTc1rJ=K4xP>@6wYZK@}e;Bu_<&FGCs5BS0(axYZupEC=epRR_7)+P(JFuc|vy$hB@gH`aDO`HRbzTNqBb%ENcx^^r- zk;~ShY_n@QCjkX)Tu21`8ehjLtV2>S)I~BrJVwjX`q+EZGj3;KJ6I1)8>38 zssw9F4=pFHSlqwkPv(8s+~feB=i*QLlR;qxZ9CbDhPn5HSC06DPNzL0e`}s=gDS(A zKfcC~L zQ7xSkelrkv$%cYMDmPr^nhcZ{=H1vbrc~-2pch|%C%NER zeenEy#}ubG8<^dfAHav8Su7@lc^wn9h1hi0T65#Pf7>=N>y5_pDNC z?;X(e3my{N9M;n)zJ=Se_F>fiG!@?^9P)|*3yJ0r!THmQY3P&+)>E05u6GbPJaRGX zO3Y24aPb?nV=W?ym;sZ5nk$HV6BT@~nSA3N|Cb(S%1e1cTzPYD&N}5WMxQ`EW3C(r5t>=0F z`?s2T@6qebx3Bme3cxT(+y0fGi-qpiqAs_WozaN+j5O~ zlv^Pg4LeBiFonvBqjn_~Put~JH1p42J!=xOIBhnxS#^Z14E|~DxxY55?WHbj!XFm* z+ZIA~lx;Swjs-8{%bd52=xZ<4J`R$0_ULqk)pY|*CGXyVd*~wZ(H&{EEv^U3dFd83 zUjU2bl{fKYwWb%;n!|0sr^2GksBADbhES~<9UCqzfnn-u2U*h{VL%K1dT@H7KA0x4 zdoGdpa`0iA*7vN-F^j%-LtV!UMPku584CWQYUSA0gim$*j>N2*e{9y@ARb73pyys> zhF$a*>O%thYY2aac8AXDL-0j_a2ewx%L_bDYAK5$mzPYd5l90{q&N=9*)G|}{07vewQ5y1w zJ$a$|M;2OV_s!$#B(v5j`S!8HHOe_jYi>1s+tHLH&{4UD(Xzu!d{{L=f#~Xyjk45- zzmS{12O3ugsi3_>WmIyhcsHx{O4jfcq28McXkG7bKRp#LV9CD+#h`Tl?=QnMI0 z@$cQ@)L=&Zi7}e~*~cw8_%OM6@txN7$ZU}8Q$ZQ8e=Ns0`cK2J4rmRb@++qZsZJYk z!v=$FGfMK=j(Th;pvE{2!)RJe$IhxgGh>w}7nTPrB~(ZM1Ieo9 zmBXUXHuv`(ow+zW0znE)&_qF|q*|98{~)c2SMV122frc33bpH103F%+eSRejeg*SZ}jV`VH2DuU(;F zA3Iagg9}~n$Re@j0NCqaOP=Fjp!>H1tcn%P{6B@)yB zQeW+weF`xz1}ibkNhA-g)@H$^#)>?_+kg7RO+^f9W&FQrF zy|vc^yh=Vb!TGBg%|F(H8844|3&mHWbn?CV6Mo2kiwHhnetP9}N;A-yo1ex_m%l%uif6_RMv2~m7Xq!ogHP=4JswkG&QB!9fdV^Wko^Zb zAQ%W71k!v?_ef`)atO!o2(JXcpRnsvYIbntRkij{xvb?SUyQuT|{d8DnDU@e$FW`(W^1Z({C_+>gg5w@;tr#^akRYR18q zdeT%So4LsamfG-an)Ksq_RHh+AF`M-%iTvpOwgR}QF?b}Uw8N~J>zu47&jz6&yFw+uQ zPnacu%qmJ5H8&0D-xEwuIsR5Ak*vcZ$uByMk0VPb@2?!zT1IzNTJ?~9Z%{iP=nOmi zcKbad_~u%ho=#c4?@7*naUA4lM>%N=c#SH7msTt zd|RGUpm|wA*q<603#(U zt@BFn)We)ahpVg7S1t(!Fz@+1w1-!>{lf5tjE8jNY{SLeoOoyO&HH0AUyThejT)s z1pG{fa3Fm;xf3R?n@Ct$Ezx66v$?)qZ z>{`**029`?EZxQ?YmRj!C4?&}lbGhSeX!eINy!0o{Vmlk0&O~q<`Ai7Hl?HnAkY@? zI`bU1NRX>0OD9ekvon+z4@dlg8FhA%hQ_fKcKQt-&$I-Eg*gQ2V|-H?%uYiLl%O2f z?7HxqBQxro%_siH8iqVYpOn>S?$J?LrF|C9g8&${kQ&@~=pb0~_>c3o(n!Q07#AYa zYN->Zv&_CT&5h zf$o2R(*+V>6ZnvO^%WoQLLqc^zq$6!?T_!vaYbLeuSYuJ`8C?A0pRED#Z(rWt9$>R za2`D$IB6=3ll5-RC@8Z+ZCobtk)#AU&ea!zoFQ#1`y7TQT$e6aYu!uEwfwIW7XWg<-jv{R`naK2`-0Pm}D4kzl$*;`S%X}BcPM$f*@N>R#KGa=-T<&%4rAt#Vt1Z zY1h=lXF4%YtH6$*8|!DnxW{t{v^j|PEz>v|0)Nqbe6=O_M}6qxLeSHC_fIOwL>ZVB z;>g4Ww7h4)vC1Re5}~}3k#PHNAnytG317M5!lT!-InQrXN#<|`;H;Uv_{)J)R6nm= z%6Y_VlxEHM2V!Mi69(tx3#5AEI;2wIp2dI9=3Q=zv|?+%X{1~;B$=U-Ji4L3Eimf;K(fYpw>NeB>xV`6v-(@(q1v%bP#>`=Te5Eq~ zvLnV3PUa?F(7AnX07?^uczmaghmxPAr%_!Ue7=A7{r(_gV!NKEN^&<0>}Bx)LXpoT z<}OsQ^6m81`sbQ!s2mt)bUGQ+FQ0C*<5it3EQQGlDI8yP+SF#gA%7e?w$J1|Af%6V zShg1*t~g)!e9FJF*uTLM*XD3|n{#DZx(ZWNP z_~F!2Wz6`U$!0wE*=rJG6|f7OlwMOi;IxO*=c4&0G=sFf>d$INql%o+dIS|iHNHIm zu18|Gbb`^+y%8ZoOv@%pp5m#gdK&m+LQAgi9So&(pyxR`xGPxw9weRqntKGP+5L*1 z^D9mMmMYX}mi`lM)w9)CTez=4(CsMs)%Nj0g-HHp%ctgQH$e;9Buxq{U3V0JJN^C- zMzN5sR+5ff2<~cfGzy>5kYz^;CK$0g=<4gZyBH00=1}5Ze%f6=6E5k2I8A+HTsTZ# zK^|;?;GLRmBPqU6EjauGc+rH*3{UO4?_pPyGj;{7(9-;0tTtG*Vk1k-$@AL0g! znhTME^88;JjoSK4MjcOd?`Kf$PoU*E6)CLFIG#%Xb&+|HFI%}{+PyDt=(aQ)*jhOg zM<eWH<|8F=X# zzS4aCkT|j3#@Py26`O<{r-aI4{DeRkUvy!SW{f!d$t(eX%~^9fXXuGeH@8Fv54X?G zU&?{G!OfV~X8m*b0hQD7>#{HKpx@g}Qmx)JeyFX1R}{6~aPf3i-SyEyo_LKHN_X&3WtSFr~i=-)a_%HA`5YGSTypS66;}Y2Z1Cog5~xD%YwHfyusH2 zZulOLNV1pc$?`WEa#%340&@4Bp(9^9MXW_7p5hJ%?fFVzOjstsO5dnV_x+nUtI8pb zf^Vo+Hq6&+Gx0Ml5A$O2>InCQTSH_fCsI`ENHSbaD7MK7@3f?Zwb`0>NZ-4r8SfB} zOwJ&5_BAy(>Md=?tx!3|CTm~KUq`X0UuuUjR8IK-QBbIfDtK>+8$Huod?Rf?XE(TS44CMpw%hpGwZy+>Kep_a#N6CY zeD@w4^f2EF@@@ggeJ7H`e26aQsMI!Z=&<|RK;J$T>ng56Kgip!Y(am?ddPaL>Pes8 zCfQ*$aBJO zpK`P>T<5}5l&CUgJwU~30D}*%cH?{t2DT;oL*|mjn{W4RK#z-|e#lU7OJlM;mj2Yu zg!<1cH@mBEqsx3#8;HWd?J}b2p}>Q2az-Sh?0+*|8AG)45bplV^RPx_wEiOd zuG{$YKuRtR(jo2F6>XDY8~Qg(wZU9Q21|z#VHKZ;fDCOBUlqbpA^9L^v+#?QTYces z@9FQ7&#O;u+U&%AXtR10|1z$yR9EUGKs3S5-0@^#VNd|Z|J6YB2MQBHVwFHt7eeAw z?+WMpD9&Zx4MYIxZd?il>F$%G2B!k=x7WB7PJhwZA{q-3E~ezj2Y9;;TQ4Vg0v>qm z=18Z=Oq9cXr}RvD`Ue*UX%r?LLv0Gi+)P&Xzd380{7feWnqEa5|E4A&zVlnEA1@mZ z#I{xQ!39&P9dKL0^e1uHkjux;1~i@*Z?yGK{9DPmKwgMW>~hV+Z3?8d4y^8#G2-`U zyN8*;)e}?Qi*^vkt}1FZK!gf^aX|;(tw|btq33p{cJ2GDsJ!N$;0LB{);1r{_?JDw zF)5LZ(tZ@lDdSGi6RskuP=K_*3V~W-<@m?VK{R@+yT=am`P>B^w?LpI9$?_t=nFP| zsgvm>q4u4|zFmFykqB0{PRKjMNA@Kz%ef;f!s&lun)T(>L}i-O2(7XxvEkgkSzeN# zz4)Il%5FJj{Iizq+goU90mrTd0r|g>?-}onQGhyEGPkb3R9Ba_+I{ac8W>9jRXe9~ zpC#&8&ELrF$&hd4In#QNu=W|VK0fS&ikdcn*cRQ=PV{$Q)VVe6E$sE5fj@edJCp{g$2zAgU4A_J62 z+<&83aQvm+lF`N!Wwad67`{=p+!OdQwQ*PDe71FqWhNb?OtA4EE=asrzYelox$5q} z=J=Qt(!S7V0JwpRFV^%yZO9Kk!{ys!B?{SqE;_Ds=wNBFD+q)H9+H6r7($2T$wY*s z+`nE>@VN2x#$F)cp$r_5rM>uC-ECwvt6uhATmkMZULIr?o4(g)WENFPCw(XDO^&ET zG97F(GyYQ&3F9N4J@0bH@dtS!AUKGNNV+XZ zmG;38vxL9gD%yH05rv46xQapq2cRM^ligj4G&M+H%Q4hZ8<=+7kG0w=@Q}KlV;P{l z`1;bX@kGGZ(g?I*h5(pcu;Y{uyL8=mb?Qi_0Afhh3J-DA~4LkHV;(-rx8X2xl z{D}{{LVMRePVW|RK(gM2E-aIK*Y|Eo4sj3vL%NPM@RB-Ehd0Rc-K~l%K0|AsYNB4T z-KN?mXA5`a1%t+}c29pSoF$=>DAkj>H&(4D>FJ zL#mwXudh_}((qjl1LcOAtU3Aq*<5a_m<3os587E{%n63TgzKgFWc>w;Z(Xl!J%=3-MX12llEa1i2?P+VgLk>EPn3FCcv-EbOK$f$_ z=jN}>ANA6tZ-%ZB%Jt$5C?ePq!phtn!5H-9-h*Ych&6f3?~b~O@>+(ppK|7f*X~iz zTg``%U+t7Uefhe$bgL!04L~QUV(-$q=d*_d4)(UKUBR6`hIPaOB79`l4*svTJrMu? zhJ4#D6JJ#kD_EVe0%sY7oAJ%Zo&aD5RIQ2O$2n4J%IqEKOU}%MHBTr}ov%WL5ES#T zHL4NcZ6l?q4-^98>nRmZ)tS2OTV**2emZy2DnRy|9w|luK>#De0d1$HerBFed1K*S zwa4ic;1udh_iw3x+jc^^q*@(dvVqTGVcKI7Fn+17 zA#$R1ZCLHHUby-GL&|Go*bj$Zha)}-IUT&nClnY6kYCS*k!O@Mn;qljJeMOfo+yKL zm#=Sxlu;L?#`o)RR3^LA2ig#BSqOTYeEXWxQYp`8z?aT`k*Bk=@!@(qv6$A5skA;Z z=huH9gn+LK+j{Fl`Mk4%yg$Oi5MPxXj4Me;-*@jG?@zoRyhojaO(#!kWRpFfAfACBVGnV&iK`O8g3CT4CbgB~dijLc`}ZA@lbwPZi!9_K>8{ ztnB7lL~$EZZNf5|@NaT#@&3IWaIfa(8k0QoC9GYfY3QM$bj$gdSVfaqYR;z{fpBpo zZqo>oQjvA$13VQqY6XhM>i|0i8NJlbZfIStZr4`^5hm`qRHQn+F1=RWi*~80TihW@ zkb9Wra2*E)wZF1)b#U&QI%GO~o3(qK&S)HUQ3*zzzr2-lu4w;=u>9yva?sudW{~Fz z1>g*Fjg24|G^#*H>U^HP5@i%-9qY`AmF?3ve&CA$AS(*!;>4 z4`|@Nx9&`Rfy+6OR&TqUyMx64V8m6_G!&)T)e6n)s9&@;!-7R{(GS?(X|=@k1VtR) zkfQjn>YY5taWe64)$(w(Ca6?6mR$L#iOJgFT8EGQ)c;R^R_icyb z8vt8SFBZwJYfbDe$chq2&i^CXn4Y|Sr*XWO8$vq|Z{~O9JY8DH2N{dpxugm>#^d#w zXpYy>xlz2oqI=YnmA)$2I>WrebcbD7Ga$}L1eHBgf9h!TP9wxl!J@kSFC7Pd8Ped@ zhJu_&@sk8Jc1M6Qlwl3rZX6sDd!9Ji1*e?j@|iPng()Y47i8)C^;!mmTJRw!`#6(Y%AOVR z8=cQwiJ#|&zR($)*$Ja_`^^PmWsT4Yc)jXQ>=X}A4X4ed<*Z3c%$Ix9GMoY?NQk$R zD%gN%kK;od!Z7*iD-%CD=?yK7xXoJ?1pC&RaEKIsz{$Wnr@BWZDV^#rK)R__&-lGQ z&ZgWq_i414uyVP-I$ci5Gp1}eCn$v#WE4jtA1q9-j9^g&cz9E z9p6CvlgUR~1|};P%0;_KSLx)vy(k0@f!!68d|0V^E~|% z+!el(_`GtkCS8=rX(ad|CE9(SB>rsWU&AldlaZhYK_Zyzy_e+9Z)Z5ZOUZ!DDP+-) z)IsnM;lc4%FKDCR%BhuXr=v*GOlZ~k=s(-^FOaz=8Rsu!+uxcvfXefosm_pk>799? zNzVSMgJ>slA~M)${kQWfmDRt)+BdBZ?(nG2vIeR2>4rU^!M-LDv43ytTJX6HHv4w{5Lm=xFMRO*VbS%+_vzg0x^&cNt zp`KWz0~=zauT3$9eAR``rWB$W`MQ!4RM0w;b66|h^EjX!*BeC)(>Ut>#R)33fBth%@T9u--UR~Ro)-gW$y7cxDCns9pV~B_` zgoHKZygK07advET){9u^kWAV>X*ObeYu)O2G zSmhs$bdaTn5~QI^O=?GP-n~0Jk~rorZ2(wU4(W2i_@E}G-=!7$-2?OtA==oleFMqc zx6t%$I(0kyKV~9aDgoj<#%jbr=Wv(ScWMeJKa4lVb1%rF>wy0m!G^6%22xg7rC?2~Ie%#W}EU&+`S z&j@{&5s{&?ySglk$W<%kyx`k>@ku?aqzd#LIfDP!%?nl<>s#!`yH?#lT(cswoeoY1OGlzdWKvl zKNW>Chvy6Ztmlw^Mp-;u^QRF2DD_8wkAi)WD1>`3imC)D-B+LaO_b=}UL}e9D+e+r z$8thtIn6wVm;zbn;iUXMIM(*rqBWwfYc$cc)-k{od0Agr{QWcmKLwJqcju#)2<8WG zo%`u^j;>D1dwpbR{77go@97?Vl%8Md7uHrU)%v3W(m#{x!}S$m=k&xlkTLz|+y)c? zmwQ@pc|Dr~#w#K`-V!FvZXoH7%5P%f=pXd%ZQ?`Zb<%?^Q7Ky zVa~I$vw$xIBm>1s zt{gut&3^QO&{#O>_Gqg#t^TqlF0^+h#pm8$HcD}o4!~SB09}2DOdQDop!Qf-Y1Ea$ zy40f!uNEWU%^Z4GRg z4-!Ec*(yxUjuD96D7>hphWN@8po`)3NjbZ&LheAfuIfdG-bqI?_6)G*!FE z+qcWq_IH}=4Ee?mH)q5f4R8)XuPAAn#jXicmE2_V5D9izGok7ZeWTd*=UYwLfY@*^ zNU;cafNh{#teEQQK0zZF(z zl}SDBC#@=?@#xbsHc)&fmgVt&?qJaHneXtbwp6y5799I4#5jsRVESmV|;QU zwG#Jhta!WPet%d4Nra(tTMU!~{#jc^I9ch@tb%ARInLXlZHQ9PE=jJZq2Ss`dJ=@j zMBz|vGhS%bT8lQ#pOraRJ7TUSVmj0vSopd`B#Wap?#d`^CsLCM4(VxQF$QGv-b8%$ z)S03)Jr~~!H&S_J+Ye>Bp{moH~FJ;F+{@fZ)-50~qg} zw1EtTE)yPf<&^sf(Q(#JPn1)4!$2j-UF35IC^f<~Bx&MbC+Z1AX{x%T^NkGwCNw{X zoH{h8&=d6>thTW*&Z_weQCCp{!>zH=xgAZ=ABok{FRxp?eyx@mt>7$!&=QWVD2G%y z16;9EEgTd~7tZ)B#nTeg?=C^;$-|JwoUZ+1t7`?g!Uxmvn za*93p5dz%(WA`Faqb#GM5>}7Gm~RNu3oIL)zf<4PY0`hS%K`IopL%Egs8d$f1^adV zCH%E|=@Mg`B8)5U!|23@7%GF->0b1}vL$Z!n4t?kAkq~Db$M-VkSRF=wq+>5%>N|@ zN{}|m$S~+TOGE5SeT7pzKr8mkeUGpxZ!u~9z9at-I}(Mh7q`5`iSjsSZ=v`PF;66B z9i8vY!KtugVwXHP^IMz{E|kNZ`!YCPq1%vrTcBBcz%%tG)RS9{pfd%^LF{M7h=<0l zuPTY3Rm?R9$wcI9fvM3dvkpHwzfV&e-_QT_KELF~Y{asvsI~)fqhOy#i^3M|o>Q-U zv2o&Lo+k=rkjtO>=;zG^UE@3z1Y{UNvOQaBIp{eP(pW0H2Psf~ZDuj#pHL+XV6E&r zF1!2-e#8kWkUz!4#8e3BEG{(^+S^(4!OE*(`Q>P^V*f_bN3Q6kbX(&-o(?Dk9D++v zrPvNWyJA?#dTVXo^9!k^f2u0DGO_`7SDJEYIN`i(MsB|SHU`dExC8fN%{Tfbg^RM; zx@rS=!Hczwri?DsFi9B;5B$;C#R2&w&zsDnW46`Kp z>Fr%TLgx8v89R`xt2b042$PSeGc>)+G!8vq)GEvr5Clr8I>hdvbWN;GJY`iNrPp(# z*w58 zfr`ny#z4s1uOY}u7ApXT3C$;oV~cQ=roEFJgF6N|?_ggGtS?BXr4-q+i0USj83G~5 zJKVy$T5?M9z$?TG6XXUfe%sA~kGp%PUtYU|L7S%&Ajk}~Ugo()+}TIZ9WjncP6dgY z&-~)#aKGxD`(+K(RY!u&HU@1I_Mc-ZM!=sa@2;Fo(t-0)aRNhm+*S`^TKwi*dZF(h zL!9MzB%q)$WEHxX_>18&** zqtMs>Ky=WPo18vIm%AGx&J$; zRQxMUU6KHJOp-T&7m)X;OWUhPe-GViuQ7Nt`)c%&fT_qAUkQ@Q zePRH3zt&`n+TMWg&wGgmJg zX(3ueOsd9_wT)tWqis;15cIzqcC&As#n5H>N&L zH7~rEuc7=XX+7F$;Pa@hh>xO8gq{2=`ihVg>!3Ju>aYqy?a>(+q)v-zO%9{MsrQ#2 z5B03Pa@@!vZ<7SZ;TiHV3xv@Sr*R(sM4@H+(GyJT_aSTFL@BezKW=)==bf)HAT%i9 zaI?B+58@{RSooVZjqx=)z!|5xq$Y18KpE-PEAj~bd;Qh9UzS1@1uEpXP8uc1AV}~T z3kaCY=j>c;Xn1!f_2MxLUHxSHhBaPF;ebl;cfV)9RD|f%-`*dJou;3%4*&{ZvN_{rV&GG&@Rul_wc`j z6DO4USk8g96vafc;Dp&AfWoMn3pyLGaDP~(h?sh)Daw#3Y4wY;^GvMZwBnTKUoig& zctM*!S{kJl8A%R&wxTtvP;=0f!c^kL6kpcNRi}=YM;u@bRPY_1f;;djvki9JlfPQz zi;1UX+jM2rr~{v+4?rL{G#`Yq>bKpL0F&V`rJOWVq6nuurm$>~^u zOa3{B=3qI+V-rd+lFFwInbthrvWYBEMzG&Y&G97&QSHcqxGZIM^aQ^t;*JJ}Okcw^%|r&g-x&Q|LhwXj4Hi z@s*ekt-kl>vP~<*TVi?sFMy6xY*$kqlmqn$ePIWIFvaDu5s?BdTTqVP=Q3o2YOFtD zO(#B5S`Gaw8EIju7N9Epx+x)RLh4jSmk|Rst184vst7_^o1o1UAw3+;uiz zc~PV=z0|W}*qKtPP+k9pPLLM5KX9FaaETK@sP1f*-b-(2#q+S+gIa|^haQ<&@g%3+ zC&F$SYhfM%Va#F-=-H6R%=*qA2Byl`0@5(X2vo0`6}ti~~rX6uW4epcJGE*K`FbVoe{~9y!#WT4DwT})gEH>vdQe?1-X~h?_x;o6 ziMBE>`SEwZi4!_dq|tFcsxagaLmc^=$;t}nFsII`%a-XH1l1OZd7G}8gDPc~$tU;k z>#amh^qs7!mHUL$VtS5?ckj;=VunxTeW-ewbGkw?A)kd7JJ=_jUc4>ZpJ7q7Hi`7! zcz{~bg>ems)uywTAj2qsNpW-7U{-W7S|PYrji((|GEH@|E~)mp>*d!Tuo1s?BYn{h z+AMVG?^;qYo7Y%ZLj$X@I|#OYPZ=FK+FThck3552LWU_}O6ADQgoNxW0xN6WFt*Aq zo~dMt=h;^$RMK@Dm`u(q0#_}!@2NlJcymRX2S!qN$d(8sPp`TN`@KEn9DF+P0WizH zlQFmb5|uyl#AzrB6E$ls{JAhAo*<)9>>u%9@wN>v>L}V;*1@AhDg?v+gJwW)&&=5B zJ9$Fs@m>#qn8j9;pBi4s-e4+(YDOPo#-|I9{rDO;ZLs5X#9dWbDFBR-d}A^Qm#RoZ zrdv(3TFC47{oeH5em$Ozi%K>2H}=;`#G*-rmm(}VR4Z1iuU zlloBTS>4mA* zJQx6!mcdfl(8nU{S$AqpM4f5dJJRAe2M)L{r9(kZ{#6HfRK2oIzXG9JdH1JxLr)IU@&~Lwn?&D)cliB0Rxa+vMTdiSYNE;rM7YJeQ4lTW4=CqZh zRc5OBwL+TT6mdRL%ad-1+a96eIe{Bhw%nyMP+- zQP1#RSM3fSl)IH$LQ7SF79c|wEDP9N+sqnD2m3p_J%;<@cn6~SG&!ajsmYf#5UD_S zV*f&)j>Z-; zx@1@wM5~2w(H@bc#rg4X7AbKe`?%8;w8%{?(oJ>_PhZs8_oPbjOVjBn87nB1Y9zKK z>F`rZY>)f!n`Xzbn9l7KP(*&$Cw8dyJsQnp*?fh}7+*_IT44D7%Vk}rBx|8;Ol5nX zK}`h*nj@cICY|=FkwD1Al`HN5GJNyk)yOk>qgI|z{G!Wv;6;4OvyHd+bViNK6q!p_ zyBVcpzQhGocA+tgWRT6hAPZflXRPG_4EXTq;o~m>H`c#*mbwbLNx260KbrIFa4Cqr zX`!Mqmc;8zZY;r&#C;-c_}kwIpruNly7XS(N&T@U_eeiPnDqcHg_fUvD8crG(f|FE zo-yj#&}fJtYzXaWM8m!!r{!itTl{_QO=?#(P`htpD_gQz4Ypx!#AD+PF+# zS2%|ws*Me-mvx%O8PH|u-3mfZE-(eS+EhQJ*VitjqB$Q;j>4k9E61-nlX(^H{%Nnq z+`soUVC&c-vgNb=aDmRjn)Pp7&uQ~W2D)pIqLTc+7AFsRsq-Qtr0-PD>f3k)YQ+|+ ztS8SD%217ej$7x!@8sA-xA;H9V-b#3nz<&%$iwFHDl8nJ5Up==Sh|{Z9o*7zqpwDx zX_ct-*N;PhXCTNQ&C=u9%S4-(Yx{I$lcMx}-B%|NHi~yKLLBTE^uOJjT9W+sS}nM^ zGY;EH;PB2glB5R|9n8!bQ<%#IBvKVs%6fB6+Zr6Y$~^>8ZIURhFB}vPPx%UQ{RdTn z_@6YIybt$K`Oa2wO5Fp#I*8gSwC(SkyU+7K%^jqgWH+{NT>Cj7fA}7Wm9DvU+soNd zDhK?fc6Fpv8xOXm06<`zn}Msi{PzE7IuCa?|L_0bi6C}r*G!9|cFmgMUDV!GYsA*t zdlQt}rPN-n+G^JxQ8j85rAF;trNrL+?$7tSet$r&B(MA2=XJ*8JP!rXy-a=4y|ojg zkOHlRBczGt{a%7kK~tmf!`8<^r0c+Q)JE5p89+$@(*o)NjZ*0D|KW)HSX-7o zG-&mxbXh--=}6=rjLxa@KR1-ngFS?Ne4psw~9Znm;_O{4j z%mzI|kd9NE64~TxRY-j#2ON>B-^-+Y`S`cqK?7Dzn?v46F-JF>L2OpI=hKd(is;aH z;F$rZSUu^FTKdKlb&M*?SfHF+Nd;vA)0s?3exHv`QFUtxQOHDop&{#Oay5v?$9_lJ zb?+sI<_@~@NGi{|A_JV3Y=GQ%LI}*}Fuj;jD$YEGA7=XU~Z7HHp z_w=5NYG`FgX~vQ2Wt`5PrF@Z+{13WeWigVY3whNPNTP0oUQXlewA66?B@(zu=f*(` zRPu6-%S;b(Wa(#mrg_k?b)9&FXF4ezbpc` zLkr-_Yp(6-ckv3`K>nPmJE1#yG=q-lRjq%f3<_J0m)X=8aj6rxe=yZ(p$$@*4o zm7~weNX!MY&Fr+*z*BWhCZ(DmvCK|<8%@2v5qx6`DOi3@-Qz&5Q0-@vfe4N;NU)X1 zzR01{HU9Ks(x{-JKWnzknmEi-u(To!$h-@aKI-MVx$fvztJFr|kIu99q;-VA1imQ# z7*eSmuqk>UPlQZdWu8j&t?|=n7O7>;QeS=&GRIG)7u6N8!RXANavu<6(qNM=lzhn8 zY7dPtHYU+FkS5`O%Y)1;`oroLvfBc}TaMq8B&}Pb;?MH2HBB)Ixz!>hrhaZfqf3=& zwn6X8fO#ge5IgP?v0u2y@fmB?fY@WndODiR45=L6o9z) zov2c)XqN|jXYa(LAE`WEV{dAuup&Z706c$5K0SpnF{s_I)ZMVFQ;rJr)Sc`ZNy4C> z{UHtXb^sj~OM=S2yrDjI*Pf0#7bjylRG6#alj_)v41YYOE_E2Az>CP*{QLLd=@iwc zIhkKKJN*zNdyo0Nda%B_-JnSBsz8SiQ%(W*fB3@&eRFO|l#b3)mlxfXLK6FhRx$OP z3Aeqs|DOerV!OHtA*3GpItY`28PK?YGkDFJ{It}YFOK}Xv@lTps*z3L3q(KO(o!Me zuh$@>st$v`yYJ$1pf);ne;8bvkn6EA=IhmRwrfZ~RvM5iz2U%HPTwXowi5aW#4O*NfwxoS`W7rthm$7=G@b zw;y~2?+=GBf4PGc;`op|6h=QFLkNHRlDgC1i$3rAcW~R85-TP5@bMm!Qf)P9 zvvohCiuJ3o@`DYdGb{cltFlOK25r~X6G`sCiMoPSPwI9H&Q;$bo`&N=V(q2Vrx542 zC%sPZBii?@Z{%o-R{bjc^mqZ@Rw%jGfKIz6$b8OdTSQn1$r>WyQJ+NW^Oz#kEB}1q zvuQeMBd+fGEI|f63&-QeBp0Xz?iYVPJMbY#-|mAng-^{R>T%E8AJ^e$hgGcKII22vl7%*mjZHb@4>Oz7TiNaa)8OFa&K_I#w^z``Jkny!lb9n@q?vZp4OTVm~s zt`bMLkSTtt0v|7gpAN69{Dm3kW&rQ{z2WXlon6YV*wkag zem*zL&Gl5%Pm{GuZ|Pi|a6e)2`KMCwNm(?uk9L)%Srkk;GSQ;XtFfr-fih1$}%)UCMuUl>!Kfiu-r?skj9y?k{|ab zPIAq*nOsFZ#?o2L2CWg1;;Il~QL*CMN!{VvHoa;aV&z%ZUW+6t%TOLJtpDT5I2)=mg$DrkB|M5r<{2mP zw(pCQnBu946e|T{Xka@IjD1ffLR(ZH|INk2MjFugjuifC3fWweemp}CwdEVlcDKWa zWK`WX;?9+!?B)4P)l^*w_c8f}`OH`bCi^}Wv6*Ec)812a+AkPf=YjZ)M^v3mNuuAp zjlo`R-vN9~4I{uh`@Qi$%rUFJ=Jy*dk1IVl}FiC%p*FMC5nEYSK~DF)@&ib z>_uNy+fXgg{5IR!iV9zk+Zy#C-ggol>ln#bz!geIo;a!R?s+F~-zoIhKE!I=c7efo@BC zc!#Yq{aUcR#9*&AL#F1KTt;tr75TDw6-7bqXt-0kn%lXbjeQB9l7S!MJx8!_J2ABa z3jW>yMT#cjV;$8b^)330r{Gqdgxn6enIv^@(k|D7caol&YMj;Owhkza&%b3tC^1CV z|38_(99&vbA)0@Dy*s~Cn>lNvndwcMYhW?Tl(!*1>iOhFsbaBBm1&m}bgj^n#<%jt z)WmZ#8PpL=VaLC4(o-^298OP2U)=e4r`*ypy2Ia-nr#{Oio+d`i?~a}NMv<6=lTzm zhHX7EI_V?Qgf(0RftKY*FKc_nbd|(#QpBx*Bmmr(D9kJ@(Y)*6w?c&r@rQ{SZ(fbG`A>5A{{L8xhaePOBl}*rYkC*UsIoZmE z+BPamdQwhM&O5M`u( zEvtoI?K79-8s#20ASL1z|LM|&0l9lg_dh_1-rvt2b@O+ptZLBOc$Q4G`cp$V9Me_{ zN$-PKn+r+tAdV;l4^AF{=-a*VeClZ*YwBbbneHsgtK&!8*E>?FFu0)zt;)UG@7y%) zY%1vwh5qgEqB}%v zp^U+r;Wc1nJ{>j7Uo!Mi;FTZwqavc1hYU~rI_jCYRbs_#7ScAS<~))Qf-dv3-w0He z^w013{E+uCF$q-@Gmz(>Wqn#CDt&S>GckXD7foJVE|fB0s%MTlpvsD_L82lU21v()5Nc_DEKnwQ z74I1KPeo@IwGo%5Rv5oq6W#26z1Jrs8H-q}QJmi7x{p8F?mRx7p#T-czdc||Kmd|J0Uh2 z^zct{5@>Miw#a>*^NU|Rua`D>GWQb>2Y5?N3=zmOcO)A;zily%UJP9d4V`UB1$*mX zkWX4(CM3C1j<6uQF}0ykhgT()-(r7hfOPA4Spr+}&L8prnDtIFD$COwtR-kk!TFhN zp8OErgd3g540!KfUp4vuXQP=BB`7(}-0J&gVV|1(3%Nos*yLT&+Wn2OF+PkpriO{~ zwZM3fe zoO>3N4?DgM-5QD+;-AXK&_<^@!kFv?w`bQ4;q z*p13-w2$`Eibn}(wN*zbgLOxp*0v5<$jQwYxNp>lp^`B|J}4~P3Xk`r@iq#5Vjdxj zAF_Y*zNGwNqPsKaI79H|U!3FS%6V)%-j7!UM2I$G*Rc!Tmzg|dq&>D559#GpO;`aADF9Uy-KYyr0iAzr`)I*|ge2>p0erv|7H9w_< zns`~@?RJ#O1{iy8RM66eVWjII=WZm={^v7y+dF7y#q`5aKitlq&@0oW-sGI7lR!f2 zwpSJKpD}1xw=_Q?o&jIQ(1h<>_l-%j-yCMXdBk%y2Vi;c(`^t}*XHNWpB#kB+rRq; z0a2UYR2mNTk@d(Uti5WVo>c26c>Rz_8w);b@YHih2mVLsf+x1{>K52DSr5ZB3|b76 zbZ;$`5DW~%^-RAszHc4^*t*LKB4>~a+{C7ys*@#9@MdtALllclihk( zSz?^h{yj_2J){ftbvv&ZrQ7$dL#Y3=65@W4)>p7JIhdM5CRt0#wv2%L295y!g!dD@ z^iv4-J7rslK+ss-y^e*c|Ds(x%LEFY2ALf1u#d_w8x0o-u_nuW_217QRv;Z@=_6<9?Nu@eBj$yM$K(?-8m6kSt>$+0 zt2?-H(#UwdPSlB`BTQ?7x-%931=YYw)e7<%9Aav0Y%(DED*(i{QMiau7wGh_p~GX- zOY}lOO+3x`WPLWv>b}s^qc8_)2;aFq^*OICaK5A9|MuLPQMM}VZJQmQtgm1EGX(aCe8-h&Et(8L$%>Xk;6Sm6smY~E#uuOV?^lHI7vGFezI7ugkEPx8Js{j{Q5z`?y43YvhV7MYsx*sF4d zrj@pb&L`cuLx31U5TUY~F>k8d2L;9z!(`8IG<^4Z3=YGEKHI{h2q2Ycyfys)MKOUk zqO$&%uef8chw5&=yxb1C|1xrY$b98DxV^TZ{%Hb6O*J%OHyZ!^*4f}>W8uTs4(%ui zyrBvDXy>C|j1l>fN;3H$FjU*Bec&sc@uBG8*FQptWMR|ojB}|K#8%m@|E*Sty|DA_ zb0ImpId@m&`mXh+vly^sw~Ara8K&tuTNA1g4?k}ITJ_P% z8$bPb?#uu~Owrh+=>6f>JVf}egN0;F`7t&bbP%Y!%f&j0%%8g{jr`a-9b<-Xw=F(A z3qcm^SCN9waU`x}jL2R-G!fjI$4iw<*43Cp_VUBA?&ie^bDvh^{@M-B5~NLJkCf;* zfra*VVo0a-&S11&GMw|J*_zFEx#a+~znOo%n+EIJi{Hm#!2D*GpfFnBh&rsC-z4&@ z;?4KKaLk;@Nq@@-`m2F{;` zg`y%g%p#T+931vbTPOMMuIH&coXzOX8(;k-fk1|>t`@II(qM3Y$tu-M{#cM^?A;gC z&vBP?FpI?#Xz|<)&}rJat84>>m!2YX8>`nu0g_CJs*(+!QhVL*vB}_m-Kt0gdbUVuf;NCp2zd@bTAz3O+L%+6 zTY8{mC#Y7~UryIyU^5=3bBbZY3SzTMd-{TrAkkb~3pc+UvNdU{d7h~+Jn2^ELM#{D;PN0h+V&S+ zgF}HM7CG2MgpuMePknfK|NYOJRSm{~@7kL*K@Dm=jyG%4tx(=cLWxP?X#VIqAVvX~ z@NbywfJY3h7#seHMCN<2hbrefr8StkdfP^#uLo4GE;spmr5X*f|2^}fQ7%L0P6O$- zcyka~8%tPggV8dpZ1Y{d_#UL1+Ub7=UURpebV^pw6AGnUo!!*KZ2&?W2!5K&=hVMB z^0BA*E5S(aU$H-&sNL z9efi=@mwpxk~`tzFQVftnq)sh%IG6ITG<3}@IZhdAtwe9(CiP)`I|^*et}gDyxEN} z;UeN~K39UYolFYmN+WCo@Ac+mZ=_@|H*@F=@;^M!U6RYuWpZTBD!MgZxurdk>9@k> zC)JMB8wgzpMzlhkPHR~xP!Q>R8H`wP8glcv9D$?ULSe0SyBk@h5X2GSp_oB4ZuuK^n@nlP9O&T%Rb(Cy!JJkW&?cDVR>QpbYI38r@KA_yn zIp*cBQ1zdXkQ9t>(0i0gg7 zL~QF#1Rv$bT`zilEU;rz^V5Hizn{_CxKy6^J4_=UpLF=O#D=5~CSS%8^8$lWz{F<$ zch*}hzHGH;7XB{LMP=l3kg_svX*X$S0dcRFQxu;xj-L^Sm~-=L*UzArEc9LNFIgSC zb$rwS`8ZZFj5$~*t`-3M`Z0?gfyB`%(#`;M>D^&gb!BDZ-i*^0BbEZHWc9G)82chh zolnCEyMyzmc9iR6F~5!dcQ)6WsY9zz|3qoNi@;Urnn{X;0J@G3F8XavTQ!#pfHYKm zPKhxT6DH_eIlHKmx?|+Es<=oi_Pb5>Bhc{Wb+w;Uv;L#Ez53EOk!RB!vYVmt{E?)^ z`o5DCm!=S6TIUJLUd{FN6&rqL8fB1CT9F0O}}Z-vMk8l$?D5t z=hCa6U{dY)BH{fn`C!^__()VS_HC|YG+rM8&YM0)L0gv^Yi(jm>dn^*+X3HhBG`jf zu$XYA#@{G64vu&TSDh{|w_7p2vwZfCIr>GG?EYktf=P9U?!#sWhMolR$fU_qjhK0* zF5!ol>QdZoB=A-ql=`duKfV}Fv+W*h5$<1WynN(K-Qrc5FV)#t;Zf{fSF?2^I7ja+|z4}OjsyJ=`>fKwwg?G-GdpK z&xLIa~!d} z)@{UT&h--DAP{gg=U>NzWXautHmcJ44))E2vcG3NRLs7s6Di?_9K}WA+Unq+KXrNa z#6r7rH}<7Lw~G(O*D;&#)T5Lm(p=JcVw$cJA{WU@sD(E5$YyiML3;HS*qrZvJ|K5n&;eOd@ZNMPA>qxM(pY! z4T#pe(j~YmbpL2K6^<8OyIZIzAGl+>y<`yOhd`@-kHUk1hZb_ShbJLJZ>$@8W-SuI zD(esBwzZA!`=1G*38tkGtch$&$U>bfoR4XG9RKI~pQ||*SAA7l_rIrvUj|)AG@MBSGXn?0Y)? zzSN@ohX=y*^4d&rd)AoM?M{hmYg+dl-JR)Q5o|$1KcclBf7_%K!|t8|YXnf`%T?cv zUuIj_fQB(Srf#|G3L3V$;1Fk-#M|0>z;*b-($UoDUe48lM#ROhde#Mfuj|F)MmHKX0>;J&*!`rUvdL4AYZJmfo!1 zQheDb1Lry|p=8zKvM3k=bgjwE?SEKPJJ&s}AI7`LOh-0!eL`C;8rx7E@HAa+tmFLF z|MsD{aTg!%#r2AY7IkJ}O80}f@28Z~gM_6`MK9jaV*X!hicVMPOM~QBKPiDUXVXX? zasBkf&jBb*rQX$1v)PruGLMLcnSe783#CL!?lxks|bfXh!x^~ zBWdgknM-~Ahkt8Po>bz0PFEIA9iQ}9Jtc$w%uH~Rc87uuE)FMKY|RgyD}Ot=I`zW= z0>QLIl3+EI1aEjIokuIJ z@N=@d7y=x91TNmJ$38vvr*6AVB`YEJD#D4}85v#8XxZ8Y`q< zF!j?SmY&ogmpmmR1VZ*SJWmI8{_!!XfZHZ=()DWJhvl5RN0K47X$A3yN0ww^$){^Z?H4Qp^t^O||Py18^ZEYP@CKmvvN zt{D%EiqUs3hWzjJ3Mw1da(~Z6OosYu!n0p6&W;dNtIR_hG5AO{jNmyTmG3K~#`19} zH|9jo;%ffwTPyY=_wR`=@r@Y3Y@gG|Ng-6rjS!BOP_EqHD23l_y$KMK_`A?6-;Sqq zwYF9FGGs=oa5IZ+$%~{3Bn@hylK8s;8zTOM^HS!LEu3JjT_S9p>g9MthaZi=!dsI& zM@y@XRZmd0ALZ)GcHfH=3QAH$tqXk~Ik~_iP+F7z{>-1aHQ~$i??2KvCOpAa2hRUJ z;IGflIK*~$kyO$9Ftl#$&YtmEvOP2zFpisRI);F-qpdVM&qhLN%Q zW6iUHFOQfsmP5?EOS0>oroYa8f5iL&XDTu^avnoi9V1#4G8f_w4WKwxeO%fOmis!c zrFpC1QTPx)VsbEFlod+|x!!B1_r434@VBIX+a?P?`9n1zL-)@b5Bo(Q`$SQa>Ols8 z#Px5pNlXfDlr_88`cRr*E!^=;VArsBa>HO5It6e49A*)_&HmERrao^9Q4P&;ABZO;fs~3u1o%!TEWk1ZL zeY#aYa$D7TNUKT8dH&57RLu0%c0KuXU#_co&m{U?qSk<@yZgh4%e?LI%5Fvcn+o&M zfzJ=`+Eu;We!lCAk>Tv!FM7Qm8oHG|x6SX=B_&XC2dJOJgPKW;B?yFEdV#!23)WvE z-e~qgy43XJL4xUjw5|e(^!DGoCn`I?7xSj-!D0FWW~;OEy|#!SW9+YCt1|%H_n(>q zX0fw@JZrMwXfNcXedo7adPaQBUYUYU#50q3<#d2(5(AfY>#viBNfb%}k=yY9SpeN% zZnsniwE(1(E(F)XS0ZrCcNYNw0a{8p@!Kzirhbmg(qc(i3Kp5rdd+6*zf6qS3s93L zEB2WuH{$6wwVZDoiBe8SH#h3HW>r;&RSkw6?+Xor97r6zicd`=eCdMABcz&wu`G z(X4T}1yxeJ3+SUt;%X5vxFZ4ny}YoI4i}mM4p40~N1>I1bm^Nmbc69KDog9cEMh+{ zvJhj+Oo2AHMXivMKXuO>$3Rhydpe?yJ@!Y8+YYSm^EaR9+OtJUr%3kpAn6Y%*-5I8GXkW269J1To zK)YG>Ic1V7ruXMrPhIhcnKs)dJSE&PSp-9nMiCLTd>=R;B%Sth)E_YHsw+S)^{~!= zq0v}}sK}#EnEiW`&%TN;Vm+#I!-{4WRiFLxy!u-z7N=e+4X2Bvp~|+wOD7k<%eq95 zz}!QCJq<9M%h6^|XN~?8V=g_GP$lgOO;*vFs;jYN+WbK$Mz>PQX!LYu$IiQ~t*wgH zDV*ru!0h=eNVd^xn~>IV@9ZrLvj4*Wd&ceB?|XeC--Fp2nH^{LJ z#?$tCg|wqJuItZI+?ZMaA=zm8l39v^tgF1R+F3E; z^WGn9jr`T73+>sB@mdepi?7!*`yIjyr)fciUjxW2mlu5V9J-b{38|(V%IJ@Oorsp* z&3U>sOTp9=BtUjBLZKEe;ofoprMTJa6Gt06CDr$i`m#rdv{lhq35g50OpJAJkVlM^ zEl-F3w$Al1E&{91r}b~GM&uutgRJGm{4YAu)&Hh=v(7u_dtuK!h`xrw$JwWB=4w2QT!?NY{8B!245ms zehW*^Yj`I0*c3Z)O`i;*Mg8Gjtetg44q1br3~o|%rEmRNTY3*>coYC1v{eoy;^lc7 zHw#Kz8?9P!_PszE5nKG}8fY^Edn6;Yj;|h4vO6RpfmC^L?32ac2@!IfSM~9;gTDQL zoT{N|i+{Ru6I47rSeJ+qBCKFUY@PL%0^ONufJBeUph9JGblPgGwCqPqiuAyM03&zZ zE5GwE=FudsCX0)Lb`?3?FpGLP*CD%f>j&DY8{NnxT)uHzE3m)`sDO~oHwQ9LXX8!96 zROzc*;O*GS-P35rB2@x03>6$V#V%ZzZ)}CHuhB=ry7%?Z6NUclr|hEAIDNv7vjVF> zFZ8GKXvQTB>=xP{80&2Hu`V(FQl8~$Qqp6;>etK1 zZoT-3)gaRL9AMQ{PK&iK0gsKz9i5WFRs(4}&9(^n z>O(5z!R);HZjphkPZZQ*;B(-SS8u2+00<(j&L;7c-4@mGo|a0!K3|SLwxjl=0t+g) zn+wO!rgR_J6nD;yfqbRWu1jO_dX)pI!nXK32tbbl6o!I6DTG0D2qA1qB48!4+QKV- z3!i;491&)qK|_C+sjp_i-q~1{6sn(Nxfm^|=}Bs3>cI_yzC{bh;01lN)xj1ua(qo? zXgo~>^$o>jod2apEcq=?=6=5Rq&9M?eDuDratmbs$ye3nd`YUjpU~EXKB@otgAJ(- z$MrSxc59>wBxhbsiNxf|xj|c>vpr+zmi_j@)%=IMG$wkrG}%2%OJzG{MfokW@@@58 zGB-VLg<`J-aCk-X*e0j=fx-h*FqScsW$7)xbrR2=nUAO+00kh*5-wR?dY-8dyQ(CM zUU)cfM3bSGBrtXg~)BjsYB)bM+8$NnGj>QrkahyKBjMz$aQfFKT_%>W3E0X+TTcP#WO z_FM<{(+#DNm~U;*bht2H8a=*#=8J*bljIMuB6!_a%(jd4|9je_2umuRikFor3&;z+ z(gs07Qd!wGC?a1%H-neet7z$Ko8bYQTo0~njZTSAx)A`DJyGRDp%V< zZ}ykU|9hHgFRVT!_tcC&%)kH=xU~w7h0&%wKQI0%*>Qv7NjUCX6fFkzrIInMyMKC~D6E=nn?T_bi#}L2oH(mV#J|lcRmN2jsrz~_1J^sO{^UJfo962p<$(k^Uu0K$WF>aqn3>BM5ycGS%+Zb-kj^HBMY_SPanNxWNT&^A!%Y*c_2+ zF0+PA#(>%BPbR~%<60;Xj(Ehd>D%8X8aKgVDHxCT630_}PkF<-r}&youKQW7D78XX^Xnl)5pik(qS>?m zS^QUug!>;_a;}O~#b$+46nzq@UQbU<_tC6}kyUwbOeJyC$lVOfvyYb@u~OW_DrPb4 zM^HE^V9Towf*POmAd9aa4-)D*m{S*y$6HPNyK6VNV18sj)1Sx0dE_*D165UtnOD7^ zC+;jcNvACP_8x6DsS2lv+{y5fDV9%KdcI=En#5lsj}FW!OC$^l*;C>*zdsKDV$%0zOjCV+)+XRN(k(S zDDGilMr^Eld^2T{X1FFEWWa~_R&rp3xZ2p)8yqM9ryB!h?J{oX|nZ zw-n>)mj$_^z-lri>bV%EaU9JO3LI`#v=BZCgJeyIM(jenVmQDRK;u%+!QzL8?$lYH zlqC_(;-!XE?<&KBY`Cq?(FTbQ&qqG5R(YWHQ7tP>TYK&{knV)#uj!-a;s|+G+4B!1 z`wME@KSp1lU;slgY(f*T?+6@xCwS&fB0&cG)`B!z;(uz6>QoKl~ank{kaYv|dI zOxn^UN!d9H5Ix*IyhDc_B&ZH<@Uz6D{r7d7)|CN{LLd;|h?^KlhwuvmF(8G1HKqY7 zH}RD(9doJK2L0X1`F5HofgeBcJYj13ozFmbz96^CxO}LgXgiS+W)+E9>ED9iY<_Rd zf2mdsS5$n7FmF25ovUts#|*Phxu%RF0le-oqU{1PgBZ+e1%?@oy@#qs-37;& zqLQRzupV)?t>xHfusv8O-8uX+jMNR1&;oO`!P@~(kqCCM#p23H?}}l5R$Uzc1ny)? z2CmI-yNk)bbNZ8F*;mQZ23DIj70+v~M|$={2~axA&t0vdkU3A0OBvhW!kH7eJ-OL8 z%loAq_|R7m70zbmekZ6IzGs*=`G^9{O@Ks-G#~(j3@F0hy6g>w@3j1tx^3TUcUIy1 z{x9|UE2jqD7n9E4X>|ccE$GB+K;`MMpq}0bbU$EFYWzs$VFV{0d#y$~dp9~;-IB=< z|ILywSPHpE&-;Bk)kZCK;`guT0XmHnvHm(PE{_kD8=uc#@3x-$oOqKqu&G!a; zOmm$bda#8+MBHaJa8nygrS~N;*aJLK^K;cf;j`qCqqFh>%uVQDKj$aWTR4K66<=xbp0q#6KEt3*90>;v1lO zE6fxFG;=$=&E4+#YvbWM{?iY)(hXQ4syiTQzXewHFOjS;csL*jgQZpAL9wEQ(u=^k|aG#4$K71(<_ugCo>R0DBLMJCE_GGl&Ggf~SUwf0JnuZdXns(}7m;d3fy*->! z!p+2>ohwN1Ds5C#c7Xqo>GQ;Y zI8rtxp~hZ}m;aS$l$sd6b@$~9h4gWvCTV@Fwz-_FZ(n@P02Uu(EL>jgH-A6wh-?XW z9?CtOB+H2!A`@h#99<(&*OzZ(%>RBT>NQV(_ubKd5n5{#cnNdTWh{jAX5PD!t@mYBWFN)ruQpU2oGXpfvO#CF*Xid7cSr5 zr{H^Qs*DFH9dbw?T!Ab1a5kcQO>cn*R#p&l>~A+h6gAY59bXB6s*+0hex%AW8t(gu z?_qVSYOr2AU2l8G){c03Y9fA14~gR>)RnW$Rhp<}T+)AseuqNIkxmnYMLvE!ciThO zw0+W3ldoaUwOcp|_GW2c1a(k~H&F>aUbr*(GTX{<6sl#eMTH?kGC zxa}qPx!Tmf_8i|tThmosuZiV;aJe6!-H-&igTYjWN)z~B;6i6zPzLtm{?#TGx2|s@~jzAGWw3e<0=75CSh;C?+JQm*wB}$r37&e>wYl*S5lc+p(sw=km zq@ch7VLrPMwUIlfzb5LE%!G+jzzpF~{~=rtzS2w>3Ohdgg;28`v6h~z-|?TTsU^RB zYw~UVLzoQQ^*0%2gT0F#gL-6vJ;c0q$$oQt9-+}>F*Y|HbBlX==k_#@d=*p@)pZls zjwrncC|Kr+!EsUR$_OYu9=Vv8)%C%4wWFKU&O2xy0~ZC^vB&-m8HsD#7Nw-Wa?izb zDPukW6OqS!J9HD5M$-~Ji5aw!4m>gE`wDKTB1<=y5^i|+j8{ndvQE#}T{+%cl!^9Y9a`tY zv@R|n5e>(|s*>@b7m>sgK9y8HPG*tDLO1D~6Z)%oS*iKGJo)=aHfw6M$#~Juz|5%O zE&)cE7i-+%hFEIJVlWBi!UF%ud0RUePHz?@uP#gntseJ=9C){!Kk8_?Mzj#bJ{31M z`_Qf45d6=@`LHQKrt&SRD;@NC>l>Y>M^Su@eDUkk^W`tR3}~|^MbN)~&Pe!PQC=v= z+-p=_*Oq4yMp*TXG_C z+XbnVfXJRTaTRCGNi`-;_b~&D;4bl>@X))DDe`V{lvk%CbCHV2qPUgM*YqrWD<6s} zRyRL=4}QnK`}c_~HK0)VQ%M&ccMl`9gpKB}=q#ljr0o&r#{FhH60B+<@R_fH&0N9B z^R{`irvDRwJsBedd|%vHV=(1{`2=u=jWt|xXDdP8&GGx17sDeiJhXRn&tFop-1gvq zv#pmK561j_s0pBmA-4C>_5HYoW$&wj3xc7lEauC%L6bud+gz?K=OjW17_>a!YvAq? zKq#Ft?_ly~h|zxpuDRU|aC&!qlZeo*s(8Y-+l}U?TkyuCzInEV*&a{DzgAG?t34in0M*T-Sc1Z|Gw&QCM&(StB~dXqd5ph%_JjJ_Nv+MJ zXU%~u3!RlL#UtJY?LJQ`C~`%_k^D&XI88MHUf24Me*d$%D!wk(5O20r-fSudao1Ww zX4#ntxquzP3nf+mg*tk8(*;AFD=-SSkA=oRDPRjbbUeL}maZAoEH>iKW`lD#KXf>Y z$=3T3)fTWx_;;}47Zkuu-0;*fn5Q4}`4EC|yxhp`J55?@mE~Xaf^9`_=KBmkKIkn7 zD{|R1ZPIsW7D< z>k=>$!>hiVywwjn|Y2SJ<9M-uQ`YTW&TD&7!k?hQ@;$cdKP zdlu6(W#e|F{`MLs&-{N5q6u^mm_g;ZAvN!?yJn=oMyXRm^|#_hs#|gKRTj389UZHD+=K${dLaj9#AaW^S_CcjPtKO^bS+U;e^? zKUTDHVs%#|R&Y81zgUW(c!-e2NgQ)9`~-mY1C_WIO91b<&f_dxYd-GwY7eabc4t2c zngRqf1y;V(y{5E%h*BGI4vaX&tgJZo&DRb#AF)3;joWBL2a zZn<(;9BtFBo-F~&%NOvF9JD}E_P%{$L~tMkw~41Qp=A|s-a2Kzsk$zXZ#q2TNY}}! zFp~qQV>qss-Y1{^(8|=IHVk>;l)qa4b&VpE0gCg@e6DJhN)1Pv@zlsO4yEBVSMeQN zyjU1EMijqFlJ?sOH=RfNo%K%^rgt6KzuM~S|5WppW$Mstnk6W8aw?rSfIn3fTF{b;Qao7Eg80nxNtlGg%9f=xr18fr1fp=?G{3lV?T=9mI(Nwb7{jOsY*XD9oKGSY0+K=xOG5mSfJe%Ty%GpU)$0RrO*ud zY7HrP+x(pB`#OMDKY`KQ{`fk8XfaO-kUg;)?~$K7T;k7`Tzqe@oh!=rrnQl`@3Y#* zYiU-cmi0hZ>Q&#IqTINM+?PLD;-2$`_F{xqd>$RQS{8ae&!0PVkju4a{U)=x5G_3XJp$;(%oAqr_trQow2nl*&D&fp1DAN9P}LR)C- zJiO$${x;^ECoIldbEoGUH)3;_-z<*QvwqJ0+YL@pG}&OekOab>b3Z)bhQZ~r%c+p7 zx33>V`2Gd??(}i?DFM~4U8}@Ie_ZiC!l8n2sH!RCQc&=`myJuf3B-^0=ph?rri~(o zPkZ81V*$47d#InNWmf4}0j+5IM)62cq%czE@qR=T#uH|B^{ErZ*HUz3}!(_-wnvym%DHXG6?({DGW^(?sgzWc`>&2|>f2@tu%_oCH zR46PIfkXp0=f`5%5>!MW_gZ$|wqokH}u~fQB*^1VmJn zVI2?P0(=x@sb#3B$Z!BMqzna7hTy=5^{c;c{!d=X-R17y@4L_4%Iol~tf$TDY6|N{ zuAk2yKE5nic&o5dB+*CCA$7)2zDPY#<7fjzEomZdN+lXBV4X&%@T(11rwZc&d%~qN zyHwM?b&Zy0$Df5yzD6F0ZK(y*&cK#ZM1>B^d&#(?CueK5$NKiXbrl`M);T{pzmo2^ zDvcz1Zu-_KP9K@^wM{<_ZEM%I+Npf?N6Og;!OMNF!Agsdbpxkw;UW03h0rz5IZagdl&CM+i~*Q(XAh@ z`$ZcXJN)gJGJXB+1BIA-W72gmx*6y5icQgzlr9D{lEOjyIzh$ zc>~e(D=ZZo&45x>2Ge$EAvkGt(L#cb`e-^=s^uCQ2d_TYNti#~6MlVh>owoiSn+DX z=D!WXoF6{^xDfzU*;^d!-FI%A?X-v>+*(UXdqm1R!S#OUUR998i4#o9-O_O2N5yp| ztQW7RWYmeCDZJ?m^z}8Cj zXs9)Dy{n*Z9HrZsj_rlE09ang&Az6$FYg=$YKgS}InpH3Ox@!A)c7%D0R2I!vn&0_bJ zc7)4$1-Iu(SeuRZZHR`*8@4KF@kU%}p~YVt)mDBS4zh^>HPh26#!qJ_mCCASoG*1m z*N%`TE?V1RIbAM^{k3D+l7n`Q(%2wge4;@l^CHL*?2i*gr&J2tW1u>FJGm{sthEBCqY$z*MVRU}5#5em| zrK#V%_ga1C^~1M&pP2+O$9``~CkEqyRQU~VYJ&V3=NeB6p;KD7AU>}#K_cf-9JFx_ zQR{JIQsa)`6r8av+Fvm@t5k@fSWCvFtihjBoqS&XFN*=M@$CT{%IO4%mp+CH1cyYV9) z)kUVQ-_Az*X7AZscl#i$r*uH@Noi8xX3Ob+0@}@ZY_FE8od5Rb{OqZj>1Sb=9YXmVQW2C0mGH0cL^9UWTK)V^ zfCu;dH`}#t8{6dOi~k;Y>KZBA9HaRCZuk9a`%X=L-DfhOMf9D`4F?t$)|;`eA&|`l zIc7G(51(S#89fcVR;wmLf8alO@@Rpl=l|i=V_Ei)-qMc{@s){n@t@sS=0i z%|NV@<^AH|&(8*x!n^P?Ci zrC>x6x{6>&gx#btY16je>c?X8Do=0gO&Z6w$fH--mQrnzP;=9FaK9!g1BEBhJUG# zpqy7SY%zkOyPlwaL%&hVDf3rP{&kEzWxD#CMX?)7RQ3CN49Kn)uU&VsqH*Ij$u*zP z7U#hQEIfGTbu}e9NX(27HvJm$JszvC5jHyVr8*tHX!w2P!_*A#cnut{Wv3H@sLH(*^ z$K@v1U3}U8o6f93+X9kyEmu)elXMQN^kXA(Lr0fB4$-c<#cF4t}$E@YP}-qL)Om6A8sGC zh*^R{9)62&tM^^jP9i)r5ER;3WF+kVbZHz{;)_Bh*rAGKtjHVu!Q{PG;m|Aw2hwwb zcV9{Hnw|{;?fSwMe{JZ>*m*uptaS^OVsN6K38b9NZJXswLvJzQSES%@QDJ@Bx6B6} z{w`)=^J4S)+m%gCi=O#ys8iDF7m+dTRUIFRF-Rrm0nl?C+>K7C8C+Srv*lB?SgSUE z)DXm~@l}l1x7%hDcGc0>Ajn_|-=h zn8uNvnl-9Ivve#t!MD%<$?%C$lhbv5+++56!lc%RfeW?&c<)F6|IMg)Yd4T^O9 z+}C40&Rnh29jK}~H?UWj7MxmC{aY?M@S9V+)l;TEj}8YYMhdqi8_IWz!~BB&?$_Ny zYw+2snAF_6pnI{yY^1-#?DE4?gbR3daK|rXE%#cx z^Rp9=1cgpDoeb=`8Z+I7nWKK%BQq=E;y+8KJvupGA_#6st_UbofjMxnon6I*qxu&M z8|%*gNE&+E8YEFUWtn3)w4!?K=vU*pwcO>B&x=J&{~mNU0YF|OH+-HLq~a4SfE~cU z(|xRAy{((z>64zTb$!RL@#{~mi<^DFMmuG_+qprY;$Z9U@fsW#j!~yFd9q}A{LJ{` z?C`5hw)MeHwcFVHEc&}MA1|+fHcKydN1cE2pGvPyVMmj_{R8yt$e%w}0}d;3tx}eo zg8*^D8pnoXRRj@wHOGF?S^vhR%&*s~u+_5?OHZ0ZN4&?Gcc}3Lz{4%vc#JP~EY#h+ zM#wt0H{mvjx5B#1HwvD0_sec?YkdxU|CuBB?U^&WqNR9e#@p^>)^_XtEskco?{bz} zsq4cjE5aG3WI=hRXe2SL9=ymvfeLwokc#_u_d#J-_v;gZVv*sAN5pIZ!x@XT zL(DE;HWf5r;h;Gl<|m|SufJFbSf1x%ZG*T|GHxqB>;6ttk|~&x^j=P@@G5w2n3}wh zkL!`Qg{H>vk#cZp`T@41YWT%(FtoM9)h+B_n*U zQsIfoT@~gZ+fxQz=k8wj8r%{JD`f`8=Nl7lHd-i+%#N>LzB?CWowGa|-ks|9OQ$|n zZS=9ou*!{vwtyA`LBrT)&qr6iZYN~tJ+xtN#P}~!de2b zfQ?ddsyLenr@TFM^`(My!abebUA1cnTqWeS z^?aPtu}jP35NyF?#~`4}x$ymK#zQupRsch)v7}TC z>4iXkLL92g1jaoR^=73$}74s_V@(IQ?S0 zI?DkkIR?W1NR6ZJG02f(E%;-#);wi$ur3p%yJ2B1pE^g6j-|wVzHL7g%m7H6$?VXY z$p*mJoWEw*dkJ%VmPRx}{ZZa?88%FKF$k_Jjz7=im$}GPKa0Hi;_56( zwjXFbF0Mj&QT!FVQ=+}bRVUma`@bq?El*$J`P+u?SzjiTFP#cArjC}cAFZ*HWk2o| zMFEvji%{tadOuH8Jab@F9Y5k9}P z$Msx@3wguPe7p0Jg%L5Z%?d_7HZfu5+`P=8PJ*Hx>qxoT`S@5pF0jn}q)x`@a6#)) zg^$1M=!8-nKfMy*qV0xdl=3s}9?YG<_sP(;SG!Urps_IwT`Qb>y*Sw_2E1KM@l7mg zX>O4s9-Y(-+W=9L&vu}NdVEh2TjXcc`y6X`Btc}X0!3{(uilqsY`HiWx>9m;Xh{p& z%HW@pfIRK7_v}W&a?554&^|`WRXqcf!#;}C(4p`9o7aqAsfE(~v4yBo9EGXm<1wyP zc~sH1-#0!6z-@=fz=)A^Cj{pP^z1h0G(3872ClSy^NlqML4g&xR-CMhCn`;{iN38Y zLn7k!Rw{S~Q5I&uk3}Ay~(BqAW6ax^DDGsm1W{;*ZeqDOJ zLxKqA?DV7I*!$_FVUXd8es|aP0qI+0ZP%$G_#mEp~OGz??luvvU z)bJx)hpZhz)NRJeSp_j7QDBm?^u!w<@QGa4wHkXuWGNy6WB_p3cdy?luCA_#qO)rR zt)hT_8XQ~?t^`3|bkW=aRYw$yU!kV#Y?3KY&)EsYa<%}>d%H^7x0~wf?VDf?Su`Cy)0OE#+5xvtv3d*Ju4Y)}z z;CJ;sp$}%_+8;~&D*|em<4KVddCzMh3l%EM&^^%!Z3+R>MXze<3yjHGtNRsU4Il6> zQ`l~l&q}glR*90o$pyqt!&d=ZIIwWy(58e6fX|LVqs!5pnj6kYRKFNGRMv+Cx=6E0 zkgm6oJ;EMXqk!@Ylhqa*JPO0S+3CJ4mxgg23Mh3vd(s=k-!}l0?8cj?9>34U3qt@@ zS{Ajl+KHkQ6k$B1dg701>zJHZDu{dBx@K@p!?t*_LuQrbbEuZ7XPpWN{e`;nA65*A z&x6aR?7P=}kRY7HI3s&#^$^a^z;tNjT7uO-&Jxnvx35@l3v}jv1t_D2979>gsz`bmg zLfTZFME$8LK0CVU3>x;sry%N9*DOCkKKDqCM-whQ?M{N%Uq(YV z%$T}xCRYwD#48TJaw_L%*v6$@Umqao%3|9{9aq>rD}HO==d6FFjt@44+J2;j+h}FS z8}~hWYdx{5wRRR{X20~eF_Vt_u%s!Xs^<n`GWg}dsjcU z5ATJ~Y_O1d6Cj#6yO_K4K}0dA#Tn_)O{YLsc?7*4RGk5S2>}{q=BB0`Q9Hc8?4YqL z_F4vz9v5x>Ao^n(c*S6kNy16jwf15IMag47#~dFhZ;29aeGEKqTr9_&GBijN7NAaE zEm0P3qW3}AxB1={6@?*I!Q<0 zOD6jHNaPYrY8_erZ%q$z;c^IBBg@XmB&#&4`o90=WsyU9Bxv-R=%L!f49M|{&mD5~ z-NoI>(Ej2?g``ZXO;vA^mDXC*t|TB&i7y7aoAY=1;+xFz!*l2jCFIypZ8P*pERG5V zJ`+tB$>-r~Q%(}l zvCp;j4qH-tCw!On`WXDLutWcro`539P*G=+2v#_DdmBGTUN((Xx)a!(5-8sYmTT|v zcXg3~bIa(Z-~YREMh<9AgDv7m$8v!?pyw3Hxi+f8F`gkSi-2Ix{A=fA;RDe^)ABaC z*(C7UKoa}xuF&DkAFC=C%&`Y(eD*sn=w2h!5=I*$R8%0A&bi*5(I%E*eeWbvHydd1 zzmVcu=$6qnyhL*V5fhyqu;$i0rNRjujM-bsmz&*!Ms=zn5tO&y=`}4oQ`V^a{Oa8# zUeJ9t^848@Bax%KpG8l1+%d{*szG+3KZ~ugB-_Cr_Jmk+9yOKNQGb9nX`n&2;k(e7Af0h@)JXyTV)-zl zZJW-NFPPe0w!9kDZ7Qd094<_2SrI&C*gb||GxE>oE-{pgpI*YXxtOvy=)+{HkfXNb zfTEWI$PW8I@}p|ilVijCLM%y5Y*RTD$ZLknYQVJz=`XKOz$eEBbyG*Y(5!e4B^e3VR1G|Sf%aa-(3LvhsjP8gxJBX?e9H0nT&B(WL?_~uWl^vN}2 z)bez2yM^#Qq<)xca$+_C+7jBXf&R}A5Viw|?(e4z&R0LVGXz3>=)mZ6vvfF5m_l7! z0_ss@PBr&$ALHSqnkIa`a)G<+r^w%H9W9|P$8HciGrba3v`#DUm=oIw>~LyDu%J#4 zXW=1Uh5s4oE_qW3QYTC*gP>9c-WgaUMtBpBi6b=tQG%Q7&bvKSqRcr$X-knxp&s^;4E}M|LM}il!Ykc~!=2_>Qy)FHRlKwatJ z!)ctDgA-^Y1I;ex)wy3E$l~)z*efw+p)%QNoCwf0$&+D(r@zLd2;Y+d*DL6h`~wWT zt;hGfi6{KAJw3#0Bc!J4G>pm|z1Wp^^+^#C1*+^=SYQN-UXl}hA%*pR-6{o()q=`@ zJc$4T+a2t&un8U&>71aFZ~eoVqFRU$KH+?EN)`(w3ej&yHD3xqdFOQqphHq#B!RY8 z#3h^%oC2#0oyi%v)(G=EhI{-$&#h@9c@SakAJp&F2w9;EAJV0zO>N=*ha_u-;hqHM zpKsk1fnM&quPT}GSV`b;3_paRc#&~qmKe=JgH!I712T_Ba`Xn@r^Y$#+Ax+5K1u*Rn7n?KDgtcpC6Ja6 z3*zNcH~F_;BZpcV`CoQXvDxnI?L>D!E602y#zrIyYCNo1kYorpal2DVR&ddw)gH-> zA>JFK2zAle+u>_1=uvPWTej`fa4mNf7;=I zi3SpD&Iib0hJ(5UX!sc=2@CKBWthB|DpfGteHn*DdUW$y+%E?*toCYX>Fal=QQQWB z>@lpDN=Y%TI~_xav&YT#`5@$ZKIDgV`!ltgn@>|3J7+eh>LK>>VX9Y5}R-S4A__eUTW(a^O8Ed;8R5ceUk|zr7`0L3)OcNo+ zSh^W_cpu!X^PFf(fo;bFWZJ*v_suE3dY(io+-EQh)R4fU=m;SW3q$SM?P6pSSR4YV zk;$*r1Wamw_7}n;|Rm};vcQ# z(kNaL1a3!wx;`KxMf{PBlk>WKlCJS_9h7;SZ@<6oDpiCu;}I`fx>PV)Oc)##hL--L zAU+7&3%j5{pJUEyjU8$eXNp1imD2>F9>`ga5+r9ZY5RFJH90EzZIP($OR!Lw{6F4m z4|wRu%|RR^4fbW{VYpG~-s#EM`$g>Vsk12}_k>+o{s8-oIY)`?&F-&y{~4M*&*dhQ z(iCH_Hbw9y5ujCEDu1%NyiF7rw*Huz^R8e{8kABL8`wav!2go({7e@zW1g3BZSAb9 z?K=8S8Fg0vas)t#gTn~k+vv2J`~opbem(g1`5uiy{G?sIysVIAKjMj*)5>E+ERrxK r>6(fwS@RuG&xqsvYab-)!_+Dv*Uq~Cm;zf25MEmxT^uS{9Pa-C_>(L^ literal 0 HcmV?d00001 From 3531544f3ed341643beded804e79a6163001dfa8 Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:38:18 +0100 Subject: [PATCH 11/27] Update kuzzle.yaml with Logo --- templates/compose/kuzzle.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/kuzzle.yaml b/templates/compose/kuzzle.yaml index f0ceaee68..a195cc600 100644 --- a/templates/compose/kuzzle.yaml +++ b/templates/compose/kuzzle.yaml @@ -1,6 +1,7 @@ # documentation: https://kuzzle.io # slogan: Kuzzle is a generic backend offering the basic building blocks common to every application. # tags: backend, api, realtime, websocket, mqtt, rest, sdk, iot, geofencing, low-code +# logo: svgs/kuzzle.png # port: 7512 services: From a10c1abf560a10c794ec14d4f1a4d4bddd523f07 Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:38:57 +0100 Subject: [PATCH 12/27] Update pairdrop.yaml with Logo --- templates/compose/pairdrop.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/pairdrop.yaml b/templates/compose/pairdrop.yaml index 44bac7000..3e71e8b84 100644 --- a/templates/compose/pairdrop.yaml +++ b/templates/compose/pairdrop.yaml @@ -1,6 +1,7 @@ # documentation: https://pairdrop.net/ # slogan: Pairdrop is a self-hosted file sharing and collaboration platform, offering secure file sharing and collaboration capabilities for efficient teamwork. # tags: file, sharing, collaboration, teamwork +# logo: svgs/pairdrop.png # port: 3000 services: From de7f343ec9db0d4e7edc35ad63ea4fa178613dff Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:39:58 +0100 Subject: [PATCH 13/27] Update invoice-ninja.yaml with Logo --- templates/compose/invoice-ninja.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/invoice-ninja.yaml b/templates/compose/invoice-ninja.yaml index 9a767126b..beb05d983 100644 --- a/templates/compose/invoice-ninja.yaml +++ b/templates/compose/invoice-ninja.yaml @@ -1,6 +1,7 @@ # documentation: https://invoiceninja.github.io/selfhost.html # slogan: The leading open-source invoicing platform # tags: invoicing, billing, accounting, finance, self-hosted +# logo: svgs/invoiceninja.png # port: 9000 services: From 951a2fd003d7e34b2e35a4430b116d9f4ee3a36d Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:44:05 +0100 Subject: [PATCH 14/27] Upload penpot.svg and whoogle.png --- public/svgs/penpot.svg | 3 +++ public/svgs/whoogle.png | Bin 0 -> 24077 bytes 2 files changed, 3 insertions(+) create mode 100644 public/svgs/penpot.svg create mode 100644 public/svgs/whoogle.png diff --git a/public/svgs/penpot.svg b/public/svgs/penpot.svg new file mode 100644 index 000000000..6439292bd --- /dev/null +++ b/public/svgs/penpot.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/svgs/whoogle.png b/public/svgs/whoogle.png new file mode 100644 index 0000000000000000000000000000000000000000..0d89d25f24480a0ea6469267f570898e9f244a9c GIT binary patch literal 24077 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 K@g=4n|M`DDpF0l# literal 0 HcmV?d00001 From 621a006e5461042952ffbc9ad531612b21c43827 Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:44:50 +0100 Subject: [PATCH 15/27] Update penpot.yaml with Logo --- templates/compose/penpot.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/penpot.yaml b/templates/compose/penpot.yaml index 9bc21b398..05b73cdca 100644 --- a/templates/compose/penpot.yaml +++ b/templates/compose/penpot.yaml @@ -1,6 +1,7 @@ # documentation: https://help.penpot.app/technical-guide/getting-started/#install-with-docker # slogan: Penpot is the first Open Source design and prototyping platform for product teams. # tags: penpot,design,prototyping,figma,open,source +# logo: svgs/penpot.svg services: frontend: From 6de9be9ce7973a1a5953a54caba6d360c6d3af62 Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:45:09 +0100 Subject: [PATCH 16/27] Update whoogle.yaml with Logo --- templates/compose/whoogle.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/whoogle.yaml b/templates/compose/whoogle.yaml index c049dac47..0be303f9a 100644 --- a/templates/compose/whoogle.yaml +++ b/templates/compose/whoogle.yaml @@ -1,6 +1,7 @@ # documentation: https://github.com/benbusby/whoogle-search?tab=readme-ov-file # slogan: Whoogle is a self-hosted, privacy-focused search engine front-end for accessing Google search results without tracking and data collection. # tags: privacy, search engine +# logo: svgs/whoogle.png # port: 5000 services: From f82d95e908861565bdccb1eaaffb3e79bf878f30 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 13:07:56 +0100 Subject: [PATCH 17/27] refactor: update Traefik configuration for improved security and logging - Removed unnecessary volume mapping for production environment. - Added insecure API access and debug logging for development environment. - Ensured consistent handling of Docker provider exposure settings. - Updated certificate resolver storage path for clarity. --- bootstrap/helpers/proxy.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index a8ef0fe5a..463e89b6f 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -173,13 +173,12 @@ function generate_default_proxy_configuration(Server $server) ], 'volumes' => [ '/var/run/docker.sock:/var/run/docker.sock:ro', - "{$proxy_path}:/traefik", + ], 'command' => [ '--ping=true', '--ping.entrypoint=http', '--api.dashboard=true', - '--api.insecure=false', '--entrypoints.http.address=:80', '--entrypoints.https.address=:443', '--entrypoints.http.http.encodequerysemicolons=true', @@ -187,21 +186,26 @@ function generate_default_proxy_configuration(Server $server) '--entrypoints.https.http.encodequerysemicolons=true', '--entryPoints.https.http2.maxConcurrentStreams=50', '--entrypoints.https.http3', - '--providers.docker.exposedbydefault=false', '--providers.file.directory=/traefik/dynamic/', + '--providers.docker.exposedbydefault=false', '--providers.file.watch=true', '--certificatesresolvers.letsencrypt.acme.httpchallenge=true', - '--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json', '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http', + '--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json', ], 'labels' => $labels, ], ], ]; if (isDev()) { - // $config['services']['traefik']['command'][] = "--log.level=debug"; + $config['services']['traefik']['command'][] = '--api.insecure=true'; + $config['services']['traefik']['command'][] = '--log.level=debug'; $config['services']['traefik']['command'][] = '--accesslog.filepath=/traefik/access.log'; $config['services']['traefik']['command'][] = '--accesslog.bufferingsize=100'; + $config['services']['traefik']['volumes'][] = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/:/traefik'; + } else { + $config['services']['traefik']['command'][] = '--api.insecure=false'; + $config['services']['traefik']['volumes'][] = "{$proxy_path}:/traefik"; } if ($server->isSwarm()) { data_forget($config, 'services.traefik.container_name'); From 659309c5b4e493bd7270925f1512a430e459fa2b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 13:08:04 +0100 Subject: [PATCH 18/27] refactor: improve proxy configuration and code consistency in Server model - Standardized spacing in conditional statements for better readability. - Updated proxy redirect handling to ensure default values are set correctly. - Modified dynamic configuration path concatenation for clarity. - Changed router rule to 'PathPrefix' and adjusted priority for better routing behavior. - Replaced empty check with 'filled' helper for improved code clarity. --- app/Models/Server.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index 3ddea4f32..8c7c7b769 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -105,12 +105,12 @@ class Server extends BaseModel ]); } } - if (!isset($server->proxy->redirect_enabled)) { + if (! isset($server->proxy->redirect_enabled)) { $server->proxy->redirect_enabled = true; } }); static::retrieved(function ($server) { - if (!isset($server->proxy->redirect_enabled)) { + if (! isset($server->proxy->redirect_enabled)) { $server->proxy->redirect_enabled = true; } }); @@ -197,7 +197,7 @@ class Server extends BaseModel $banner = "# This file is generated by Coolify, do not edit it manually.\n". "# Disable the default redirect to customize (only if you know what are you doing).\n\n"; - $dynamic_conf_path = $this->proxyPath() . '/dynamic'; + $dynamic_conf_path = $this->proxyPath().'/dynamic'; $proxy_type = $this->proxyType(); $redirect_enabled = $this->proxy->redirect_enabled ?? true; $redirect_url = $this->proxy->redirect_url; @@ -214,7 +214,7 @@ class Server extends BaseModel "rm -f $dynamic_conf_path/default_redirect_404.caddy", ], $this); - if (!$redirect_enabled) { + if (! $redirect_enabled) { instant_remote_process(["rm -f $default_redirect_file"], $this); } else { if ($proxy_type === ProxyTypes::CADDY->value) { @@ -237,11 +237,8 @@ class Server extends BaseModel 1 => 'https', ], 'service' => 'noop', - 'rule' => 'HostRegexp(`.+`)', - 'tls' => [ - 'certResolver' => 'letsencrypt', - ], - 'priority' => 1, + 'rule' => 'PathPrefix(`/`)', + 'priority' => -1000, ], ], 'services' => [ @@ -253,10 +250,11 @@ class Server extends BaseModel ], ], ]; - if (!empty($redirect_url)) { + if (filled($redirect_url)) { $dynamic_conf['http']['routers']['catchall']['middlewares'] = [ 0 => 'redirect-regexp', ]; + $dynamic_conf['http']['services']['noop']['loadBalancer']['servers'][0] = [ 'url' => '', ]; @@ -607,6 +605,7 @@ $schema://$host { $memory = json_decode($memory, true); $parsedCollection = collect($memory)->map(function ($metric) { $usedPercent = $metric['usedPercent'] ?? 0.0; + return [(int) $metric['time'], (float) $usedPercent]; }); From baf6c20997e16c2a979c45542a2f352424965fe0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 13:31:55 +0100 Subject: [PATCH 19/27] fix: restart proxy --- app/Livewire/Server/Proxy/Deploy.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php index 8fcff85d6..4f9d41092 100644 --- a/app/Livewire/Server/Proxy/Deploy.php +++ b/app/Livewire/Server/Proxy/Deploy.php @@ -65,7 +65,7 @@ class Deploy extends Component public function restart() { try { - $this->stop(forceStop: false); + $this->stop(); $this->dispatch('checkProxy'); } catch (\Throwable $e) { return handleError($e, $this); @@ -105,6 +105,7 @@ class Deploy extends Component $startTime = Carbon::now()->getTimestamp(); while ($process->running()) { + ray('running'); if (Carbon::now()->getTimestamp() - $startTime >= $timeout) { $this->forceStopContainer($containerName); break; From e84dba493e7eb1d57b8e696d2fb7e32b835c0718 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 13:39:00 +0100 Subject: [PATCH 20/27] fix --- app/Actions/Proxy/StartProxy.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index 7c93720cb..9bc506d9b 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -2,6 +2,7 @@ namespace App\Actions\Proxy; +use App\Enums\ProxyTypes; use App\Events\ProxyStarted; use App\Models\Server; use Lorisleiva\Actions\Concerns\AsAction; @@ -37,11 +38,16 @@ class StartProxy "echo 'Successfully started coolify-proxy.'", ]); } else { - $caddfile = 'import /dynamic/*.caddy'; + if (isDev()) { + if ($proxyType === ProxyTypes::CADDY->value) { + $proxy_path = '/data/coolify/proxy/caddy'; + } + } + $caddyfile = 'import /dynamic/*.caddy'; $commands = $commands->merge([ "mkdir -p $proxy_path/dynamic", "cd $proxy_path", - "echo '$caddfile' > $proxy_path/dynamic/Caddyfile", + "echo '$caddyfile' > $proxy_path/dynamic/Caddyfile", "echo 'Creating required Docker Compose file.'", "echo 'Pulling docker image.'", 'docker compose pull', From 5453b9030ef61842824a153fcb8d960e07613002 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 13:39:20 +0100 Subject: [PATCH 21/27] fix: dev mode --- app/Models/Server.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index 8c7c7b769..4c4ca774f 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -201,7 +201,11 @@ class Server extends BaseModel $proxy_type = $this->proxyType(); $redirect_enabled = $this->proxy->redirect_enabled ?? true; $redirect_url = $this->proxy->redirect_url; - + if (isDev()) { + if ($proxy_type === ProxyTypes::CADDY->value) { + $dynamic_conf_path = '/data/coolify/proxy/caddy/dynamic'; + } + } if ($proxy_type === ProxyTypes::TRAEFIK->value) { $default_redirect_file = "$dynamic_conf_path/default_redirect_503.yaml"; } elseif ($proxy_type === ProxyTypes::CADDY->value) { @@ -214,18 +218,18 @@ class Server extends BaseModel "rm -f $dynamic_conf_path/default_redirect_404.caddy", ], $this); - if (! $redirect_enabled) { + if ($redirect_enabled === false) { instant_remote_process(["rm -f $default_redirect_file"], $this); } else { if ($proxy_type === ProxyTypes::CADDY->value) { - if (empty($redirect_url)) { + if (filled($redirect_url)) { + $conf = ":80, :443 { + redir $redirect_url +}"; + } else { $conf = ':80, :443 { respond 503 }'; - } else { - $conf = ":80, :443 { - redir $redirect_url -}"; } } elseif ($proxy_type === ProxyTypes::TRAEFIK->value) { $dynamic_conf = [ From 2b8b85618153f976e77d398c55670b7f313f4045 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 13:39:28 +0100 Subject: [PATCH 22/27] fix: ui --- resources/views/livewire/server/proxy.blade.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/views/livewire/server/proxy.blade.php b/resources/views/livewire/server/proxy.blade.php index 5748a5876..00e0b04b2 100644 --- a/resources/views/livewire/server/proxy.blade.php +++ b/resources/views/livewire/server/proxy.blade.php @@ -27,12 +27,12 @@ helper="If set, all resources will only have docker container labels for {{ str($server->proxyType())->title() }}.
For applications, labels needs to be regenerated manually.
Resources needs to be restarted." id="server.settings.generate_exact_labels" label="Generate labels only for {{ str($server->proxyType())->title() }}" instantSave /> - -
Default request handler
-
- + @if ($redirect_enabled) - + @endif
@if ($server->proxyType() === ProxyTypes::TRAEFIK->value) From 8803fdb58388b24fd156c0e39b2acdea052fbe6e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 13:41:49 +0100 Subject: [PATCH 23/27] Update service templates and trigger configuration - Modified the 'compose' field in service-templates.json to include a new base64 encoded string for better compatibility. - Changed the default 'PLATFORM_WS_PORT' from 3030 to 3000 in trigger.yaml to align with the updated configuration. - Removed the redundant 'PORT' environment variable from the common environment settings in trigger.yaml. --- templates/compose/trigger.yaml | 5 ++--- templates/service-templates.json | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/templates/compose/trigger.yaml b/templates/compose/trigger.yaml index a0007031d..83aa0dfe0 100644 --- a/templates/compose/trigger.yaml +++ b/templates/compose/trigger.yaml @@ -5,7 +5,6 @@ # port: 3000 x-common-env: &common-env - PORT: 3030 REMIX_APP_PORT: 3000 NODE_ENV: production RUNTIME_PLATFORM: docker-compose @@ -118,7 +117,7 @@ services: environment: <<: *common-env PLATFORM_HOST: trigger - PLATFORM_WS_PORT: 3030 + PLATFORM_WS_PORT: 3000 SECURE_CONNECTION: "false" PLATFORM_SECRET: $PROVIDER_SECRET coordinator: @@ -133,7 +132,7 @@ services: environment: <<: *common-env PLATFORM_HOST: trigger - PLATFORM_WS_PORT: 3030 + PLATFORM_WS_PORT: 3000 SECURE_CONNECTION: "false" PLATFORM_SECRET: $COORDINATOR_SECRET healthcheck: diff --git a/templates/service-templates.json b/templates/service-templates.json index 1532c1609..2e1e3bbf9 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -2696,7 +2696,7 @@ "trigger": { "documentation": "https://trigger.dev?utm_source=coolify.io", "slogan": "The open source Background Jobs framework for TypeScript", - "compose": "eC1jb21tb24tZW52OgogIFBPUlQ6IDMwMzAKICBSRU1JWF9BUFBfUE9SVDogMzAwMAogIE5PREVfRU5WOiBwcm9kdWN0aW9uCiAgUlVOVElNRV9QTEFURk9STTogZG9ja2VyLWNvbXBvc2UKICBWM19FTkFCTEVEOiB0cnVlCiAgSU5URVJOQUxfT1RFTF9UUkFDRV9ESVNBQkxFRDogMQogIElOVEVSTkFMX09URUxfVFJBQ0VfTE9HR0lOR19FTkFCTEVEOiAwCiAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogIFBPU1RHUkVTX0RCOiAnJHtQT1NUR1JFU19EQjotdHJpZ2dlcn0nCiAgTUFHSUNfTElOS19TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X01BR0lDCiAgU0VTU0lPTl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT04KICBFTkNSWVBUSU9OX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfRU5DUllQVElPTgogIFBST1ZJREVSX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfUFJPVklERVIKICBDT09SRElOQVRPUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0NPT1JESU5BVE9SCiAgREFUQUJBU0VfSE9TVDogJ3Bvc3RncmVzcWw6NTQzMicKICBEQVRBQkFTRV9VUkw6ICdwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVM6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNAcG9zdGdyZXNxbDo1NDMyLyRQT1NUR1JFU19EQj9zc2xtb2RlPWRpc2FibGUnCiAgRElSRUNUX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICBSRURJU19IT1NUOiByZWRpcwogIFJFRElTX1BPUlQ6IDYzNzkKICBSRURJU19UTFNfRElTQUJMRUQ6IHRydWUKICBDT09SRElOQVRPUl9IT1NUOiAxMjcuMC4wLjEKICBDT09SRElOQVRPUl9QT1JUOiA5MDIwCiAgV0hJVEVMSVNURURfRU1BSUxTOiAnJwogIEFETUlOX0VNQUlMUzogJycKICBERUZBVUxUX09SR19FWEVDVVRJT05fQ09OQ1VSUkVOQ1lfTElNSVQ6IDMwMAogIERFRkFVTFRfRU5WX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMTAwCiAgREVQTE9ZX1JFR0lTVFJZX0hPU1Q6IGRvY2tlci5pbwogIERFUExPWV9SRUdJU1RSWV9OQU1FU1BBQ0U6IHRyaWdnZXIKICBSRUdJU1RSWV9IT1NUOiAnJHtERVBMT1lfUkVHSVNUUllfSE9TVH0nCiAgUkVHSVNUUllfTkFNRVNQQUNFOiAnJHtERVBMT1lfUkVHSVNUUllfTkFNRVNQQUNFfScKICBBVVRIX0dJVEhVQl9DTElFTlRfSUQ6ICcke0FVVEhfR0lUSFVCX0NMSUVOVF9JRH0nCiAgQVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVH0nCiAgUkVTRU5EX0FQSV9LRVk6ICcke1JFU0VORF9BUElfS0VZfScKICBGUk9NX0VNQUlMOiAnJHtGUk9NX0VNQUlMfScKICBSRVBMWV9UT19FTUFJTDogJyR7UkVQTFlfVE9fRU1BSUx9JwogIExPR0lOX09SSUdJTjogJFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAKICBBUFBfT1JJR0lOOiAkU0VSVklDRV9GUUROX1RSSUdHRVJfMzAwMAogIERFVl9PVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwL290ZWwKICBPVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICdodHRwOi8vdHJpZ2dlcjozMDQwL290ZWwnCiAgRUxFQ1RSSUNfT1JJR0lOOiAnaHR0cDovL2VsZWN0cmljOjMwMDAnCnNlcnZpY2VzOgogIHRyaWdnZXI6CiAgICBpbWFnZTogJ2doY3IuaW8vdHJpZ2dlcmRvdGRldi90cmlnZ2VyLmRldjp2MycKICAgIGVudmlyb25tZW50OgogICAgICBTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwOiAnJwogICAgICBQT1JUOiAzMDMwCiAgICAgIFJFTUlYX0FQUF9QT1JUOiAzMDAwCiAgICAgIE5PREVfRU5WOiBwcm9kdWN0aW9uCiAgICAgIFJVTlRJTUVfUExBVEZPUk06IGRvY2tlci1jb21wb3NlCiAgICAgIFYzX0VOQUJMRUQ6IHRydWUKICAgICAgSU5URVJOQUxfT1RFTF9UUkFDRV9ESVNBQkxFRDogMQogICAgICBJTlRFUk5BTF9PVEVMX1RSQUNFX0xPR0dJTkdfRU5BQkxFRDogMAogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogJyR7UE9TVEdSRVNfREI6LXRyaWdnZXJ9JwogICAgICBNQUdJQ19MSU5LX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICAgICAgU0VTU0lPTl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT04KICAgICAgRU5DUllQVElPTl9LRVk6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0VOQ1JZUFRJT04KICAgICAgUFJPVklERVJfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9QUk9WSURFUgogICAgICBDT09SRElOQVRPUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0NPT1JESU5BVE9SCiAgICAgIERBVEFCQVNFX0hPU1Q6ICdwb3N0Z3Jlc3FsOjU0MzInCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgRElSRUNUX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgUkVESVNfSE9TVDogcmVkaXMKICAgICAgUkVESVNfUE9SVDogNjM3OQogICAgICBSRURJU19UTFNfRElTQUJMRUQ6IHRydWUKICAgICAgQ09PUkRJTkFUT1JfSE9TVDogMTI3LjAuMC4xCiAgICAgIENPT1JESU5BVE9SX1BPUlQ6IDkwMjAKICAgICAgV0hJVEVMSVNURURfRU1BSUxTOiAnJwogICAgICBBRE1JTl9FTUFJTFM6ICcnCiAgICAgIERFRkFVTFRfT1JHX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMzAwCiAgICAgIERFRkFVTFRfRU5WX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMTAwCiAgICAgIERFUExPWV9SRUdJU1RSWV9IT1NUOiBkb2NrZXIuaW8KICAgICAgREVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRTogdHJpZ2dlcgogICAgICBSRUdJU1RSWV9IT1NUOiAnJHtERVBMT1lfUkVHSVNUUllfSE9TVH0nCiAgICAgIFJFR0lTVFJZX05BTUVTUEFDRTogJyR7REVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRX0nCiAgICAgIEFVVEhfR0lUSFVCX0NMSUVOVF9JRDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX0lEfScKICAgICAgQVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVH0nCiAgICAgIFJFU0VORF9BUElfS0VZOiAnJHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIEZST01fRU1BSUw6ICcke0ZST01fRU1BSUx9JwogICAgICBSRVBMWV9UT19FTUFJTDogJyR7UkVQTFlfVE9fRU1BSUx9JwogICAgICBMT0dJTl9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIEFQUF9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIERFVl9PVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwL290ZWwKICAgICAgT1RFTF9FWFBPUlRFUl9PVExQX0VORFBPSU5UOiAnaHR0cDovL3RyaWdnZXI6MzA0MC9vdGVsJwogICAgICBFTEVDVFJJQ19PUklHSU46ICdodHRwOi8vZWxlY3RyaWM6MzAwMCcKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgZWxlY3RyaWM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiAidGltZW91dCAxMHMgYmFzaCAtYyAnOj4gL2Rldi90Y3AvMTI3LjAuMC4xLzMwMDAnIHx8IGV4aXQgMSIKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgZWxlY3RyaWM6CiAgICBpbWFnZTogZWxlY3RyaWNzcWwvZWxlY3RyaWMKICAgIGVudmlyb25tZW50OgogICAgICBQT1JUOiAzMDMwCiAgICAgIFJFTUlYX0FQUF9QT1JUOiAzMDAwCiAgICAgIE5PREVfRU5WOiBwcm9kdWN0aW9uCiAgICAgIFJVTlRJTUVfUExBVEZPUk06IGRvY2tlci1jb21wb3NlCiAgICAgIFYzX0VOQUJMRUQ6IHRydWUKICAgICAgSU5URVJOQUxfT1RFTF9UUkFDRV9ESVNBQkxFRDogMQogICAgICBJTlRFUk5BTF9PVEVMX1RSQUNFX0xPR0dJTkdfRU5BQkxFRDogMAogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogJyR7UE9TVEdSRVNfREI6LXRyaWdnZXJ9JwogICAgICBNQUdJQ19MSU5LX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICAgICAgU0VTU0lPTl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT04KICAgICAgRU5DUllQVElPTl9LRVk6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0VOQ1JZUFRJT04KICAgICAgUFJPVklERVJfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9QUk9WSURFUgogICAgICBDT09SRElOQVRPUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0NPT1JESU5BVE9SCiAgICAgIERBVEFCQVNFX0hPU1Q6ICdwb3N0Z3Jlc3FsOjU0MzInCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgRElSRUNUX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgUkVESVNfSE9TVDogcmVkaXMKICAgICAgUkVESVNfUE9SVDogNjM3OQogICAgICBSRURJU19UTFNfRElTQUJMRUQ6IHRydWUKICAgICAgQ09PUkRJTkFUT1JfSE9TVDogMTI3LjAuMC4xCiAgICAgIENPT1JESU5BVE9SX1BPUlQ6IDkwMjAKICAgICAgV0hJVEVMSVNURURfRU1BSUxTOiAnJwogICAgICBBRE1JTl9FTUFJTFM6ICcnCiAgICAgIERFRkFVTFRfT1JHX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMzAwCiAgICAgIERFRkFVTFRfRU5WX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMTAwCiAgICAgIERFUExPWV9SRUdJU1RSWV9IT1NUOiBkb2NrZXIuaW8KICAgICAgREVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRTogdHJpZ2dlcgogICAgICBSRUdJU1RSWV9IT1NUOiAnJHtERVBMT1lfUkVHSVNUUllfSE9TVH0nCiAgICAgIFJFR0lTVFJZX05BTUVTUEFDRTogJyR7REVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRX0nCiAgICAgIEFVVEhfR0lUSFVCX0NMSUVOVF9JRDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX0lEfScKICAgICAgQVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVH0nCiAgICAgIFJFU0VORF9BUElfS0VZOiAnJHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIEZST01fRU1BSUw6ICcke0ZST01fRU1BSUx9JwogICAgICBSRVBMWV9UT19FTUFJTDogJyR7UkVQTFlfVE9fRU1BSUx9JwogICAgICBMT0dJTl9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIEFQUF9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIERFVl9PVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwL290ZWwKICAgICAgT1RFTF9FWFBPUlRFUl9PVExQX0VORFBPSU5UOiAnaHR0cDovL3RyaWdnZXI6MzA0MC9vdGVsJwogICAgICBFTEVDVFJJQ19PUklHSU46ICdodHRwOi8vZWxlY3RyaWM6MzAwMCcKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSBwd2QKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6NycKICAgIGVudmlyb25tZW50OgogICAgICAtIEFMTE9XX0VNUFRZX1BBU1NXT1JEPXllcwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdyZWRpcy1jbGkgLWggbG9jYWxob3N0IC1wIDYzNzkgcGluZycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3JlZGlzLWRhdGE6L2RhdGEnCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICBQT1JUOiAzMDMwCiAgICAgIFJFTUlYX0FQUF9QT1JUOiAzMDAwCiAgICAgIE5PREVfRU5WOiBwcm9kdWN0aW9uCiAgICAgIFJVTlRJTUVfUExBVEZPUk06IGRvY2tlci1jb21wb3NlCiAgICAgIFYzX0VOQUJMRUQ6IHRydWUKICAgICAgSU5URVJOQUxfT1RFTF9UUkFDRV9ESVNBQkxFRDogMQogICAgICBJTlRFUk5BTF9PVEVMX1RSQUNFX0xPR0dJTkdfRU5BQkxFRDogMAogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogJyR7UE9TVEdSRVNfREI6LXRyaWdnZXJ9JwogICAgICBNQUdJQ19MSU5LX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICAgICAgU0VTU0lPTl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT04KICAgICAgRU5DUllQVElPTl9LRVk6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0VOQ1JZUFRJT04KICAgICAgUFJPVklERVJfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9QUk9WSURFUgogICAgICBDT09SRElOQVRPUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0NPT1JESU5BVE9SCiAgICAgIERBVEFCQVNFX0hPU1Q6ICdwb3N0Z3Jlc3FsOjU0MzInCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgRElSRUNUX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgUkVESVNfSE9TVDogcmVkaXMKICAgICAgUkVESVNfUE9SVDogNjM3OQogICAgICBSRURJU19UTFNfRElTQUJMRUQ6IHRydWUKICAgICAgQ09PUkRJTkFUT1JfSE9TVDogMTI3LjAuMC4xCiAgICAgIENPT1JESU5BVE9SX1BPUlQ6IDkwMjAKICAgICAgV0hJVEVMSVNURURfRU1BSUxTOiAnJwogICAgICBBRE1JTl9FTUFJTFM6ICcnCiAgICAgIERFRkFVTFRfT1JHX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMzAwCiAgICAgIERFRkFVTFRfRU5WX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMTAwCiAgICAgIERFUExPWV9SRUdJU1RSWV9IT1NUOiBkb2NrZXIuaW8KICAgICAgREVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRTogdHJpZ2dlcgogICAgICBSRUdJU1RSWV9IT1NUOiAnJHtERVBMT1lfUkVHSVNUUllfSE9TVH0nCiAgICAgIFJFR0lTVFJZX05BTUVTUEFDRTogJyR7REVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRX0nCiAgICAgIEFVVEhfR0lUSFVCX0NMSUVOVF9JRDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX0lEfScKICAgICAgQVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVH0nCiAgICAgIFJFU0VORF9BUElfS0VZOiAnJHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIEZST01fRU1BSUw6ICcke0ZST01fRU1BSUx9JwogICAgICBSRVBMWV9UT19FTUFJTDogJyR7UkVQTFlfVE9fRU1BSUx9JwogICAgICBMT0dJTl9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIEFQUF9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIERFVl9PVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwL290ZWwKICAgICAgT1RFTF9FWFBPUlRFUl9PVExQX0VORFBPSU5UOiAnaHR0cDovL3RyaWdnZXI6MzA0MC9vdGVsJwogICAgICBFTEVDVFJJQ19PUklHSU46ICdodHRwOi8vZWxlY3RyaWM6MzAwMCcKICAgIGNvbW1hbmQ6CiAgICAgIC0gJy1jJwogICAgICAtIHdhbF9sZXZlbD1sb2dpY2FsCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgZG9ja2VyLXByb3ZpZGVyOgogICAgaW1hZ2U6ICdnaGNyLmlvL3RyaWdnZXJkb3RkZXYvcHJvdmlkZXIvZG9ja2VyOnYzJwogICAgcGxhdGZvcm06IGxpbnV4L2FtZDY0CiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgIHVzZXI6IHJvb3QKICAgIGRlcGVuZHNfb246CiAgICAgIHRyaWdnZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICBQT1JUOiAzMDMwCiAgICAgIFJFTUlYX0FQUF9QT1JUOiAzMDAwCiAgICAgIE5PREVfRU5WOiBwcm9kdWN0aW9uCiAgICAgIFJVTlRJTUVfUExBVEZPUk06IGRvY2tlci1jb21wb3NlCiAgICAgIFYzX0VOQUJMRUQ6IHRydWUKICAgICAgSU5URVJOQUxfT1RFTF9UUkFDRV9ESVNBQkxFRDogMQogICAgICBJTlRFUk5BTF9PVEVMX1RSQUNFX0xPR0dJTkdfRU5BQkxFRDogMAogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogJyR7UE9TVEdSRVNfREI6LXRyaWdnZXJ9JwogICAgICBNQUdJQ19MSU5LX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICAgICAgU0VTU0lPTl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT04KICAgICAgRU5DUllQVElPTl9LRVk6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0VOQ1JZUFRJT04KICAgICAgUFJPVklERVJfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9QUk9WSURFUgogICAgICBDT09SRElOQVRPUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0NPT1JESU5BVE9SCiAgICAgIERBVEFCQVNFX0hPU1Q6ICdwb3N0Z3Jlc3FsOjU0MzInCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgRElSRUNUX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgUkVESVNfSE9TVDogcmVkaXMKICAgICAgUkVESVNfUE9SVDogNjM3OQogICAgICBSRURJU19UTFNfRElTQUJMRUQ6IHRydWUKICAgICAgQ09PUkRJTkFUT1JfSE9TVDogMTI3LjAuMC4xCiAgICAgIENPT1JESU5BVE9SX1BPUlQ6IDkwMjAKICAgICAgV0hJVEVMSVNURURfRU1BSUxTOiAnJwogICAgICBBRE1JTl9FTUFJTFM6ICcnCiAgICAgIERFRkFVTFRfT1JHX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMzAwCiAgICAgIERFRkFVTFRfRU5WX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMTAwCiAgICAgIERFUExPWV9SRUdJU1RSWV9IT1NUOiBkb2NrZXIuaW8KICAgICAgREVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRTogdHJpZ2dlcgogICAgICBSRUdJU1RSWV9IT1NUOiAnJHtERVBMT1lfUkVHSVNUUllfSE9TVH0nCiAgICAgIFJFR0lTVFJZX05BTUVTUEFDRTogJyR7REVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRX0nCiAgICAgIEFVVEhfR0lUSFVCX0NMSUVOVF9JRDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX0lEfScKICAgICAgQVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVH0nCiAgICAgIFJFU0VORF9BUElfS0VZOiAnJHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIEZST01fRU1BSUw6ICcke0ZST01fRU1BSUx9JwogICAgICBSRVBMWV9UT19FTUFJTDogJyR7UkVQTFlfVE9fRU1BSUx9JwogICAgICBMT0dJTl9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIEFQUF9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIERFVl9PVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwL290ZWwKICAgICAgT1RFTF9FWFBPUlRFUl9PVExQX0VORFBPSU5UOiAnaHR0cDovL3RyaWdnZXI6MzA0MC9vdGVsJwogICAgICBFTEVDVFJJQ19PUklHSU46ICdodHRwOi8vZWxlY3RyaWM6MzAwMCcKICAgICAgUExBVEZPUk1fSE9TVDogdHJpZ2dlcgogICAgICBQTEFURk9STV9XU19QT1JUOiAzMDMwCiAgICAgIFNFQ1VSRV9DT05ORUNUSU9OOiAnZmFsc2UnCiAgICAgIFBMQVRGT1JNX1NFQ1JFVDogJFBST1ZJREVSX1NFQ1JFVAogIGNvb3JkaW5hdG9yOgogICAgaW1hZ2U6ICdnaGNyLmlvL3RyaWdnZXJkb3RkZXYvY29vcmRpbmF0b3I6djMnCiAgICBwbGF0Zm9ybTogbGludXgvYW1kNjQKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgdXNlcjogcm9vdAogICAgZGVwZW5kc19vbjoKICAgICAgdHJpZ2dlcjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFBPUlQ6IDMwMzAKICAgICAgUkVNSVhfQVBQX1BPUlQ6IDMwMDAKICAgICAgTk9ERV9FTlY6IHByb2R1Y3Rpb24KICAgICAgUlVOVElNRV9QTEFURk9STTogZG9ja2VyLWNvbXBvc2UKICAgICAgVjNfRU5BQkxFRDogdHJ1ZQogICAgICBJTlRFUk5BTF9PVEVMX1RSQUNFX0RJU0FCTEVEOiAxCiAgICAgIElOVEVSTkFMX09URUxfVFJBQ0VfTE9HR0lOR19FTkFCTEVEOiAwCiAgICAgIFBPU1RHUkVTX1VTRVI6ICRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX0RCOiAnJHtQT1NUR1JFU19EQjotdHJpZ2dlcn0nCiAgICAgIE1BR0lDX0xJTktfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9NQUdJQwogICAgICBTRVNTSU9OX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfU0VTU0lPTgogICAgICBFTkNSWVBUSU9OX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfRU5DUllQVElPTgogICAgICBQUk9WSURFUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1BST1ZJREVSCiAgICAgIENPT1JESU5BVE9SX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfQ09PUkRJTkFUT1IKICAgICAgREFUQUJBU0VfSE9TVDogJ3Bvc3RncmVzcWw6NTQzMicKICAgICAgREFUQUJBU0VfVVJMOiAncG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBvc3RncmVzcWw6NTQzMi8kUE9TVEdSRVNfREI/c3NsbW9kZT1kaXNhYmxlJwogICAgICBESVJFQ1RfVVJMOiAncG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBvc3RncmVzcWw6NTQzMi8kUE9TVEdSRVNfREI/c3NsbW9kZT1kaXNhYmxlJwogICAgICBSRURJU19IT1NUOiByZWRpcwogICAgICBSRURJU19QT1JUOiA2Mzc5CiAgICAgIFJFRElTX1RMU19ESVNBQkxFRDogdHJ1ZQogICAgICBDT09SRElOQVRPUl9IT1NUOiAxMjcuMC4wLjEKICAgICAgQ09PUkRJTkFUT1JfUE9SVDogOTAyMAogICAgICBXSElURUxJU1RFRF9FTUFJTFM6ICcnCiAgICAgIEFETUlOX0VNQUlMUzogJycKICAgICAgREVGQVVMVF9PUkdfRVhFQ1VUSU9OX0NPTkNVUlJFTkNZX0xJTUlUOiAzMDAKICAgICAgREVGQVVMVF9FTlZfRVhFQ1VUSU9OX0NPTkNVUlJFTkNZX0xJTUlUOiAxMDAKICAgICAgREVQTE9ZX1JFR0lTVFJZX0hPU1Q6IGRvY2tlci5pbwogICAgICBERVBMT1lfUkVHSVNUUllfTkFNRVNQQUNFOiB0cmlnZ2VyCiAgICAgIFJFR0lTVFJZX0hPU1Q6ICcke0RFUExPWV9SRUdJU1RSWV9IT1NUfScKICAgICAgUkVHSVNUUllfTkFNRVNQQUNFOiAnJHtERVBMT1lfUkVHSVNUUllfTkFNRVNQQUNFfScKICAgICAgQVVUSF9HSVRIVUJfQ0xJRU5UX0lEOiAnJHtBVVRIX0dJVEhVQl9DTElFTlRfSUR9JwogICAgICBBVVRIX0dJVEhVQl9DTElFTlRfU0VDUkVUOiAnJHtBVVRIX0dJVEhVQl9DTElFTlRfU0VDUkVUfScKICAgICAgUkVTRU5EX0FQSV9LRVk6ICcke1JFU0VORF9BUElfS0VZfScKICAgICAgRlJPTV9FTUFJTDogJyR7RlJPTV9FTUFJTH0nCiAgICAgIFJFUExZX1RPX0VNQUlMOiAnJHtSRVBMWV9UT19FTUFJTH0nCiAgICAgIExPR0lOX09SSUdJTjogJFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAKICAgICAgQVBQX09SSUdJTjogJFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAKICAgICAgREVWX09URUxfRVhQT1JURVJfT1RMUF9FTkRQT0lOVDogJFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAvb3RlbAogICAgICBPVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICdodHRwOi8vdHJpZ2dlcjozMDQwL290ZWwnCiAgICAgIEVMRUNUUklDX09SSUdJTjogJ2h0dHA6Ly9lbGVjdHJpYzozMDAwJwogICAgICBQTEFURk9STV9IT1NUOiB0cmlnZ2VyCiAgICAgIFBMQVRGT1JNX1dTX1BPUlQ6IDMwMzAKICAgICAgU0VDVVJFX0NPTk5FQ1RJT046ICdmYWxzZScKICAgICAgUExBVEZPUk1fU0VDUkVUOiAkQ09PUkRJTkFUT1JfU0VDUkVUCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gcHdkCg==", + "compose": "eC1jb21tb24tZW52OgogIFJFTUlYX0FQUF9QT1JUOiAzMDAwCiAgTk9ERV9FTlY6IHByb2R1Y3Rpb24KICBSVU5USU1FX1BMQVRGT1JNOiBkb2NrZXItY29tcG9zZQogIFYzX0VOQUJMRUQ6IHRydWUKICBJTlRFUk5BTF9PVEVMX1RSQUNFX0RJU0FCTEVEOiAxCiAgSU5URVJOQUxfT1RFTF9UUkFDRV9MT0dHSU5HX0VOQUJMRUQ6IDAKICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgUE9TVEdSRVNfREI6ICcke1BPU1RHUkVTX0RCOi10cmlnZ2VyfScKICBNQUdJQ19MSU5LX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICBTRVNTSU9OX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfU0VTU0lPTgogIEVOQ1JZUFRJT05fS0VZOiAkU0VSVklDRV9QQVNTV09SRF82NF9FTkNSWVBUSU9OCiAgUFJPVklERVJfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9QUk9WSURFUgogIENPT1JESU5BVE9SX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfQ09PUkRJTkFUT1IKICBEQVRBQkFTRV9IT1NUOiAncG9zdGdyZXNxbDo1NDMyJwogIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICBESVJFQ1RfVVJMOiAncG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBvc3RncmVzcWw6NTQzMi8kUE9TVEdSRVNfREI/c3NsbW9kZT1kaXNhYmxlJwogIFJFRElTX0hPU1Q6IHJlZGlzCiAgUkVESVNfUE9SVDogNjM3OQogIFJFRElTX1RMU19ESVNBQkxFRDogdHJ1ZQogIENPT1JESU5BVE9SX0hPU1Q6IDEyNy4wLjAuMQogIENPT1JESU5BVE9SX1BPUlQ6IDkwMjAKICBXSElURUxJU1RFRF9FTUFJTFM6ICcnCiAgQURNSU5fRU1BSUxTOiAnJwogIERFRkFVTFRfT1JHX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMzAwCiAgREVGQVVMVF9FTlZfRVhFQ1VUSU9OX0NPTkNVUlJFTkNZX0xJTUlUOiAxMDAKICBERVBMT1lfUkVHSVNUUllfSE9TVDogZG9ja2VyLmlvCiAgREVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRTogdHJpZ2dlcgogIFJFR0lTVFJZX0hPU1Q6ICcke0RFUExPWV9SRUdJU1RSWV9IT1NUfScKICBSRUdJU1RSWV9OQU1FU1BBQ0U6ICcke0RFUExPWV9SRUdJU1RSWV9OQU1FU1BBQ0V9JwogIEFVVEhfR0lUSFVCX0NMSUVOVF9JRDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX0lEfScKICBBVVRIX0dJVEhVQl9DTElFTlRfU0VDUkVUOiAnJHtBVVRIX0dJVEhVQl9DTElFTlRfU0VDUkVUfScKICBSRVNFTkRfQVBJX0tFWTogJyR7UkVTRU5EX0FQSV9LRVl9JwogIEZST01fRU1BSUw6ICcke0ZST01fRU1BSUx9JwogIFJFUExZX1RPX0VNQUlMOiAnJHtSRVBMWV9UT19FTUFJTH0nCiAgTE9HSU5fT1JJR0lOOiAkU0VSVklDRV9GUUROX1RSSUdHRVJfMzAwMAogIEFQUF9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgREVWX09URUxfRVhQT1JURVJfT1RMUF9FTkRQT0lOVDogJFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAvb3RlbAogIE9URUxfRVhQT1JURVJfT1RMUF9FTkRQT0lOVDogJ2h0dHA6Ly90cmlnZ2VyOjMwNDAvb3RlbCcKICBFTEVDVFJJQ19PUklHSU46ICdodHRwOi8vZWxlY3RyaWM6MzAwMCcKc2VydmljZXM6CiAgdHJpZ2dlcjoKICAgIGltYWdlOiAnZ2hjci5pby90cmlnZ2VyZG90ZGV2L3RyaWdnZXIuZGV2OnYzJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDA6ICcnCiAgICAgIFJFTUlYX0FQUF9QT1JUOiAzMDAwCiAgICAgIE5PREVfRU5WOiBwcm9kdWN0aW9uCiAgICAgIFJVTlRJTUVfUExBVEZPUk06IGRvY2tlci1jb21wb3NlCiAgICAgIFYzX0VOQUJMRUQ6IHRydWUKICAgICAgSU5URVJOQUxfT1RFTF9UUkFDRV9ESVNBQkxFRDogMQogICAgICBJTlRFUk5BTF9PVEVMX1RSQUNFX0xPR0dJTkdfRU5BQkxFRDogMAogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogJyR7UE9TVEdSRVNfREI6LXRyaWdnZXJ9JwogICAgICBNQUdJQ19MSU5LX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICAgICAgU0VTU0lPTl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT04KICAgICAgRU5DUllQVElPTl9LRVk6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0VOQ1JZUFRJT04KICAgICAgUFJPVklERVJfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9QUk9WSURFUgogICAgICBDT09SRElOQVRPUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0NPT1JESU5BVE9SCiAgICAgIERBVEFCQVNFX0hPU1Q6ICdwb3N0Z3Jlc3FsOjU0MzInCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgRElSRUNUX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgUkVESVNfSE9TVDogcmVkaXMKICAgICAgUkVESVNfUE9SVDogNjM3OQogICAgICBSRURJU19UTFNfRElTQUJMRUQ6IHRydWUKICAgICAgQ09PUkRJTkFUT1JfSE9TVDogMTI3LjAuMC4xCiAgICAgIENPT1JESU5BVE9SX1BPUlQ6IDkwMjAKICAgICAgV0hJVEVMSVNURURfRU1BSUxTOiAnJwogICAgICBBRE1JTl9FTUFJTFM6ICcnCiAgICAgIERFRkFVTFRfT1JHX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMzAwCiAgICAgIERFRkFVTFRfRU5WX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMTAwCiAgICAgIERFUExPWV9SRUdJU1RSWV9IT1NUOiBkb2NrZXIuaW8KICAgICAgREVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRTogdHJpZ2dlcgogICAgICBSRUdJU1RSWV9IT1NUOiAnJHtERVBMT1lfUkVHSVNUUllfSE9TVH0nCiAgICAgIFJFR0lTVFJZX05BTUVTUEFDRTogJyR7REVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRX0nCiAgICAgIEFVVEhfR0lUSFVCX0NMSUVOVF9JRDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX0lEfScKICAgICAgQVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVH0nCiAgICAgIFJFU0VORF9BUElfS0VZOiAnJHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIEZST01fRU1BSUw6ICcke0ZST01fRU1BSUx9JwogICAgICBSRVBMWV9UT19FTUFJTDogJyR7UkVQTFlfVE9fRU1BSUx9JwogICAgICBMT0dJTl9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIEFQUF9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIERFVl9PVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwL290ZWwKICAgICAgT1RFTF9FWFBPUlRFUl9PVExQX0VORFBPSU5UOiAnaHR0cDovL3RyaWdnZXI6MzA0MC9vdGVsJwogICAgICBFTEVDVFJJQ19PUklHSU46ICdodHRwOi8vZWxlY3RyaWM6MzAwMCcKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgZWxlY3RyaWM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiAidGltZW91dCAxMHMgYmFzaCAtYyAnOj4gL2Rldi90Y3AvMTI3LjAuMC4xLzMwMDAnIHx8IGV4aXQgMSIKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgZWxlY3RyaWM6CiAgICBpbWFnZTogZWxlY3RyaWNzcWwvZWxlY3RyaWMKICAgIGVudmlyb25tZW50OgogICAgICBSRU1JWF9BUFBfUE9SVDogMzAwMAogICAgICBOT0RFX0VOVjogcHJvZHVjdGlvbgogICAgICBSVU5USU1FX1BMQVRGT1JNOiBkb2NrZXItY29tcG9zZQogICAgICBWM19FTkFCTEVEOiB0cnVlCiAgICAgIElOVEVSTkFMX09URUxfVFJBQ0VfRElTQUJMRUQ6IDEKICAgICAgSU5URVJOQUxfT1RFTF9UUkFDRV9MT0dHSU5HX0VOQUJMRUQ6IDAKICAgICAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICBQT1NUR1JFU19QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgUE9TVEdSRVNfREI6ICcke1BPU1RHUkVTX0RCOi10cmlnZ2VyfScKICAgICAgTUFHSUNfTElOS19TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X01BR0lDCiAgICAgIFNFU1NJT05fU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9TRVNTSU9OCiAgICAgIEVOQ1JZUFRJT05fS0VZOiAkU0VSVklDRV9QQVNTV09SRF82NF9FTkNSWVBUSU9OCiAgICAgIFBST1ZJREVSX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfUFJPVklERVIKICAgICAgQ09PUkRJTkFUT1JfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9DT09SRElOQVRPUgogICAgICBEQVRBQkFTRV9IT1NUOiAncG9zdGdyZXNxbDo1NDMyJwogICAgICBEQVRBQkFTRV9VUkw6ICdwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVM6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNAcG9zdGdyZXNxbDo1NDMyLyRQT1NUR1JFU19EQj9zc2xtb2RlPWRpc2FibGUnCiAgICAgIERJUkVDVF9VUkw6ICdwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVM6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNAcG9zdGdyZXNxbDo1NDMyLyRQT1NUR1JFU19EQj9zc2xtb2RlPWRpc2FibGUnCiAgICAgIFJFRElTX0hPU1Q6IHJlZGlzCiAgICAgIFJFRElTX1BPUlQ6IDYzNzkKICAgICAgUkVESVNfVExTX0RJU0FCTEVEOiB0cnVlCiAgICAgIENPT1JESU5BVE9SX0hPU1Q6IDEyNy4wLjAuMQogICAgICBDT09SRElOQVRPUl9QT1JUOiA5MDIwCiAgICAgIFdISVRFTElTVEVEX0VNQUlMUzogJycKICAgICAgQURNSU5fRU1BSUxTOiAnJwogICAgICBERUZBVUxUX09SR19FWEVDVVRJT05fQ09OQ1VSUkVOQ1lfTElNSVQ6IDMwMAogICAgICBERUZBVUxUX0VOVl9FWEVDVVRJT05fQ09OQ1VSUkVOQ1lfTElNSVQ6IDEwMAogICAgICBERVBMT1lfUkVHSVNUUllfSE9TVDogZG9ja2VyLmlvCiAgICAgIERFUExPWV9SRUdJU1RSWV9OQU1FU1BBQ0U6IHRyaWdnZXIKICAgICAgUkVHSVNUUllfSE9TVDogJyR7REVQTE9ZX1JFR0lTVFJZX0hPU1R9JwogICAgICBSRUdJU1RSWV9OQU1FU1BBQ0U6ICcke0RFUExPWV9SRUdJU1RSWV9OQU1FU1BBQ0V9JwogICAgICBBVVRIX0dJVEhVQl9DTElFTlRfSUQ6ICcke0FVVEhfR0lUSFVCX0NMSUVOVF9JRH0nCiAgICAgIEFVVEhfR0lUSFVCX0NMSUVOVF9TRUNSRVQ6ICcke0FVVEhfR0lUSFVCX0NMSUVOVF9TRUNSRVR9JwogICAgICBSRVNFTkRfQVBJX0tFWTogJyR7UkVTRU5EX0FQSV9LRVl9JwogICAgICBGUk9NX0VNQUlMOiAnJHtGUk9NX0VNQUlMfScKICAgICAgUkVQTFlfVE9fRU1BSUw6ICcke1JFUExZX1RPX0VNQUlMfScKICAgICAgTE9HSU5fT1JJR0lOOiAkU0VSVklDRV9GUUROX1RSSUdHRVJfMzAwMAogICAgICBBUFBfT1JJR0lOOiAkU0VSVklDRV9GUUROX1RSSUdHRVJfMzAwMAogICAgICBERVZfT1RFTF9FWFBPUlRFUl9PVExQX0VORFBPSU5UOiAkU0VSVklDRV9GUUROX1RSSUdHRVJfMzAwMC9vdGVsCiAgICAgIE9URUxfRVhQT1JURVJfT1RMUF9FTkRQT0lOVDogJ2h0dHA6Ly90cmlnZ2VyOjMwNDAvb3RlbCcKICAgICAgRUxFQ1RSSUNfT1JJR0lOOiAnaHR0cDovL2VsZWN0cmljOjMwMDAnCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gcHdkCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjcnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBBTExPV19FTVBUWV9QQVNTV09SRD15ZXMKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncmVkaXMtY2xpIC1oIGxvY2FsaG9zdCAtcCA2Mzc5IHBpbmcnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCiAgICB2b2x1bWVzOgogICAgICAtICdyZWRpcy1kYXRhOi9kYXRhJwogIHBvc3RncmVzcWw6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3RncmVzcWwtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgUkVNSVhfQVBQX1BPUlQ6IDMwMDAKICAgICAgTk9ERV9FTlY6IHByb2R1Y3Rpb24KICAgICAgUlVOVElNRV9QTEFURk9STTogZG9ja2VyLWNvbXBvc2UKICAgICAgVjNfRU5BQkxFRDogdHJ1ZQogICAgICBJTlRFUk5BTF9PVEVMX1RSQUNFX0RJU0FCTEVEOiAxCiAgICAgIElOVEVSTkFMX09URUxfVFJBQ0VfTE9HR0lOR19FTkFCTEVEOiAwCiAgICAgIFBPU1RHUkVTX1VTRVI6ICRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX0RCOiAnJHtQT1NUR1JFU19EQjotdHJpZ2dlcn0nCiAgICAgIE1BR0lDX0xJTktfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9NQUdJQwogICAgICBTRVNTSU9OX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfU0VTU0lPTgogICAgICBFTkNSWVBUSU9OX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfRU5DUllQVElPTgogICAgICBQUk9WSURFUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1BST1ZJREVSCiAgICAgIENPT1JESU5BVE9SX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfQ09PUkRJTkFUT1IKICAgICAgREFUQUJBU0VfSE9TVDogJ3Bvc3RncmVzcWw6NTQzMicKICAgICAgREFUQUJBU0VfVVJMOiAncG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBvc3RncmVzcWw6NTQzMi8kUE9TVEdSRVNfREI/c3NsbW9kZT1kaXNhYmxlJwogICAgICBESVJFQ1RfVVJMOiAncG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBvc3RncmVzcWw6NTQzMi8kUE9TVEdSRVNfREI/c3NsbW9kZT1kaXNhYmxlJwogICAgICBSRURJU19IT1NUOiByZWRpcwogICAgICBSRURJU19QT1JUOiA2Mzc5CiAgICAgIFJFRElTX1RMU19ESVNBQkxFRDogdHJ1ZQogICAgICBDT09SRElOQVRPUl9IT1NUOiAxMjcuMC4wLjEKICAgICAgQ09PUkRJTkFUT1JfUE9SVDogOTAyMAogICAgICBXSElURUxJU1RFRF9FTUFJTFM6ICcnCiAgICAgIEFETUlOX0VNQUlMUzogJycKICAgICAgREVGQVVMVF9PUkdfRVhFQ1VUSU9OX0NPTkNVUlJFTkNZX0xJTUlUOiAzMDAKICAgICAgREVGQVVMVF9FTlZfRVhFQ1VUSU9OX0NPTkNVUlJFTkNZX0xJTUlUOiAxMDAKICAgICAgREVQTE9ZX1JFR0lTVFJZX0hPU1Q6IGRvY2tlci5pbwogICAgICBERVBMT1lfUkVHSVNUUllfTkFNRVNQQUNFOiB0cmlnZ2VyCiAgICAgIFJFR0lTVFJZX0hPU1Q6ICcke0RFUExPWV9SRUdJU1RSWV9IT1NUfScKICAgICAgUkVHSVNUUllfTkFNRVNQQUNFOiAnJHtERVBMT1lfUkVHSVNUUllfTkFNRVNQQUNFfScKICAgICAgQVVUSF9HSVRIVUJfQ0xJRU5UX0lEOiAnJHtBVVRIX0dJVEhVQl9DTElFTlRfSUR9JwogICAgICBBVVRIX0dJVEhVQl9DTElFTlRfU0VDUkVUOiAnJHtBVVRIX0dJVEhVQl9DTElFTlRfU0VDUkVUfScKICAgICAgUkVTRU5EX0FQSV9LRVk6ICcke1JFU0VORF9BUElfS0VZfScKICAgICAgRlJPTV9FTUFJTDogJyR7RlJPTV9FTUFJTH0nCiAgICAgIFJFUExZX1RPX0VNQUlMOiAnJHtSRVBMWV9UT19FTUFJTH0nCiAgICAgIExPR0lOX09SSUdJTjogJFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAKICAgICAgQVBQX09SSUdJTjogJFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAKICAgICAgREVWX09URUxfRVhQT1JURVJfT1RMUF9FTkRQT0lOVDogJFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAvb3RlbAogICAgICBPVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICdodHRwOi8vdHJpZ2dlcjozMDQwL290ZWwnCiAgICAgIEVMRUNUUklDX09SSUdJTjogJ2h0dHA6Ly9lbGVjdHJpYzozMDAwJwogICAgY29tbWFuZDoKICAgICAgLSAnLWMnCiAgICAgIC0gd2FsX2xldmVsPWxvZ2ljYWwKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBkb2NrZXItcHJvdmlkZXI6CiAgICBpbWFnZTogJ2doY3IuaW8vdHJpZ2dlcmRvdGRldi9wcm92aWRlci9kb2NrZXI6djMnCiAgICBwbGF0Zm9ybTogbGludXgvYW1kNjQKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgdXNlcjogcm9vdAogICAgZGVwZW5kc19vbjoKICAgICAgdHJpZ2dlcjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFJFTUlYX0FQUF9QT1JUOiAzMDAwCiAgICAgIE5PREVfRU5WOiBwcm9kdWN0aW9uCiAgICAgIFJVTlRJTUVfUExBVEZPUk06IGRvY2tlci1jb21wb3NlCiAgICAgIFYzX0VOQUJMRUQ6IHRydWUKICAgICAgSU5URVJOQUxfT1RFTF9UUkFDRV9ESVNBQkxFRDogMQogICAgICBJTlRFUk5BTF9PVEVMX1RSQUNFX0xPR0dJTkdfRU5BQkxFRDogMAogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogJyR7UE9TVEdSRVNfREI6LXRyaWdnZXJ9JwogICAgICBNQUdJQ19MSU5LX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICAgICAgU0VTU0lPTl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT04KICAgICAgRU5DUllQVElPTl9LRVk6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0VOQ1JZUFRJT04KICAgICAgUFJPVklERVJfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9QUk9WSURFUgogICAgICBDT09SRElOQVRPUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0NPT1JESU5BVE9SCiAgICAgIERBVEFCQVNFX0hPU1Q6ICdwb3N0Z3Jlc3FsOjU0MzInCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgRElSRUNUX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgUkVESVNfSE9TVDogcmVkaXMKICAgICAgUkVESVNfUE9SVDogNjM3OQogICAgICBSRURJU19UTFNfRElTQUJMRUQ6IHRydWUKICAgICAgQ09PUkRJTkFUT1JfSE9TVDogMTI3LjAuMC4xCiAgICAgIENPT1JESU5BVE9SX1BPUlQ6IDkwMjAKICAgICAgV0hJVEVMSVNURURfRU1BSUxTOiAnJwogICAgICBBRE1JTl9FTUFJTFM6ICcnCiAgICAgIERFRkFVTFRfT1JHX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMzAwCiAgICAgIERFRkFVTFRfRU5WX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMTAwCiAgICAgIERFUExPWV9SRUdJU1RSWV9IT1NUOiBkb2NrZXIuaW8KICAgICAgREVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRTogdHJpZ2dlcgogICAgICBSRUdJU1RSWV9IT1NUOiAnJHtERVBMT1lfUkVHSVNUUllfSE9TVH0nCiAgICAgIFJFR0lTVFJZX05BTUVTUEFDRTogJyR7REVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRX0nCiAgICAgIEFVVEhfR0lUSFVCX0NMSUVOVF9JRDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX0lEfScKICAgICAgQVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVH0nCiAgICAgIFJFU0VORF9BUElfS0VZOiAnJHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIEZST01fRU1BSUw6ICcke0ZST01fRU1BSUx9JwogICAgICBSRVBMWV9UT19FTUFJTDogJyR7UkVQTFlfVE9fRU1BSUx9JwogICAgICBMT0dJTl9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIEFQUF9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIERFVl9PVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwL290ZWwKICAgICAgT1RFTF9FWFBPUlRFUl9PVExQX0VORFBPSU5UOiAnaHR0cDovL3RyaWdnZXI6MzA0MC9vdGVsJwogICAgICBFTEVDVFJJQ19PUklHSU46ICdodHRwOi8vZWxlY3RyaWM6MzAwMCcKICAgICAgUExBVEZPUk1fSE9TVDogdHJpZ2dlcgogICAgICBQTEFURk9STV9XU19QT1JUOiAzMDAwCiAgICAgIFNFQ1VSRV9DT05ORUNUSU9OOiAnZmFsc2UnCiAgICAgIFBMQVRGT1JNX1NFQ1JFVDogJFBST1ZJREVSX1NFQ1JFVAogIGNvb3JkaW5hdG9yOgogICAgaW1hZ2U6ICdnaGNyLmlvL3RyaWdnZXJkb3RkZXYvY29vcmRpbmF0b3I6djMnCiAgICBwbGF0Zm9ybTogbGludXgvYW1kNjQKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgdXNlcjogcm9vdAogICAgZGVwZW5kc19vbjoKICAgICAgdHJpZ2dlcjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFJFTUlYX0FQUF9QT1JUOiAzMDAwCiAgICAgIE5PREVfRU5WOiBwcm9kdWN0aW9uCiAgICAgIFJVTlRJTUVfUExBVEZPUk06IGRvY2tlci1jb21wb3NlCiAgICAgIFYzX0VOQUJMRUQ6IHRydWUKICAgICAgSU5URVJOQUxfT1RFTF9UUkFDRV9ESVNBQkxFRDogMQogICAgICBJTlRFUk5BTF9PVEVMX1RSQUNFX0xPR0dJTkdfRU5BQkxFRDogMAogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogJyR7UE9TVEdSRVNfREI6LXRyaWdnZXJ9JwogICAgICBNQUdJQ19MSU5LX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfTUFHSUMKICAgICAgU0VTU0lPTl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFU1NJT04KICAgICAgRU5DUllQVElPTl9LRVk6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0VOQ1JZUFRJT04KICAgICAgUFJPVklERVJfU0VDUkVUOiAkU0VSVklDRV9QQVNTV09SRF82NF9QUk9WSURFUgogICAgICBDT09SRElOQVRPUl9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0NPT1JESU5BVE9SCiAgICAgIERBVEFCQVNFX0hPU1Q6ICdwb3N0Z3Jlc3FsOjU0MzInCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgRElSRUNUX1VSTDogJ3Bvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCP3NzbG1vZGU9ZGlzYWJsZScKICAgICAgUkVESVNfSE9TVDogcmVkaXMKICAgICAgUkVESVNfUE9SVDogNjM3OQogICAgICBSRURJU19UTFNfRElTQUJMRUQ6IHRydWUKICAgICAgQ09PUkRJTkFUT1JfSE9TVDogMTI3LjAuMC4xCiAgICAgIENPT1JESU5BVE9SX1BPUlQ6IDkwMjAKICAgICAgV0hJVEVMSVNURURfRU1BSUxTOiAnJwogICAgICBBRE1JTl9FTUFJTFM6ICcnCiAgICAgIERFRkFVTFRfT1JHX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMzAwCiAgICAgIERFRkFVTFRfRU5WX0VYRUNVVElPTl9DT05DVVJSRU5DWV9MSU1JVDogMTAwCiAgICAgIERFUExPWV9SRUdJU1RSWV9IT1NUOiBkb2NrZXIuaW8KICAgICAgREVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRTogdHJpZ2dlcgogICAgICBSRUdJU1RSWV9IT1NUOiAnJHtERVBMT1lfUkVHSVNUUllfSE9TVH0nCiAgICAgIFJFR0lTVFJZX05BTUVTUEFDRTogJyR7REVQTE9ZX1JFR0lTVFJZX05BTUVTUEFDRX0nCiAgICAgIEFVVEhfR0lUSFVCX0NMSUVOVF9JRDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX0lEfScKICAgICAgQVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVDogJyR7QVVUSF9HSVRIVUJfQ0xJRU5UX1NFQ1JFVH0nCiAgICAgIFJFU0VORF9BUElfS0VZOiAnJHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIEZST01fRU1BSUw6ICcke0ZST01fRU1BSUx9JwogICAgICBSRVBMWV9UT19FTUFJTDogJyR7UkVQTFlfVE9fRU1BSUx9JwogICAgICBMT0dJTl9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIEFQUF9PUklHSU46ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwCiAgICAgIERFVl9PVEVMX0VYUE9SVEVSX09UTFBfRU5EUE9JTlQ6ICRTRVJWSUNFX0ZRRE5fVFJJR0dFUl8zMDAwL290ZWwKICAgICAgT1RFTF9FWFBPUlRFUl9PVExQX0VORFBPSU5UOiAnaHR0cDovL3RyaWdnZXI6MzA0MC9vdGVsJwogICAgICBFTEVDVFJJQ19PUklHSU46ICdodHRwOi8vZWxlY3RyaWM6MzAwMCcKICAgICAgUExBVEZPUk1fSE9TVDogdHJpZ2dlcgogICAgICBQTEFURk9STV9XU19QT1JUOiAzMDAwCiAgICAgIFNFQ1VSRV9DT05ORUNUSU9OOiAnZmFsc2UnCiAgICAgIFBMQVRGT1JNX1NFQ1JFVDogJENPT1JESU5BVE9SX1NFQ1JFVAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtIHB3ZAo=", "tags": [ "trigger.dev", "background jobs", From 1886347b55eebf88072b22f30c26b35aec20db7c Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 14:08:34 +0100 Subject: [PATCH 24/27] fix: proxy change behaviour --- app/Livewire/Server/Proxy.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php index 5d2f851db..4e325c1ff 100644 --- a/app/Livewire/Server/Proxy.php +++ b/app/Livewire/Server/Proxy.php @@ -16,6 +16,7 @@ class Proxy extends Component public $proxy_settings = null; public bool $redirect_enabled = true; + public ?string $redirect_url = null; protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit']; @@ -40,7 +41,7 @@ class Proxy extends Component { $this->server->proxy = null; $this->server->save(); - $this->dispatch('proxyChanged'); + $this->dispatch('reloadWindow'); } public function selectProxy($proxy_type) @@ -48,7 +49,7 @@ class Proxy extends Component try { $this->server->changeProxy($proxy_type, async: false); $this->selectedProxy = $this->server->proxy->type; - $this->dispatch('proxyStatusUpdated'); + $this->dispatch('reloadWindow'); } catch (\Throwable $e) { return handleError($e, $this); } From 00882eeb313c21b04e60fb880b9aa5782694cce7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 14:19:09 +0100 Subject: [PATCH 25/27] feat: add TLS configuration for default redirect in Server model --- app/Models/Server.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Models/Server.php b/app/Models/Server.php index 4c4ca774f..6dfb0a4a1 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -242,6 +242,9 @@ class Server extends BaseModel ], 'service' => 'noop', 'rule' => 'PathPrefix(`/`)', + 'tls' => [ + 'certResolver' => 'letsencrypt', + ], 'priority' => -1000, ], ], From 08d992a1c235856e1b94d1220ff9054a5320b48d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Dec 2024 14:33:22 +0100 Subject: [PATCH 26/27] fix --- app/Jobs/SendMessageToSlackJob.php | 17 +++++++++-------- app/Notifications/Channels/SlackChannel.php | 6 +++--- app/Notifications/Test.php | 2 +- ...0_add_slack_notifications_to_teams_table.php | 17 +++++++++-------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/Jobs/SendMessageToSlackJob.php b/app/Jobs/SendMessageToSlackJob.php index b78088f50..470002d23 100644 --- a/app/Jobs/SendMessageToSlackJob.php +++ b/app/Jobs/SendMessageToSlackJob.php @@ -18,6 +18,7 @@ class SendMessageToSlackJob implements ShouldQueue private SlackMessage $message, private string $webhookUrl ) { + $this->onQueue('high'); } public function handle(): void @@ -28,7 +29,7 @@ class SendMessageToSlackJob implements ShouldQueue 'type' => 'section', 'text' => [ 'type' => 'plain_text', - 'text' => "Coolify Notification", + 'text' => 'Coolify Notification', ], ], ], @@ -47,12 +48,12 @@ class SendMessageToSlackJob implements ShouldQueue 'type' => 'section', 'text' => [ 'type' => 'mrkdwn', - 'text' => $this->message->description - ] - ] - ] - ] - ] + 'text' => $this->message->description, + ], + ], + ], + ], + ], ]); } -} \ No newline at end of file +} diff --git a/app/Notifications/Channels/SlackChannel.php b/app/Notifications/Channels/SlackChannel.php index 0d359ef5f..32fdbe9cf 100644 --- a/app/Notifications/Channels/SlackChannel.php +++ b/app/Notifications/Channels/SlackChannel.php @@ -14,9 +14,9 @@ class SlackChannel { $message = $notification->toSlack(); $webhookUrl = $notifiable->routeNotificationForSlack(); - if (!$webhookUrl) { + if (! $webhookUrl) { return; } - dispatch(new SendMessageToSlackJob($message, $webhookUrl))->onQueue('high'); + SendMessageToSlackJob::dispatch($message, $webhookUrl); } -} \ No newline at end of file +} diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index ce998d9f6..03f6c3296 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -51,7 +51,7 @@ class Test extends Notification implements ShouldQueue color: DiscordMessage::successColor(), ); - $message->addField(name: 'Dashboard', value: '[Link](' . base_url() . ')', inline: true); + $message->addField(name: 'Dashboard', value: '[Link]('.base_url().')', inline: true); return $message; } diff --git a/database/migrations/2024_11_12_213200_add_slack_notifications_to_teams_table.php b/database/migrations/2024_11_12_213200_add_slack_notifications_to_teams_table.php index c3896a053..a6457269a 100644 --- a/database/migrations/2024_11_12_213200_add_slack_notifications_to_teams_table.php +++ b/database/migrations/2024_11_12_213200_add_slack_notifications_to_teams_table.php @@ -4,18 +4,19 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration { +return new class extends Migration +{ public function up(): void { Schema::table('teams', function (Blueprint $table) { $table->boolean('slack_enabled')->default(false); $table->string('slack_webhook_url')->nullable(); - $table->boolean('slack_notifications_test')->default(false); - $table->boolean('slack_notifications_deployments')->default(false); - $table->boolean('slack_notifications_status_changes')->default(false); - $table->boolean('slack_notifications_database_backups')->default(false); - $table->boolean('slack_notifications_scheduled_tasks')->default(false); - $table->boolean('slack_notifications_server_disk_usage')->default(false); + $table->boolean('slack_notifications_test')->default(true); + $table->boolean('slack_notifications_deployments')->default(true); + $table->boolean('slack_notifications_status_changes')->default(true); + $table->boolean('slack_notifications_database_backups')->default(true); + $table->boolean('slack_notifications_scheduled_tasks')->default(true); + $table->boolean('slack_notifications_server_disk_usage')->default(true); }); } @@ -34,4 +35,4 @@ return new class extends Migration { ]); }); } -}; \ No newline at end of file +}; From 4927590fc87a0be0ef711bba64ce53d71e1f561f Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:33:45 +0100 Subject: [PATCH 27/27] Update service-templates.json --- templates/service-templates.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/templates/service-templates.json b/templates/service-templates.json index 2e1e3bbf9..34da885d5 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -1125,7 +1125,7 @@ "applications", "interface" ], - "logo": "svgs/default.webp", + "logo": "svgs/heimdall.svg", "minversion": "0.0.0" }, "heyform": { @@ -1251,7 +1251,7 @@ "finance", "self-hosted" ], - "logo": "svgs/default.webp", + "logo": "svgs/invoiceninja.png", "minversion": "0.0.0", "port": "9000" }, @@ -1417,7 +1417,7 @@ "geofencing", "low-code" ], - "logo": "svgs/default.webp", + "logo": "svgs/kuzzle.png", "minversion": "0.0.0", "port": "7512" }, @@ -2112,7 +2112,7 @@ "collaboration", "teamwork" ], - "logo": "svgs/default.webp", + "logo": "svgs/pairdrop.png", "minversion": "0.0.0", "port": "3000" }, @@ -2137,7 +2137,7 @@ "open", "source" ], - "logo": "svgs/default.webp", + "logo": "svgs/penpot.svg", "minversion": "0.0.0" }, "phpmyadmin": { @@ -2245,7 +2245,7 @@ "postiz": { "documentation": "https://docs.postiz.com?utm_source=coolify.io", "slogan": "Open source social media scheduling tool.", - "compose": "c2VydmljZXM6CiAgcG9zdGl6OgogICAgaW1hZ2U6ICdnaGNyLmlvL2dpdHJvb21ocS9wb3N0aXotYXBwOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9QT1NUSVpfNTAwMAogICAgICAtICdNQUlOX1VSTD0ke1NFUlZJQ0VfRlFETl9QT1NUSVp9JwogICAgICAtICdGUk9OVEVORF9VUkw9JHtTRVJWSUNFX0ZRRE5fUE9TVElafScKICAgICAgLSAnTkVYVF9QVUJMSUNfQkFDS0VORF9VUkw9JHtTRVJWSUNFX0ZRRE5fUE9TVElafS9hcGknCiAgICAgIC0gJ0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVFNFQ1JFVH0nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3Jlc3FsOi8vcG9zdGdyZXM6JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1wb3N0aXotZGJ9JwogICAgICAtICdSRURJU19VUkw9cmVkaXM6Ly9kZWZhdWx0OiR7U0VSVklDRV9QQVNTV09SRF9SRURJU31AcmVkaXM6NjM3OScKICAgICAgLSAnQkFDS0VORF9JTlRFUk5BTF9VUkw9aHR0cDovL2xvY2FsaG9zdDozMDAwJwogICAgICAtICdDTE9VREZMQVJFX0FDQ09VTlRfSUQ9JHtDTE9VREZMQVJFX0FDQ09VTlRfSUR9JwogICAgICAtICdDTE9VREZMQVJFX0FDQ0VTU19LRVk9JHtDTE9VREZMQVJFX0FDQ0VTU19LRVl9JwogICAgICAtICdDTE9VREZMQVJFX1NFQ1JFVF9BQ0NFU1NfS0VZPSR7Q0xPVURGTEFSRV9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgICAgIC0gJ0NMT1VERkxBUkVfQlVDS0VUTkFNRT0ke0NMT1VERkxBUkVfQlVDS0VUTkFNRX0nCiAgICAgIC0gJ0NMT1VERkxBUkVfQlVDS0VUX1VSTD0ke0NMT1VERkxBUkVfQlVDS0VUX1VSTH0nCiAgICAgIC0gJ0NMT1VERkxBUkVfUkVHSU9OPSR7Q0xPVURGTEFSRV9SRUdJT059JwogICAgICAtICdTVE9SQUdFX1BST1ZJREVSPSR7U1RPUkFHRV9QUk9WSURFUjotbG9jYWx9JwogICAgICAtICdVUExPQURfRElSRUNUT1JZPSR7VVBMT0FEX0RJUkVDVE9SWTotL3VwbG9hZHN9JwogICAgICAtICdORVhUX1BVQkxJQ19VUExPQURfRElSRUNUT1JZPSR7TkVYVF9QVUJMSUNfVVBMT0FEX0RJUkVDVE9SWTotL3VwbG9hZHN9JwogICAgICAtICdORVhUX1BVQkxJQ19VUExPQURfU1RBVElDX0RJUkVDVE9SWT0ke05FWFRfUFVCTElDX1VQTE9BRF9TVEFUSUNfRElSRUNUT1JZfScKICAgICAgLSAnUkVTRU5EX0FQSV9LRVk9JHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIC0gJ0VNQUlMX0ZST01fQUREUkVTUz0ke0VNQUlMX0ZST01fQUREUkVTU30nCiAgICAgIC0gJ0VNQUlMX0ZST01fTkFNRT0ke0VNQUlMX0ZST01fTkFNRX0nCiAgICAgIC0gJ1hfQVBJX0tFWT0ke1NFUlZJQ0VfWF9BUEl9JwogICAgICAtICdYX0FQSV9TRUNSRVQ9JHtTRVJWSUNFX1hfU0VDUkVUfScKICAgICAgLSAnTElOS0VESU5fQ0xJRU5UX0lEPSR7U0VSVklDRV9MSU5LRURJTl9JRH0nCiAgICAgIC0gJ0xJTktFRElOX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0xJTktFRElOX1NFQ1JFVH0nCiAgICAgIC0gJ1JFRERJVF9DTElFTlRfSUQ9JHtTRVJWSUNFX1JFRERJVF9BUEl9JwogICAgICAtICdSRURESVRfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfUkVERElUX1NFQ1JFVH0nCiAgICAgIC0gJ0dJVEhVQl9DTElFTlRfSUQ9JHtTRVJWSUNFX0dJVEhVQl9JRH0nCiAgICAgIC0gJ0dJVEhVQl9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9HSVRIVUJfU0VDUkVUfScKICAgICAgLSAnVEhSRUFEU19BUFBfSUQ9JHtTRVJWSUNFX1RIUkVBRFNfSUR9JwogICAgICAtICdUSFJFQURTX0FQUF9TRUNSRVQ9JHtTRVJWSUNFX1RIUkVBRFNfU0VDUkVUfScKICAgICAgLSAnRkFDRUJPT0tfQVBQX0lEPSR7U0VSVklDRV9GQUNFQk9PS19JRH0nCiAgICAgIC0gJ0ZBQ0VCT09LX0FQUF9TRUNSRVQ9JHtTRVJWSUNFX0ZBQ0VCT09LX1NFQ1JFVH0nCiAgICAgIC0gJ1lPVVRVQkVfQ0xJRU5UX0lEPSR7U0VSVklDRV9ZT1VUVUJFX0lEfScKICAgICAgLSAnWU9VVFVCRV9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9ZT1VUVUJFX1NFQ1JFVH0nCiAgICAgIC0gJ1RJS1RPS19DTElFTlRfSUQ9JHtTRVJWSUNFX1RJS1RPS19JRH0nCiAgICAgIC0gJ1RJS1RPS19DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9USUtUT0tfU0VDUkVUfScKICAgICAgLSAnUElOVEVSRVNUX0NMSUVOVF9JRD0ke1NFUlZJQ0VfUElOVEVSRVNUX0lEfScKICAgICAgLSAnUElOVEVSRVNUX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1BJTlRFUkVTVF9TRUNSRVR9JwogICAgICAtICdEUklCQkJMRV9DTElFTlRfSUQ9JHtTRVJWSUNFX0RSSUJCTEVfSUR9JwogICAgICAtICdEUklCQkJMRV9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9EUklCQkxFX1NFQ1JFVH0nCiAgICAgIC0gJ0RJU0NPUkRfQ0xJRU5UX0lEPSR7U0VSVklDRV9ESVNDT1JEX0lEfScKICAgICAgLSAnRElTQ09SRF9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9ESVNDT1JEX1NFQ1JFVH0nCiAgICAgIC0gJ0RJU0NPUkRfQk9UX1RPS0VOX0lEPSR7U0VSVklDRV9ESVNDT1JEX1RPS0VOfScKICAgICAgLSAnU0xBQ0tfSUQ9JHtTRVJWSUNFX1NMQUNLX0lEfScKICAgICAgLSAnU0xBQ0tfU0VDUkVUPSR7U0VSVklDRV9TTEFDS19TRUNSRVR9JwogICAgICAtICdTTEFDS19TSUdOSU5HX1NFQ1JFVD0ke1NMQUNLX1NJR05JTkdfU0VDUkVUfScKICAgICAgLSAnTUFTVE9ET05fQ0xJRU5UX0lEPSR7U0VSVklDRV9NQVNUT0RPTl9JRH0nCiAgICAgIC0gJ01BU1RPRE9OX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX01BU1RPRE9OX1NFQ1JFVH0nCiAgICAgIC0gJ0JFRUhJSVZFX0FQSV9LRVk9JHtTRVJWSUNFX0JFRUhJSVZFX0tFWX0nCiAgICAgIC0gJ0JFRUhJSVZFX1BVQkxJQ0FUSU9OX0lEPSR7U0VSVklDRV9CRUVISUlWRV9QVUJJRH0nCiAgICAgIC0gJ09QRU5BSV9BUElfS0VZPSR7U0VSVklDRV9PUEVOQUlfS0VZfScKICAgICAgLSAnTkVYVF9QVUJMSUNfRElTQ09SRF9TVVBQT1JUPSR7TkVYVF9QVUJMSUNfRElTQ09SRF9TVVBQT1JUfScKICAgICAgLSAnTkVYVF9QVUJMSUNfUE9MT1ROTz0ke05FWFRfUFVCTElDX1BPTE9UTk99JwogICAgICAtIElTX0dFTkVSQUw9dHJ1ZQogICAgICAtICdOWF9BRERfUExVR0lOUz0ke05YX0FERF9QTFVHSU5TOi1mYWxzZX0nCiAgICAgIC0gJ0ZFRV9BTU9VTlQ9JHtGRUVfQU1PVU5UOi0wLjA1fScKICAgICAgLSAnU1RSSVBFX1BVQkxJU0hBQkxFX0tFWT0ke1NUUklQRV9QVUJMSVNIQUJMRV9LRVl9JwogICAgICAtICdTVFJJUEVfU0VDUkVUX0tFWT0ke1NUUklQRV9TRUNSRVRfS0VZfScKICAgICAgLSAnU1RSSVBFX1NJR05JTkdfS0VZPSR7U1RSSVBFX1NJR05JTkdfS0VZfScKICAgICAgLSAnU1RSSVBFX1NJR05JTkdfS0VZX0NPTk5FQ1Q9JHtTVFJJUEVfU0lHTklOR19LRVlfQ09OTkVDVH0nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0aXpfY29uZmlnOi9jb25maWcvJwogICAgICAtICdwb3N0aXpfdXBsb2FkczovdXBsb2Fkcy8nCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByZWRpczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICd3Z2V0IC1xTy0gaHR0cDovLzEyNy4wLjAuMTo1MDAwLycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNC41JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGl6X3Bvc3RncmVzcWxfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19VU0VSPXBvc3RncmVzCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1wb3N0aXotZGJ9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCOi1wb3N0aXotZGJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjcuMicKICAgIGVudmlyb25tZW50OgogICAgICAtICdSRURJU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLXJlcXVpcmVwYXNzICR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0aXpfcmVkaXNfZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSByZWRpcy1jbGkKICAgICAgICAtICctYScKICAgICAgICAtICcke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDIwCg==", + "compose": "c2VydmljZXM6CiAgcG9zdGl6OgogICAgaW1hZ2U6ICdnaGNyLmlvL2dpdHJvb21ocS9wb3N0aXotYXBwOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9QT1NUSVpfNTAwMAogICAgICAtICdNQUlOX1VSTD0ke1NFUlZJQ0VfRlFETl9QT1NUSVp9JwogICAgICAtICdGUk9OVEVORF9VUkw9JHtTRVJWSUNFX0ZRRE5fUE9TVElafScKICAgICAgLSAnTkVYVF9QVUJMSUNfQkFDS0VORF9VUkw9JHtTRVJWSUNFX0ZRRE5fUE9TVElafS9hcGknCiAgICAgIC0gJ0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVFNFQ1JFVH0nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3Jlc3FsOi8vcG9zdGdyZXM6JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1wb3N0aXotZGJ9JwogICAgICAtICdSRURJU19VUkw9cmVkaXM6Ly9kZWZhdWx0OiR7U0VSVklDRV9QQVNTV09SRF9SRURJU31AcmVkaXM6NjM3OScKICAgICAgLSAnQkFDS0VORF9JTlRFUk5BTF9VUkw9aHR0cDovL2xvY2FsaG9zdDozMDAwJwogICAgICAtICdDTE9VREZMQVJFX0FDQ09VTlRfSUQ9JHtDTE9VREZMQVJFX0FDQ09VTlRfSUR9JwogICAgICAtICdDTE9VREZMQVJFX0FDQ0VTU19LRVk9JHtDTE9VREZMQVJFX0FDQ0VTU19LRVl9JwogICAgICAtICdDTE9VREZMQVJFX1NFQ1JFVF9BQ0NFU1NfS0VZPSR7Q0xPVURGTEFSRV9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgICAgIC0gJ0NMT1VERkxBUkVfQlVDS0VUTkFNRT0ke0NMT1VERkxBUkVfQlVDS0VUTkFNRX0nCiAgICAgIC0gJ0NMT1VERkxBUkVfQlVDS0VUX1VSTD0ke0NMT1VERkxBUkVfQlVDS0VUX1VSTH0nCiAgICAgIC0gJ0NMT1VERkxBUkVfUkVHSU9OPSR7Q0xPVURGTEFSRV9SRUdJT059JwogICAgICAtICdTVE9SQUdFX1BST1ZJREVSPSR7U1RPUkFHRV9QUk9WSURFUjotbG9jYWx9JwogICAgICAtICdVUExPQURfRElSRUNUT1JZPSR7VVBMT0FEX0RJUkVDVE9SWTotL3VwbG9hZHN9JwogICAgICAtICdORVhUX1BVQkxJQ19VUExPQURfRElSRUNUT1JZPSR7TkVYVF9QVUJMSUNfVVBMT0FEX0RJUkVDVE9SWTotL3VwbG9hZHN9JwogICAgICAtICdORVhUX1BVQkxJQ19VUExPQURfU1RBVElDX0RJUkVDVE9SWT0ke05FWFRfUFVCTElDX1VQTE9BRF9TVEFUSUNfRElSRUNUT1JZfScKICAgICAgLSAnUkVTRU5EX0FQSV9LRVk9JHtSRVNFTkRfQVBJX0tFWX0nCiAgICAgIC0gJ0VNQUlMX0ZST01fQUREUkVTUz0ke0VNQUlMX0ZST01fQUREUkVTU30nCiAgICAgIC0gJ0VNQUlMX0ZST01fTkFNRT0ke0VNQUlMX0ZST01fTkFNRX0nCiAgICAgIC0gJ0VNQUlMX1BST1ZJREVSPSR7RU1BSUxfUFJPVklERVJ9JwogICAgICAtICdYX0FQSV9LRVk9JHtTRVJWSUNFX1hfQVBJfScKICAgICAgLSAnWF9BUElfU0VDUkVUPSR7U0VSVklDRV9YX1NFQ1JFVH0nCiAgICAgIC0gJ0xJTktFRElOX0NMSUVOVF9JRD0ke1NFUlZJQ0VfTElOS0VESU5fSUR9JwogICAgICAtICdMSU5LRURJTl9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9MSU5LRURJTl9TRUNSRVR9JwogICAgICAtICdSRURESVRfQ0xJRU5UX0lEPSR7U0VSVklDRV9SRURESVRfQVBJfScKICAgICAgLSAnUkVERElUX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1JFRERJVF9TRUNSRVR9JwogICAgICAtICdHSVRIVUJfQ0xJRU5UX0lEPSR7U0VSVklDRV9HSVRIVUJfSUR9JwogICAgICAtICdHSVRIVUJfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfR0lUSFVCX1NFQ1JFVH0nCiAgICAgIC0gJ1RIUkVBRFNfQVBQX0lEPSR7U0VSVklDRV9USFJFQURTX0lEfScKICAgICAgLSAnVEhSRUFEU19BUFBfU0VDUkVUPSR7U0VSVklDRV9USFJFQURTX1NFQ1JFVH0nCiAgICAgIC0gJ0ZBQ0VCT09LX0FQUF9JRD0ke1NFUlZJQ0VfRkFDRUJPT0tfSUR9JwogICAgICAtICdGQUNFQk9PS19BUFBfU0VDUkVUPSR7U0VSVklDRV9GQUNFQk9PS19TRUNSRVR9JwogICAgICAtICdZT1VUVUJFX0NMSUVOVF9JRD0ke1NFUlZJQ0VfWU9VVFVCRV9JRH0nCiAgICAgIC0gJ1lPVVRVQkVfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfWU9VVFVCRV9TRUNSRVR9JwogICAgICAtICdUSUtUT0tfQ0xJRU5UX0lEPSR7U0VSVklDRV9USUtUT0tfSUR9JwogICAgICAtICdUSUtUT0tfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfVElLVE9LX1NFQ1JFVH0nCiAgICAgIC0gJ1BJTlRFUkVTVF9DTElFTlRfSUQ9JHtTRVJWSUNFX1BJTlRFUkVTVF9JRH0nCiAgICAgIC0gJ1BJTlRFUkVTVF9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9QSU5URVJFU1RfU0VDUkVUfScKICAgICAgLSAnRFJJQkJCTEVfQ0xJRU5UX0lEPSR7U0VSVklDRV9EUklCQkxFX0lEfScKICAgICAgLSAnRFJJQkJCTEVfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfRFJJQkJMRV9TRUNSRVR9JwogICAgICAtICdESVNDT1JEX0NMSUVOVF9JRD0ke1NFUlZJQ0VfRElTQ09SRF9JRH0nCiAgICAgIC0gJ0RJU0NPUkRfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfRElTQ09SRF9TRUNSRVR9JwogICAgICAtICdESVNDT1JEX0JPVF9UT0tFTl9JRD0ke1NFUlZJQ0VfRElTQ09SRF9UT0tFTn0nCiAgICAgIC0gJ1NMQUNLX0lEPSR7U0VSVklDRV9TTEFDS19JRH0nCiAgICAgIC0gJ1NMQUNLX1NFQ1JFVD0ke1NFUlZJQ0VfU0xBQ0tfU0VDUkVUfScKICAgICAgLSAnU0xBQ0tfU0lHTklOR19TRUNSRVQ9JHtTTEFDS19TSUdOSU5HX1NFQ1JFVH0nCiAgICAgIC0gJ01BU1RPRE9OX0NMSUVOVF9JRD0ke1NFUlZJQ0VfTUFTVE9ET05fSUR9JwogICAgICAtICdNQVNUT0RPTl9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9NQVNUT0RPTl9TRUNSRVR9JwogICAgICAtICdCRUVISUlWRV9BUElfS0VZPSR7U0VSVklDRV9CRUVISUlWRV9LRVl9JwogICAgICAtICdCRUVISUlWRV9QVUJMSUNBVElPTl9JRD0ke1NFUlZJQ0VfQkVFSElJVkVfUFVCSUR9JwogICAgICAtICdPUEVOQUlfQVBJX0tFWT0ke1NFUlZJQ0VfT1BFTkFJX0tFWX0nCiAgICAgIC0gJ05FWFRfUFVCTElDX0RJU0NPUkRfU1VQUE9SVD0ke05FWFRfUFVCTElDX0RJU0NPUkRfU1VQUE9SVH0nCiAgICAgIC0gJ05FWFRfUFVCTElDX1BPTE9UTk89JHtORVhUX1BVQkxJQ19QT0xPVE5PfScKICAgICAgLSBJU19HRU5FUkFMPXRydWUKICAgICAgLSAnTlhfQUREX1BMVUdJTlM9JHtOWF9BRERfUExVR0lOUzotZmFsc2V9JwogICAgICAtICdGRUVfQU1PVU5UPSR7RkVFX0FNT1VOVDotMC4wNX0nCiAgICAgIC0gJ1NUUklQRV9QVUJMSVNIQUJMRV9LRVk9JHtTVFJJUEVfUFVCTElTSEFCTEVfS0VZfScKICAgICAgLSAnU1RSSVBFX1NFQ1JFVF9LRVk9JHtTVFJJUEVfU0VDUkVUX0tFWX0nCiAgICAgIC0gJ1NUUklQRV9TSUdOSU5HX0tFWT0ke1NUUklQRV9TSUdOSU5HX0tFWX0nCiAgICAgIC0gJ1NUUklQRV9TSUdOSU5HX0tFWV9DT05ORUNUPSR7U1RSSVBFX1NJR05JTkdfS0VZX0NPTk5FQ1R9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGl6X2NvbmZpZzovY29uZmlnLycKICAgICAgLSAncG9zdGl6X3VwbG9hZHM6L3VwbG9hZHMvJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6NTAwMC8nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTQuNScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3Rpel9wb3N0Z3Jlc3FsX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj1wb3N0Z3JlcwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotcG9zdGl6LWRifScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQjotcG9zdGl6LWRifScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3LjInCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUkVESVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1yZXF1aXJlcGFzcyAke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGl6X3JlZGlzX2RhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSAnLWEnCiAgICAgICAgLSAnJHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgICAgICAtIHBpbmcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAyMAo=", "tags": [ "post everywhere", "social media", @@ -2971,7 +2971,7 @@ "privacy", "search engine" ], - "logo": "svgs/default.webp", + "logo": "svgs/whoogle.png", "minversion": "0.0.0", "port": "5000" },