From 79a169f53e3bc5164292e17bc159737293207812 Mon Sep 17 00:00:00 2001 From: Telokis <6382729+Telokis@users.noreply.github.com> Date: Sun, 11 Aug 2024 15:18:11 +0200 Subject: [PATCH 001/395] Add initial docker-compose.yaml for Pterodactyl --- public/svgs/pterodactyl.png | Bin 0 -> 32004 bytes templates/compose/pterodactyl.yaml | 157 +++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 public/svgs/pterodactyl.png create mode 100644 templates/compose/pterodactyl.yaml diff --git a/public/svgs/pterodactyl.png b/public/svgs/pterodactyl.png new file mode 100644 index 0000000000000000000000000000000000000000..a5addb87c5cf70b426a5a13a563a677088cad3a3 GIT binary patch literal 32004 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

MZBCd6fS^X@TN}n= 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 Ke1)t<@c#oi$r%~| literal 0 HcmV?d00001 diff --git a/templates/compose/pterodactyl.yaml b/templates/compose/pterodactyl.yaml new file mode 100644 index 000000000..436065a40 --- /dev/null +++ b/templates/compose/pterodactyl.yaml @@ -0,0 +1,157 @@ +# documentation: https://pterodactyl.io/ +# slogan: Pterodactyl is a free, open-source game server management panel +# tags: game, game server, management, panel, minecraft +# logo: svgs/pterodactyl.png +# port: 80 + +services: + mariadb: + image: mariadb:10.5 + restart: unless-stopped + command: --default-authentication-plugin=mysql_native_password + healthcheck: + test: + ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized || exit 1"] + start_period: 10s + interval: 10s + timeout: 1s + retries: 3 + environment: + - SERVICE_PASSWORD_MYSQL + - MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MYSQLROOT + - MYSQL_DATABASE=panel + - MYSQL_USER=pterodactyl + - MYSQL_PASSWORD=$SERVICE_PASSWORD_MYSQL + volumes: + - pterodactyl-db:/var/lib/mysql + + redis: + image: redis:alpine + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "redis-cli ping || exit 1"] + interval: 10s + timeout: 1s + retries: 3 + + pterodactyl: + image: ghcr.io/pterodactyl/panel:latest + restart: unless-stopped + volumes: + - "panel-var:/app/var/" + - "panel-nginx:/etc/nginx/http.d/" + - "panel-certs:/etc/letsencrypt/" + - "panel-logs:/app/storage/logs" + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"] + interval: 10s + timeout: 1s + retries: 3 + environment: + - SERVICE_FQDN_PTERODACTYL + + - APP_ENV=production + - APP_ENVIRONMENT_ONLY=false + - APP_URL=${PTERODACTYL_PUBLIC_FQDN:-$SERVICE_FQDN_PTERODACTYL} + - APP_TIMEZONE=${TIMEZONE:-UTC} + - APP_SERVICE_AUTHOR=$APP_SERVICE_AUTHOR + - LOG_LEVEL=${LOG_LEVEL:-debug} + + - CACHE_DRIVER=redis + - SESSION_DRIVER=redis + - QUEUE_DRIVER=redis + - REDIS_HOST=redis + + - DB_HOST=mariadb + - DB_PORT=3306 + - DB_PASSWORD=$SERVICE_PASSWORD_MYSQL + + - MAIL_FROM=$MAIL_FROM + - MAIL_DRIVER=$MAIL_DRIVER + - MAIL_HOST=$MAIL_HOST + - MAIL_PORT=$MAIL_PORT + - MAIL_USERNAME=$MAIL_USERNAME + - MAIL_PASSWORD=$MAIL_PASSWORD + - MAIL_ENCRYPTION=$MAIL_ENCRYPTION + + wings: + image: ghcr.io/pterodactyl/wings:latest + restart: unless-stopped + environment: + - TZ=${TIMEZONE:-UTC} + - WINGS_USERNAME=pterodactyl + healthcheck: + test: ["CMD", "curl", "-sf http://localhost:8080"] + interval: 10s + timeout: 1s + retries: 3 + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + - "/var/lib/docker/containers/:/var/lib/docker/containers/" + - "wings-lib:/var/lib/pterodactyl/" + - "wings-logs:/var/log/pterodactyl/" + + - type: bind + source: ./etc/config.yml + target: /etc/pterodactyl/config.yml + content: | + docker: + network: + interface: 172.28.0.1 + dns: + - 1.1.1.1 + - 1.0.0.1 + name: pterodactyl_nw + ispn: false + driver: "" + network_mode: pterodactyl_nw + is_internal: false + enable_icc: true + network_mtu: 1500 + interfaces: + v4: + subnet: 172.28.0.0/16 + gateway: 172.28.0.1 + v6: + subnet: fdba:17c8:6c94::/64 + gateway: fdba:17c8:6c94::1011 + +volumes: + panel-var: + panel-nginx: + panel-certs: + panel-logs: + wings-lib: + wings-logs: + pterodactyl-db: +# Instructions: +# - Wait for the Pterodactyl service to be healthy (can take a few minutes) +# - Use the command `php artisan p:user:make --no-interaction --admin=1 --email= --username= --name-first= --name-last= --password=` in the Pterodactyl container to create an admin user. +# - Login to the panel using the created user +# - Go to the Admin panel (Cog icon at the top) +# - Go to "Locations" on the left +# - Create a new location +# - Go to "Nodes" on the left +# - Create a new node +# - Specify a temporary FQDN like "localhost" +# - /!\ Check "Use HTTP Connection" /!\ +# - Go to the "Configuration" tab of your newly created node +# - Copy the configuration file +# - In Coolify go to the "Storage" menu for your resource. +# - Find the big text area associated with `config.yml` for Wings +# - Paste the configuration at the top of this file, above `docker:` +# - Edit the line `remote: ''` to `remote: 'http://pterodactyl'` +# - Save the file +# - On the Pterodactyl panel for the node, go to the "Settings" tab +# - Change the "Fully Qualified Domain Name" from `localhost` to `wings` +# - Go to the "About" tab and confirm that the "Information" section shows the "Daemon Version" properly. + +# +----------+--------------------------------------+ +# | Field | Value | +# +----------+--------------------------------------+ +# | UUID | 6b3083ca-274b-4a77-b88f-6fbf5e4f286f | +# | Email | telokis@example.com | +# | Username | telokis | +# | Name | Telo Kis | +# | Admin | Yes | +# +----------+--------------------------------------+ From de6be8c840f7737a6f51e8cd5f620f0e6c47fecc Mon Sep 17 00:00:00 2001 From: Telokis <6382729+Telokis@users.noreply.github.com> Date: Sun, 11 Aug 2024 21:06:24 +0200 Subject: [PATCH 002/395] Fix a lot of small mistakes --- templates/compose/pterodactyl.yaml | 45 +++--------------------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/templates/compose/pterodactyl.yaml b/templates/compose/pterodactyl.yaml index 436065a40..19315b4ce 100644 --- a/templates/compose/pterodactyl.yaml +++ b/templates/compose/pterodactyl.yaml @@ -41,7 +41,6 @@ services: - "panel-var:/app/var/" - "panel-nginx:/etc/nginx/http.d/" - "panel-certs:/etc/letsencrypt/" - - "panel-logs:/app/storage/logs" healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"] interval: 10s @@ -54,7 +53,7 @@ services: - APP_ENVIRONMENT_ONLY=false - APP_URL=${PTERODACTYL_PUBLIC_FQDN:-$SERVICE_FQDN_PTERODACTYL} - APP_TIMEZONE=${TIMEZONE:-UTC} - - APP_SERVICE_AUTHOR=$APP_SERVICE_AUTHOR + - APP_SERVICE_AUTHOR=${APP_SERVICE_AUTHOR:-author@example.com} - LOG_LEVEL=${LOG_LEVEL:-debug} - CACHE_DRIVER=redis @@ -78,17 +77,14 @@ services: image: ghcr.io/pterodactyl/wings:latest restart: unless-stopped environment: + - SERVICE_FQDN_WINGS_8080 - TZ=${TIMEZONE:-UTC} - WINGS_USERNAME=pterodactyl - healthcheck: - test: ["CMD", "curl", "-sf http://localhost:8080"] - interval: 10s - timeout: 1s - retries: 3 volumes: - "/var/run/docker.sock:/var/run/docker.sock" - "/var/lib/docker/containers/:/var/lib/docker/containers/" - - "wings-lib:/var/lib/pterodactyl/" + - "/var/lib/pterodactyl/:/var/lib/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902 + - "/tmp/pterodactyl/:/tmp/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902 - "wings-logs:/var/log/pterodactyl/" - type: bind @@ -120,38 +116,5 @@ volumes: panel-var: panel-nginx: panel-certs: - panel-logs: - wings-lib: wings-logs: pterodactyl-db: -# Instructions: -# - Wait for the Pterodactyl service to be healthy (can take a few minutes) -# - Use the command `php artisan p:user:make --no-interaction --admin=1 --email= --username= --name-first= --name-last= --password=` in the Pterodactyl container to create an admin user. -# - Login to the panel using the created user -# - Go to the Admin panel (Cog icon at the top) -# - Go to "Locations" on the left -# - Create a new location -# - Go to "Nodes" on the left -# - Create a new node -# - Specify a temporary FQDN like "localhost" -# - /!\ Check "Use HTTP Connection" /!\ -# - Go to the "Configuration" tab of your newly created node -# - Copy the configuration file -# - In Coolify go to the "Storage" menu for your resource. -# - Find the big text area associated with `config.yml` for Wings -# - Paste the configuration at the top of this file, above `docker:` -# - Edit the line `remote: ''` to `remote: 'http://pterodactyl'` -# - Save the file -# - On the Pterodactyl panel for the node, go to the "Settings" tab -# - Change the "Fully Qualified Domain Name" from `localhost` to `wings` -# - Go to the "About" tab and confirm that the "Information" section shows the "Daemon Version" properly. - -# +----------+--------------------------------------+ -# | Field | Value | -# +----------+--------------------------------------+ -# | UUID | 6b3083ca-274b-4a77-b88f-6fbf5e4f286f | -# | Email | telokis@example.com | -# | Username | telokis | -# | Name | Telo Kis | -# | Admin | Yes | -# +----------+--------------------------------------+ From b997b7393b40f300ea1064cc84d304aeb8ae9911 Mon Sep 17 00:00:00 2001 From: Kael Date: Fri, 11 Oct 2024 02:44:52 +1100 Subject: [PATCH 003/395] 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 004/395] 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 96ef0ef749bd31fdbd56241956d4161ebc9b8b77 Mon Sep 17 00:00:00 2001 From: Danilo Martinelli Date: Sun, 29 Sep 2024 13:51:41 -0300 Subject: [PATCH 005/395] feat: :passport_control: integrate Authentik authentication with Coolify - Configured Authentik as the OAuth provider in Coolify. --- app/Livewire/SettingsOauth.php | 1 + app/Providers/EventServiceProvider.php | 1 + bootstrap/helpers/socialite.php | 11 ++++ composer.json | 1 + composer.lock | 50 +++++++++++++++++++ config/services.php | 7 +++ ...5_add_base_url_to_oauth_settings_table.php | 32 ++++++++++++ database/seeders/OauthSettingSeeder.php | 4 ++ lang/en.json | 1 + .../views/livewire/settings-oauth.blade.php | 4 ++ 10 files changed, 112 insertions(+) create mode 100644 database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php diff --git a/app/Livewire/SettingsOauth.php b/app/Livewire/SettingsOauth.php index c3884589f..472d35ba0 100644 --- a/app/Livewire/SettingsOauth.php +++ b/app/Livewire/SettingsOauth.php @@ -17,6 +17,7 @@ class SettingsOauth extends Component $carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable'; $carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable'; $carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable'; + $carry["oauth_settings_map.$setting->provider.base_url"] = 'nullable'; return $carry; }, []); diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 7ba72e10d..2147495fa 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -21,6 +21,7 @@ class EventServiceProvider extends ServiceProvider ], \SocialiteProviders\Manager\SocialiteWasCalled::class => [ \SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle', + \SocialiteProviders\Authentik\AuthentikExtendSocialite::class.'@handle', ], ProxyStarted::class => [ ProxyStartedNotification::class, diff --git a/bootstrap/helpers/socialite.php b/bootstrap/helpers/socialite.php index a23dc24d3..2922f5a00 100644 --- a/bootstrap/helpers/socialite.php +++ b/bootstrap/helpers/socialite.php @@ -18,6 +18,17 @@ function get_socialite_provider(string $provider) return Socialite::driver('azure')->setConfig($azure_config); } + if ($provider == 'authentik') { + $authentik_config = new \SocialiteProviders\Manager\Config( + $oauth_setting->client_id, + $oauth_setting->client_secret, + $oauth_setting->redirect_uri, + ['base_url' => $oauth_setting->base_url], + ); + + return Socialite::driver('authentik')->setConfig($authentik_config); + } + $config = [ 'client_id' => $oauth_setting->client_id, 'client_secret' => $oauth_setting->client_secret, diff --git a/composer.json b/composer.json index fbd77d0cf..8763d1b07 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "pusher/pusher-php-server": "^7.2", "resend/resend-laravel": "^0.13.0", "sentry/sentry-laravel": "^4.6", + "socialiteproviders/authentik": "^5.2", "socialiteproviders/microsoft-azure": "^5.1", "spatie/laravel-activitylog": "^4.7.3", "spatie/laravel-data": "^3.4.3", diff --git a/composer.lock b/composer.lock index 0b8da82d0..04c4741ba 100644 --- a/composer.lock +++ b/composer.lock @@ -7586,6 +7586,56 @@ ], "time": "2024-09-19T12:58:53+00:00" }, + { + "name": "socialiteproviders/authentik", + "version": "5.2.0", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Authentik.git", + "reference": "4cf129cf04728a38e0531c54454464b162f0fa66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Authentik/zipball/4cf129cf04728a38e0531c54454464b162f0fa66", + "reference": "4cf129cf04728a38e0531c54454464b162f0fa66", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0", + "socialiteproviders/manager": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "SocialiteProviders\\Authentik\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "rf152", + "email": "git@rf152.co.uk" + } + ], + "description": "Authentik OAuth2 Provider for Laravel Socialite", + "keywords": [ + "authentik", + "laravel", + "oauth", + "provider", + "socialite" + ], + "support": { + "docs": "https://socialiteproviders.com/authentik", + "issues": "https://github.com/socialiteproviders/providers/issues", + "source": "https://github.com/socialiteproviders/providers" + }, + "time": "2023-11-07T22:21:16+00:00" + }, { "name": "socialiteproviders/manager", "version": "v4.6.0", diff --git a/config/services.php b/config/services.php index 9fd55870f..46fd12ec3 100644 --- a/config/services.php +++ b/config/services.php @@ -38,4 +38,11 @@ return [ 'tenant' => env('AZURE_TENANT_ID'), 'proxy' => env('AZURE_PROXY'), ], + + 'authentik' => [ + 'base_url' => env('AUTHENTIK_BASE_URL'), + 'client_id' => env('AUTHENTIK_CLIENT_ID'), + 'client_secret' => env('AUTHENTIK_CLIENT_SECRET'), + 'redirect' => env('AUTHENTIK_REDIRECT_URI'), + ], ]; diff --git a/database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php b/database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php new file mode 100644 index 000000000..8df9b48a2 --- /dev/null +++ b/database/migrations/2024_09_29_163625_add_base_url_to_oauth_settings_table.php @@ -0,0 +1,32 @@ +string('base_url')->nullable(); + }); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('oauth_settings', function (Blueprint $table) { + Schema::table('oauth_settings', function (Blueprint $table) { + $table->dropColumn('base_url'); + }); + }); + } +}; diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php index 16abf9e04..d0365c542 100644 --- a/database/seeders/OauthSettingSeeder.php +++ b/database/seeders/OauthSettingSeeder.php @@ -32,5 +32,9 @@ class OauthSettingSeeder extends Seeder 'id' => 4, 'provider' => 'google', ]); + OauthSetting::firstOrCreate([ + 'id' => 5, + 'provider' => 'authentik', + ]); } } diff --git a/lang/en.json b/lang/en.json index fa69c7035..383bbda02 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,5 +1,6 @@ { "auth.login": "Login", + "auth.login.authentik": "Login with Authentik", "auth.login.azure": "Login with Microsoft", "auth.login.bitbucket": "Login with Bitbucket", "auth.login.github": "Login with GitHub", diff --git a/resources/views/livewire/settings-oauth.blade.php b/resources/views/livewire/settings-oauth.blade.php index 9a94d3c2b..2362ad880 100644 --- a/resources/views/livewire/settings-oauth.blade.php +++ b/resources/views/livewire/settings-oauth.blade.php @@ -32,6 +32,10 @@ @endif + @if ($oauth_setting->provider == 'authentik') + + @endif
@endforeach From d4d63ff273ce776644bd8cd47afa2b9c643aae8e Mon Sep 17 00:00:00 2001 From: Kael Date: Wed, 30 Oct 2024 17:00:55 +1100 Subject: [PATCH 006/395] feat: add deploy-only token permission --- app/Livewire/Security/ApiTokens.php | 16 ++++++++++++++-- .../views/livewire/security/api-tokens.blade.php | 1 + routes/api.php | 3 ++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/Livewire/Security/ApiTokens.php b/app/Livewire/Security/ApiTokens.php index fe68a8ba5..9add0b0ca 100644 --- a/app/Livewire/Security/ApiTokens.php +++ b/app/Livewire/Security/ApiTokens.php @@ -12,10 +12,9 @@ class ApiTokens extends Component public $tokens = []; public bool $viewSensitiveData = false; - public bool $readOnly = true; - public bool $rootAccess = false; + public bool $triggerDeploy = false; public array $permissions = ['read-only']; @@ -62,12 +61,25 @@ class ApiTokens extends Component $this->permissions = ['*']; $this->readOnly = false; $this->viewSensitiveData = false; + $this->triggerDeploy = false; } else { $this->readOnly = true; $this->permissions = ['read-only']; } } + public function updatedTriggerDeploy() + { + if ($this->triggerDeploy) { + $this->permissions[] = 'trigger-deploy'; + $this->permissions = array_diff($this->permissions, ['*']); + $this->rootAccess = false; + } else { + $this->permissions = array_diff($this->permissions, ['trigger-deploy']); + } + $this->makeSureOneIsSelected(); + } + public function makeSureOneIsSelected() { if (count($this->permissions) == 0) { diff --git a/resources/views/livewire/security/api-tokens.blade.php b/resources/views/livewire/security/api-tokens.blade.php index 1bcd64710..a360d4a3b 100644 --- a/resources/views/livewire/security/api-tokens.blade.php +++ b/resources/views/livewire/security/api-tokens.blade.php @@ -39,6 +39,7 @@ + @if (session()->has('token')) diff --git a/routes/api.php b/routes/api.php index b63fde871..05fe4f5e8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -54,7 +54,8 @@ Route::group([ Route::patch('/security/keys/{uuid}', [SecurityController::class, 'update_key'])->middleware([IgnoreReadOnlyApiToken::class]); Route::delete('/security/keys/{uuid}', [SecurityController::class, 'delete_key'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy']) + ->middleware([IgnoreReadOnlyApiToken::class, 'auth:sanctum', 'ability:trigger-deploy']); Route::get('/deployments', [DeployController::class, 'deployments']); Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid']); From 652023566707ac65942c5b8f3181f2ac354e7584 Mon Sep 17 00:00:00 2001 From: Kael Date: Wed, 30 Oct 2024 19:06:50 +1100 Subject: [PATCH 007/395] middleware should allow, not deny --- .../Api/ApplicationsController.php | 2 +- .../Controllers/Api/DatabasesController.php | 2 +- app/Http/Controllers/Api/DeployController.php | 2 +- .../Controllers/Api/SecurityController.php | 2 +- .../Controllers/Api/ServersController.php | 4 +- .../Controllers/Api/ServicesController.php | 2 +- app/Http/Controllers/Api/TeamController.php | 2 +- .../Middleware/IgnoreReadOnlyApiToken.php | 28 ---- app/Http/Middleware/OnlyRootApiToken.php | 25 --- app/Livewire/Security/ApiTokens.php | 66 +------- app/View/Components/Forms/Checkbox.php | 1 + ..._10_30_074601_rename_token_permissions.php | 44 +++++ .../views/components/forms/checkbox.blade.php | 5 +- .../livewire/security/api-tokens.blade.php | 17 +- routes/api.php | 158 +++++++++--------- 15 files changed, 149 insertions(+), 211 deletions(-) delete mode 100644 app/Http/Middleware/IgnoreReadOnlyApiToken.php delete mode 100644 app/Http/Middleware/OnlyRootApiToken.php create mode 100644 database/migrations/2024_10_30_074601_rename_token_permissions.php diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 46dd8120e..0a088d1c3 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -29,7 +29,7 @@ class ApplicationsController extends Controller $application->makeHidden([ 'id', ]); - if ($token->can('view:sensitive')) { + if ($token->can('read:sensitive')) { return serializeApiResponse($application); } $application->makeHidden([ diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 65873f818..e30388ec8 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -24,7 +24,7 @@ class DatabasesController extends Controller 'id', 'laravel_through_key', ]); - if ($token->can('view:sensitive')) { + if ($token->can('read:sensitive')) { return serializeApiResponse($database); } diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 666dc55a5..1d162c7ee 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -17,7 +17,7 @@ class DeployController extends Controller private function removeSensitiveData($deployment) { $token = auth()->user()->currentAccessToken(); - if ($token->can('view:sensitive')) { + if ($token->can('read:sensitive')) { return serializeApiResponse($deployment); } diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php index bb474aed3..aa636983f 100644 --- a/app/Http/Controllers/Api/SecurityController.php +++ b/app/Http/Controllers/Api/SecurityController.php @@ -12,7 +12,7 @@ class SecurityController extends Controller private function removeSensitiveData($team) { $token = auth()->user()->currentAccessToken(); - if ($token->can('view:sensitive')) { + if ($token->can('read:sensitive')) { return serializeApiResponse($team); } $team->makeHidden([ diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index af4e008ef..34498bbb6 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -20,7 +20,7 @@ class ServersController extends Controller private function removeSensitiveDataFromSettings($settings) { $token = auth()->user()->currentAccessToken(); - if ($token->can('view:sensitive')) { + if ($token->can('read:sensitive')) { return serializeApiResponse($settings); } $settings = $settings->makeHidden([ @@ -36,7 +36,7 @@ class ServersController extends Controller $server->makeHidden([ 'id', ]); - if ($token->can('view:sensitive')) { + if ($token->can('read:sensitive')) { return serializeApiResponse($server); } diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 89418517b..8ba2a938c 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -22,7 +22,7 @@ class ServicesController extends Controller $service->makeHidden([ 'id', ]); - if ($token->can('view:sensitive')) { + if ($token->can('read:sensitive')) { return serializeApiResponse($service); } diff --git a/app/Http/Controllers/Api/TeamController.php b/app/Http/Controllers/Api/TeamController.php index 3f951c6f7..239c950c0 100644 --- a/app/Http/Controllers/Api/TeamController.php +++ b/app/Http/Controllers/Api/TeamController.php @@ -15,7 +15,7 @@ class TeamController extends Controller 'custom_server_limit', 'pivot', ]); - if ($token->can('view:sensitive')) { + if ($token->can('read:sensitive')) { return serializeApiResponse($team); } $team->makeHidden([ diff --git a/app/Http/Middleware/IgnoreReadOnlyApiToken.php b/app/Http/Middleware/IgnoreReadOnlyApiToken.php deleted file mode 100644 index bd6cd1f8a..000000000 --- a/app/Http/Middleware/IgnoreReadOnlyApiToken.php +++ /dev/null @@ -1,28 +0,0 @@ -user()->currentAccessToken(); - if ($token->can('*')) { - return $next($request); - } - if ($token->can('read-only')) { - return response()->json(['message' => 'You are not allowed to perform this action.'], 403); - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/OnlyRootApiToken.php b/app/Http/Middleware/OnlyRootApiToken.php deleted file mode 100644 index 8ff1fa0e5..000000000 --- a/app/Http/Middleware/OnlyRootApiToken.php +++ /dev/null @@ -1,25 +0,0 @@ -user()->currentAccessToken(); - if ($token->can('*')) { - return $next($request); - } - - return response()->json(['message' => 'You are not allowed to perform this action.'], 403); - } -} diff --git a/app/Livewire/Security/ApiTokens.php b/app/Livewire/Security/ApiTokens.php index 9add0b0ca..6e58df0f0 100644 --- a/app/Livewire/Security/ApiTokens.php +++ b/app/Livewire/Security/ApiTokens.php @@ -11,12 +11,7 @@ class ApiTokens extends Component public $tokens = []; - public bool $viewSensitiveData = false; - public bool $readOnly = true; - public bool $rootAccess = false; - public bool $triggerDeploy = false; - - public array $permissions = ['read-only']; + public array $permissions = ['read']; public $isApiEnabled; @@ -31,60 +26,13 @@ class ApiTokens extends Component $this->tokens = auth()->user()->tokens->sortByDesc('created_at'); } - public function updatedViewSensitiveData() - { - if ($this->viewSensitiveData) { - $this->permissions[] = 'view:sensitive'; - $this->permissions = array_diff($this->permissions, ['*']); - $this->rootAccess = false; - } else { - $this->permissions = array_diff($this->permissions, ['view:sensitive']); - } - $this->makeSureOneIsSelected(); - } - - public function updatedReadOnly() - { - if ($this->readOnly) { - $this->permissions[] = 'read-only'; - $this->permissions = array_diff($this->permissions, ['*']); - $this->rootAccess = false; - } else { - $this->permissions = array_diff($this->permissions, ['read-only']); - } - $this->makeSureOneIsSelected(); - } - - public function updatedRootAccess() - { - if ($this->rootAccess) { - $this->permissions = ['*']; - $this->readOnly = false; - $this->viewSensitiveData = false; - $this->triggerDeploy = false; - } else { - $this->readOnly = true; - $this->permissions = ['read-only']; - } - } - - public function updatedTriggerDeploy() - { - if ($this->triggerDeploy) { - $this->permissions[] = 'trigger-deploy'; - $this->permissions = array_diff($this->permissions, ['*']); - $this->rootAccess = false; - } else { - $this->permissions = array_diff($this->permissions, ['trigger-deploy']); - } - $this->makeSureOneIsSelected(); - } - - public function makeSureOneIsSelected() + public function updated() { if (count($this->permissions) == 0) { - $this->permissions = ['read-only']; - $this->readOnly = true; + $this->permissions = ['read']; + } + if (in_array('read:sensitive', $this->permissions) && !in_array('read', $this->permissions)) { + $this->permissions[] = 'read'; } } @@ -94,7 +42,7 @@ class ApiTokens extends Component $this->validate([ 'description' => 'required|min:3|max:255', ]); - $token = auth()->user()->createToken($this->description, $this->permissions); + $token = auth()->user()->createToken($this->description, array_values($this->permissions)); $this->tokens = auth()->user()->tokens; session()->flash('token', $token->plainTextToken); } catch (\Exception $e) { diff --git a/app/View/Components/Forms/Checkbox.php b/app/View/Components/Forms/Checkbox.php index 414dbf2ae..8abe96c1b 100644 --- a/app/View/Components/Forms/Checkbox.php +++ b/app/View/Components/Forms/Checkbox.php @@ -15,6 +15,7 @@ class Checkbox extends Component public ?string $id = null, public ?string $name = null, public ?string $value = null, + public ?string $domValue = null, public ?string $label = null, public ?string $helper = null, public string|bool $instantSave = false, diff --git a/database/migrations/2024_10_30_074601_rename_token_permissions.php b/database/migrations/2024_10_30_074601_rename_token_permissions.php new file mode 100644 index 000000000..d35d75481 --- /dev/null +++ b/database/migrations/2024_10_30_074601_rename_token_permissions.php @@ -0,0 +1,44 @@ +abilities)) $abilities->push('write', 'read', 'read:sensitive'); + if (in_array('read-only', $token->abilities)) $abilities->push('read'); + if (in_array('view:sensitive', $token->abilities)) $abilities->push('read', 'read:sensitive'); + $token->abilities = $abilities->unique()->values()->all(); + $token->save(); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tokens = PersonalAccessToken::all(); + foreach ($tokens as $token) { + $abilities = collect(); + if (in_array('write', $token->abilities)) { + $abilities->push('*'); + } else { + if (in_array('read', $token->abilities)) $abilities->push('read-only'); + if (in_array('read:sensitive', $token->abilities)) $abilities->push('view:sensitive'); + } + $token->abilities = $abilities->unique()->values()->all(); + $token->save(); + } + } +}; diff --git a/resources/views/components/forms/checkbox.blade.php b/resources/views/components/forms/checkbox.blade.php index fed6ad77f..3f01a70c5 100644 --- a/resources/views/components/forms/checkbox.blade.php +++ b/resources/views/components/forms/checkbox.blade.php @@ -5,6 +5,7 @@ 'disabled' => false, 'instantSave' => false, 'value' => null, + 'domValue' => null, 'hideLabel' => false, 'fullWidth' => false, ]) @@ -33,5 +34,7 @@ merge(['class' => $defaultClass]) }} @if ($instantSave) wire:loading.attr="disabled" wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}' - wire:model={{ $id }} @else wire:model={{ $value ?? $id }} @endif /> + wire:model="{{ $id }}" @else wire:model="{{ $value ?? $id }}" @endif + @if ($domValue) value="{{ $domValue }}" @endif + /> diff --git a/resources/views/livewire/security/api-tokens.blade.php b/resources/views/livewire/security/api-tokens.blade.php index a360d4a3b..3ff52417f 100644 --- a/resources/views/livewire/security/api-tokens.blade.php +++ b/resources/views/livewire/security/api-tokens.blade.php @@ -25,21 +25,20 @@
@if ($permissions) @foreach ($permissions as $permission) - @if ($permission === '*') -
Root access, be careful!
- @else -
{{ $permission }}
- @endif +
{{ $permission }}
@endforeach @endif
+ @if (in_array('write', $permissions)) +
Root access, be careful!
+ @endif

Token Permissions

- - - - + + + +
@if (session()->has('token')) diff --git a/routes/api.php b/routes/api.php index 05fe4f5e8..230290d57 100644 --- a/routes/api.php +++ b/routes/api.php @@ -11,8 +11,6 @@ use App\Http\Controllers\Api\ServersController; use App\Http\Controllers\Api\ServicesController; use App\Http\Controllers\Api\TeamController; use App\Http\Middleware\ApiAllowed; -use App\Http\Middleware\IgnoreReadOnlyApiToken; -use App\Http\Middleware\OnlyRootApiToken; use App\Jobs\PushServerUpdateJob; use App\Models\Server; use Illuminate\Support\Facades\Route; @@ -21,7 +19,7 @@ Route::get('/health', [OtherController::class, 'healthcheck']); Route::post('/feedback', [OtherController::class, 'feedback']); Route::group([ - 'middleware' => ['auth:sanctum', OnlyRootApiToken::class], + 'middleware' => ['auth:sanctum', 'ability:write'], 'prefix' => 'v1', ], function () { Route::get('/enable', [OtherController::class, 'enable_api']); @@ -31,105 +29,103 @@ Route::group([ 'middleware' => ['auth:sanctum', ApiAllowed::class], 'prefix' => 'v1', ], function () { - Route::get('/version', [OtherController::class, 'version']); + Route::get('/version', [OtherController::class, 'version'])->middleware(['ability:read']); - Route::get('/teams', [TeamController::class, 'teams']); - Route::get('/teams/current', [TeamController::class, 'current_team']); - Route::get('/teams/current/members', [TeamController::class, 'current_team_members']); - Route::get('/teams/{id}', [TeamController::class, 'team_by_id']); - Route::get('/teams/{id}/members', [TeamController::class, 'members_by_id']); + Route::get('/teams', [TeamController::class, 'teams'])->middleware(['ability:read']); + Route::get('/teams/current', [TeamController::class, 'current_team'])->middleware(['ability:read']); + Route::get('/teams/current/members', [TeamController::class, 'current_team_members'])->middleware(['ability:read']); + Route::get('/teams/{id}', [TeamController::class, 'team_by_id'])->middleware(['ability:read']); + Route::get('/teams/{id}/members', [TeamController::class, 'members_by_id'])->middleware(['ability:read']); - Route::get('/projects', [ProjectController::class, 'projects']); - Route::get('/projects/{uuid}', [ProjectController::class, 'project_by_uuid']); - Route::get('/projects/{uuid}/{environment_name}', [ProjectController::class, 'environment_details']); + Route::get('/projects', [ProjectController::class, 'projects'])->middleware(['ability:read']); + Route::get('/projects/{uuid}', [ProjectController::class, 'project_by_uuid'])->middleware(['ability:read']); + Route::get('/projects/{uuid}/{environment_name}', [ProjectController::class, 'environment_details'])->middleware(['ability:read']); - Route::post('/projects', [ProjectController::class, 'create_project']); - Route::patch('/projects/{uuid}', [ProjectController::class, 'update_project']); - Route::delete('/projects/{uuid}', [ProjectController::class, 'delete_project']); + Route::post('/projects', [ProjectController::class, 'create_project'])->middleware(['ability:read']); + Route::patch('/projects/{uuid}', [ProjectController::class, 'update_project'])->middleware(['ability:write']); + Route::delete('/projects/{uuid}', [ProjectController::class, 'delete_project'])->middleware(['ability:write']); - Route::get('/security/keys', [SecurityController::class, 'keys']); - Route::post('/security/keys', [SecurityController::class, 'create_key'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/security/keys', [SecurityController::class, 'keys'])->middleware(['ability:read']); + Route::post('/security/keys', [SecurityController::class, 'create_key'])->middleware(['ability:write']); - Route::get('/security/keys/{uuid}', [SecurityController::class, 'key_by_uuid']); - Route::patch('/security/keys/{uuid}', [SecurityController::class, 'update_key'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::delete('/security/keys/{uuid}', [SecurityController::class, 'delete_key'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/security/keys/{uuid}', [SecurityController::class, 'key_by_uuid'])->middleware(['ability:read']); + Route::patch('/security/keys/{uuid}', [SecurityController::class, 'update_key'])->middleware(['ability:write']); + Route::delete('/security/keys/{uuid}', [SecurityController::class, 'delete_key'])->middleware(['ability:write']); - Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy']) - ->middleware([IgnoreReadOnlyApiToken::class, 'auth:sanctum', 'ability:trigger-deploy']); - Route::get('/deployments', [DeployController::class, 'deployments']); - Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid']); + Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware(['ability:write,deploy']); + Route::get('/deployments', [DeployController::class, 'deployments'])->middleware(['ability:read']); + Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid'])->middleware(['ability:read']); - Route::get('/servers', [ServersController::class, 'servers']); - Route::get('/servers/{uuid}', [ServersController::class, 'server_by_uuid']); - Route::get('/servers/{uuid}/domains', [ServersController::class, 'domains_by_server']); - Route::get('/servers/{uuid}/resources', [ServersController::class, 'resources_by_server']); + Route::get('/servers', [ServersController::class, 'servers'])->middleware(['ability:read']); + Route::get('/servers/{uuid}', [ServersController::class, 'server_by_uuid'])->middleware(['ability:read']); + Route::get('/servers/{uuid}/domains', [ServersController::class, 'domains_by_server'])->middleware(['ability:read']); + Route::get('/servers/{uuid}/resources', [ServersController::class, 'resources_by_server'])->middleware(['ability:read']); - Route::get('/servers/{uuid}/validate', [ServersController::class, 'validate_server']); + Route::get('/servers/{uuid}/validate', [ServersController::class, 'validate_server'])->middleware(['ability:read']); - Route::post('/servers', [ServersController::class, 'create_server']); - Route::patch('/servers/{uuid}', [ServersController::class, 'update_server']); - Route::delete('/servers/{uuid}', [ServersController::class, 'delete_server']); + Route::post('/servers', [ServersController::class, 'create_server'])->middleware(['ability:read']); + Route::patch('/servers/{uuid}', [ServersController::class, 'update_server'])->middleware(['ability:write']); + Route::delete('/servers/{uuid}', [ServersController::class, 'delete_server'])->middleware(['ability:write']); - Route::get('/resources', [ResourcesController::class, 'resources']); + Route::get('/resources', [ResourcesController::class, 'resources'])->middleware(['ability:read']); - Route::get('/applications', [ApplicationsController::class, 'applications']); - Route::post('/applications/public', [ApplicationsController::class, 'create_public_application'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/applications/private-github-app', [ApplicationsController::class, 'create_private_gh_app_application'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/applications/private-deploy-key', [ApplicationsController::class, 'create_private_deploy_key_application'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/applications/dockerfile', [ApplicationsController::class, 'create_dockerfile_application'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/applications/dockerimage', [ApplicationsController::class, 'create_dockerimage_application'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/applications/dockercompose', [ApplicationsController::class, 'create_dockercompose_application'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/applications', [ApplicationsController::class, 'applications'])->middleware(['ability:read']); + Route::post('/applications/public', [ApplicationsController::class, 'create_public_application'])->middleware(['ability:write']); + Route::post('/applications/private-github-app', [ApplicationsController::class, 'create_private_gh_app_application'])->middleware(['ability:write']); + Route::post('/applications/private-deploy-key', [ApplicationsController::class, 'create_private_deploy_key_application'])->middleware(['ability:write']); + Route::post('/applications/dockerfile', [ApplicationsController::class, 'create_dockerfile_application'])->middleware(['ability:write']); + Route::post('/applications/dockerimage', [ApplicationsController::class, 'create_dockerimage_application'])->middleware(['ability:write']); + Route::post('/applications/dockercompose', [ApplicationsController::class, 'create_dockercompose_application'])->middleware(['ability:write']); - Route::get('/applications/{uuid}', [ApplicationsController::class, 'application_by_uuid']); - Route::patch('/applications/{uuid}', [ApplicationsController::class, 'update_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::delete('/applications/{uuid}', [ApplicationsController::class, 'delete_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/applications/{uuid}', [ApplicationsController::class, 'application_by_uuid'])->middleware(['ability:read']); + Route::patch('/applications/{uuid}', [ApplicationsController::class, 'update_by_uuid'])->middleware(['ability:write']); + Route::delete('/applications/{uuid}', [ApplicationsController::class, 'delete_by_uuid'])->middleware(['ability:write']); - Route::get('/applications/{uuid}/envs', [ApplicationsController::class, 'envs']); - Route::post('/applications/{uuid}/envs', [ApplicationsController::class, 'create_env'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::patch('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); - // Route::post('/applications/{uuid}/execute', [ApplicationsController::class, 'execute_command_by_uuid'])->middleware([OnlyRootApiToken::class]); + Route::get('/applications/{uuid}/envs', [ApplicationsController::class, 'envs'])->middleware(['ability:read']); + Route::post('/applications/{uuid}/envs', [ApplicationsController::class, 'create_env'])->middleware(['ability:write']); + Route::patch('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware(['ability:write']); + Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware(['ability:write']); + Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware(['ability:write']); + // Route::post('/applications/{uuid}/execute', [ApplicationsController::class, 'execute_command_by_uuid'])->middleware(['ability:write']); - Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::match(['get', 'post'], '/applications/{uuid}/restart', [ApplicationsController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::match(['get', 'post'], '/applications/{uuid}/stop', [ApplicationsController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware(['ability:write']); + Route::match(['get', 'post'], '/applications/{uuid}/restart', [ApplicationsController::class, 'action_restart'])->middleware(['ability:write']); + Route::match(['get', 'post'], '/applications/{uuid}/stop', [ApplicationsController::class, 'action_stop'])->middleware(['ability:write']); - Route::get('/databases', [DatabasesController::class, 'databases']); - Route::post('/databases/postgresql', [DatabasesController::class, 'create_database_postgresql'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/databases/mysql', [DatabasesController::class, 'create_database_mysql'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/databases/mariadb', [DatabasesController::class, 'create_database_mariadb'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/databases/mongodb', [DatabasesController::class, 'create_database_mongodb'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/databases/redis', [DatabasesController::class, 'create_database_redis'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/databases/clickhouse', [DatabasesController::class, 'create_database_clickhouse'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/databases/dragonfly', [DatabasesController::class, 'create_database_dragonfly'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::post('/databases/keydb', [DatabasesController::class, 'create_database_keydb'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/databases', [DatabasesController::class, 'databases'])->middleware(['ability:read']); + Route::post('/databases/postgresql', [DatabasesController::class, 'create_database_postgresql'])->middleware(['ability:write']); + Route::post('/databases/mysql', [DatabasesController::class, 'create_database_mysql'])->middleware(['ability:write']); + Route::post('/databases/mariadb', [DatabasesController::class, 'create_database_mariadb'])->middleware(['ability:write']); + Route::post('/databases/mongodb', [DatabasesController::class, 'create_database_mongodb'])->middleware(['ability:write']); + Route::post('/databases/redis', [DatabasesController::class, 'create_database_redis'])->middleware(['ability:write']); + Route::post('/databases/clickhouse', [DatabasesController::class, 'create_database_clickhouse'])->middleware(['ability:write']); + Route::post('/databases/dragonfly', [DatabasesController::class, 'create_database_dragonfly'])->middleware(['ability:write']); + Route::post('/databases/keydb', [DatabasesController::class, 'create_database_keydb'])->middleware(['ability:write']); - Route::get('/databases/{uuid}', [DatabasesController::class, 'database_by_uuid']); - Route::patch('/databases/{uuid}', [DatabasesController::class, 'update_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::delete('/databases/{uuid}', [DatabasesController::class, 'delete_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/databases/{uuid}', [DatabasesController::class, 'database_by_uuid'])->middleware(['ability:read']); + Route::patch('/databases/{uuid}', [DatabasesController::class, 'update_by_uuid'])->middleware(['ability:write']); + Route::delete('/databases/{uuid}', [DatabasesController::class, 'delete_by_uuid'])->middleware(['ability:write']); - Route::match(['get', 'post'], '/databases/{uuid}/start', [DatabasesController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::match(['get', 'post'], '/databases/{uuid}/restart', [DatabasesController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::match(['get', 'post'], '/databases/{uuid}/stop', [DatabasesController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::match(['get', 'post'], '/databases/{uuid}/start', [DatabasesController::class, 'action_deploy'])->middleware(['ability:write']); + Route::match(['get', 'post'], '/databases/{uuid}/restart', [DatabasesController::class, 'action_restart'])->middleware(['ability:write']); + Route::match(['get', 'post'], '/databases/{uuid}/stop', [DatabasesController::class, 'action_stop'])->middleware(['ability:write']); - Route::get('/services', [ServicesController::class, 'services']); - Route::post('/services', [ServicesController::class, 'create_service'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/services', [ServicesController::class, 'services'])->middleware(['ability:read']); + Route::post('/services', [ServicesController::class, 'create_service'])->middleware(['ability:write']); - Route::get('/services/{uuid}', [ServicesController::class, 'service_by_uuid']); - // Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/services/{uuid}', [ServicesController::class, 'service_by_uuid'])->middleware(['ability:read']); + // Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware(['ability:write']); + Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware(['ability:write']); - Route::get('/services/{uuid}/envs', [ServicesController::class, 'envs']); - Route::post('/services/{uuid}/envs', [ServicesController::class, 'create_env'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::patch('/services/{uuid}/envs/bulk', [ServicesController::class, 'create_bulk_envs'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::patch('/services/{uuid}/envs', [ServicesController::class, 'update_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::delete('/services/{uuid}/envs/{env_uuid}', [ServicesController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); - - Route::match(['get', 'post'], '/services/{uuid}/start', [ServicesController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::match(['get', 'post'], '/services/{uuid}/restart', [ServicesController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]); - Route::match(['get', 'post'], '/services/{uuid}/stop', [ServicesController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/services/{uuid}/envs', [ServicesController::class, 'envs'])->middleware(['ability:read']); + Route::post('/services/{uuid}/envs', [ServicesController::class, 'create_env'])->middleware(['ability:write']); + Route::patch('/services/{uuid}/envs/bulk', [ServicesController::class, 'create_bulk_envs'])->middleware(['ability:write']); + Route::patch('/services/{uuid}/envs', [ServicesController::class, 'update_env_by_uuid'])->middleware(['ability:write']); + Route::delete('/services/{uuid}/envs/{env_uuid}', [ServicesController::class, 'delete_env_by_uuid'])->middleware(['ability:write']); + Route::match(['get', 'post'], '/services/{uuid}/start', [ServicesController::class, 'action_deploy'])->middleware(['ability:write']); + Route::match(['get', 'post'], '/services/{uuid}/restart', [ServicesController::class, 'action_restart'])->middleware(['ability:write']); + Route::match(['get', 'post'], '/services/{uuid}/stop', [ServicesController::class, 'action_stop'])->middleware(['ability:write']); }); Route::group([ From ed3b99771311aeaf15b99c2da243f7c1b85615f8 Mon Sep 17 00:00:00 2001 From: librelol Date: Fri, 8 Nov 2024 19:38:16 -0500 Subject: [PATCH 008/395] Add redlib.yaml --- templates/compose/redlib.yaml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 templates/compose/redlib.yaml diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml new file mode 100644 index 000000000..4460bab00 --- /dev/null +++ b/templates/compose/redlib.yaml @@ -0,0 +1,30 @@ +# documentation: https://github.com/redlib-org/redlib +# An alternative private front-end to Reddit, with its origins in Libreddit. +# tags: frontend, feed +# logo: svgs/freshrss.png +# port: 8080 + +services: + redlib: + image: 'quay.io/redlib/redlib:latest' + restart: always + container_name: redlib + environment: + - SERVICE_FQDN_REDLIB_8080 + user: nobody + read_only: true + security_opt: + - 'no-new-privileges:true' + cap_drop: + - ALL + env_file: .env + healthcheck: + test: + - CMD + - wget + - '--spider' + - '-q' + - '--tries=1' + - 'http://localhost:8080/settings' + interval: 5m + timeout: 3s \ No newline at end of file From a987da7f7043e83f75046c130830a078a00d1a6f Mon Sep 17 00:00:00 2001 From: librelol Date: Fri, 8 Nov 2024 19:49:01 -0500 Subject: [PATCH 009/395] Add privatebin.yaml --- templates/compose/privatebin.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 templates/compose/privatebin.yaml diff --git a/templates/compose/privatebin.yaml b/templates/compose/privatebin.yaml new file mode 100644 index 000000000..30dccdbb6 --- /dev/null +++ b/templates/compose/privatebin.yaml @@ -0,0 +1,14 @@ +# documentation: https://github.com/PrivateBin/PrivateBin/blob/master/doc/README.md +# PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. +# tags: text, sharing +# port: 8080 + +services: + privatebin: + image: privatebin/nginx-fpm-alpine + restart: always + read_only: true + environment: + - SERVICE_FQDN_PRIVATEBIN_8080 + volumes: + - 'privatebin-data:/srv/data' From 85d07283222934682bf5c988754f954798e1c870 Mon Sep 17 00:00:00 2001 From: konstchri <79702630+konstchri@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:21:45 +0200 Subject: [PATCH 010/395] [Fix]: Caddy docker labels do not honor "strip prefix" option --- bootstrap/helpers/docker.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 40eacf5c8..98039ba4b 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -283,6 +283,10 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $host_without_www = str($host)->replace('www.', ''); $schema = $url->getScheme(); $port = $url->getPort(); + $handle = "handle_path" + if ( ! $is_stripprefix_enabled){ + $handle = "handle" + } if (is_null($port) && ! is_null($onlyPort)) { $port = $onlyPort; } @@ -293,12 +297,13 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $labels->push("caddy_{$loop}.header=-Server"); $labels->push("caddy_{$loop}.try_files={path} /index.html /index.php"); + if ($port) { - $labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams $port}}"); + $labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams $port}}"); } else { - $labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams}}"); + $labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams}}"); } - $labels->push("caddy_{$loop}.handle_path={$path}*"); + $labels->push("caddy_{$loop}.{$handle}={$path}*"); if ($is_gzip_enabled) { $labels->push("caddy_{$loop}.encode=zstd gzip"); } From 1a27196e1abc53957ba2a2cf1048fc5bf34a7f9e Mon Sep 17 00:00:00 2001 From: Konstantinos Christoforou Date: Tue, 12 Nov 2024 19:22:07 +0200 Subject: [PATCH 011/395] fix typo --- bootstrap/helpers/docker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 98039ba4b..ca933e6ae 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -283,9 +283,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $host_without_www = str($host)->replace('www.', ''); $schema = $url->getScheme(); $port = $url->getPort(); - $handle = "handle_path" + $handle = "handle_path"; if ( ! $is_stripprefix_enabled){ - $handle = "handle" + $handle = "handle"; } if (is_null($port) && ! is_null($onlyPort)) { $port = $onlyPort; From 95fda8c238b82dcb6b6cdb63084b809dfdc14943 Mon Sep 17 00:00:00 2001 From: lloydrichards Date: Tue, 12 Nov 2024 21:50:17 +0100 Subject: [PATCH 012/395] feat(service): :sparkles: add mealie --- public/svgs/mealie.png | Bin 0 -> 16365 bytes templates/compose/mealie.yaml | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 public/svgs/mealie.png create mode 100644 templates/compose/mealie.yaml diff --git a/public/svgs/mealie.png b/public/svgs/mealie.png new file mode 100644 index 0000000000000000000000000000000000000000..74a2d7b62151c53bb18fab1721055fb97bc42fb2 GIT binary patch literal 16365 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 Y@BfP*1#bt!kMRQ( Date: Tue, 12 Nov 2024 22:37:55 +0100 Subject: [PATCH 013/395] 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 014/395] 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 5ec202dbe0b7fd22b76bad9340227136a7bb2ffd Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:48:56 +0100 Subject: [PATCH 015/395] Update Dockerfile --- docker/prod/Dockerfile | 145 +++++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 56 deletions(-) diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index ca34bb05b..74c1e196b 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -1,6 +1,6 @@ # Versions # https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine -ARG SERVERSIDEUP_PHP_VERSION=8.2-fpm-nginx-v2.2.1 +ARG SERVERSIDEUP_PHP_VERSION=8.3-fpm-nginx-alpine # https://github.com/minio/mc/releases ARG MINIO_VERSION=RELEASE.2024-11-05T11-29-45Z # https://github.com/cloudflare/cloudflared/releases @@ -8,82 +8,115 @@ ARG CLOUDFLARED_VERSION=2024.11.0 # https://www.postgresql.org/support/versioning/ - Can not updated automatically so keep it at 15 ARG POSTGRES_VERSION=15 - +# ================================================================= +# Stage 1: Composer dependencies +# ================================================================= FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} AS base -WORKDIR /var/www/html -COPY composer.json composer.lock ./ +WORKDIR /var/www/html +COPY --chown=www-data:www-data composer.json composer.lock ./ RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist -FROM node:20 AS static-assets -WORKDIR /app -COPY . . -COPY --from=base --chown=9999:9999 /var/www/html . -RUN npm install -RUN npm run build +# ================================================================= +# Stage 2: Frontend assets compilation +# ================================================================= +FROM node:20-alpine AS static-assets +WORKDIR /app +COPY package*.json vite.config.js tailwind.config.js postcss.config.cjs ./ +COPY public ./public +COPY resources ./resources +RUN npm ci && npm run build + +# ================================================================= +# Stage 3: Get MinIO client +# ================================================================= FROM minio/mc:${MINIO_VERSION} AS minio-client +# ================================================================= +# Final Stage: Production image +# ================================================================= FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} ARG TARGETPLATFORM -ARG CLOUDFLARED_VERSION ARG POSTGRES_VERSION +ARG CLOUDFLARED_VERSION ARG CI=true WORKDIR /var/www/html -RUN apt-get update -# Postgres version requirements -RUN apt install dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl -y -RUN curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null +USER root -RUN echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list +# Install PostgreSQL repository and keys +RUN apk add --no-cache gnupg && \ + mkdir -p /usr/share/keyrings && \ + curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg -RUN apt-get update -RUN apt-get install postgresql-client-${POSTGRES_VERSION} -y +# Install system dependencies +RUN apk add --no-cache \ + postgresql${POSTGRES_VERSION}-client \ + openssh-client \ + git \ + git-lfs \ + jq \ + lsof -# Coolify requirements -RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof vim -RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* +# Configure shell aliases +RUN echo "alias ll='ls -al'" >> /etc/profile && \ + echo "alias a='php artisan'" >> /etc/profile && \ + echo "alias logs='tail -f storage/logs/laravel.log'" >> /etc/profile +# Install Cloudflared based on architecture +RUN mkdir -p /usr/local/bin && \ + if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \ + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \ + elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \ + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \ + fi && \ + chmod +x /usr/local/bin/cloudflared + +# Configure PHP +RUN echo 'upload_max_filesize=256M' > /usr/local/etc/php/conf.d/upload-limits.ini && \ + echo 'post_max_size=256M' >> /usr/local/etc/php/conf.d/upload-limits.ini +ENV PHP_OPCACHE_ENABLE=1 + +# Copy application files from previous stages +COPY --from=base --chown=www-data:www-data /var/www/html/vendor ./vendor +COPY --from=static-assets --chown=www-data:www-data /app/public/build ./public/build + +# Copy application source code +COPY --chown=www-data:www-data composer.json composer.lock ./ +COPY --chown=www-data:www-data app ./app +COPY --chown=www-data:www-data bootstrap ./bootstrap +COPY --chown=www-data:www-data config ./config +COPY --chown=www-data:www-data database ./database +COPY --chown=www-data:www-data lang ./lang +COPY --chown=www-data:www-data public ./public +COPY --chown=www-data:www-data routes ./routes +COPY --chown=www-data:www-data storage ./storage +COPY --chown=www-data:www-data templates ./templates +COPY --chown=www-data:www-data artisan artisan + +# Configure Nginx and S6 overlay COPY docker/prod/nginx.conf /etc/nginx/conf.d/custom.conf - -COPY --from=base --chown=9999:9999 /var/www/html . - -COPY --chown=9999:9999 . . -RUN composer dump-autoload - -COPY --from=static-assets --chown=9999:9999 /app/public/build ./public/build COPY --chmod=755 docker/prod/etc/s6-overlay/ /etc/s6-overlay/ -RUN php artisan route:clear -RUN php artisan view:clear -RUN php artisan config:clear -RUN php artisan route:cache -RUN php artisan view:cache -RUN php artisan config:cache - -RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc -RUN echo "alias a='php artisan'" >>/etc/bash.bashrc -RUN echo "alias logs='tail -f storage/logs/laravel.log'" >>/etc/bash.bashrc - -RUN mkdir -p /usr/local/bin - -RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \ - echo 'amd64' && \ - curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ - ;fi" - -RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ - echo 'arm64' && \ - curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ - ;fi" - -RUN { \ - echo 'upload_max_filesize=256M'; \ - echo 'post_max_size=256M'; \ - } > /etc/php/current_version/cli/conf.d/upload-limits.ini +RUN mkdir -p /etc/nginx/conf.d && \ + chown -R www-data:www-data /etc/nginx && \ + chmod -R 755 /etc/nginx +# Install MinIO client COPY --from=minio-client /usr/bin/mc /usr/bin/mc -RUN chmod +x /usr/bin/mc \ No newline at end of file +RUN chmod +x /usr/bin/mc + +# Switch to non-root user +USER www-data + +# Optimize Laravel application +RUN composer dump-autoload && \ + php artisan route:clear && \ + php artisan view:clear && \ + php artisan config:clear && \ + php artisan route:cache && \ + php artisan view:cache && \ + php artisan config:cache From 4b0d3cb93fdf57eec25865f94a98a5ba4504315e Mon Sep 17 00:00:00 2001 From: Adrian Barrio Date: Wed, 13 Nov 2024 21:37:10 +0100 Subject: [PATCH 016/395] feat: add MacOS template --- public/svgs/macos.svg | 1 + templates/compose/macos.yaml | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 public/svgs/macos.svg create mode 100644 templates/compose/macos.yaml diff --git a/public/svgs/macos.svg b/public/svgs/macos.svg new file mode 100644 index 000000000..483fa6a17 --- /dev/null +++ b/public/svgs/macos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/compose/macos.yaml b/templates/compose/macos.yaml new file mode 100644 index 000000000..70cebc144 --- /dev/null +++ b/templates/compose/macos.yaml @@ -0,0 +1,22 @@ +# documentation: https://github.com/dockur/macos +# slogan: Run macOS in a containerized environment. +# tags: macos, virtualization, container, os +# logo: svgs/macos.svg +# port: 8006 + +services: + macos: + image: dockurr/macos + volumes: + - macos-storage:/storage + environment: + - SERVICE_FQDN_MACOS_8006 + - VERSION=15 + cap_add: + - NET_ADMIN + stop_grace_period: 2m + healthcheck: + test: ["CMD", "nc", "-z", "127.0.0.1", "8006"] + interval: 2s + timeout: 10s + retries: 10 From ee2c0f00cafd7ef5fb6c955283070747999280a9 Mon Sep 17 00:00:00 2001 From: Adrian Barrio Date: Wed, 13 Nov 2024 21:37:18 +0100 Subject: [PATCH 017/395] feat: add Windows template --- public/svgs/windows.svg | 1 + templates/compose/windows.yaml | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 public/svgs/windows.svg create mode 100644 templates/compose/windows.yaml diff --git a/public/svgs/windows.svg b/public/svgs/windows.svg new file mode 100644 index 000000000..2c7392e9c --- /dev/null +++ b/public/svgs/windows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/compose/windows.yaml b/templates/compose/windows.yaml new file mode 100644 index 000000000..ddbc4c3dc --- /dev/null +++ b/templates/compose/windows.yaml @@ -0,0 +1,22 @@ +# documentation: https://github.com/dockur/windows +# slogan: Run Windows in a containerized environment. +# tags: windows, virtualization, container, os +# logo: svgs/windows.svg +# port: 8006 + +services: + windows: + image: dockurr/windows + volumes: + - windows-storage:/storage + environment: + - SERVICE_FQDN_WINDOWS_8006 + - VERSION=11 + cap_add: + - NET_ADMIN + stop_grace_period: 2m + healthcheck: + test: ["CMD", "nc", "-z", "127.0.0.1", "8006"] + interval: 2s + timeout: 10s + retries: 10 From 0928261019593519be6600c555b5452dd743ea72 Mon Sep 17 00:00:00 2001 From: librelol Date: Wed, 13 Nov 2024 16:16:57 -0500 Subject: [PATCH 018/395] Removed .env from redlib and added the proper environment variables --- templates/compose/redlib.yaml | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml index 4460bab00..b41d4ba60 100644 --- a/templates/compose/redlib.yaml +++ b/templates/compose/redlib.yaml @@ -10,14 +10,36 @@ services: restart: always container_name: redlib environment: - - SERVICE_FQDN_REDLIB_8080 + - SERVICE_FQDN_REDLIB_8080=${SERVICE_FQDN_REDLIB_8080} + - REDLIB_SFW_ONLY=off + - REDLIB_BANNER="" + - REDLIB_ROBOTS_DISABLE_INDEXING=off + - REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io + - REDLIB_DEFAULT_THEME=system + - REDLIB_DEFAULT_FRONT_PAGE=default + - REDLIB_DEFAULT_LAYOUT=card + - REDLIB_DEFAULT_WIDE=off + - REDLIB_DEFAULT_POST_SORT=hot + - REDLIB_DEFAULT_COMMENT_SORT=confidence + - REDLIB_DEFAULT_BLUR_SPOILER=off + - REDLIB_DEFAULT_SHOW_NSFW=off + - REDLIB_DEFAULT_BLUR_NSFW=off + - REDLIB_DEFAULT_USE_HLS=off + - REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=off + - REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off + - REDLIB_DEFAULT_SUBSCRIPTIONS="" + - REDLIB_DEFAULT_FILTERS="" + - REDLIB_DEFAULT_HIDE_AWARDS=off + - REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY=off + - REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION=off + - REDLIB_DEFAULT_HIDE_SCORE=off + - REDLIB_DEFAULT_FIXED_NAVBAR=on user: nobody read_only: true security_opt: - 'no-new-privileges:true' cap_drop: - ALL - env_file: .env healthcheck: test: - CMD @@ -27,4 +49,4 @@ services: - '--tries=1' - 'http://localhost:8080/settings' interval: 5m - timeout: 3s \ No newline at end of file + timeout: 3s From 943ca56201aec11e7da5ca102d9f3f7b02124f00 Mon Sep 17 00:00:00 2001 From: librelol Date: Wed, 13 Nov 2024 16:24:34 -0500 Subject: [PATCH 019/395] Removed useless ending of a environment variable and added a proper logo for redlib --- templates/compose/redlib.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml index b41d4ba60..bce9f43db 100644 --- a/templates/compose/redlib.yaml +++ b/templates/compose/redlib.yaml @@ -1,7 +1,7 @@ # documentation: https://github.com/redlib-org/redlib # An alternative private front-end to Reddit, with its origins in Libreddit. # tags: frontend, feed -# logo: svgs/freshrss.png +# logo: svgs/redlib.svg # port: 8080 services: @@ -10,7 +10,7 @@ services: restart: always container_name: redlib environment: - - SERVICE_FQDN_REDLIB_8080=${SERVICE_FQDN_REDLIB_8080} + - SERVICE_FQDN_REDLIB_8080 - REDLIB_SFW_ONLY=off - REDLIB_BANNER="" - REDLIB_ROBOTS_DISABLE_INDEXING=off From a90c4a3e87dcb8462dbd47e9344092f888785179 Mon Sep 17 00:00:00 2001 From: librelol Date: Wed, 13 Nov 2024 16:28:50 -0500 Subject: [PATCH 020/395] Forgot to include the logos and added the proper logo for privatebin and adjusted its configuration to reflect that --- public/svgs/privatebin.svg | 1 + public/svgs/redlib.svg | 7 +++++++ templates/compose/privatebin.yaml | 1 + 3 files changed, 9 insertions(+) create mode 100644 public/svgs/privatebin.svg create mode 100644 public/svgs/redlib.svg diff --git a/public/svgs/privatebin.svg b/public/svgs/privatebin.svg new file mode 100644 index 000000000..d63c65dbd --- /dev/null +++ b/public/svgs/privatebin.svg @@ -0,0 +1 @@ + diff --git a/public/svgs/redlib.svg b/public/svgs/redlib.svg new file mode 100644 index 000000000..16f73b5dd --- /dev/null +++ b/public/svgs/redlib.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/templates/compose/privatebin.yaml b/templates/compose/privatebin.yaml index 30dccdbb6..96ffa4311 100644 --- a/templates/compose/privatebin.yaml +++ b/templates/compose/privatebin.yaml @@ -1,6 +1,7 @@ # documentation: https://github.com/PrivateBin/PrivateBin/blob/master/doc/README.md # PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. # tags: text, sharing +# logo: svgs/privatebin.svg # port: 8080 services: From a2e0a378b6f6214ecad0a0294172fc37681cd8a4 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:53:54 +0100 Subject: [PATCH 021/395] install vim --- docker/prod/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 74c1e196b..84073144b 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -59,7 +59,8 @@ RUN apk add --no-cache \ git \ git-lfs \ jq \ - lsof + lsof \ + vim # Configure shell aliases RUN echo "alias ll='ls -al'" >> /etc/profile && \ From 13f898681b2c5918d4e6c9b4637c937d6a57977a Mon Sep 17 00:00:00 2001 From: librelol Date: Fri, 15 Nov 2024 12:10:18 -0500 Subject: [PATCH 022/395] Adjusted the default front page to use worldnews as for whatever reason the built in option "default" wouldn't work. But you are able to chang it to anything you want. --- templates/compose/redlib.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml index bce9f43db..08acc5ef7 100644 --- a/templates/compose/redlib.yaml +++ b/templates/compose/redlib.yaml @@ -28,7 +28,6 @@ services: - REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=off - REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off - REDLIB_DEFAULT_SUBSCRIPTIONS="" - - REDLIB_DEFAULT_FILTERS="" - REDLIB_DEFAULT_HIDE_AWARDS=off - REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY=off - REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION=off From c710869fca41ddf990f4f15148561e0cc2fa2ccc Mon Sep 17 00:00:00 2001 From: librelol Date: Fri, 15 Nov 2024 12:17:03 -0500 Subject: [PATCH 023/395] Added a healthcheck to private bin --- templates/compose/privatebin.yaml | 12 ++++++++++++ templates/compose/redlib.yaml | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/templates/compose/privatebin.yaml b/templates/compose/privatebin.yaml index 96ffa4311..567272cb4 100644 --- a/templates/compose/privatebin.yaml +++ b/templates/compose/privatebin.yaml @@ -13,3 +13,15 @@ services: - SERVICE_FQDN_PRIVATEBIN_8080 volumes: - 'privatebin-data:/srv/data' + healthcheck: + test: + - CMD + - wget + - '--spider' + - '-q' + - 'http://localhost:8080/' + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + disable: false diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml index 08acc5ef7..6e871433b 100644 --- a/templates/compose/redlib.yaml +++ b/templates/compose/redlib.yaml @@ -1,7 +1,7 @@ # documentation: https://github.com/redlib-org/redlib # An alternative private front-end to Reddit, with its origins in Libreddit. # tags: frontend, feed -# logo: svgs/redlib.svg +# logo: svgs/freshrss.png # port: 8080 services: @@ -14,9 +14,9 @@ services: - REDLIB_SFW_ONLY=off - REDLIB_BANNER="" - REDLIB_ROBOTS_DISABLE_INDEXING=off + - REDLIB_DEFAULT_FRONT_PAGE=worldnews - REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io - REDLIB_DEFAULT_THEME=system - - REDLIB_DEFAULT_FRONT_PAGE=default - REDLIB_DEFAULT_LAYOUT=card - REDLIB_DEFAULT_WIDE=off - REDLIB_DEFAULT_POST_SORT=hot From be2d9a1c21d415c87832d41c57bf2d058093cacd Mon Sep 17 00:00:00 2001 From: librelol Date: Fri, 15 Nov 2024 12:19:19 -0500 Subject: [PATCH 024/395] Accidently changed the logo of redlib back to freshrss. Fixed. --- templates/compose/redlib.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml index 6e871433b..c5b2f0d94 100644 --- a/templates/compose/redlib.yaml +++ b/templates/compose/redlib.yaml @@ -1,7 +1,7 @@ # documentation: https://github.com/redlib-org/redlib # An alternative private front-end to Reddit, with its origins in Libreddit. # tags: frontend, feed -# logo: svgs/freshrss.png +# logo: svgs/redlib.svg # port: 8080 services: From 25c90964c1b2e08a7ed250576bd35fa930ac3390 Mon Sep 17 00:00:00 2001 From: librelol Date: Fri, 15 Nov 2024 12:23:39 -0500 Subject: [PATCH 025/395] Made the healthchecks more consistent alongside of templates. --- templates/compose/privatebin.yaml | 15 ++++----------- templates/compose/redlib.yaml | 13 ++++--------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/templates/compose/privatebin.yaml b/templates/compose/privatebin.yaml index 567272cb4..c2f625c8f 100644 --- a/templates/compose/privatebin.yaml +++ b/templates/compose/privatebin.yaml @@ -14,14 +14,7 @@ services: volumes: - 'privatebin-data:/srv/data' healthcheck: - test: - - CMD - - wget - - '--spider' - - '-q' - - 'http://localhost:8080/' - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s - disable: false + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/"] + interval: 5s + timeout: 20s + retries: 10 diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml index c5b2f0d94..f4ef7f10c 100644 --- a/templates/compose/redlib.yaml +++ b/templates/compose/redlib.yaml @@ -40,12 +40,7 @@ services: cap_drop: - ALL healthcheck: - test: - - CMD - - wget - - '--spider' - - '-q' - - '--tries=1' - - 'http://localhost:8080/settings' - interval: 5m - timeout: 3s + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/"] + interval: 5s + timeout: 20s + retries: 10 From 5270103bc1bfd3593462cd17b527e4fe58f271fd Mon Sep 17 00:00:00 2001 From: librelol Date: Fri, 15 Nov 2024 17:16:24 -0500 Subject: [PATCH 026/395] Added the proper coolify variables. --- templates/compose/redlib.yaml | 45 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml index f4ef7f10c..3c0004e82 100644 --- a/templates/compose/redlib.yaml +++ b/templates/compose/redlib.yaml @@ -10,29 +10,28 @@ services: restart: always container_name: redlib environment: - - SERVICE_FQDN_REDLIB_8080 - - REDLIB_SFW_ONLY=off - - REDLIB_BANNER="" - - REDLIB_ROBOTS_DISABLE_INDEXING=off - - REDLIB_DEFAULT_FRONT_PAGE=worldnews - - REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io - - REDLIB_DEFAULT_THEME=system - - REDLIB_DEFAULT_LAYOUT=card - - REDLIB_DEFAULT_WIDE=off - - REDLIB_DEFAULT_POST_SORT=hot - - REDLIB_DEFAULT_COMMENT_SORT=confidence - - REDLIB_DEFAULT_BLUR_SPOILER=off - - REDLIB_DEFAULT_SHOW_NSFW=off - - REDLIB_DEFAULT_BLUR_NSFW=off - - REDLIB_DEFAULT_USE_HLS=off - - REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=off - - REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off - - REDLIB_DEFAULT_SUBSCRIPTIONS="" - - REDLIB_DEFAULT_HIDE_AWARDS=off - - REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY=off - - REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION=off - - REDLIB_DEFAULT_HIDE_SCORE=off - - REDLIB_DEFAULT_FIXED_NAVBAR=on + - SERVICE_FQDN_REDLIB_8080=${SERVICE_FQDN_REDLIB_8080:-localhost} + - REDLIB_SFW_ONLY=${REDLIB_SFW_ONLY:-off} + - REDLIB_BANNER=${REDLIB_BANNER:-""} + - REDLIB_ROBOTS_DISABLE_INDEXING=${REDLIB_ROBOTS_DISABLE_INDEXING:-off} + - REDLIB_DEFAULT_FRONT_PAGE=${REDLIB_DEFAULT_FRONT_PAGE:-worldnews} + - REDLIB_PUSHSHIFT_FRONTEND=${REDLIB_PUSHSHIFT_FRONTEND:-undelete.pullpush.io} + - REDLIB_DEFAULT_LAYOUT=${REDLIB_DEFAULT_LAYOUT:-card} + - REDLIB_DEFAULT_WIDE=${REDLIB_DEFAULT_WIDE:-off} + - REDLIB_DEFAULT_POST_SORT=${REDLIB_DEFAULT_POST_SORT:-hot} + - REDLIB_DEFAULT_COMMENT_SORT=${REDLIB_DEFAULT_COMMENT_SORT:-confidence} + - REDLIB_DEFAULT_BLUR_SPOILER=${REDLIB_DEFAULT_BLUR_SPOILER:-off} + - REDLIB_DEFAULT_SHOW_NSFW=${REDLIB_DEFAULT_SHOW_NSFW:-off} + - REDLIB_DEFAULT_BLUR_NSFW=${REDLIB_DEFAULT_BLUR_NSFW:-off} + - REDLIB_DEFAULT_USE_HLS=${REDLIB_DEFAULT_USE_HLS:-off} + - REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=${REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION:-off} + - REDLIB_DEFAULT_AUTOPLAY_VIDEOS=${REDLIB_DEFAULT_AUTOPLAY_VIDEOS:-off} + - REDLIB_DEFAULT_SUBSCRIPTIONS=${REDLIB_DEFAULT_SUBSCRIPTIONS:-""} + - REDLIB_DEFAULT_HIDE_AWARDS=${REDLIB_DEFAULT_HIDE_AWARDS:-off} + - REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY=${REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY:-off} + - REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION=${REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION:-off} + - REDLIB_DEFAULT_HIDE_SCORE=${REDLIB_DEFAULT_HIDE_SCORE:-off} + - REDLIB_DEFAULT_FIXED_NAVBAR=${REDLIB_DEFAULT_FIXED_NAVBAR:-on} user: nobody read_only: true security_opt: From 6be792121775e48efb09507ac6b1456dadea4dba Mon Sep 17 00:00:00 2001 From: librelol Date: Fri, 15 Nov 2024 17:29:24 -0500 Subject: [PATCH 027/395] Added gothub as well along side its logo and proper coolify variables --- public/svgs/gothub.svg | 753 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 753 insertions(+) create mode 100644 public/svgs/gothub.svg diff --git a/public/svgs/gothub.svg b/public/svgs/gothub.svg new file mode 100644 index 000000000..61fe71555 --- /dev/null +++ b/public/svgs/gothub.svg @@ -0,0 +1,753 @@ + + + + + + gothub/public/assets/logo.svg at dev - gothub/gothub - Codeberg.org + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+ +
+
+
+
+ + + + + +
+
+ +
+
+ + + + + + +
+
+ +
+ + + + + + + + +
+
+ + + 14 + +
+
+ + +
+
+ + + 132 + +
+
+ + + + + +
+ + Fork + + + + 21 + +
+ + + + +
+ +
+ + + +
+ + + + + + +
+
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + gothub/public/assets/logo.svg + +
+
+ + + +
+
+ +
+ + +
+
+ + + + + Odyssey + + + + 7dbaef6386 + + + + + + I guess I'll commit + + +
Signed-off-by: Odyssey <odyssey346@disroot.org>
+ +
+ + +
+ + +
+ 2022-11-29 17:59:04 +01:00 +
+ + +
+ + +

+
+ +
+ + +
+ 150 lines +
+ + + +
+ 5.9 KiB +
+ + + +
+ XML +
+ + + + + +
+ + +
+
+ +
+ + +
+ + +
+ + Raw + + Permalink + + + Blame + + History + +
+ + + + + + + + + + + + + + + + + + + +
+

+
+ + + + + + +
+ +
+ + + +
+ +
+
+
+ + +
+
+ + + + +
+ + + + + + + + + + + + From 9009c763826a405956c1792632964a0add7624c7 Mon Sep 17 00:00:00 2001 From: librelol Date: Fri, 15 Nov 2024 17:30:32 -0500 Subject: [PATCH 028/395] Whatever stupid reason gothub wasnt adding --- templates/compose/gothub.yaml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 templates/compose/gothub.yaml diff --git a/templates/compose/gothub.yaml b/templates/compose/gothub.yaml new file mode 100644 index 000000000..c0a651f05 --- /dev/null +++ b/templates/compose/gothub.yaml @@ -0,0 +1,30 @@ +# documentation: https://gothub.app/docs/ +# slogan: Alternative front-end for GitHub written with Go. +# tags: frontend, git +# logo: svgs/gothub.svg +# port: 3000 + +version: '3' +services: + gothub: + image: 'codeberg.org/gothub/gothub:latest' + restart: unless-stopped + environment: + - SERVICE_FQDN_GOTHUB_3000 + - GOTHUB_SETUP_COMPLETE=${GOTHUB_SETUP_COMPLETE:-false} + - GOTHUB_PROXYING_ENABLED=${GOTHUB_PROXYING_ENABLED:-false} + - GOTHUB_IP_LOGGED=${GOTHUB_IP_LOGGED:-false} + - GOTHUB_REQUEST_URL_LOGGED=${GOTHUB_REQUEST_URL_LOGGED:-false} + - GOTHUB_USER_AGENT_LOGGED=${GOTHUB_USER_AGENT_LOGGED:-false} + - GOTHUB_DIAGNOSTIC_INFO_LOGGED=${GOTHUB_DIAGNOSTIC_INFO_LOGGED:-false} + - GOTHUB_INSTANCE_PRIVACY_POLICY=${GOTHUB_INSTANCE_PRIVACY_POLICY:-"https://your.website/privacy-policy"} + - GOTHUB_INSTANCE_COUNTRY=${GOTHUB_INSTANCE_COUNTRY:-Finland} + - GOTHUB_INSTANCE_PROVIDER=${GOTHUB_INSTANCE_PROVIDER:-Hetzner} + - GOTHUB_INSTANCE_CLOUDFLARE=${GOTHUB_INSTANCE_CLOUDFLARE:-false} + - DOCKER=true + + healthcheck: + test: 'wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/version || exit 1' + interval: 30s + timeout: 5s + retries: 2 \ 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 029/395] 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 47c442431b6bf44cd89263babe2341b14ceca573 Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:05:55 +0100 Subject: [PATCH 030/395] Update postiz.yaml ### Proposed Improvements to Postiz Template I'd like to propose several improvements to the current Postiz template that enhance security, reliability, and configuration flexibility: #### Security Enhancements - Added Redis ACL configuration with proper authentication - Implemented secure healthchecks with authentication - Enhanced PostgreSQL security configurations #### Reliability Improvements - Added memory limits and resource management for Redis - Implemented proper data persistence configurations - Added tmpfs for temporary files - More comprehensive healthcheck configurations with proper retry/timeout strategies - Better dependency management with health conditions #### Configuration Flexibility - Support for all environment variables from Postiz documentation - Added Cloudflare R2 integration support - Logical grouping of environment variables - Default values for critical settings - Better volume management with explicit drivers The improved template provides a more production-ready setup while maintaining compatibility with Coolify's requirements. It follows best practices for Docker deployments and provides better security out of the box. --- templates/compose/postiz.yaml | 183 +++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 59 deletions(-) diff --git a/templates/compose/postiz.yaml b/templates/compose/postiz.yaml index 34f268015..1ad216f26 100644 --- a/templates/compose/postiz.yaml +++ b/templates/compose/postiz.yaml @@ -6,92 +6,157 @@ services: postiz: - image: ghcr.io/gitroomhq/postiz-app:latest + image: 'ghcr.io/gitroomhq/postiz-app:latest' environment: + # Required Settings - SERVICE_FQDN_POSTIZ_5000 - - MAIN_URL=${SERVICE_FQDN_POSTIZ} - - FRONTEND_URL=${SERVICE_FQDN_POSTIZ} - - NEXT_PUBLIC_BACKEND_URL=${SERVICE_FQDN_POSTIZ}/api - - JWT_SECRET=${SERVICE_PASSWORD_JWTSECRET} - - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRESQL}:${SERVICE_PASSWORD_POSTGRESQL}@postgresql:5432/${POSTGRESQL_DATABASE:-postiz-db} - - REDIS_URL=redis://${SERVICE_USER_REDIS}:${SERVICE_PASSWORD_REDIS}@redis:6379 - - BACKEND_INTERNAL_URL=http://localhost:3000 - - IS_GENERAL=true - - STORAGE_PROVIDER=local - - UPLOAD_DIRECTORY=/uploads - - NEXT_PUBLIC_UPLOAD_DIRECTORY=/uploads - - X_API_KEY=${SERVICE_X_API} - - X_API_SECRET=${SERVICE_X_SECRET} - - REDDIT_CLIENT_ID=${SERVICE_REDDIT_API} - - REDDIT_CLIENT_SECRET=${SERVICE_REDDIT_SECRET} - - TIKTOK_CLIENT_ID=${SERVICE_TIKTOK_ID} - - TIKTOK_CLIENT_SECRET=${SERVICE_TIKTOK_SECRET} - - SLACK_ID=${SERVICE_SLACK_ID} - - SLACK_SECRET=${SERVICE_SLACK_SECRET} - - PINTEREST_CLIENT_ID=${SERVICE_PINTEREST_ID} - - PINTEREST_CLIENT_SECRET=${SERVICE_PINTEREST_SECRET} - - DRIBBLE_CLIENT_ID=${SERVICE_DRIBBLE_ID} - - DRIBBLE_CLIENT_SECRET=${SERVICE_DRIBBLE_SECRET} - - DISCORD_CLIENT_ID=${SERVICE_DISCORD_ID} - - DISCORD_CLIENT_SECRET=${SERVICE_DISCORD_SECRET} - - DISCORD_BOT_TOKEN_ID=${SERVICE_DISCORD_TOKEN} - - YOUTUBE_CLIENT_ID=${SERVICE_YOUTUBE_ID} - - YOUTUBE_CLIENT_SECRET=${SERVICE_YOUTUBE_SECRET} - - MASTODON_CLIENT_ID=${SERVICE_MASTODON_ID} - - MASTODON_CLIENT_SECRET=${SERVICE_MASTODON_SECRET} - - LINKEDIN_CLIENT_ID=${SERVICE_LINKEDIN_ID} - - LINKEDIN_CLIENT_SECRET=${SERVICE_LINKEDIN_SECRET} - - INSTAGRAM_APP_ID=${SERVICE_INSTAGRAM_ID} - - INSTAGRAM_APP_SECRET=${SERVICE_INSTAGRAM_SECRET} - - FACEBOOK_APP_ID=${SERVICE_FACEBOOK_ID} - - FACEBOOK_APP_SECRET=${SERVICE_FACEBOOK_SECRET} - - THREADS_APP_ID=${SERVICE_THREADS_ID} - - THREADS_APP_SECRET=${SERVICE_THREADS_SECRET} - - GITHUB_CLIENT_ID=${SERVICE_GITHUB_ID} - - GITHUB_CLIENT_SECRET=${SERVICE_GITHUB_SECRET} - - BEEHIIVE_API_KEY=${SERVICE_BEEHIIVE_KEY} - - BEEHIIVE_PUBLICATION_ID=${SERVICE_BEEHIIVE_PUBID} - - OPENAI_API_KEY=${SERVICE_OPENAI_KEY} + - 'MAIN_URL=${SERVICE_FQDN_POSTIZ}' + - 'FRONTEND_URL=${SERVICE_FQDN_POSTIZ}' + - 'NEXT_PUBLIC_BACKEND_URL=${SERVICE_FQDN_POSTIZ}/api' + - 'DATABASE_URL=postgresql://${SERVICE_USER_POSTGRESQL}:${SERVICE_PASSWORD_POSTGRESQL}@postgres:5432/${POSTGRESQL_DATABASE:-postiz-db}' + - 'REDIS_URL=redis://${SERVICE_USER_REDIS}:${SERVICE_PASSWORD_REDIS}@redis:6379' + - 'JWT_SECRET=${SERVICE_PASSWORD_JWTSECRET}' + - 'BACKEND_INTERNAL_URL=http://localhost:3000' + + # Cloudflare R2 Settings + - 'CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}' + - 'CLOUDFLARE_ACCESS_KEY=${CLOUDFLARE_ACCESS_KEY}' + - 'CLOUDFLARE_SECRET_ACCESS_KEY=${CLOUDFLARE_SECRET_ACCESS_KEY}' + - 'CLOUDFLARE_BUCKETNAME=${CLOUDFLARE_BUCKETNAME}' + - 'CLOUDFLARE_BUCKET_URL=${CLOUDFLARE_BUCKET_URL}' + - 'CLOUDFLARE_REGION=${CLOUDFLARE_REGION}' + + # Storage Settings + - 'STORAGE_PROVIDER=${STORAGE_PROVIDER:-local}' + - 'UPLOAD_DIRECTORY=${UPLOAD_DIRECTORY:-/uploads}' + - 'NEXT_PUBLIC_UPLOAD_DIRECTORY=${NEXT_PUBLIC_UPLOAD_DIRECTORY:-/uploads}' + - 'NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY=${NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY}' + + # Email Settings + - 'RESEND_API_KEY=${RESEND_API_KEY}' + - 'EMAIL_FROM_ADDRESS=${EMAIL_FROM_ADDRESS}' + - 'EMAIL_FROM_NAME=${EMAIL_FROM_NAME}' + + # Social Media API Settings + - 'X_API_KEY=${SERVICE_X_API}' + - 'X_API_SECRET=${SERVICE_X_SECRET}' + - 'LINKEDIN_CLIENT_ID=${SERVICE_LINKEDIN_ID}' + - 'LINKEDIN_CLIENT_SECRET=${SERVICE_LINKEDIN_SECRET}' + - 'REDDIT_CLIENT_ID=${SERVICE_REDDIT_API}' + - 'REDDIT_CLIENT_SECRET=${SERVICE_REDDIT_SECRET}' + - 'GITHUB_CLIENT_ID=${SERVICE_GITHUB_ID}' + - 'GITHUB_CLIENT_SECRET=${SERVICE_GITHUB_SECRET}' + - 'THREADS_APP_ID=${SERVICE_THREADS_ID}' + - 'THREADS_APP_SECRET=${SERVICE_THREADS_SECRET}' + - 'FACEBOOK_APP_ID=${SERVICE_FACEBOOK_ID}' + - 'FACEBOOK_APP_SECRET=${SERVICE_FACEBOOK_SECRET}' + - 'YOUTUBE_CLIENT_ID=${SERVICE_YOUTUBE_ID}' + - 'YOUTUBE_CLIENT_SECRET=${SERVICE_YOUTUBE_SECRET}' + - 'TIKTOK_CLIENT_ID=${SERVICE_TIKTOK_ID}' + - 'TIKTOK_CLIENT_SECRET=${SERVICE_TIKTOK_SECRET}' + - 'PINTEREST_CLIENT_ID=${SERVICE_PINTEREST_ID}' + - 'PINTEREST_CLIENT_SECRET=${SERVICE_PINTEREST_SECRET}' + - 'DRIBBBLE_CLIENT_ID=${SERVICE_DRIBBLE_ID}' + - 'DRIBBBLE_CLIENT_SECRET=${SERVICE_DRIBBLE_SECRET}' + - 'DISCORD_CLIENT_ID=${SERVICE_DISCORD_ID}' + - 'DISCORD_CLIENT_SECRET=${SERVICE_DISCORD_SECRET}' + - 'DISCORD_BOT_TOKEN_ID=${SERVICE_DISCORD_TOKEN}' + - 'SLACK_ID=${SERVICE_SLACK_ID}' + - 'SLACK_SECRET=${SERVICE_SLACK_SECRET}' + - 'SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET}' + - 'MASTODON_CLIENT_ID=${SERVICE_MASTODON_ID}' + - 'MASTODON_CLIENT_SECRET=${SERVICE_MASTODON_SECRET}' + + # Integration APIs + - 'BEEHIIVE_API_KEY=${SERVICE_BEEHIIVE_KEY}' + - 'BEEHIIVE_PUBLICATION_ID=${SERVICE_BEEHIIVE_PUBID}' + - 'OPENAI_API_KEY=${SERVICE_OPENAI_KEY}' + + # Misc Settings + - 'NEXT_PUBLIC_DISCORD_SUPPORT=${NEXT_PUBLIC_DISCORD_SUPPORT}' + - 'NEXT_PUBLIC_POLOTNO=${NEXT_PUBLIC_POLOTNO}' + - 'IS_GENERAL=${IS_GENERAL:-true}' + - 'NX_ADD_PLUGINS=${NX_ADD_PLUGINS:-false}' + + # Payment Settings + - 'FEE_AMOUNT=${FEE_AMOUNT:-0.05}' + - 'STRIPE_PUBLISHABLE_KEY=${STRIPE_PUBLISHABLE_KEY}' + - 'STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}' + - 'STRIPE_SIGNING_KEY=${STRIPE_SIGNING_KEY}' + - 'STRIPE_SIGNING_KEY_CONNECT=${STRIPE_SIGNING_KEY_CONNECT}' + volumes: - - postiz_config:/config/ - - postiz_uploads:/uploads/ + - 'postiz_config:/config/' + - 'postiz_uploads:/uploads/' depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: - test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5000/"] + test: + - CMD-SHELL + - 'wget -qO- http://127.0.0.1:5000/' interval: 5s timeout: 20s retries: 10 postgres: - image: postgres:14.5 + image: 'postgres:14.5' volumes: - - postiz_postgresql_data:/var/lib/postgresql/data + - 'postiz_postgresql_data:/var/lib/postgresql/data' environment: - - POSTGRES_USER=${SERVICE_USER_POSTGRESQL} - - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL} - - POSTGRES_DB=${POSTGRESQL_DATABASE:-postiz-db} + - 'POSTGRES_USER=${SERVICE_USER_POSTGRESQL}' + - 'POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}' + - 'POSTGRES_DB=${POSTGRESQL_DATABASE:-postiz-db}' healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + test: + - CMD-SHELL + - 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}' interval: 5s timeout: 20s retries: 10 redis: - image: redis:7.2 + image: 'redis:7.2' + command: > + redis-server + --port 6379 + --save 60 1 + --loglevel warning + --protected-mode yes + --aclfile /data/users.acl volumes: - - postiz_redis_data:/data - environment: - - REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS} - - REDIS_USER=${SERVICE_USER_REDIS} + - 'postiz_redis_data:/data' + - type: tmpfs + target: /tmp healthcheck: test: - CMD - redis-cli - - PING + - '-u' + - 'redis://${SERVICE_USER_REDIS}:${SERVICE_PASSWORD_REDIS}@localhost:6379' + - ping interval: 5s timeout: 10s retries: 20 + deploy: + resources: + limits: + memory: 256M + entrypoint: > + sh -c " + echo 'user default off' > /data/users.acl && + echo 'user ${SERVICE_USER_REDIS} on >${SERVICE_PASSWORD_REDIS} ~* &* +@all' >> /data/users.acl && + redis-server --aclfile /data/users.acl + " + +volumes: + postiz_config: + driver: local + postiz_uploads: + driver: local + postiz_postgresql_data: + driver: local + postiz_redis_data: + driver: local From 57f26c5d9af32c81e196d6c36c87a358a6632196 Mon Sep 17 00:00:00 2001 From: Drdiffie <61631493+dr-diffie@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:45:43 +0100 Subject: [PATCH 031/395] Update postiz.yaml with Peaklabs revisions --- templates/compose/postiz.yaml | 180 ++++++++++++++-------------------- 1 file changed, 76 insertions(+), 104 deletions(-) diff --git a/templates/compose/postiz.yaml b/templates/compose/postiz.yaml index 1ad216f26..b8ecd0f11 100644 --- a/templates/compose/postiz.yaml +++ b/templates/compose/postiz.yaml @@ -6,88 +6,88 @@ services: postiz: - image: 'ghcr.io/gitroomhq/postiz-app:latest' + image: ghcr.io/gitroomhq/postiz-app:latest environment: - # Required Settings - SERVICE_FQDN_POSTIZ_5000 - - 'MAIN_URL=${SERVICE_FQDN_POSTIZ}' - - 'FRONTEND_URL=${SERVICE_FQDN_POSTIZ}' - - 'NEXT_PUBLIC_BACKEND_URL=${SERVICE_FQDN_POSTIZ}/api' - - 'DATABASE_URL=postgresql://${SERVICE_USER_POSTGRESQL}:${SERVICE_PASSWORD_POSTGRESQL}@postgres:5432/${POSTGRESQL_DATABASE:-postiz-db}' - - 'REDIS_URL=redis://${SERVICE_USER_REDIS}:${SERVICE_PASSWORD_REDIS}@redis:6379' - - 'JWT_SECRET=${SERVICE_PASSWORD_JWTSECRET}' - - 'BACKEND_INTERNAL_URL=http://localhost:3000' + - MAIN_URL=${SERVICE_FQDN_POSTIZ} + - FRONTEND_URL=${SERVICE_FQDN_POSTIZ} + - NEXT_PUBLIC_BACKEND_URL=${SERVICE_FQDN_POSTIZ}/api + - JWT_SECRET=${SERVICE_PASSWORD_JWTSECRET} + - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRESQL}:${SERVICE_PASSWORD_POSTGRESQL}@postgres:5432/${POSTGRESQL_DATABASE:-postiz-db} + # Changed Redis URL to use default username + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 + - BACKEND_INTERNAL_URL=http://localhost:3000 # Cloudflare R2 Settings - - 'CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}' - - 'CLOUDFLARE_ACCESS_KEY=${CLOUDFLARE_ACCESS_KEY}' - - 'CLOUDFLARE_SECRET_ACCESS_KEY=${CLOUDFLARE_SECRET_ACCESS_KEY}' - - 'CLOUDFLARE_BUCKETNAME=${CLOUDFLARE_BUCKETNAME}' - - 'CLOUDFLARE_BUCKET_URL=${CLOUDFLARE_BUCKET_URL}' - - 'CLOUDFLARE_REGION=${CLOUDFLARE_REGION}' + - CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID} + - CLOUDFLARE_ACCESS_KEY=${CLOUDFLARE_ACCESS_KEY} + - CLOUDFLARE_SECRET_ACCESS_KEY=${CLOUDFLARE_SECRET_ACCESS_KEY} + - CLOUDFLARE_BUCKETNAME=${CLOUDFLARE_BUCKETNAME} + - CLOUDFLARE_BUCKET_URL=${CLOUDFLARE_BUCKET_URL} + - CLOUDFLARE_REGION=${CLOUDFLARE_REGION} # Storage Settings - - 'STORAGE_PROVIDER=${STORAGE_PROVIDER:-local}' - - 'UPLOAD_DIRECTORY=${UPLOAD_DIRECTORY:-/uploads}' - - 'NEXT_PUBLIC_UPLOAD_DIRECTORY=${NEXT_PUBLIC_UPLOAD_DIRECTORY:-/uploads}' - - 'NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY=${NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY}' + - STORAGE_PROVIDER=${STORAGE_PROVIDER:-local} + - UPLOAD_DIRECTORY=${UPLOAD_DIRECTORY:-/uploads} + - NEXT_PUBLIC_UPLOAD_DIRECTORY=${NEXT_PUBLIC_UPLOAD_DIRECTORY:-/uploads} + - NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY=${NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY} # Email Settings - - 'RESEND_API_KEY=${RESEND_API_KEY}' - - 'EMAIL_FROM_ADDRESS=${EMAIL_FROM_ADDRESS}' - - 'EMAIL_FROM_NAME=${EMAIL_FROM_NAME}' + - RESEND_API_KEY=${RESEND_API_KEY} + - EMAIL_FROM_ADDRESS=${EMAIL_FROM_ADDRESS} + - EMAIL_FROM_NAME=${EMAIL_FROM_NAME} # Social Media API Settings - - 'X_API_KEY=${SERVICE_X_API}' - - 'X_API_SECRET=${SERVICE_X_SECRET}' - - 'LINKEDIN_CLIENT_ID=${SERVICE_LINKEDIN_ID}' - - 'LINKEDIN_CLIENT_SECRET=${SERVICE_LINKEDIN_SECRET}' - - 'REDDIT_CLIENT_ID=${SERVICE_REDDIT_API}' - - 'REDDIT_CLIENT_SECRET=${SERVICE_REDDIT_SECRET}' - - 'GITHUB_CLIENT_ID=${SERVICE_GITHUB_ID}' - - 'GITHUB_CLIENT_SECRET=${SERVICE_GITHUB_SECRET}' - - 'THREADS_APP_ID=${SERVICE_THREADS_ID}' - - 'THREADS_APP_SECRET=${SERVICE_THREADS_SECRET}' - - 'FACEBOOK_APP_ID=${SERVICE_FACEBOOK_ID}' - - 'FACEBOOK_APP_SECRET=${SERVICE_FACEBOOK_SECRET}' - - 'YOUTUBE_CLIENT_ID=${SERVICE_YOUTUBE_ID}' - - 'YOUTUBE_CLIENT_SECRET=${SERVICE_YOUTUBE_SECRET}' - - 'TIKTOK_CLIENT_ID=${SERVICE_TIKTOK_ID}' - - 'TIKTOK_CLIENT_SECRET=${SERVICE_TIKTOK_SECRET}' - - 'PINTEREST_CLIENT_ID=${SERVICE_PINTEREST_ID}' - - 'PINTEREST_CLIENT_SECRET=${SERVICE_PINTEREST_SECRET}' - - 'DRIBBBLE_CLIENT_ID=${SERVICE_DRIBBLE_ID}' - - 'DRIBBBLE_CLIENT_SECRET=${SERVICE_DRIBBLE_SECRET}' - - 'DISCORD_CLIENT_ID=${SERVICE_DISCORD_ID}' - - 'DISCORD_CLIENT_SECRET=${SERVICE_DISCORD_SECRET}' - - 'DISCORD_BOT_TOKEN_ID=${SERVICE_DISCORD_TOKEN}' - - 'SLACK_ID=${SERVICE_SLACK_ID}' - - 'SLACK_SECRET=${SERVICE_SLACK_SECRET}' - - 'SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET}' - - 'MASTODON_CLIENT_ID=${SERVICE_MASTODON_ID}' - - 'MASTODON_CLIENT_SECRET=${SERVICE_MASTODON_SECRET}' + - X_API_KEY=${SERVICE_X_API} + - X_API_SECRET=${SERVICE_X_SECRET} + - LINKEDIN_CLIENT_ID=${SERVICE_LINKEDIN_ID} + - LINKEDIN_CLIENT_SECRET=${SERVICE_LINKEDIN_SECRET} + - REDDIT_CLIENT_ID=${SERVICE_REDDIT_API} + - REDDIT_CLIENT_SECRET=${SERVICE_REDDIT_SECRET} + - GITHUB_CLIENT_ID=${SERVICE_GITHUB_ID} + - GITHUB_CLIENT_SECRET=${SERVICE_GITHUB_SECRET} + - THREADS_APP_ID=${SERVICE_THREADS_ID} + - THREADS_APP_SECRET=${SERVICE_THREADS_SECRET} + - FACEBOOK_APP_ID=${SERVICE_FACEBOOK_ID} + - FACEBOOK_APP_SECRET=${SERVICE_FACEBOOK_SECRET} + - YOUTUBE_CLIENT_ID=${SERVICE_YOUTUBE_ID} + - YOUTUBE_CLIENT_SECRET=${SERVICE_YOUTUBE_SECRET} + - TIKTOK_CLIENT_ID=${SERVICE_TIKTOK_ID} + - TIKTOK_CLIENT_SECRET=${SERVICE_TIKTOK_SECRET} + - PINTEREST_CLIENT_ID=${SERVICE_PINTEREST_ID} + - PINTEREST_CLIENT_SECRET=${SERVICE_PINTEREST_SECRET} + - DRIBBBLE_CLIENT_ID=${SERVICE_DRIBBLE_ID} + - DRIBBBLE_CLIENT_SECRET=${SERVICE_DRIBBLE_SECRET} + - DISCORD_CLIENT_ID=${SERVICE_DISCORD_ID} + - DISCORD_CLIENT_SECRET=${SERVICE_DISCORD_SECRET} + - DISCORD_BOT_TOKEN_ID=${SERVICE_DISCORD_TOKEN} + - SLACK_ID=${SERVICE_SLACK_ID} + - SLACK_SECRET=${SERVICE_SLACK_SECRET} + - SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET} + - MASTODON_CLIENT_ID=${SERVICE_MASTODON_ID} + - MASTODON_CLIENT_SECRET=${SERVICE_MASTODON_SECRET} # Integration APIs - - 'BEEHIIVE_API_KEY=${SERVICE_BEEHIIVE_KEY}' - - 'BEEHIIVE_PUBLICATION_ID=${SERVICE_BEEHIIVE_PUBID}' - - 'OPENAI_API_KEY=${SERVICE_OPENAI_KEY}' + - BEEHIIVE_API_KEY=${SERVICE_BEEHIIVE_KEY} + - BEEHIIVE_PUBLICATION_ID=${SERVICE_BEEHIIVE_PUBID} + - OPENAI_API_KEY=${SERVICE_OPENAI_KEY} # Misc Settings - - 'NEXT_PUBLIC_DISCORD_SUPPORT=${NEXT_PUBLIC_DISCORD_SUPPORT}' - - 'NEXT_PUBLIC_POLOTNO=${NEXT_PUBLIC_POLOTNO}' - - 'IS_GENERAL=${IS_GENERAL:-true}' - - 'NX_ADD_PLUGINS=${NX_ADD_PLUGINS:-false}' + - NEXT_PUBLIC_DISCORD_SUPPORT=${NEXT_PUBLIC_DISCORD_SUPPORT} + - NEXT_PUBLIC_POLOTNO=${NEXT_PUBLIC_POLOTNO} + - IS_GENERAL=${IS_GENERAL:-true} + - NX_ADD_PLUGINS=${NX_ADD_PLUGINS:-false} # Payment Settings - - 'FEE_AMOUNT=${FEE_AMOUNT:-0.05}' - - 'STRIPE_PUBLISHABLE_KEY=${STRIPE_PUBLISHABLE_KEY}' - - 'STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}' - - 'STRIPE_SIGNING_KEY=${STRIPE_SIGNING_KEY}' - - 'STRIPE_SIGNING_KEY_CONNECT=${STRIPE_SIGNING_KEY_CONNECT}' + - FEE_AMOUNT=${FEE_AMOUNT:-0.05} + - STRIPE_PUBLISHABLE_KEY=${STRIPE_PUBLISHABLE_KEY} + - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} + - STRIPE_SIGNING_KEY=${STRIPE_SIGNING_KEY} + - STRIPE_SIGNING_KEY_CONNECT=${STRIPE_SIGNING_KEY_CONNECT} volumes: - - 'postiz_config:/config/' - - 'postiz_uploads:/uploads/' + - postiz_config:/config/ + - postiz_uploads:/uploads/ depends_on: postgres: condition: service_healthy @@ -96,67 +96,39 @@ services: healthcheck: test: - CMD-SHELL - - 'wget -qO- http://127.0.0.1:5000/' + - wget -qO- http://127.0.0.1:5000/ interval: 5s timeout: 20s retries: 10 postgres: - image: 'postgres:14.5' + image: postgres:14.5 volumes: - - 'postiz_postgresql_data:/var/lib/postgresql/data' + - postiz_postgresql_data:/var/lib/postgresql/data environment: - - 'POSTGRES_USER=${SERVICE_USER_POSTGRESQL}' - - 'POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}' - - 'POSTGRES_DB=${POSTGRESQL_DATABASE:-postiz-db}' + - POSTGRES_USER=${SERVICE_USER_POSTGRESQL} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL} + - POSTGRES_DB=${POSTGRESQL_DATABASE:-postiz-db} healthcheck: test: - CMD-SHELL - - 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}' + - pg_isready -U ${SERVICE_USER_POSTGRESQL} -d ${POSTGRESQL_DATABASE:-postiz-db} interval: 5s timeout: 20s retries: 10 redis: - image: 'redis:7.2' - command: > - redis-server - --port 6379 - --save 60 1 - --loglevel warning - --protected-mode yes - --aclfile /data/users.acl + image: redis:7.2 + command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS} volumes: - - 'postiz_redis_data:/data' - - type: tmpfs - target: /tmp + - postiz_redis_data:/data healthcheck: test: - CMD - redis-cli - - '-u' - - 'redis://${SERVICE_USER_REDIS}:${SERVICE_PASSWORD_REDIS}@localhost:6379' + - -a + - ${SERVICE_PASSWORD_REDIS} - ping interval: 5s timeout: 10s retries: 20 - deploy: - resources: - limits: - memory: 256M - entrypoint: > - sh -c " - echo 'user default off' > /data/users.acl && - echo 'user ${SERVICE_USER_REDIS} on >${SERVICE_PASSWORD_REDIS} ~* &* +@all' >> /data/users.acl && - redis-server --aclfile /data/users.acl - " - -volumes: - postiz_config: - driver: local - postiz_uploads: - driver: local - postiz_postgresql_data: - driver: local - postiz_redis_data: - driver: local From dc2db1330d840a69e0390a90a02a9123efa71e2a Mon Sep 17 00:00:00 2001 From: Taylor Brazelton Date: Thu, 21 Nov 2024 15:40:53 -0800 Subject: [PATCH 032/395] Added convertx service. --- public/svgs/convertx.png | Bin 0 -> 51678 bytes templates/compose/convertx.yml | 14 ++++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 public/svgs/convertx.png create mode 100644 templates/compose/convertx.yml diff --git a/public/svgs/convertx.png b/public/svgs/convertx.png new file mode 100644 index 0000000000000000000000000000000000000000..7f4c41e2e1d3da87202898f87a605691ef0d35be GIT binary patch literal 51678 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 Dq#6^2 literal 0 HcmV?d00001 diff --git a/templates/compose/convertx.yml b/templates/compose/convertx.yml new file mode 100644 index 000000000..6d4522ca1 --- /dev/null +++ b/templates/compose/convertx.yml @@ -0,0 +1,14 @@ +# documentation: https://github.com/C4illin/ConvertX +# slogan: A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia. +# tags: converter,file,documents,files,directories, +# logo: svgs/convertx.svg +# port: 3000 +services: + convertx: + image: ghcr.io/c4illin/convertx + container_name: convertx + restart: unless-stopped + environment: + - JWT_SECRET=${SERVICE_PASSWORD_CONVERTXJWTSECRET} + volumes: + - "convertx:/app/data" From 81e9c6d8a3c02229bef75f586acadfc357a1400f Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:26:28 +0100 Subject: [PATCH 033/395] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a3ce0132..e834ecb34 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ By subscribing to the cloud version, you get the Coolify server for the same pri | Andras Bacsai | Peak | |------------|------------| | Andras Bacsai | Peak Labs | -| | | +| | | # Repo Activity From 8a5a67813c3cc48d21bbbcf999199b3d8d72d168 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:29:02 +0100 Subject: [PATCH 034/395] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e834ecb34..a3ea4a272 100644 --- a/README.md +++ b/README.md @@ -147,10 +147,10 @@ By subscribing to the cloud version, you get the Coolify server for the same pri # Core Maintainers -| Andras Bacsai | Peak | +| Andras Bacsai | 🏔️ Peak | |------------|------------| | Andras Bacsai | Peak Labs | -| | | +| | | # Repo Activity From cfbe21feccbfb815feefb8034f1d4de38b842790 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:29:30 +0100 Subject: [PATCH 035/395] Revert "Update README.md" This reverts commit 8a5a67813c3cc48d21bbbcf999199b3d8d72d168. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a3ea4a272..e834ecb34 100644 --- a/README.md +++ b/README.md @@ -147,10 +147,10 @@ By subscribing to the cloud version, you get the Coolify server for the same pri # Core Maintainers -| Andras Bacsai | 🏔️ Peak | +| Andras Bacsai | Peak | |------------|------------| | Andras Bacsai | Peak Labs | -| | | +| | | # Repo Activity From 6497514f4ebab60959627cbac04d7378700d8119 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:29:52 +0100 Subject: [PATCH 036/395] Reapply "Update README.md" This reverts commit cfbe21feccbfb815feefb8034f1d4de38b842790. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e834ecb34..a3ea4a272 100644 --- a/README.md +++ b/README.md @@ -147,10 +147,10 @@ By subscribing to the cloud version, you get the Coolify server for the same pri # Core Maintainers -| Andras Bacsai | Peak | +| Andras Bacsai | 🏔️ Peak | |------------|------------| | Andras Bacsai | Peak Labs | -| | | +| | | # Repo Activity From e97a122d6360beff054e7875c75f4f447487378e Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:34:42 +0100 Subject: [PATCH 037/395] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3ea4a272..8f0df0bda 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ By subscribing to the cloud version, you get the Coolify server for the same pri | Andras Bacsai | 🏔️ Peak | |------------|------------| -| Andras Bacsai | Peak Labs | +| Andras Bacsai | peaklabs-dev | | | | # Repo Activity From dbbdb5b770a3f98ab4ce588d5b02a714b5d78976 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:42:55 +0100 Subject: [PATCH 038/395] upgrade to php 8.4 and set user and groupe ID --- docker/prod/Dockerfile | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 0459383f9..b2ecd0afa 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -1,6 +1,6 @@ # Versions # https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine -ARG SERVERSIDEUP_PHP_VERSION=8.3-fpm-nginx-alpine +ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine # https://github.com/minio/mc/releases ARG MINIO_VERSION=RELEASE.2024-11-05T11-29-45Z # https://github.com/cloudflare/cloudflared/releases @@ -8,15 +8,26 @@ ARG CLOUDFLARED_VERSION=2024.11.0 # https://www.postgresql.org/support/versioning/ - Can not updated automatically so keep it at 15 ARG POSTGRES_VERSION=15 +# Add user/group build arguments +ARG USER_ID=9999 +ARG GROUP_ID=9999 + # ================================================================= # Stage 1: Composer dependencies # ================================================================= FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} AS base +# Set the user ID and group ID for www-data +USER root +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx + WORKDIR /var/www/html COPY --chown=www-data:www-data composer.json composer.lock ./ RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist +USER www-data + # ================================================================= # Stage 2: Frontend assets compilation # ================================================================= @@ -38,6 +49,8 @@ FROM minio/mc:${MINIO_VERSION} AS minio-client # ================================================================= FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} +ARG USER_ID +ARG GROUP_ID ARG TARGETPLATFORM ARG POSTGRES_VERSION ARG CLOUDFLARED_VERSION @@ -47,6 +60,10 @@ WORKDIR /var/www/html USER root +# Set the user ID and group ID for www-data +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx + # Install PostgreSQL repository and keys RUN apk add --no-cache gnupg && \ mkdir -p /usr/share/keyrings && \ From 652e9f3fdbaceb52d25835e379ed9f21c84b5e76 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:49:03 +0100 Subject: [PATCH 039/395] fix user id and group id --- docker/prod/Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index b2ecd0afa..9f33717b7 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -8,7 +8,7 @@ ARG CLOUDFLARED_VERSION=2024.11.0 # https://www.postgresql.org/support/versioning/ - Can not updated automatically so keep it at 15 ARG POSTGRES_VERSION=15 -# Add user/group build arguments +# Add user/group ARG USER_ID=9999 ARG GROUP_ID=9999 @@ -19,6 +19,10 @@ FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} AS base # Set the user ID and group ID for www-data USER root + +ARG USER_ID +ARG GROUP_ID + RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx From 71a97f1d1082899bcbeadb2e56a9d287fb27d453 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:49:09 +0100 Subject: [PATCH 040/395] upgrade dep --- composer.lock | 484 +++++++++++++++++++++----------------------------- 1 file changed, 204 insertions(+), 280 deletions(-) diff --git a/composer.lock b/composer.lock index 8ea0d9a5a..a164a7b79 100644 --- a/composer.lock +++ b/composer.lock @@ -979,16 +979,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.327.1", + "version": "3.329.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3d52ec587989b136e486f94eff3dd316465aeb42" + "reference": "37249799204e04cf3686d2b361cfd417d3dab123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3d52ec587989b136e486f94eff3dd316465aeb42", - "reference": "3d52ec587989b136e486f94eff3dd316465aeb42", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/37249799204e04cf3686d2b361cfd417d3dab123", + "reference": "37249799204e04cf3686d2b361cfd417d3dab123", "shasum": "" }, "require": { @@ -1018,7 +1018,7 @@ "paragonie/random_compat": ">= 2", "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "sebastian/comparator": "^1.2.3 || ^4.0", "yoast/phpunit-polyfills": "^1.0" }, @@ -1071,9 +1071,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.327.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.329.0" }, - "time": "2024-11-15T01:53:30+00:00" + "time": "2024-11-21T19:16:09+00:00" }, { "name": "bacon/bacon-qr-code", @@ -2545,28 +2545,28 @@ }, { "name": "jean85/pretty-package-versions", - "version": "2.0.6", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", "shasum": "" }, "require": { - "composer-runtime-api": "^2.0.0", - "php": "^7.1|^8.0" + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^7.5|^8.5|^9.4", - "vimeo/psalm": "^4.3" + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "vimeo/psalm": "^4.3 || ^5.0" }, "type": "library", "extra": { @@ -2598,9 +2598,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" }, - "time": "2024-03-08T09:58:59+00:00" + "time": "2024-11-18T16:19:46+00:00" }, { "name": "kelunik/certificate", @@ -2727,16 +2727,16 @@ }, { "name": "laravel/framework", - "version": "v11.31.0", + "version": "v11.33.2", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40" + "reference": "6b9832751cf8eed18b3c73df5071f78f0682aa5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/365090ed2c68244e3141cdb5e247cdf3dfba2c40", - "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40", + "url": "https://api.github.com/repos/laravel/framework/zipball/6b9832751cf8eed18b3c73df5071f78f0682aa5d", + "reference": "6b9832751cf8eed18b3c73df5071f78f0682aa5d", "shasum": "" }, "require": { @@ -2756,7 +2756,7 @@ "guzzlehttp/guzzle": "^7.8", "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", - "laravel/serializable-closure": "^1.3", + "laravel/serializable-closure": "^1.3|^2.0", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", "monolog/monolog": "^3.0", @@ -2839,9 +2839,9 @@ "league/flysystem-path-prefixing": "^3.3", "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", - "mockery/mockery": "^1.6", + "mockery/mockery": "^1.6.10", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^9.5", + "orchestra/testbench-core": "^9.6", "pda/pheanstalk": "^5.0", "phpstan/phpstan": "^1.11.5", "phpunit/phpunit": "^10.5|^11.0", @@ -2932,7 +2932,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-11-12T15:36:15+00:00" + "time": "2024-11-19T22:47:13+00:00" }, { "name": "laravel/horizon", @@ -3153,16 +3153,16 @@ }, { "name": "laravel/sanctum", - "version": "v4.0.3", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab" + "reference": "819782c75aaf2b08da1765503893bd2b8023d3b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/54aea9d13743ae8a6cdd3c28dbef128a17adecab", - "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/819782c75aaf2b08da1765503893bd2b8023d3b3", + "reference": "819782c75aaf2b08da1765503893bd2b8023d3b3", "shasum": "" }, "require": { @@ -3213,36 +3213,36 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2024-09-27T14:55:41+00:00" + "time": "2024-11-15T14:47:23+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.6", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "f865a58ea3a0107c336b7045104c75243fa59d96" + "reference": "0d8d3d8086984996df86596a86dea60398093a81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f865a58ea3a0107c336b7045104c75243fa59d96", - "reference": "f865a58ea3a0107c336b7045104c75243fa59d96", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/0d8d3d8086984996df86596a86dea60398093a81", + "reference": "0d8d3d8086984996df86596a86dea60398093a81", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "php": "^8.1" }, "require-dev": { - "illuminate/support": "^8.0|^9.0|^10.0|^11.0", - "nesbot/carbon": "^2.61|^3.0", - "pestphp/pest": "^1.21.3", - "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + "illuminate/support": "^10.0|^11.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -3274,7 +3274,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-11-11T17:06:04+00:00" + "time": "2024-11-19T01:38:44+00:00" }, { "name": "laravel/socialite", @@ -5102,31 +5102,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3" + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/42c84e4e8090766bbd6445d06cd6e57650626ea3", - "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.1.5" + "symfony/console": "^7.1.8" }, "require-dev": { - "illuminate/console": "^11.28.0", - "laravel/pint": "^1.18.1", + "illuminate/console": "^11.33.2", + "laravel/pint": "^1.18.2", "mockery/mockery": "^1.6.12", "pestphp/pest": "^2.36.0", - "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan": "^1.12.11", "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^7.1.5", + "symfony/var-dumper": "^7.1.8", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -5169,7 +5169,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.2.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" }, "funding": [ { @@ -5185,7 +5185,7 @@ "type": "github" } ], - "time": "2024-10-15T16:15:16+00:00" + "time": "2024-11-21T10:39:51+00:00" }, { "name": "nyholm/psr7", @@ -5473,151 +5473,23 @@ }, "time": "2024-09-04T12:51:01+00:00" }, - { - "name": "php-di/invoker", - "version": "2.3.4", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2023-09-08T09:24:21+00:00" - }, - { - "name": "php-di/php-di", - "version": "7.0.7", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", - "shasum": "" - }, - "require": { - "laravel/serializable-closure": "^1.0", - "php": ">=8.0", - "php-di/invoker": "^2.0", - "psr/container": "^1.1 || ^2.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3", - "friendsofphp/proxy-manager-lts": "^1", - "mnapoli/phpunit-easymock": "^1.3", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.6" - }, - "suggest": { - "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2024-07-21T15:55:45+00:00" - }, { "name": "phpdocumentor/reflection", - "version": "6.0.0", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/Reflection.git", - "reference": "61e2f1fe7683e9647b9ed8d9e53d08699385267d" + "reference": "bb4dea805a645553d6d989b23dad9f8041f39502" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/61e2f1fe7683e9647b9ed8d9e53d08699385267d", - "reference": "61e2f1fe7683e9647b9ed8d9e53d08699385267d", + "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/bb4dea805a645553d6d989b23dad9f8041f39502", + "reference": "bb4dea805a645553d6d989b23dad9f8041f39502", "shasum": "" }, "require": { "nikic/php-parser": "~4.18 || ^5.0", - "php": "8.1.*|8.2.*|8.3.*", + "php": "8.1.*|8.2.*|8.3.*|8.4.*", "phpdocumentor/reflection-common": "^2.1", "phpdocumentor/reflection-docblock": "^5", "phpdocumentor/type-resolver": "^1.2", @@ -5664,9 +5536,9 @@ ], "support": { "issues": "https://github.com/phpDocumentor/Reflection/issues", - "source": "https://github.com/phpDocumentor/Reflection/tree/6.0.0" + "source": "https://github.com/phpDocumentor/Reflection/tree/6.1.0" }, - "time": "2024-05-23T19:28:12+00:00" + "time": "2024-11-22T15:11:54+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -6077,16 +5949,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.10", + "version": "1.12.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "fc463b5d0fe906dcf19689be692c65c50406a071" + "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071", - "reference": "fc463b5d0fe906dcf19689be692c65c50406a071", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d1fc20a962a91be578bcfe7cf939e6e1a2ff733", + "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733", "shasum": "" }, "require": { @@ -6131,7 +6003,60 @@ "type": "github" } ], - "time": "2024-11-11T15:37:09+00:00" + "time": "2024-11-17T14:08:01+00:00" + }, + { + "name": "pimple/pimple", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1 || ^2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "https://pimple.symfony.com", + "keywords": [ + "container", + "dependency injection" + ], + "support": { + "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" + }, + "time": "2021-10-28T11:13:42+00:00" }, { "name": "pion/laravel-chunk-upload", @@ -6201,23 +6126,23 @@ }, { "name": "poliander/cron", - "version": "3.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/poliander/cron.git", - "reference": "9e037c06aab233787999dfba38f1a12d100510c1" + "reference": "213c477b3d9d6fcf8f0944298f481c1649a92b3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/poliander/cron/zipball/9e037c06aab233787999dfba38f1a12d100510c1", - "reference": "9e037c06aab233787999dfba38f1a12d100510c1", + "url": "https://api.github.com/repos/poliander/cron/zipball/213c477b3d9d6fcf8f0944298f481c1649a92b3b", + "reference": "213c477b3d9d6fcf8f0944298f481c1649a92b3b", "shasum": "" }, "require": { - "php": "8.1.* || 8.2.* || 8.3.*" + "php": "8.2.* || 8.3.* || 8.4.*" }, "require-dev": { - "phpunit/phpunit": "~10.0" + "phpunit/phpunit": "~11.0" }, "type": "library", "autoload": { @@ -6239,9 +6164,9 @@ "homepage": "https://github.com/poliander/cron", "support": { "issues": "https://github.com/poliander/cron/issues", - "source": "https://github.com/poliander/cron/tree/3.1.0" + "source": "https://github.com/poliander/cron/tree/3.2.0" }, - "time": "2023-11-23T21:56:03+00:00" + "time": "2024-11-22T08:35:47+00:00" }, { "name": "pragmarx/google2fa", @@ -7731,16 +7656,16 @@ }, { "name": "spatie/backtrace", - "version": "1.6.2", + "version": "1.6.3", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" + "reference": "7c18db2bc667ac84e5d7c18e33f16c38ff2d8838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", - "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/7c18db2bc667ac84e5d7c18e33f16c38ff2d8838", + "reference": "7c18db2bc667ac84e5d7c18e33f16c38ff2d8838", "shasum": "" }, "require": { @@ -7778,7 +7703,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.6.2" + "source": "https://github.com/spatie/backtrace/tree/1.6.3" }, "funding": [ { @@ -7790,20 +7715,20 @@ "type": "other" } ], - "time": "2024-07-22T08:21:24+00:00" + "time": "2024-11-18T14:58:58+00:00" }, { "name": "spatie/laravel-activitylog", - "version": "4.9.0", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-activitylog.git", - "reference": "e0fc28178515a5396f48e107ed697719189bbe02" + "reference": "9abddaa9f2681d97943748c7fa04161cf4642e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/e0fc28178515a5396f48e107ed697719189bbe02", - "reference": "e0fc28178515a5396f48e107ed697719189bbe02", + "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/9abddaa9f2681d97943748c7fa04161cf4642e8c", + "reference": "9abddaa9f2681d97943748c7fa04161cf4642e8c", "shasum": "" }, "require": { @@ -7869,7 +7794,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-activitylog/issues", - "source": "https://github.com/spatie/laravel-activitylog/tree/4.9.0" + "source": "https://github.com/spatie/laravel-activitylog/tree/4.9.1" }, "funding": [ { @@ -7881,7 +7806,7 @@ "type": "github" } ], - "time": "2024-10-18T13:38:47+00:00" + "time": "2024-11-18T11:31:57+00:00" }, { "name": "spatie/laravel-data", @@ -7969,16 +7894,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.16.5", + "version": "1.16.6", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2" + "reference": "1f26942dc1e5c49eacfced34fdbc29ed234bd7b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/c7413972cf22ffdff97b68499c22baa04eddb6a2", - "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/1f26942dc1e5c49eacfced34fdbc29ed234bd7b3", + "reference": "1f26942dc1e5c49eacfced34fdbc29ed234bd7b3", "shasum": "" }, "require": { @@ -8017,7 +7942,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.5" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.6" }, "funding": [ { @@ -8025,7 +7950,7 @@ "type": "github" } ], - "time": "2024-08-27T18:56:10+00:00" + "time": "2024-11-18T15:02:02+00:00" }, { "name": "spatie/laravel-ray", @@ -8469,16 +8394,16 @@ }, { "name": "stripe/stripe-php", - "version": "v16.2.0", + "version": "v16.3.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "813ae4961755af28a13bda451689f7a6ed6498cb" + "reference": "48af6bc64ca8157b3fdce100e856069963bac466" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/813ae4961755af28a13bda451689f7a6ed6498cb", - "reference": "813ae4961755af28a13bda451689f7a6ed6498cb", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/48af6bc64ca8157b3fdce100e856069963bac466", + "reference": "48af6bc64ca8157b3fdce100e856069963bac466", "shasum": "" }, "require": { @@ -8522,9 +8447,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v16.2.0" + "source": "https://github.com/stripe/stripe-php/tree/v16.3.0" }, - "time": "2024-10-29T21:15:53+00:00" + "time": "2024-11-20T23:30:16+00:00" }, { "name": "symfony/clock", @@ -11312,16 +11237,16 @@ }, { "name": "voku/portable-ascii", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b56450eed252f6801410d810c8e1727224ae0743" + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", - "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", "shasum": "" }, "require": { @@ -11346,7 +11271,7 @@ "authors": [ { "name": "Lars Moelleken", - "homepage": "http://www.moelleken.org/" + "homepage": "https://www.moelleken.org/" } ], "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", @@ -11358,7 +11283,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" }, "funding": [ { @@ -11382,7 +11307,7 @@ "type": "tidelift" } ], - "time": "2022-03-08T17:03:00+00:00" + "time": "2024-11-21T01:49:47+00:00" }, { "name": "webmozart/assert", @@ -11554,31 +11479,30 @@ }, { "name": "zbateson/mail-mime-parser", - "version": "3.0.3", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/zbateson/mail-mime-parser.git", - "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19" + "reference": "ff49e02f6489b38f7cc3d1bd3971adc0f872569c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/e0d4423fe27850c9dd301190767dbc421acc2f19", - "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/ff49e02f6489b38f7cc3d1bd3971adc0f872569c", + "reference": "ff49e02f6489b38f7cc3d1bd3971adc0f872569c", "shasum": "" }, "require": { - "guzzlehttp/psr7": "^2.5", - "php": ">=8.0", - "php-di/php-di": "^6.0|^7.0", - "psr/log": "^1|^2|^3", - "zbateson/mb-wrapper": "^2.0", - "zbateson/stream-decorators": "^2.1" + "guzzlehttp/psr7": "^1.7.0|^2.0", + "php": ">=7.1", + "pimple/pimple": "^3.0", + "zbateson/mb-wrapper": "^1.0.1", + "zbateson/stream-decorators": "^1.0.6" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", - "monolog/monolog": "^2|^3", + "mikey179/vfsstream": "^1.6.0", "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "<10" }, "suggest": { "ext-iconv": "For best support/performance", @@ -11626,24 +11550,24 @@ "type": "github" } ], - "time": "2024-08-10T18:44:09+00:00" + "time": "2024-04-28T00:58:54+00:00" }, { "name": "zbateson/mb-wrapper", - "version": "2.0.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/zbateson/mb-wrapper.git", - "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619" + "reference": "09a8b77eb94af3823a9a6623dcc94f8d988da67f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/9e4373a153585d12b6c621ac4a6bb143264d4619", - "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619", + "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/09a8b77eb94af3823a9a6623dcc94f8d988da67f", + "reference": "09a8b77eb94af3823a9a6623dcc94f8d988da67f", "shasum": "" }, "require": { - "php": ">=8.0", + "php": ">=7.1", "symfony/polyfill-iconv": "^1.9", "symfony/polyfill-mbstring": "^1.9" }, @@ -11687,7 +11611,7 @@ ], "support": { "issues": "https://github.com/zbateson/mb-wrapper/issues", - "source": "https://github.com/zbateson/mb-wrapper/tree/2.0.0" + "source": "https://github.com/zbateson/mb-wrapper/tree/1.2.1" }, "funding": [ { @@ -11695,31 +11619,31 @@ "type": "github" } ], - "time": "2024-03-20T01:38:07+00:00" + "time": "2024-03-18T04:31:04+00:00" }, { "name": "zbateson/stream-decorators", - "version": "2.1.1", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/zbateson/stream-decorators.git", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5" + "reference": "783b034024fda8eafa19675fb2552f8654d3a3e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/32a2a62fb0f26313395c996ebd658d33c3f9c4e5", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5", + "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/783b034024fda8eafa19675fb2552f8654d3a3e9", + "reference": "783b034024fda8eafa19675fb2552f8654d3a3e9", "shasum": "" }, "require": { - "guzzlehttp/psr7": "^2.5", - "php": ">=8.0", - "zbateson/mb-wrapper": "^2.0" + "guzzlehttp/psr7": "^1.9 | ^2.0", + "php": ">=7.2", + "zbateson/mb-wrapper": "^1.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6|^10.0" + "phpunit/phpunit": "<10.0" }, "type": "library", "autoload": { @@ -11750,7 +11674,7 @@ ], "support": { "issues": "https://github.com/zbateson/stream-decorators/issues", - "source": "https://github.com/zbateson/stream-decorators/tree/2.1.1" + "source": "https://github.com/zbateson/stream-decorators/tree/1.2.1" }, "funding": [ { @@ -11758,7 +11682,7 @@ "type": "github" } ], - "time": "2024-04-29T21:42:39+00:00" + "time": "2023-05-30T22:51:52+00:00" }, { "name": "zircote/swagger-php", @@ -12022,16 +11946,16 @@ }, { "name": "fakerphp/faker", - "version": "v1.24.0", + "version": "v1.24.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "a136842a532bac9ecd8a1c723852b09915d7db50" + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50", - "reference": "a136842a532bac9ecd8a1c723852b09915d7db50", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", "shasum": "" }, "require": { @@ -12079,9 +12003,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.24.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" }, - "time": "2024-11-07T15:11:20+00:00" + "time": "2024-11-21T13:46:39+00:00" }, { "name": "fidry/cpu-core-counter", @@ -12340,16 +12264,16 @@ }, { "name": "laravel/pint", - "version": "v1.18.1", + "version": "v1.18.2", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9" + "reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9", + "url": "https://api.github.com/repos/laravel/pint/zipball/f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64", + "reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64", "shasum": "" }, "require": { @@ -12402,7 +12326,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-09-24T17:22:50+00:00" + "time": "2024-11-20T09:33:46+00:00" }, { "name": "laravel/telescope", @@ -13225,16 +13149,16 @@ }, { "name": "php-webdriver/webdriver", - "version": "1.15.1", + "version": "1.15.2", "source": { "type": "git", "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "cd52d9342c5aa738c2e75a67e47a1b6df97154e8" + "reference": "998e499b786805568deaf8cbf06f4044f05d91bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/cd52d9342c5aa738c2e75a67e47a1b6df97154e8", - "reference": "cd52d9342c5aa738c2e75a67e47a1b6df97154e8", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/998e499b786805568deaf8cbf06f4044f05d91bf", + "reference": "998e499b786805568deaf8cbf06f4044f05d91bf", "shasum": "" }, "require": { @@ -13256,7 +13180,7 @@ "php-parallel-lint/php-parallel-lint": "^1.2", "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^5.0 || ^6.0" + "symfony/var-dumper": "^5.0 || ^6.0 || ^7.0" }, "suggest": { "ext-SimpleXML": "For Firefox profile creation" @@ -13285,9 +13209,9 @@ ], "support": { "issues": "https://github.com/php-webdriver/php-webdriver/issues", - "source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.1" + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.2" }, - "time": "2023-10-20T12:21:20+00:00" + "time": "2024-11-21T15:12:59+00:00" }, { "name": "phpunit/php-code-coverage", From 9d80ae76c6fa248fb63124d9c6da5399bd962d49 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:54:26 +0100 Subject: [PATCH 041/395] fix versions, remove some comments --- docker/prod/Dockerfile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 9f33717b7..352114040 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -1,12 +1,12 @@ # Versions -# https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine +# https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine # https://github.com/minio/mc/releases ARG MINIO_VERSION=RELEASE.2024-11-05T11-29-45Z # https://github.com/cloudflare/cloudflared/releases ARG CLOUDFLARED_VERSION=2024.11.0 -# https://www.postgresql.org/support/versioning/ - Can not updated automatically so keep it at 15 -ARG POSTGRES_VERSION=15 +# https://www.postgresql.org/support/versioning/ - Upgraded to 16 if the we have a manual upgrade guide? +ARG POSTGRES_VERSION=16 # Add user/group ARG USER_ID=9999 @@ -17,7 +17,6 @@ ARG GROUP_ID=9999 # ================================================================= FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} AS base -# Set the user ID and group ID for www-data USER root ARG USER_ID @@ -64,7 +63,6 @@ WORKDIR /var/www/html USER root -# Set the user ID and group ID for www-data RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx From 569560f46c14f7fddec1b01daad932db4005e043 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 22 Nov 2024 18:09:43 +0100 Subject: [PATCH 042/395] fix: view issue --- resources/views/components/resources/breadcrumbs.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php index f9733f63a..f91e04037 100644 --- a/resources/views/components/resources/breadcrumbs.blade.php +++ b/resources/views/components/resources/breadcrumbs.blade.php @@ -8,7 +8,7 @@
  • {{ $this->parameters['environment_name'] }} + href="{{ route('project.resource.index', ['environment_name' => data_get($resource, 'environment.name'), 'project_uuid' => data_get($resource, 'environment.project.uuid')]) }}">{{ data_get($resource, 'environment.name') }}
      { + this.resizeTerminal(); + }); } }, @@ -101,12 +122,19 @@ export function initializeTerminalComponent() { `${connectionString.protocol}://${connectionString.host}${connectionString.port}${connectionString.path}` this.socket = new WebSocket(url); + this.socket.onopen = () => { + console.log('[Terminal] WebSocket connection established. Cool cool cool cool cool cool.'); + }; + this.socket.onmessage = this.handleSocketMessage.bind(this); this.socket.onerror = (e) => { - console.error('WebSocket error:', e); + console.error('[Terminal] WebSocket error.'); }; this.socket.onclose = () => { - console.log('WebSocket connection closed'); + console.warn('[Terminal] WebSocket connection closed.'); + this.resetTerminal(); + this.message = '(connection closed)'; + this.terminalActive = false; this.reconnect(); }; } @@ -117,19 +145,18 @@ export function initializeTerminalComponent() { clearInterval(this.reconnectInterval); } this.reconnectInterval = setInterval(() => { - console.log('Attempting to reconnect...'); + console.warn('[Terminal] Attempting to reconnect...'); this.initializeWebSocket(); if (this.socket && this.socket.readyState === WebSocket.OPEN) { - console.log('Reconnected successfully'); + console.log('[Terminal] Reconnected successfully'); clearInterval(this.reconnectInterval); this.reconnectInterval = null; - window.location.reload(); + } }, 2000); }, handleSocketMessage(event) { - this.message = '(connection closed)'; if (event.data === 'pty-ready') { if (!this.term._initialized) { this.term.open(document.getElementById('terminal')); @@ -150,8 +177,17 @@ export function initializeTerminalComponent() { this.term.reset(); this.commandBuffer = ''; } else { - this.pendingWrites++; - this.term.write(event.data, this.flowControlCallback.bind(this)); + try { + this.pendingWrites++; + this.term.write(event.data, (err) => { + if (err) { + console.error('[Terminal] Write error:', err); + } + this.flowControlCallback(); + }); + } catch (error) { + console.error('[Terminal] Write operation failed:', error); + } } }, @@ -173,11 +209,15 @@ export function initializeTerminalComponent() { if (!this.term) return; this.term.onData((data) => { - this.socket.send(JSON.stringify({ message: data })); - if (data === '\r') { - this.commandBuffer = ''; + if (this.socket.readyState === WebSocket.OPEN) { + this.socket.send(JSON.stringify({ message: data })); + if (data === '\r') { + this.commandBuffer = ''; + } else { + this.commandBuffer += data; + } } else { - this.commandBuffer += data; + console.warn('[Terminal] WebSocket not ready, data not sent'); } }); diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 57f1807c8..5213479c8 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -96,6 +96,20 @@ enableStats: false, enableLogging: true, enabledTransports: ['ws', 'wss'], + disableStats: true, + // Add auto reconnection settings + enabledTransports: ['ws', 'wss'], + disabledTransports: ['sockjs', 'xhr_streaming', 'xhr_polling'], + // Attempt to reconnect on connection lost + autoReconnect: true, + // Wait 1 second before first reconnect attempt + reconnectionDelay: 1000, + // Maximum delay between reconnection attempts + maxReconnectionDelay: 1000, + // Multiply delay by this number for each reconnection attempt + reconnectionDelayGrowth: 1, + // Maximum number of reconnection attempts + maxAttempts: 15 }); @endauth let checkHealthInterval = null; diff --git a/resources/views/livewire/layout-popups.blade.php b/resources/views/livewire/layout-popups.blade.php index 8e75a2eee..41d249cb0 100644 --- a/resources/views/livewire/layout-popups.blade.php +++ b/resources/views/livewire/layout-popups.blade.php @@ -11,16 +11,22 @@ let checkNumber = 1; let checkPusherInterval = null; + let checkReconnectInterval = null; + if (!this.popups.realtime) { checkPusherInterval = setInterval(() => { - if (window.Echo && window.Echo.connector.pusher.connection.state !== 'connected') { - checkNumber++; - if (checkNumber > 5) { - this.popups.realtime = true; - console.error( - 'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)' - ); - clearInterval(checkPusherInterval); + if (window.Echo) { + if (window.Echo.connector.pusher.connection.state === 'connected') { + this.popups.realtime = false; + } else { + checkNumber++; + if (checkNumber > 5) { + this.popups.realtime = true; + console.error( + 'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)' + ); + } + } } }, 2000); diff --git a/resources/views/livewire/project/shared/execute-container-command.blade.php b/resources/views/livewire/project/shared/execute-container-command.blade.php index cb39e0855..f9760ed65 100644 --- a/resources/views/livewire/project/shared/execute-container-command.blade.php +++ b/resources/views/livewire/project/shared/execute-container-command.blade.php @@ -26,8 +26,8 @@ @else @if (count($containers) > 0) @if (count($containers) === 1) -
      + Reconnect
      @else From 9525d91d559dd507ff6fd493765f7c383780d658 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 13:17:09 +0100 Subject: [PATCH 073/395] fix --- app/Events/DatabaseProxyStopped.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Events/DatabaseProxyStopped.php b/app/Events/DatabaseProxyStopped.php index b457dc6a0..96b35a5ca 100644 --- a/app/Events/DatabaseProxyStopped.php +++ b/app/Events/DatabaseProxyStopped.php @@ -18,7 +18,7 @@ class DatabaseProxyStopped implements ShouldBroadcast public function __construct($teamId = null) { if (is_null($teamId)) { - $teamId = Auth::user()->currentTeam()->id ?? null; + $teamId = Auth::user()?->currentTeam()?->id ?? null; } if (is_null($teamId)) { throw new \Exception('Team id is null'); From 53d709467a689f7bd6d029aa8be713eff1d50979 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 13:22:05 +0100 Subject: [PATCH 074/395] fix: sentry error --- app/Livewire/Destination/New/Docker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Livewire/Destination/New/Docker.php b/app/Livewire/Destination/New/Docker.php index f86f42e34..337f1d067 100644 --- a/app/Livewire/Destination/New/Docker.php +++ b/app/Livewire/Destination/New/Docker.php @@ -35,7 +35,7 @@ class Docker extends Component $this->network = new Cuid2; $this->servers = Server::isUsable()->get(); if ($server_id) { - $this->selectedServer = $this->servers->find($server_id); + $this->selectedServer = $this->servers->find($server_id) ?: $this->servers->first(); $this->serverId = $this->selectedServer->id; } else { $this->selectedServer = $this->servers->first(); From e3182b1e5a3663140e672a8065ff5263f96ed1a2 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:29:05 +0100 Subject: [PATCH 075/395] fix beszel --- templates/compose/beszel.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/compose/beszel.yaml b/templates/compose/beszel.yaml index b40d1f10e..6cf693338 100644 --- a/templates/compose/beszel.yaml +++ b/templates/compose/beszel.yaml @@ -12,13 +12,13 @@ services: environment: - SERVICE_FQDN_BESZEL_8090 volumes: - - "beszel-data:/beszel_data" + - beszel_data:/beszel_data beszel-agent: image: henrygd/beszel-agent volumes: - - "/var/run/docker.sock:/var/run/docker.sock:ro" + - /var/run/docker.sock:/var/run/docker.sock:ro environment: - PORT: 45876 - # Public Key from "Add a new system" in the UI and restart the agent - # KEY: "" + - PORT=45876 + # Public Key from "Add a new system" in the UI and restart the agent + # - KEY="" From e5cd35d2a01a776f7b147684ddab32d16c587a28 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 13:33:07 +0100 Subject: [PATCH 076/395] fix: sentry --- app/Jobs/ApplicationDeploymentJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 270243eaf..6a66bb56d 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -463,7 +463,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id')); $this->save_environment_variables(); if (! is_null($this->env_filename)) { - $services = collect($composeFile['services']); + $services = collect(data_get($composeFile, 'services', [])); $services = $services->map(function ($service, $name) { $service['env_file'] = [$this->env_filename]; From 2056f7a39611b87082119946fbd7b31b9f8734c6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:34:00 +0100 Subject: [PATCH 077/395] fix maybe --- templates/compose/maybe.yaml | 42 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/templates/compose/maybe.yaml b/templates/compose/maybe.yaml index efa8e3203..7fe3ebdff 100644 --- a/templates/compose/maybe.yaml +++ b/templates/compose/maybe.yaml @@ -6,21 +6,20 @@ services: maybe: - image: "ghcr.io/maybe-finance/maybe:latest" + image: ghcr.io/maybe-finance/maybe:latest volumes: - - "app-storage:/rails/storage" - restart: unless-stopped + - app_storage:/rails/storage environment: - SERVICE_FQDN_MAYBE - SELF_HOSTED=true - - RAILS_FORCE_SSL=false - - RAILS_ASSUME_SSL=false - - GOOD_JOB_EXECUTION_MODE=async - - SECRET_KEY_BASE=$SERVICE_BASE64_64_SECRETKEYBASE + - RAILS_FORCE_SSL=${RAILS_FORCE_SSL:-false} + - RAILS_ASSUME_SSL=${RAILS_ASSUME_SSL:-false} + - GOOD_JOB_EXECUTION_MODE=${GOOD_JOB_EXECUTION_MODE:-async} + - SECRET_KEY_BASE=${SERVICE_BASE64_64_SECRETKEYBASE} - DB_HOST=postgres - - POSTGRES_DB=${POSTGRES_DB:-maybe_production} - - POSTGRES_USER=${POSTGRES_USER:-maybe_user} - - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES + - POSTGRES_DB=${POSTGRES_DB:-maybe_db} + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} depends_on: postgres: condition: service_healthy @@ -30,22 +29,17 @@ services: - curl - "-f" - "http://localhost:3000" + postgres: - image: "postgres:16" - restart: unless-stopped + image: postgres:16 volumes: - - "postgres-data:/var/lib/postgresql/data" + - maybe_postgres_data:/var/lib/postgresql/data environment: - - POSTGRES_USER=${POSTGRES_USER:-maybe_user} - - POSTGRES_DB=${POSTGRES_DB:-maybe_production} - - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_DB=${POSTGRES_DB:-maybe_db} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} healthcheck: - test: - - CMD-SHELL - - "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 5s - timeout: 5s - retries: 5 -volumes: - app-storage: null - postgres-data: null + timeout: 20s + retries: 10 From c76e57ba8f38fd4db851259fdb14a312d9ba381c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:41:36 +0100 Subject: [PATCH 078/395] remove gothub --- public/svgs/gothub.svg | 753 ---------------------------------- templates/compose/gothub.yaml | 30 -- 2 files changed, 783 deletions(-) delete mode 100644 public/svgs/gothub.svg delete mode 100644 templates/compose/gothub.yaml diff --git a/public/svgs/gothub.svg b/public/svgs/gothub.svg deleted file mode 100644 index 61fe71555..000000000 --- a/public/svgs/gothub.svg +++ /dev/null @@ -1,753 +0,0 @@ - - - - - - gothub/public/assets/logo.svg at dev - gothub/gothub - Codeberg.org - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      - - - - - - - - - - - - - - -
      -
      - -
      -
      -
      -
      - - - - - -
      -
      - -
      -
      - - - - - - -
      -
      - -
      - - - - - - - - -
      -
      - - - 14 - -
      -
      - - -
      -
      - - - 132 - -
      -
      - - - - - -
      - - Fork - - - - 21 - -
      - - - - -
      - -
      - - - -
      - - - - - - -
      -
      - -
      - - - - - -
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      -
      - - - - - - - - - - - - - - -
      - - -
      - - - - - - - - - - - - - - - - - - - gothub/public/assets/logo.svg - -
      -
      - - - -
      -
      - -
      - - -
      -
      - - - - - Odyssey - - - - 7dbaef6386 - - - - - - I guess I'll commit - - -
      Signed-off-by: Odyssey <odyssey346@disroot.org>
      - -
      - - -
      - - -
      - 2022-11-29 17:59:04 +01:00 -
      - - -
      - - -

      -
      - -
      - - -
      - 150 lines -
      - - - -
      - 5.9 KiB -
      - - - -
      - XML -
      - - - - - -
      - - -
      -
      - -
      - - -
      - - -
      - - Raw - - Permalink - - - Blame - - History - -
      - - - - - - - - - - - - - - - - - - - -
      -

      -
      - - - - - - -
      - -
      - - - -
      - -
      -
      -
      - - -
      -
      - - - - -
      - - - - - - - - - - - - diff --git a/templates/compose/gothub.yaml b/templates/compose/gothub.yaml deleted file mode 100644 index c0a651f05..000000000 --- a/templates/compose/gothub.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# documentation: https://gothub.app/docs/ -# slogan: Alternative front-end for GitHub written with Go. -# tags: frontend, git -# logo: svgs/gothub.svg -# port: 3000 - -version: '3' -services: - gothub: - image: 'codeberg.org/gothub/gothub:latest' - restart: unless-stopped - environment: - - SERVICE_FQDN_GOTHUB_3000 - - GOTHUB_SETUP_COMPLETE=${GOTHUB_SETUP_COMPLETE:-false} - - GOTHUB_PROXYING_ENABLED=${GOTHUB_PROXYING_ENABLED:-false} - - GOTHUB_IP_LOGGED=${GOTHUB_IP_LOGGED:-false} - - GOTHUB_REQUEST_URL_LOGGED=${GOTHUB_REQUEST_URL_LOGGED:-false} - - GOTHUB_USER_AGENT_LOGGED=${GOTHUB_USER_AGENT_LOGGED:-false} - - GOTHUB_DIAGNOSTIC_INFO_LOGGED=${GOTHUB_DIAGNOSTIC_INFO_LOGGED:-false} - - GOTHUB_INSTANCE_PRIVACY_POLICY=${GOTHUB_INSTANCE_PRIVACY_POLICY:-"https://your.website/privacy-policy"} - - GOTHUB_INSTANCE_COUNTRY=${GOTHUB_INSTANCE_COUNTRY:-Finland} - - GOTHUB_INSTANCE_PROVIDER=${GOTHUB_INSTANCE_PROVIDER:-Hetzner} - - GOTHUB_INSTANCE_CLOUDFLARE=${GOTHUB_INSTANCE_CLOUDFLARE:-false} - - DOCKER=true - - healthcheck: - test: 'wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/version || exit 1' - interval: 30s - timeout: 5s - retries: 2 \ No newline at end of file From 7610f026b1418113d91728253dea46ef79f7e1d6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:41:43 +0100 Subject: [PATCH 079/395] fix private bin --- templates/compose/privatebin.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/templates/compose/privatebin.yaml b/templates/compose/privatebin.yaml index c2f625c8f..f088a9d6c 100644 --- a/templates/compose/privatebin.yaml +++ b/templates/compose/privatebin.yaml @@ -7,12 +7,10 @@ services: privatebin: image: privatebin/nginx-fpm-alpine - restart: always - read_only: true environment: - SERVICE_FQDN_PRIVATEBIN_8080 volumes: - - 'privatebin-data:/srv/data' + - privatebin_data:/srv/data healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/"] interval: 5s From 40487cb5c3e3d8b5af5dfb1f545f5a637180c319 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:41:52 +0100 Subject: [PATCH 080/395] fix redlib --- templates/compose/redlib.yaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml index 3c0004e82..19887bb3d 100644 --- a/templates/compose/redlib.yaml +++ b/templates/compose/redlib.yaml @@ -6,11 +6,9 @@ services: redlib: - image: 'quay.io/redlib/redlib:latest' - restart: always - container_name: redlib + image: quay.io/redlib/redlib:latest environment: - - SERVICE_FQDN_REDLIB_8080=${SERVICE_FQDN_REDLIB_8080:-localhost} + - SERVICE_FQDN_REDLIB_8080 - REDLIB_SFW_ONLY=${REDLIB_SFW_ONLY:-off} - REDLIB_BANNER=${REDLIB_BANNER:-""} - REDLIB_ROBOTS_DISABLE_INDEXING=${REDLIB_ROBOTS_DISABLE_INDEXING:-off} @@ -33,9 +31,8 @@ services: - REDLIB_DEFAULT_HIDE_SCORE=${REDLIB_DEFAULT_HIDE_SCORE:-off} - REDLIB_DEFAULT_FIXED_NAVBAR=${REDLIB_DEFAULT_FIXED_NAVBAR:-on} user: nobody - read_only: true security_opt: - - 'no-new-privileges:true' + - no-new-privileges:true cap_drop: - ALL healthcheck: From 40af73cb7d213e1009b37ddd15fa90d0ff1f2656 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 13:41:59 +0100 Subject: [PATCH 081/395] fix: only return server uuid on server update --- app/Http/Controllers/Api/ServersController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index a83e6ed66..8c13b1a01 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -680,7 +680,9 @@ class ServersController extends Controller ValidateServer::dispatch($server); } - return response()->json(serializeApiResponse($server))->setStatusCode(201); + return response()->json([ + 'uuid' => $server->uuid, + ])->setStatusCode(201); } #[OA\Delete( From 93f39657bb8fcf813ac7d4239b8b0e6160473f5c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:51:06 +0100 Subject: [PATCH 082/395] fix redlib and private bin slogan --- templates/compose/privatebin.yaml | 2 +- templates/compose/redlib.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/privatebin.yaml b/templates/compose/privatebin.yaml index f088a9d6c..5cd82319b 100644 --- a/templates/compose/privatebin.yaml +++ b/templates/compose/privatebin.yaml @@ -1,5 +1,5 @@ # documentation: https://github.com/PrivateBin/PrivateBin/blob/master/doc/README.md -# PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. +# slogan: PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. # tags: text, sharing # logo: svgs/privatebin.svg # port: 8080 diff --git a/templates/compose/redlib.yaml b/templates/compose/redlib.yaml index 19887bb3d..e746153d7 100644 --- a/templates/compose/redlib.yaml +++ b/templates/compose/redlib.yaml @@ -1,5 +1,5 @@ # documentation: https://github.com/redlib-org/redlib -# An alternative private front-end to Reddit, with its origins in Libreddit. +# slogan: An alternative private front-end to Reddit, with its origins in Libreddit. # tags: frontend, feed # logo: svgs/redlib.svg # port: 8080 From 894fc3e266f8be316834f7875ecd2002f510a16f Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:57:48 +0100 Subject: [PATCH 083/395] Update service-templates.json --- templates/service-templates.json | 58 +++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/templates/service-templates.json b/templates/service-templates.json index db96534b7..4f1568613 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -158,6 +158,21 @@ "logo": "svgs/babybuddy.png", "minversion": "0.0.0" }, + "beszel": { + "documentation": "https://github.com/henrygd/beszel?tab=readme-ov-file#getting-started?utm_source=coolify.io", + "slogan": "A lightweight server resource monitoring hub with historical data, docker stats, and alerts.", + "compose": "c2VydmljZXM6CiAgYmVzemVsOgogICAgaW1hZ2U6ICdoZW5yeWdkL2Jlc3plbDpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQkVTWkVMXzgwOTAKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2Jlc3plbF9kYXRhOi9iZXN6ZWxfZGF0YScKICBiZXN6ZWwtYWdlbnQ6CiAgICBpbWFnZTogaGVucnlnZC9iZXN6ZWwtYWdlbnQKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrOnJvJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9SVD00NTg3Ngo=", + "tags": [ + "beszel", + "monitoring", + "server", + "stats", + "alerts" + ], + "logo": "svgs/beszel.svg", + "minversion": "0.0.0", + "port": "8090" + }, "bitcoin-core": { "documentation": "https://hub.docker.com/r/ruimarinho/bitcoin-core/?utm_source=coolify.io", "slogan": "A self-hosted Bitcoin Core full node.", @@ -1639,6 +1654,23 @@ "minversion": "0.0.0", "port": "80" }, + "maybe": { + "documentation": "https://github.com/maybe-finance/maybe?utm_source=coolify.io", + "slogan": "Maybe: The OS for your personal finances.", + "compose": "c2VydmljZXM6CiAgbWF5YmU6CiAgICBpbWFnZTogJ2doY3IuaW8vbWF5YmUtZmluYW5jZS9tYXliZTpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdhcHBfc3RvcmFnZTovcmFpbHMvc3RvcmFnZScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NQVlCRQogICAgICAtIFNFTEZfSE9TVEVEPXRydWUKICAgICAgLSAnUkFJTFNfRk9SQ0VfU1NMPSR7UkFJTFNfRk9SQ0VfU1NMOi1mYWxzZX0nCiAgICAgIC0gJ1JBSUxTX0FTU1VNRV9TU0w9JHtSQUlMU19BU1NVTUVfU1NMOi1mYWxzZX0nCiAgICAgIC0gJ0dPT0RfSk9CX0VYRUNVVElPTl9NT0RFPSR7R09PRF9KT0JfRVhFQ1VUSU9OX01PREU6LWFzeW5jfScKICAgICAgLSAnU0VDUkVUX0tFWV9CQVNFPSR7U0VSVklDRV9CQVNFNjRfNjRfU0VDUkVUS0VZQkFTRX0nCiAgICAgIC0gREJfSE9TVD1wb3N0Z3JlcwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1tYXliZV9kYn0nCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDozMDAwJwogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21heWJlX3Bvc3RncmVzX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1tYXliZV9kYn0nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "tags": [ + "finances", + "wallets", + "coins", + "stocks", + "investments", + "open", + "source" + ], + "logo": "svgs/maybe.svg", + "minversion": "0.0.0", + "port": "3000" + }, "mealie": { "documentation": "https://docs.mealie.io/?utm_source=coolify.io", "slogan": "A recipe manager and meal planner.", @@ -2240,6 +2272,18 @@ "minversion": "0.0.0", "port": "4200" }, + "privatebin": { + "documentation": "https://github.com/PrivateBin/PrivateBin/blob/master/doc/README.md?utm_source=coolify.io", + "slogan": "PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data.", + "compose": "c2VydmljZXM6CiAgcHJpdmF0ZWJpbjoKICAgIGltYWdlOiBwcml2YXRlYmluL25naW54LWZwbS1hbHBpbmUKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9QUklWQVRFQklOXzgwODAKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3ByaXZhdGViaW5fZGF0YTovc3J2L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLXFPLSBodHRwOi8vMTI3LjAuMC4xOjgwODAvJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "tags": [ + "text", + "sharing" + ], + "logo": "svgs/privatebin.svg", + "minversion": "0.0.0", + "port": "8080" + }, "qbittorrent": { "documentation": "https://docs.linuxserver.io/images/docker-qbittorrent/?utm_source=coolify.io", "slogan": "The qBittorrent project aims to provide an open-source software alternative to \u03bcTorrent.", @@ -2316,6 +2360,18 @@ "minversion": "0.0.0", "port": "8000" }, + "redlib": { + "documentation": "https://github.com/redlib-org/redlib?utm_source=coolify.io", + "slogan": "An alternative private front-end to Reddit, with its origins in Libreddit.", + "compose": "c2VydmljZXM6CiAgcmVkbGliOgogICAgaW1hZ2U6ICdxdWF5LmlvL3JlZGxpYi9yZWRsaWI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1JFRExJQl84MDgwCiAgICAgIC0gJ1JFRExJQl9TRldfT05MWT0ke1JFRExJQl9TRldfT05MWTotb2ZmfScKICAgICAgLSAnUkVETElCX0JBTk5FUj0ke1JFRExJQl9CQU5ORVI6LSIifScKICAgICAgLSAnUkVETElCX1JPQk9UU19ESVNBQkxFX0lOREVYSU5HPSR7UkVETElCX1JPQk9UU19ESVNBQkxFX0lOREVYSU5HOi1vZmZ9JwogICAgICAtICdSRURMSUJfREVGQVVMVF9GUk9OVF9QQUdFPSR7UkVETElCX0RFRkFVTFRfRlJPTlRfUEFHRTotd29ybGRuZXdzfScKICAgICAgLSAnUkVETElCX1BVU0hTSElGVF9GUk9OVEVORD0ke1JFRExJQl9QVVNIU0hJRlRfRlJPTlRFTkQ6LXVuZGVsZXRlLnB1bGxwdXNoLmlvfScKICAgICAgLSAnUkVETElCX0RFRkFVTFRfTEFZT1VUPSR7UkVETElCX0RFRkFVTFRfTEFZT1VUOi1jYXJkfScKICAgICAgLSAnUkVETElCX0RFRkFVTFRfV0lERT0ke1JFRExJQl9ERUZBVUxUX1dJREU6LW9mZn0nCiAgICAgIC0gJ1JFRExJQl9ERUZBVUxUX1BPU1RfU09SVD0ke1JFRExJQl9ERUZBVUxUX1BPU1RfU09SVDotaG90fScKICAgICAgLSAnUkVETElCX0RFRkFVTFRfQ09NTUVOVF9TT1JUPSR7UkVETElCX0RFRkFVTFRfQ09NTUVOVF9TT1JUOi1jb25maWRlbmNlfScKICAgICAgLSAnUkVETElCX0RFRkFVTFRfQkxVUl9TUE9JTEVSPSR7UkVETElCX0RFRkFVTFRfQkxVUl9TUE9JTEVSOi1vZmZ9JwogICAgICAtICdSRURMSUJfREVGQVVMVF9TSE9XX05TRlc9JHtSRURMSUJfREVGQVVMVF9TSE9XX05TRlc6LW9mZn0nCiAgICAgIC0gJ1JFRExJQl9ERUZBVUxUX0JMVVJfTlNGVz0ke1JFRExJQl9ERUZBVUxUX0JMVVJfTlNGVzotb2ZmfScKICAgICAgLSAnUkVETElCX0RFRkFVTFRfVVNFX0hMUz0ke1JFRExJQl9ERUZBVUxUX1VTRV9ITFM6LW9mZn0nCiAgICAgIC0gJ1JFRExJQl9ERUZBVUxUX0hJREVfSExTX05PVElGSUNBVElPTj0ke1JFRExJQl9ERUZBVUxUX0hJREVfSExTX05PVElGSUNBVElPTjotb2ZmfScKICAgICAgLSAnUkVETElCX0RFRkFVTFRfQVVUT1BMQVlfVklERU9TPSR7UkVETElCX0RFRkFVTFRfQVVUT1BMQVlfVklERU9TOi1vZmZ9JwogICAgICAtICdSRURMSUJfREVGQVVMVF9TVUJTQ1JJUFRJT05TPSR7UkVETElCX0RFRkFVTFRfU1VCU0NSSVBUSU9OUzotIiJ9JwogICAgICAtICdSRURMSUJfREVGQVVMVF9ISURFX0FXQVJEUz0ke1JFRExJQl9ERUZBVUxUX0hJREVfQVdBUkRTOi1vZmZ9JwogICAgICAtICdSRURMSUJfREVGQVVMVF9ISURFX1NJREVCQVJfQU5EX1NVTU1BUlk9JHtSRURMSUJfREVGQVVMVF9ISURFX1NJREVCQVJfQU5EX1NVTU1BUlk6LW9mZn0nCiAgICAgIC0gJ1JFRExJQl9ERUZBVUxUX0RJU0FCTEVfVklTSVRfUkVERElUX0NPTkZJUk1BVElPTj0ke1JFRExJQl9ERUZBVUxUX0RJU0FCTEVfVklTSVRfUkVERElUX0NPTkZJUk1BVElPTjotb2ZmfScKICAgICAgLSAnUkVETElCX0RFRkFVTFRfSElERV9TQ09SRT0ke1JFRExJQl9ERUZBVUxUX0hJREVfU0NPUkU6LW9mZn0nCiAgICAgIC0gJ1JFRExJQl9ERUZBVUxUX0ZJWEVEX05BVkJBUj0ke1JFRExJQl9ERUZBVUxUX0ZJWEVEX05BVkJBUjotb259JwogICAgdXNlcjogbm9ib2R5CiAgICBzZWN1cml0eV9vcHQ6CiAgICAgIC0gJ25vLW5ldy1wcml2aWxlZ2VzOnRydWUnCiAgICBjYXBfZHJvcDoKICAgICAgLSBBTEwKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "tags": [ + "frontend", + "feed" + ], + "logo": "svgs/redlib.svg", + "minversion": "0.0.0", + "port": "8080" + }, "rocketchat": { "documentation": "https://github.com/RocketChat/Rocket.Chat?utm_source=coolify.io", "slogan": "Self-hosted, secure and highly customizable open-source communication platform for organizations with sophisticated security and privacy concerns.", @@ -2468,7 +2524,7 @@ "supabase": { "documentation": "https://supabase.io?utm_source=coolify.io", "slogan": "The open source Firebase alternative.", - "compose": "c2VydmljZXM6CiAgc3VwYWJhc2Uta29uZzoKICAgIGltYWdlOiAna29uZzoyLjguMScKICAgIGVudHJ5cG9pbnQ6ICdiYXNoIC1jICcnZXZhbCAiZWNobyBcIiQkKGNhdCB+L3RlbXAueW1sKVwiIiA+IH4va29uZy55bWwgJiYgL2RvY2tlci1lbnRyeXBvaW50LnNoIGtvbmcgZG9ja2VyLXN0YXJ0JycnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkdfODAwMAogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIEtPTkdfREFUQUJBU0U9b2ZmCiAgICAgIC0gS09OR19ERUNMQVJBVElWRV9DT05GSUc9L2hvbWUva29uZy9rb25nLnltbAogICAgICAtICdLT05HX0ROU19PUkRFUj1MQVNULEEsQ05BTUUnCiAgICAgIC0gJ0tPTkdfUExVR0lOUz1yZXF1ZXN0LXRyYW5zZm9ybWVyLGNvcnMsa2V5LWF1dGgsYWNsLGJhc2ljLWF1dGgnCiAgICAgIC0gS09OR19OR0lOWF9QUk9YWV9QUk9YWV9CVUZGRVJfU0laRT0xNjBrCiAgICAgIC0gJ0tPTkdfTkdJTlhfUFJPWFlfUFJPWFlfQlVGRkVSUz02NCAxNjBrJwogICAgICAtICdTVVBBQkFTRV9BTk9OX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX1NFUlZJQ0VfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnREFTSEJPQVJEX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX0FETUlOfScKICAgICAgLSAnREFTSEJPQVJEX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0nCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2FwaS9rb25nLnltbAogICAgICAgIHRhcmdldDogL2hvbWUva29uZy90ZW1wLnltbAogICAgICAgIGNvbnRlbnQ6ICJfZm9ybWF0X3ZlcnNpb246ICcyLjEnXG5fdHJhbnNmb3JtOiB0cnVlXG5cbiMjI1xuIyMjIENvbnN1bWVycyAvIFVzZXJzXG4jIyNcbmNvbnN1bWVyczpcbiAgLSB1c2VybmFtZTogREFTSEJPQVJEXG4gIC0gdXNlcm5hbWU6IGFub25cbiAgICBrZXlhdXRoX2NyZWRlbnRpYWxzOlxuICAgICAgLSBrZXk6ICRTVVBBQkFTRV9BTk9OX0tFWVxuICAtIHVzZXJuYW1lOiBzZXJ2aWNlX3JvbGVcbiAgICBrZXlhdXRoX2NyZWRlbnRpYWxzOlxuICAgICAgLSBrZXk6ICRTVVBBQkFTRV9TRVJWSUNFX0tFWVxuXG4jIyNcbiMjIyBBY2Nlc3MgQ29udHJvbCBMaXN0XG4jIyNcbmFjbHM6XG4gIC0gY29uc3VtZXI6IGFub25cbiAgICBncm91cDogYW5vblxuICAtIGNvbnN1bWVyOiBzZXJ2aWNlX3JvbGVcbiAgICBncm91cDogYWRtaW5cblxuIyMjXG4jIyMgRGFzaGJvYXJkIGNyZWRlbnRpYWxzXG4jIyNcbmJhc2ljYXV0aF9jcmVkZW50aWFsczpcbi0gY29uc3VtZXI6IERBU0hCT0FSRFxuICB1c2VybmFtZTogJERBU0hCT0FSRF9VU0VSTkFNRVxuICBwYXNzd29yZDogJERBU0hCT0FSRF9QQVNTV09SRFxuXG5cbiMjI1xuIyMjIEFQSSBSb3V0ZXNcbiMjI1xuc2VydmljZXM6XG5cbiAgIyMgT3BlbiBBdXRoIHJvdXRlc1xuICAtIG5hbWU6IGF1dGgtdjEtb3BlblxuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS92ZXJpZnlcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtb3BlblxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvdmVyaWZ5XG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAtIG5hbWU6IGF1dGgtdjEtb3Blbi1jYWxsYmFja1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS9jYWxsYmFja1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1vcGVuLWNhbGxiYWNrXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYXV0aC92MS9jYWxsYmFja1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgLSBuYW1lOiBhdXRoLXYxLW9wZW4tYXV0aG9yaXplXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5L2F1dGhvcml6ZVxuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1vcGVuLWF1dGhvcml6ZVxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvYXV0aG9yaXplXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIFNlY3VyZSBBdXRoIHJvdXRlc1xuICAtIG5hbWU6IGF1dGgtdjFcbiAgICBfY29tbWVudDogJ0dvVHJ1ZTogL2F1dGgvdjEvKiAtPiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5LyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5L1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9hdXRoL3YxL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IGZhbHNlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgUkVTVCByb3V0ZXNcbiAgLSBuYW1lOiByZXN0LXYxXG4gICAgX2NvbW1lbnQ6ICdQb3N0Z1JFU1Q6IC9yZXN0L3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlc3QtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVzdC92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgR3JhcGhRTCByb3V0ZXNcbiAgLSBuYW1lOiBncmFwaHFsLXYxXG4gICAgX2NvbW1lbnQ6ICdQb3N0Z1JFU1Q6IC9ncmFwaHFsL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC9ycGMvZ3JhcGhxbCdcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvcnBjL2dyYXBocWxcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGdyYXBocWwtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvZ3JhcGhxbC92MVxuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IHRydWVcbiAgICAgIC0gbmFtZTogcmVxdWVzdC10cmFuc2Zvcm1lclxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgYWRkOlxuICAgICAgICAgICAgaGVhZGVyczpcbiAgICAgICAgICAgICAgLSBDb250ZW50LVByb2ZpbGU6Z3JhcGhxbF9wdWJsaWNcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuXG4gICMjIFNlY3VyZSBSZWFsdGltZSByb3V0ZXNcbiAgLSBuYW1lOiByZWFsdGltZS12MS13c1xuICAgIF9jb21tZW50OiAnUmVhbHRpbWU6IC9yZWFsdGltZS92MS8qIC0+IHdzOi8vcmVhbHRpbWU6NDAwMC9zb2NrZXQvKidcbiAgICB1cmw6IGh0dHA6Ly9yZWFsdGltZS1kZXY6NDAwMC9zb2NrZXRcbiAgICBwcm90b2NvbDogd3NcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlYWx0aW1lLXYxLXdzXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVhbHRpbWUvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuICAtIG5hbWU6IHJlYWx0aW1lLXYxLXJlc3RcbiAgICBfY29tbWVudDogJ1JlYWx0aW1lOiAvcmVhbHRpbWUvdjEvKiAtPiB3czovL3JlYWx0aW1lOjQwMDAvc29ja2V0LyonXG4gICAgdXJsOiBodHRwOi8vcmVhbHRpbWUtZGV2OjQwMDAvYXBpXG4gICAgcHJvdG9jb2w6IGh0dHBcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlYWx0aW1lLXYxLXJlc3RcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9yZWFsdGltZS92MS9hcGlcbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiBmYWxzZVxuICAgICAgLSBuYW1lOiBhY2xcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfZ3JvdXBzX2hlYWRlcjogdHJ1ZVxuICAgICAgICAgIGFsbG93OlxuICAgICAgICAgICAgLSBhZG1pblxuICAgICAgICAgICAgLSBhbm9uXG5cbiAgIyMgU3RvcmFnZSByb3V0ZXM6IHRoZSBzdG9yYWdlIHNlcnZlciBtYW5hZ2VzIGl0cyBvd24gYXV0aFxuICAtIG5hbWU6IHN0b3JhZ2UtdjFcbiAgICBfY29tbWVudDogJ1N0b3JhZ2U6IC9zdG9yYWdlL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXN0b3JhZ2U6NTAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXN0b3JhZ2U6NTAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHN0b3JhZ2UtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvc3RvcmFnZS92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG5cbiAgIyMgRWRnZSBGdW5jdGlvbnMgcm91dGVzXG4gIC0gbmFtZTogZnVuY3Rpb25zLXYxXG4gICAgX2NvbW1lbnQ6ICdFZGdlIEZ1bmN0aW9uczogL2Z1bmN0aW9ucy92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1lZGdlLWZ1bmN0aW9uczo5MDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtZWRnZS1mdW5jdGlvbnM6OTAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGZ1bmN0aW9ucy12MS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9mdW5jdGlvbnMvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIEFuYWx5dGljcyByb3V0ZXNcbiAgLSBuYW1lOiBhbmFseXRpY3MtdjFcbiAgICBfY29tbWVudDogJ0FuYWx5dGljczogL2FuYWx5dGljcy92MS8qIC0+IGh0dHA6Ly9sb2dmbGFyZTo0MDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBhbmFseXRpY3MtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYW5hbHl0aWNzL3YxL1xuXG4gICMjIFNlY3VyZSBEYXRhYmFzZSByb3V0ZXNcbiAgLSBuYW1lOiBtZXRhXG4gICAgX2NvbW1lbnQ6ICdwZy1tZXRhOiAvcGcvKiAtPiBodHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwL1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogbWV0YS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9wZy9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cblxuICAjIyBQcm90ZWN0ZWQgRGFzaGJvYXJkIC0gY2F0Y2ggYWxsIHJlbWFpbmluZyByb3V0ZXNcbiAgLSBuYW1lOiBkYXNoYm9hcmRcbiAgICBfY29tbWVudDogJ1N0dWRpbzogLyogLT4gaHR0cDovL3N0dWRpbzozMDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2Utc3R1ZGlvOjMwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBkYXNoYm9hcmQtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBiYXNpYy1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4iCiAgc3VwYWJhc2Utc3R1ZGlvOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9zdHVkaW86MjAyNDA5MjMtMmUzZTkwYycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBub2RlCiAgICAgICAgLSAnLWUnCiAgICAgICAgLSAicmVxdWlyZSgnaHR0cCcpLmdldCgnaHR0cDovLzEyNy4wLjAuMTozMDAwL2FwaS9wcm9maWxlJywgKHIpID0+IHtpZiAoci5zdGF0dXNDb2RlICE9PSAyMDApIHByb2Nlc3MuZXhpdCgxKTsgZWxzZSBwcm9jZXNzLmV4aXQoMCk7IH0pLm9uKCdlcnJvcicsICgpID0+IHByb2Nlc3MuZXhpdCgxKSkiCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAzCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIEhPU1ROQU1FPTAuMC4wLjAKICAgICAgLSAnU1RVRElPX1BHX01FVEFfVVJMPWh0dHA6Ly9zdXBhYmFzZS1tZXRhOjgwODAnCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RFRkFVTFRfT1JHQU5JWkFUSU9OX05BTUU9JHtTVFVESU9fREVGQVVMVF9PUkdBTklaQVRJT046LURlZmF1bHQgT3JnYW5pemF0aW9ufScKICAgICAgLSAnREVGQVVMVF9QUk9KRUNUX05BTUU9JHtTVFVESU9fREVGQVVMVF9QUk9KRUNUOi1EZWZhdWx0IFByb2plY3R9JwogICAgICAtICdTVVBBQkFTRV9VUkw9aHR0cDovL3N1cGFiYXNlLWtvbmc6ODAwMCcKICAgICAgLSAnU1VQQUJBU0VfUFVCTElDX1VSTD0ke1NFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkd9JwogICAgICAtICdTVVBBQkFTRV9BTk9OX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX1NFUlZJQ0VfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnQVVUSF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdMT0dGTEFSRV9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9MT0dGTEFSRX0nCiAgICAgIC0gJ0xPR0ZMQVJFX1VSTD1odHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAnCiAgICAgIC0gJ1NVUEFCQVNFX1BVQkxJQ19BUEk9JHtTRVJWSUNFX0ZRRE5fU1VQQUJBU0VLT05HfScKICAgICAgLSBORVhUX1BVQkxJQ19FTkFCTEVfTE9HUz10cnVlCiAgICAgIC0gTkVYVF9BTkFMWVRJQ1NfQkFDS0VORF9QUk9WSURFUj1wb3N0Z3JlcwogIHN1cGFiYXNlLWRiOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9wb3N0Z3JlczoxNS4xLjEuNzgnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogJ3BnX2lzcmVhZHkgLVUgcG9zdGdyZXMgLWggMTI3LjAuMC4xJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLXZlY3RvcjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgY29tbWFuZDoKICAgICAgLSBwb3N0Z3JlcwogICAgICAtICctYycKICAgICAgLSBjb25maWdfZmlsZT0vZXRjL3Bvc3RncmVzcWwvcG9zdGdyZXNxbC5jb25mCiAgICAgIC0gJy1jJwogICAgICAtIGxvZ19taW5fbWVzc2FnZXM9ZmF0YWwKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0hPU1Q9L3Zhci9ydW4vcG9zdGdyZXNxbAogICAgICAtICdQR1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUE9TVEdSRVNfUE9SVD0ke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9JwogICAgICAtICdQR1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BHREFUQUJBU0U9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3N1cGFiYXNlLWRiLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2RiL3JlYWx0aW1lLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktcmVhbHRpbWUuc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IHBndXNlciBgZWNobyBcInN1cGFiYXNlX2FkbWluXCJgXG5cbmNyZWF0ZSBzY2hlbWEgaWYgbm90IGV4aXN0cyBfcmVhbHRpbWU7XG5hbHRlciBzY2hlbWEgX3JlYWx0aW1lIG93bmVyIHRvIDpwZ3VzZXI7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvX3N1cGFiYXNlLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTctX3N1cGFiYXNlLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCIkUE9TVEdSRVNfVVNFUlwiYFxuXG5DUkVBVEUgREFUQUJBU0UgX3N1cGFiYXNlIFdJVEggT1dORVIgOnBndXNlcjtcbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9wb29sZXIuc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvbWlncmF0aW9ucy85OS1wb29sZXIuc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IHBndXNlciBgZWNobyBcInN1cGFiYXNlX2FkbWluXCJgXG5cXGMgX3N1cGFiYXNlXG5jcmVhdGUgc2NoZW1hIGlmIG5vdCBleGlzdHMgX3N1cGF2aXNvcjtcbmFsdGVyIHNjaGVtYSBfc3VwYXZpc29yIG93bmVyIHRvIDpwZ3VzZXI7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvd2ViaG9va3Muc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk4LXdlYmhvb2tzLnNxbAogICAgICAgIGNvbnRlbnQ6ICJCRUdJTjtcbi0tIENyZWF0ZSBwZ19uZXQgZXh0ZW5zaW9uXG5DUkVBVEUgRVhURU5TSU9OIElGIE5PVCBFWElTVFMgcGdfbmV0IFNDSEVNQSBleHRlbnNpb25zO1xuLS0gQ3JlYXRlIHN1cGFiYXNlX2Z1bmN0aW9ucyBzY2hlbWFcbkNSRUFURSBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIEFVVEhPUklaQVRJT04gc3VwYWJhc2VfYWRtaW47XG5HUkFOVCBVU0FHRSBPTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gVEFCTEVTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gRlVOQ1RJT05TIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gU0VRVUVOQ0VTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMubWlncmF0aW9ucyBkZWZpbml0aW9uXG5DUkVBVEUgVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLm1pZ3JhdGlvbnMgKFxuICB2ZXJzaW9uIHRleHQgUFJJTUFSWSBLRVksXG4gIGluc2VydGVkX2F0IHRpbWVzdGFtcHR6IE5PVCBOVUxMIERFRkFVTFQgTk9XKClcbik7XG4tLSBJbml0aWFsIHN1cGFiYXNlX2Z1bmN0aW9ucyBtaWdyYXRpb25cbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCdpbml0aWFsJyk7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMuaG9va3MgZGVmaW5pdGlvblxuQ1JFQVRFIFRBQkxFIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyAoXG4gIGlkIGJpZ3NlcmlhbCBQUklNQVJZIEtFWSxcbiAgaG9va190YWJsZV9pZCBpbnRlZ2VyIE5PVCBOVUxMLFxuICBob29rX25hbWUgdGV4dCBOT1QgTlVMTCxcbiAgY3JlYXRlZF9hdCB0aW1lc3RhbXB0eiBOT1QgTlVMTCBERUZBVUxUIE5PVygpLFxuICByZXF1ZXN0X2lkIGJpZ2ludFxuKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfcmVxdWVzdF9pZF9pZHggT04gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIFVTSU5HIGJ0cmVlIChyZXF1ZXN0X2lkKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfaF90YWJsZV9pZF9oX25hbWVfaWR4IE9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyBVU0lORyBidHJlZSAoaG9va190YWJsZV9pZCwgaG9va19uYW1lKTtcbkNPTU1FTlQgT04gVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIElTICdTdXBhYmFzZSBGdW5jdGlvbnMgSG9va3M6IEF1ZGl0IHRyYWlsIGZvciB0cmlnZ2VyZWQgaG9va3MuJztcbkNSRUFURSBGVU5DVElPTiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KClcbiAgUkVUVVJOUyB0cmlnZ2VyXG4gIExBTkdVQUdFIHBscGdzcWxcbiAgQVMgJGZ1bmN0aW9uJFxuICBERUNMQVJFXG4gICAgcmVxdWVzdF9pZCBiaWdpbnQ7XG4gICAgcGF5bG9hZCBqc29uYjtcbiAgICB1cmwgdGV4dCA6PSBUR19BUkdWWzBdOjp0ZXh0O1xuICAgIG1ldGhvZCB0ZXh0IDo9IFRHX0FSR1ZbMV06OnRleHQ7XG4gICAgaGVhZGVycyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHBhcmFtcyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHRpbWVvdXRfbXMgaW50ZWdlciBERUZBVUxUIDEwMDA7XG4gIEJFR0lOXG4gICAgSUYgdXJsIElTIE5VTEwgT1IgdXJsID0gJ251bGwnIFRIRU5cbiAgICAgIFJBSVNFIEVYQ0VQVElPTiAndXJsIGFyZ3VtZW50IGlzIG1pc3NpbmcnO1xuICAgIEVORCBJRjtcblxuICAgIElGIG1ldGhvZCBJUyBOVUxMIE9SIG1ldGhvZCA9ICdudWxsJyBUSEVOXG4gICAgICBSQUlTRSBFWENFUFRJT04gJ21ldGhvZCBhcmd1bWVudCBpcyBtaXNzaW5nJztcbiAgICBFTkQgSUY7XG5cbiAgICBJRiBUR19BUkdWWzJdIElTIE5VTEwgT1IgVEdfQVJHVlsyXSA9ICdudWxsJyBUSEVOXG4gICAgICBoZWFkZXJzID0gJ3tcIkNvbnRlbnQtVHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIn0nOjpqc29uYjtcbiAgICBFTFNFXG4gICAgICBoZWFkZXJzID0gVEdfQVJHVlsyXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVlszXSBJUyBOVUxMIE9SIFRHX0FSR1ZbM10gPSAnbnVsbCcgVEhFTlxuICAgICAgcGFyYW1zID0gJ3t9Jzo6anNvbmI7XG4gICAgRUxTRVxuICAgICAgcGFyYW1zID0gVEdfQVJHVlszXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVls0XSBJUyBOVUxMIE9SIFRHX0FSR1ZbNF0gPSAnbnVsbCcgVEhFTlxuICAgICAgdGltZW91dF9tcyA9IDEwMDA7XG4gICAgRUxTRVxuICAgICAgdGltZW91dF9tcyA9IFRHX0FSR1ZbNF06OmludGVnZXI7XG4gICAgRU5EIElGO1xuXG4gICAgQ0FTRVxuICAgICAgV0hFTiBtZXRob2QgPSAnR0VUJyBUSEVOXG4gICAgICAgIFNFTEVDVCBodHRwX2dldCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9nZXQoXG4gICAgICAgICAgdXJsLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIFdIRU4gbWV0aG9kID0gJ1BPU1QnIFRIRU5cbiAgICAgICAgcGF5bG9hZCA9IGpzb25iX2J1aWxkX29iamVjdChcbiAgICAgICAgICAnb2xkX3JlY29yZCcsIE9MRCxcbiAgICAgICAgICAncmVjb3JkJywgTkVXLFxuICAgICAgICAgICd0eXBlJywgVEdfT1AsXG4gICAgICAgICAgJ3RhYmxlJywgVEdfVEFCTEVfTkFNRSxcbiAgICAgICAgICAnc2NoZW1hJywgVEdfVEFCTEVfU0NIRU1BXG4gICAgICAgICk7XG5cbiAgICAgICAgU0VMRUNUIGh0dHBfcG9zdCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9wb3N0KFxuICAgICAgICAgIHVybCxcbiAgICAgICAgICBwYXlsb2FkLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIEVMU0VcbiAgICAgICAgUkFJU0UgRVhDRVBUSU9OICdtZXRob2QgYXJndW1lbnQgJSBpcyBpbnZhbGlkJywgbWV0aG9kO1xuICAgIEVORCBDQVNFO1xuXG4gICAgSU5TRVJUIElOVE8gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzXG4gICAgICAoaG9va190YWJsZV9pZCwgaG9va19uYW1lLCByZXF1ZXN0X2lkKVxuICAgIFZBTFVFU1xuICAgICAgKFRHX1JFTElELCBUR19OQU1FLCByZXF1ZXN0X2lkKTtcblxuICAgIFJFVFVSTiBORVc7XG4gIEVORFxuJGZ1bmN0aW9uJDtcbi0tIFN1cGFiYXNlIHN1cGVyIGFkbWluXG5ET1xuJCRcbkJFR0lOXG4gIElGIE5PVCBFWElTVFMgKFxuICAgIFNFTEVDVCAxXG4gICAgRlJPTSBwZ19yb2xlc1xuICAgIFdIRVJFIHJvbG5hbWUgPSAnc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluJ1xuICApXG4gIFRIRU5cbiAgICBDUkVBVEUgVVNFUiBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gTk9JTkhFUklUIENSRUFURVJPTEUgTE9HSU4gTk9SRVBMSUNBVElPTjtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIFNDSEVNQSBzdXBhYmFzZV9mdW5jdGlvbnMgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuR1JBTlQgQUxMIFBSSVZJTEVHRVMgT04gQUxMIFRBQkxFUyBJTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIEFMTCBTRVFVRU5DRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5BTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBTRVQgc2VhcmNoX3BhdGggPSBcInN1cGFiYXNlX2Z1bmN0aW9uc1wiO1xuQUxURVIgdGFibGUgXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5taWdyYXRpb25zIE9XTkVSIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkFMVEVSIHRhYmxlIFwic3VwYWJhc2VfZnVuY3Rpb25zXCIuaG9va3MgT1dORVIgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuQUxURVIgZnVuY3Rpb24gXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5odHRwX3JlcXVlc3QoKSBPV05FUiBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5HUkFOVCBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gVE8gcG9zdGdyZXM7XG4tLSBSZW1vdmUgdW51c2VkIHN1cGFiYXNlX3BnX25ldF9hZG1pbiByb2xlXG5ET1xuJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX3JvbGVzXG4gICAgV0hFUkUgcm9sbmFtZSA9ICdzdXBhYmFzZV9wZ19uZXRfYWRtaW4nXG4gIClcbiAgVEhFTlxuICAgIFJFQVNTSUdOIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbiBUTyBzdXBhYmFzZV9hZG1pbjtcbiAgICBEUk9QIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbjtcbiAgICBEUk9QIFJPTEUgc3VwYWJhc2VfcGdfbmV0X2FkbWluO1xuICBFTkQgSUY7XG5FTkRcbiQkO1xuLS0gcGdfbmV0IGdyYW50cyB3aGVuIGV4dGVuc2lvbiBpcyBhbHJlYWR5IGVuYWJsZWRcbkRPXG4kJFxuQkVHSU5cbiAgSUYgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXh0ZW5zaW9uXG4gICAgV0hFUkUgZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbi0tIEV2ZW50IHRyaWdnZXIgZm9yIHBnX25ldFxuQ1JFQVRFIE9SIFJFUExBQ0UgRlVOQ1RJT04gZXh0ZW5zaW9ucy5ncmFudF9wZ19uZXRfYWNjZXNzKClcblJFVFVSTlMgZXZlbnRfdHJpZ2dlclxuTEFOR1VBR0UgcGxwZ3NxbFxuQVMgJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX2V2ZW50X3RyaWdnZXJfZGRsX2NvbW1hbmRzKCkgQVMgZXZcbiAgICBKT0lOIHBnX2V4dGVuc2lvbiBBUyBleHRcbiAgICBPTiBldi5vYmppZCA9IGV4dC5vaWRcbiAgICBXSEVSRSBleHQuZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EO1xuJCQ7XG5DT01NRU5UIE9OIEZVTkNUSU9OIGV4dGVuc2lvbnMuZ3JhbnRfcGdfbmV0X2FjY2VzcyBJUyAnR3JhbnRzIGFjY2VzcyB0byBwZ19uZXQnO1xuRE9cbiQkXG5CRUdJTlxuICBJRiBOT1QgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXZlbnRfdHJpZ2dlclxuICAgIFdIRVJFIGV2dG5hbWUgPSAnaXNzdWVfcGdfbmV0X2FjY2VzcydcbiAgKSBUSEVOXG4gICAgQ1JFQVRFIEVWRU5UIFRSSUdHRVIgaXNzdWVfcGdfbmV0X2FjY2VzcyBPTiBkZGxfY29tbWFuZF9lbmQgV0hFTiBUQUcgSU4gKCdDUkVBVEUgRVhURU5TSU9OJylcbiAgICBFWEVDVVRFIFBST0NFRFVSRSBleHRlbnNpb25zLmdyYW50X3BnX25ldF9hY2Nlc3MoKTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCcyMDIxMDgwOTE4MzQyM191cGRhdGVfZ3JhbnRzJyk7XG5BTFRFUiBmdW5jdGlvbiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KCkgU0VDVVJJVFkgREVGSU5FUjtcbkFMVEVSIGZ1bmN0aW9uIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBTRVQgc2VhcmNoX3BhdGggPSBzdXBhYmFzZV9mdW5jdGlvbnM7XG5SRVZPS0UgQUxMIE9OIEZVTkNUSU9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBGUk9NIFBVQkxJQztcbkdSQU5UIEVYRUNVVEUgT04gRlVOQ1RJT04gc3VwYWJhc2VfZnVuY3Rpb25zLmh0dHBfcmVxdWVzdCgpIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5DT01NSVQ7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcm9sZXMuc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk5LXJvbGVzLnNxbAogICAgICAgIGNvbnRlbnQ6ICItLSBOT1RFOiBjaGFuZ2UgdG8geW91ciBvd24gcGFzc3dvcmRzIGZvciBwcm9kdWN0aW9uIGVudmlyb25tZW50c1xuIFxcc2V0IHBncGFzcyBgZWNobyBcIiRQT1NUR1JFU19QQVNTV09SRFwiYFxuXG4gQUxURVIgVVNFUiBhdXRoZW50aWNhdG9yIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgcGdib3VuY2VyIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgc3VwYWJhc2VfYXV0aF9hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX3N0b3JhZ2VfYWRtaW4gV0lUSCBQQVNTV09SRCA6J3BncGFzcyc7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvand0LnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL2luaXQtc2NyaXB0cy85OS1qd3Quc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IGp3dF9zZWNyZXQgYGVjaG8gXCIkSldUX1NFQ1JFVFwiYFxuXFxzZXQgand0X2V4cCBgZWNobyBcIiRKV1RfRVhQXCJgXG5cXHNldCBkYl9uYW1lIGBlY2hvIFwiJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9XCJgXG5cbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3Rfc2VjcmV0XCIgVE8gOidqd3Rfc2VjcmV0JztcbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3RfZXhwXCIgVE8gOidqd3RfZXhwJztcbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9sb2dzLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktbG9ncy5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwic3VwYWJhc2VfYWRtaW5cImBcblxcYyBfc3VwYWJhc2VcbmNyZWF0ZSBzY2hlbWEgaWYgbm90IGV4aXN0cyBfYW5hbHl0aWNzO1xuYWx0ZXIgc2NoZW1hIF9hbmFseXRpY3Mgb3duZXIgdG8gOnBndXNlcjtcbiIKICAgICAgLSAnc3VwYWJhc2UtZGItY29uZmlnOi9ldGMvcG9zdGdyZXNxbC1jdXN0b20nCiAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9sb2dmbGFyZToxLjQuMCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo0MDAwL2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1kYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTE9HRkxBUkVfTk9ERV9IT1NUPTEyNy4wLjAuMQogICAgICAtIERCX1VTRVJOQU1FPXN1cGFiYXNlX2FkbWluCiAgICAgIC0gREJfREFUQUJBU0U9X3N1cGFiYXNlCiAgICAgIC0gJ0RCX0hPU1ROQU1FPSR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifScKICAgICAgLSAnREJfUE9SVD0ke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9JwogICAgICAtICdEQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtIERCX1NDSEVNQT1fYW5hbHl0aWNzCiAgICAgIC0gJ0xPR0ZMQVJFX0FQSV9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX0xPR0ZMQVJFfScKICAgICAgLSBMT0dGTEFSRV9TSU5HTEVfVEVOQU5UPXRydWUKICAgICAgLSBMT0dGTEFSRV9TSU5HTEVfVEVOQU5UX01PREU9dHJ1ZQogICAgICAtIExPR0ZMQVJFX1NVUEFCQVNFX01PREU9dHJ1ZQogICAgICAtIExPR0ZMQVJFX01JTl9DTFVTVEVSX1NJWkU9MQogICAgICAtICdQT1NUR1JFU19CQUNLRU5EX1VSTD1wb3N0Z3Jlc3FsOi8vc3VwYWJhc2VfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS9fc3VwYWJhc2UnCiAgICAgIC0gUE9TVEdSRVNfQkFDS0VORF9TQ0hFTUE9X2FuYWx5dGljcwogICAgICAtIExPR0ZMQVJFX0ZFQVRVUkVfRkxBR19PVkVSUklERT1tdWx0aWJhY2tlbmQ9dHJ1ZQogIHN1cGFiYXNlLXZlY3RvcjoKICAgIGltYWdlOiAndGltYmVyaW8vdmVjdG9yOjAuMjguMS1hbHBpbmUnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovL3N1cGFiYXNlLXZlY3Rvcjo5MDAxL2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvbG9ncy92ZWN0b3IueW1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL3ZlY3Rvci92ZWN0b3IueW1sCiAgICAgICAgcmVhZF9vbmx5OiB0cnVlCiAgICAgICAgY29udGVudDogImFwaTpcbiAgZW5hYmxlZDogdHJ1ZVxuICBhZGRyZXNzOiAwLjAuMC4wOjkwMDFcblxuc291cmNlczpcbiAgZG9ja2VyX2hvc3Q6XG4gICAgdHlwZTogZG9ja2VyX2xvZ3NcbiAgICBleGNsdWRlX2NvbnRhaW5lcnM6XG4gICAgICAtIHN1cGFiYXNlLXZlY3RvclxuXG50cmFuc2Zvcm1zOlxuICBwcm9qZWN0X2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIGRvY2tlcl9ob3N0XG4gICAgc291cmNlOiB8LVxuICAgICAgLnByb2plY3QgPSBcImRlZmF1bHRcIlxuICAgICAgLmV2ZW50X21lc3NhZ2UgPSBkZWwoLm1lc3NhZ2UpXG4gICAgICAuYXBwbmFtZSA9IGRlbCguY29udGFpbmVyX25hbWUpXG4gICAgICBkZWwoLmNvbnRhaW5lcl9jcmVhdGVkX2F0KVxuICAgICAgZGVsKC5jb250YWluZXJfaWQpXG4gICAgICBkZWwoLnNvdXJjZV90eXBlKVxuICAgICAgZGVsKC5zdHJlYW0pXG4gICAgICBkZWwoLmxhYmVsKVxuICAgICAgZGVsKC5pbWFnZSlcbiAgICAgIGRlbCguaG9zdClcbiAgICAgIGRlbCguc3RyZWFtKVxuICByb3V0ZXI6XG4gICAgdHlwZTogcm91dGVcbiAgICBpbnB1dHM6XG4gICAgICAtIHByb2plY3RfbG9nc1xuICAgIHJvdXRlOlxuICAgICAga29uZzogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInN1cGFiYXNlLWtvbmdcIiknXG4gICAgICBhdXRoOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2UtYXV0aFwiKSdcbiAgICAgIHJlc3Q6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1yZXN0XCIpJ1xuICAgICAgcmVhbHRpbWU6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJyZWFsdGltZS1kZXZcIiknXG4gICAgICBzdG9yYWdlOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2Utc3RvcmFnZVwiKSdcbiAgICAgIGZ1bmN0aW9uczogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInN1cGFiYXNlLWZ1bmN0aW9uc1wiKSdcbiAgICAgIGRiOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2UtZGJcIiknXG4gICMgSWdub3JlcyBub24gbmdpbnggZXJyb3JzIHNpbmNlIHRoZXkgYXJlIHJlbGF0ZWQgd2l0aCBrb25nIGJvb3RpbmcgdXBcbiAga29uZ19sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIua29uZ1xuICAgIHNvdXJjZTogfC1cbiAgICAgIHJlcSwgZXJyID0gcGFyc2VfbmdpbnhfbG9nKC5ldmVudF9tZXNzYWdlLCBcImNvbWJpbmVkXCIpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLnRpbWVzdGFtcCA9IHJlcS50aW1lc3RhbXBcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5oZWFkZXJzLnJlZmVyZXIgPSByZXEucmVmZXJlclxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LmhlYWRlcnMudXNlcl9hZ2VudCA9IHJlcS5hZ2VudFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LmhlYWRlcnMuY2ZfY29ubmVjdGluZ19pcCA9IHJlcS5jbGllbnRcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSByZXEubWV0aG9kXG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucGF0aCA9IHJlcS5wYXRoXG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucHJvdG9jb2wgPSByZXEucHJvdG9jb2xcbiAgICAgICAgICAubWV0YWRhdGEucmVzcG9uc2Uuc3RhdHVzX2NvZGUgPSByZXEuc3RhdHVzXG4gICAgICB9XG4gICAgICBpZiBlcnIgIT0gbnVsbCB7XG4gICAgICAgIGFib3J0XG4gICAgICB9XG4gICMgSWdub3JlcyBub24gbmdpbnggZXJyb3JzIHNpbmNlIHRoZXkgYXJlIHJlbGF0ZWQgd2l0aCBrb25nIGJvb3RpbmcgdXBcbiAga29uZ19lcnI6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5rb25nXG4gICAgc291cmNlOiB8LVxuICAgICAgLm1ldGFkYXRhLnJlcXVlc3QubWV0aG9kID0gXCJHRVRcIlxuICAgICAgLm1ldGFkYXRhLnJlc3BvbnNlLnN0YXR1c19jb2RlID0gMjAwXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX25naW54X2xvZyguZXZlbnRfbWVzc2FnZSwgXCJlcnJvclwiKVxuICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgIC50aW1lc3RhbXAgPSBwYXJzZWQudGltZXN0YW1wXG4gICAgICAgICAgLnNldmVyaXR5ID0gcGFyc2VkLnNldmVyaXR5XG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaG9zdCA9IHBhcnNlZC5ob3N0XG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy5jZl9jb25uZWN0aW5nX2lwID0gcGFyc2VkLmNsaWVudFxuICAgICAgICAgIHVybCwgZXJyID0gc3BsaXQocGFyc2VkLnJlcXVlc3QsIFwiIFwiKVxuICAgICAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QubWV0aG9kID0gdXJsWzBdXG4gICAgICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LnBhdGggPSB1cmxbMV1cbiAgICAgICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucHJvdG9jb2wgPSB1cmxbMl1cbiAgICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiBlcnIgIT0gbnVsbCB7XG4gICAgICAgIGFib3J0XG4gICAgICB9XG4gICMgR290cnVlIGxvZ3MgYXJlIHN0cnVjdHVyZWQganNvbiBzdHJpbmdzIHdoaWNoIGZyb250ZW5kIHBhcnNlcyBkaXJlY3RseS4gQnV0IHdlIGtlZXAgbWV0YWRhdGEgZm9yIGNvbnNpc3RlbmN5LlxuICBhdXRoX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5hdXRoXG4gICAgc291cmNlOiB8LVxuICAgICAgcGFyc2VkLCBlcnIgPSBwYXJzZV9qc29uKC5ldmVudF9tZXNzYWdlKVxuICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgIC5tZXRhZGF0YS50aW1lc3RhbXAgPSBwYXJzZWQudGltZVxuICAgICAgICAgIC5tZXRhZGF0YSA9IG1lcmdlISgubWV0YWRhdGEsIHBhcnNlZClcbiAgICAgIH1cbiAgIyBQb3N0Z1JFU1QgbG9ncyBhcmUgc3RydWN0dXJlZCBzbyB3ZSBzZXBhcmF0ZSB0aW1lc3RhbXAgZnJvbSBtZXNzYWdlIHVzaW5nIHJlZ2V4XG4gIHJlc3RfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLnJlc3RcbiAgICBzb3VyY2U6IHwtXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX3JlZ2V4KC5ldmVudF9tZXNzYWdlLCByJ14oP1A8dGltZT4uKik6ICg/UDxtc2c+LiopJCcpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLmV2ZW50X21lc3NhZ2UgPSBwYXJzZWQubXNnXG4gICAgICAgICAgLnRpbWVzdGFtcCA9IHRvX3RpbWVzdGFtcCEocGFyc2VkLnRpbWUpXG4gICAgICAgICAgLm1ldGFkYXRhLmhvc3QgPSAucHJvamVjdFxuICAgICAgfVxuICAjIFJlYWx0aW1lIGxvZ3MgYXJlIHN0cnVjdHVyZWQgc28gd2UgcGFyc2UgdGhlIHNldmVyaXR5IGxldmVsIHVzaW5nIHJlZ2V4IChpZ25vcmUgdGltZSBiZWNhdXNlIGl0IGhhcyBubyBkYXRlKVxuICByZWFsdGltZV9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIucmVhbHRpbWVcbiAgICBzb3VyY2U6IHwtXG4gICAgICAubWV0YWRhdGEucHJvamVjdCA9IGRlbCgucHJvamVjdClcbiAgICAgIC5tZXRhZGF0YS5leHRlcm5hbF9pZCA9IC5tZXRhZGF0YS5wcm9qZWN0XG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX3JlZ2V4KC5ldmVudF9tZXNzYWdlLCByJ14oP1A8dGltZT5cXGQrOlxcZCs6XFxkK1xcLlxcZCspIFxcWyg/UDxsZXZlbD5cXHcrKVxcXSAoP1A8bXNnPi4qKSQnKVxuICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgIC5ldmVudF9tZXNzYWdlID0gcGFyc2VkLm1zZ1xuICAgICAgICAgIC5tZXRhZGF0YS5sZXZlbCA9IHBhcnNlZC5sZXZlbFxuICAgICAgfVxuICAjIFN0b3JhZ2UgbG9ncyBtYXkgY29udGFpbiBqc29uIG9iamVjdHMgc28gd2UgcGFyc2UgdGhlbSBmb3IgY29tcGxldGVuZXNzXG4gIHN0b3JhZ2VfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLnN0b3JhZ2VcbiAgICBzb3VyY2U6IHwtXG4gICAgICAubWV0YWRhdGEucHJvamVjdCA9IGRlbCgucHJvamVjdClcbiAgICAgIC5tZXRhZGF0YS50ZW5hbnRJZCA9IC5tZXRhZGF0YS5wcm9qZWN0XG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX2pzb24oLmV2ZW50X21lc3NhZ2UpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLmV2ZW50X21lc3NhZ2UgPSBwYXJzZWQubXNnXG4gICAgICAgICAgLm1ldGFkYXRhLmxldmVsID0gcGFyc2VkLmxldmVsXG4gICAgICAgICAgLm1ldGFkYXRhLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lXG4gICAgICAgICAgLm1ldGFkYXRhLmNvbnRleHRbMF0uaG9zdCA9IHBhcnNlZC5ob3N0bmFtZVxuICAgICAgICAgIC5tZXRhZGF0YS5jb250ZXh0WzBdLnBpZCA9IHBhcnNlZC5waWRcbiAgICAgIH1cbiAgIyBQb3N0Z3JlcyBsb2dzIHNvbWUgbWVzc2FnZXMgdG8gc3RkZXJyIHdoaWNoIHdlIG1hcCB0byB3YXJuaW5nIHNldmVyaXR5IGxldmVsXG4gIGRiX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5kYlxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5ob3N0ID0gXCJkYi1kZWZhdWx0XCJcbiAgICAgIC5tZXRhZGF0YS5wYXJzZWQudGltZXN0YW1wID0gLnRpbWVzdGFtcFxuXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX3JlZ2V4KC5ldmVudF9tZXNzYWdlLCByJy4qKD9QPGxldmVsPklORk98Tk9USUNFfFdBUk5JTkd8RVJST1J8TE9HfEZBVEFMfFBBTklDPyk6LionLCBudW1lcmljX2dyb3VwczogdHJ1ZSlcblxuICAgICAgaWYgZXJyICE9IG51bGwgfHwgcGFyc2VkID09IG51bGwge1xuICAgICAgICAubWV0YWRhdGEucGFyc2VkLmVycm9yX3NldmVyaXR5ID0gXCJpbmZvXCJcbiAgICAgIH1cbiAgICAgIGlmIHBhcnNlZCAhPSBudWxsIHtcbiAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSBwYXJzZWQubGV2ZWxcbiAgICAgIH1cbiAgICAgIGlmIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPT0gXCJpbmZvXCIge1xuICAgICAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSBcImxvZ1wiXG4gICAgICB9XG4gICAgICAubWV0YWRhdGEucGFyc2VkLmVycm9yX3NldmVyaXR5ID0gdXBjYXNlISgubWV0YWRhdGEucGFyc2VkLmVycm9yX3NldmVyaXR5KVxuXG5zaW5rczpcbiAgbG9nZmxhcmVfYXV0aDpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIGF1dGhfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1nb3RydWUubG9ncy5wcm9kJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9yZWFsdGltZTpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIHJlYWx0aW1lX2xvZ3NcbiAgICBlbmNvZGluZzpcbiAgICAgIGNvZGVjOiAnanNvbidcbiAgICBtZXRob2Q6ICdwb3N0J1xuICAgIHJlcXVlc3Q6XG4gICAgICByZXRyeV9tYXhfZHVyYXRpb25fc2VjczogMTBcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvYXBpL2xvZ3M/c291cmNlX25hbWU9cmVhbHRpbWUubG9ncy5wcm9kJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9yZXN0OlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gcmVzdF9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPXBvc3RnUkVTVC5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX2RiOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gZGJfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgICMgV2UgbXVzdCByb3V0ZSB0aGUgc2luayB0aHJvdWdoIGtvbmcgYmVjYXVzZSBpbmdlc3RpbmcgbG9ncyBiZWZvcmUgbG9nZmxhcmUgaXMgZnVsbHkgaW5pdGlhbGlzZWQgd2lsbFxuICAgICMgbGVhZCB0byBicm9rZW4gcXVlcmllcyBmcm9tIHN0dWRpby4gVGhpcyB3b3JrcyBieSB0aGUgYXNzdW1wdGlvbiB0aGF0IGNvbnRhaW5lcnMgYXJlIHN0YXJ0ZWQgaW4gdGhlXG4gICAgIyBmb2xsb3dpbmcgb3JkZXI6IHZlY3RvciA+IGRiID4gbG9nZmxhcmUgPiBrb25nXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWtvbmc6ODAwMC9hbmFseXRpY3MvdjEvYXBpL2xvZ3M/c291cmNlX25hbWU9cG9zdGdyZXMubG9ncyZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfZnVuY3Rpb25zOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmZ1bmN0aW9uc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1kZW5vLXJlbGF5LWxvZ3MmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3N0b3JhZ2U6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSBzdG9yYWdlX2xvZ3NcbiAgICBlbmNvZGluZzpcbiAgICAgIGNvZGVjOiAnanNvbidcbiAgICBtZXRob2Q6ICdwb3N0J1xuICAgIHJlcXVlc3Q6XG4gICAgICByZXRyeV9tYXhfZHVyYXRpb25fc2VjczogMTBcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvYXBpL2xvZ3M/c291cmNlX25hbWU9c3RvcmFnZS5sb2dzLnByb2QuMiZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfa29uZzpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIGtvbmdfbG9nc1xuICAgICAgLSBrb25nX2VyclxuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1jbG91ZGZsYXJlLmxvZ3MucHJvZCZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiIKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2s6cm8nCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTE9HRkxBUkVfQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTE9HRkxBUkV9JwogICAgY29tbWFuZDoKICAgICAgLSAnLS1jb25maWcnCiAgICAgIC0gZXRjL3ZlY3Rvci92ZWN0b3IueW1sCiAgc3VwYWJhc2UtcmVzdDoKICAgIGltYWdlOiAncG9zdGdyZXN0L3Bvc3RncmVzdDp2MTIuMi4wJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUEdSU1RfREJfVVJJPXBvc3RncmVzOi8vYXV0aGVudGljYXRvcjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUEdSU1RfREJfU0NIRU1BUz0ke1BHUlNUX0RCX1NDSEVNQVM6LXB1YmxpYyxzdG9yYWdlLGdyYXBocWxfcHVibGljfScKICAgICAgLSBQR1JTVF9EQl9BTk9OX1JPTEU9YW5vbgogICAgICAtICdQR1JTVF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIFBHUlNUX0RCX1VTRV9MRUdBQ1lfR1VDUz1mYWxzZQogICAgICAtICdQR1JTVF9BUFBfU0VUVElOR1NfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnUEdSU1RfQVBQX1NFVFRJTkdTX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgIGNvbW1hbmQ6IHBvc3RncmVzdAogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgc3VwYWJhc2UtYXV0aDoKICAgIGltYWdlOiAnc3VwYWJhc2UvZ290cnVlOnYyLjE1OC4xJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo5OTk5L2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPVFJVRV9BUElfSE9TVD0wLjAuMC4wCiAgICAgIC0gR09UUlVFX0FQSV9QT1JUPTk5OTkKICAgICAgLSAnQVBJX0VYVEVSTkFMX1VSTD0ke0FQSV9FWFRFUk5BTF9VUkw6LWh0dHA6Ly9zdXBhYmFzZS1rb25nOjgwMDB9JwogICAgICAtIEdPVFJVRV9EQl9EUklWRVI9cG9zdGdyZXMKICAgICAgLSAnR09UUlVFX0RCX0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovL3N1cGFiYXNlX2F1dGhfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0dPVFJVRV9TSVRFX1VSTD0ke1NFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkd9JwogICAgICAtICdHT1RSVUVfVVJJX0FMTE9XX0xJU1Q9JHtBRERJVElPTkFMX1JFRElSRUNUX1VSTFN9JwogICAgICAtICdHT1RSVUVfRElTQUJMRV9TSUdOVVA9JHtESVNBQkxFX1NJR05VUDotZmFsc2V9JwogICAgICAtIEdPVFJVRV9KV1RfQURNSU5fUk9MRVM9c2VydmljZV9yb2xlCiAgICAgIC0gR09UUlVFX0pXVF9BVUQ9YXV0aGVudGljYXRlZAogICAgICAtIEdPVFJVRV9KV1RfREVGQVVMVF9HUk9VUF9OQU1FPWF1dGhlbnRpY2F0ZWQKICAgICAgLSAnR09UUlVFX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgICAgLSAnR09UUlVFX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0dPVFJVRV9FWFRFUk5BTF9FTUFJTF9FTkFCTEVEPSR7RU5BQkxFX0VNQUlMX1NJR05VUDotdHJ1ZX0nCiAgICAgIC0gJ0dPVFJVRV9FWFRFUk5BTF9BTk9OWU1PVVNfVVNFUlNfRU5BQkxFRD0ke0VOQUJMRV9BTk9OWU1PVVNfVVNFUlM6LWZhbHNlfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9BVVRPQ09ORklSTT0ke0VOQUJMRV9FTUFJTF9BVVRPQ09ORklSTTotZmFsc2V9JwogICAgICAtICdHT1RSVUVfU01UUF9BRE1JTl9FTUFJTD0ke1NNVFBfQURNSU5fRU1BSUx9JwogICAgICAtICdHT1RSVUVfU01UUF9IT1NUPSR7U01UUF9IT1NUfScKICAgICAgLSAnR09UUlVFX1NNVFBfUE9SVD0ke1NNVFBfUE9SVDotNTg3fScKICAgICAgLSAnR09UUlVFX1NNVFBfVVNFUj0ke1NNVFBfVVNFUn0nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX1BBU1M9JHtTTVRQX1BBU1N9JwogICAgICAtICdHT1RSVUVfU01UUF9TRU5ERVJfTkFNRT0ke1NNVFBfU0VOREVSX05BTUV9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1VSTFBBVEhTX0lOVklURT0ke01BSUxFUl9VUkxQQVRIU19JTlZJVEU6LS9hdXRoL3YxL3ZlcmlmeX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVVJMUEFUSFNfQ09ORklSTUFUSU9OPSR7TUFJTEVSX1VSTFBBVEhTX0NPTkZJUk1BVElPTjotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19SRUNPVkVSWT0ke01BSUxFUl9VUkxQQVRIU19SRUNPVkVSWTotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19FTUFJTF9DSEFOR0U9JHtNQUlMRVJfVVJMUEFUSFNfRU1BSUxfQ0hBTkdFOi0vYXV0aC92MS92ZXJpZnl9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1RFTVBMQVRFU19JTlZJVEU9JHtNQUlMRVJfVEVNUExBVEVTX0lOVklURX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0NPTkZJUk1BVElPTj0ke01BSUxFUl9URU1QTEFURVNfQ09ORklSTUFUSU9OfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfUkVDT1ZFUlk9JHtNQUlMRVJfVEVNUExBVEVTX1JFQ09WRVJZfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfTUFHSUNfTElOSz0ke01BSUxFUl9URU1QTEFURVNfTUFHSUNfTElOS30nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0VNQUlMX0NIQU5HRT0ke01BSUxFUl9URU1QTEFURVNfRU1BSUxfQ0hBTkdFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19DT05GSVJNQVRJT049JHtNQUlMRVJfU1VCSkVDVFNfQ09ORklSTUFUSU9OfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19SRUNPVkVSWT0ke01BSUxFUl9TVUJKRUNUU19SRUNPVkVSWX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfU1VCSkVDVFNfTUFHSUNfTElOSz0ke01BSUxFUl9TVUJKRUNUU19NQUdJQ19MSU5LfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19FTUFJTF9DSEFOR0U9JHtNQUlMRVJfU1VCSkVDVFNfRU1BSUxfQ0hBTkdFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19JTlZJVEU9JHtNQUlMRVJfU1VCSkVDVFNfSU5WSVRFfScKICAgICAgLSAnR09UUlVFX0VYVEVSTkFMX1BIT05FX0VOQUJMRUQ9JHtFTkFCTEVfUEhPTkVfU0lHTlVQOi10cnVlfScKICAgICAgLSAnR09UUlVFX1NNU19BVVRPQ09ORklSTT0ke0VOQUJMRV9QSE9ORV9BVVRPQ09ORklSTTotdHJ1ZX0nCiAgcmVhbHRpbWUtZGV2OgogICAgaW1hZ2U6ICdzdXBhYmFzZS9yZWFsdGltZTp2Mi4zMC4zNCcKICAgIGNvbnRhaW5lcl9uYW1lOiByZWFsdGltZS1kZXYuc3VwYWJhc2UtcmVhbHRpbWUKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctc1NmTCcKICAgICAgICAtICctLWhlYWQnCiAgICAgICAgLSAnLW8nCiAgICAgICAgLSAvZGV2L251bGwKICAgICAgICAtICctSCcKICAgICAgICAtICdBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtTRVJWSUNFX1NVUEFCQVNFQU5PTl9LRVl9JwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NDAwMC9hcGkvdGVuYW50cy9yZWFsdGltZS1kZXYvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9SVD00MDAwCiAgICAgIC0gJ0RCX0hPU1Q9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdEQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gREJfVVNFUj1zdXBhYmFzZV9hZG1pbgogICAgICAtICdEQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdEQl9OQU1FPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnREJfQUZURVJfQ09OTkVDVF9RVUVSWT1TRVQgc2VhcmNoX3BhdGggVE8gX3JlYWx0aW1lJwogICAgICAtIERCX0VOQ19LRVk9c3VwYWJhc2VyZWFsdGltZQogICAgICAtICdBUElfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSBGTFlfQUxMT0NfSUQ9Zmx5MTIzCiAgICAgIC0gRkxZX0FQUF9OQU1FPXJlYWx0aW1lCiAgICAgIC0gJ1NFQ1JFVF9LRVlfQkFTRT0ke1NFQ1JFVF9QQVNTV09SRF9SRUFMVElNRX0nCiAgICAgIC0gJ0VSTF9BRkxBR1M9LXByb3RvX2Rpc3QgaW5ldF90Y3AnCiAgICAgIC0gRU5BQkxFX1RBSUxTQ0FMRT1mYWxzZQogICAgICAtICJETlNfTk9ERVM9JyciCiAgICAgIC0gUkxJTUlUX05PRklMRT0xMDAwMAogICAgICAtIEFQUF9OQU1FPXJlYWx0aW1lCiAgICAgIC0gU0VFRF9TRUxGX0hPU1Q9dHJ1ZQogICAgY29tbWFuZDogInNoIC1jIFwiL2FwcC9iaW4vbWlncmF0ZSAmJiAvYXBwL2Jpbi9yZWFsdGltZSBldmFsICdSZWFsdGltZS5SZWxlYXNlLnNlZWRzKFJlYWx0aW1lLlJlcG8pJyAmJiAvYXBwL2Jpbi9zZXJ2ZXJcIlxuIgogIHN1cGFiYXNlLW1pbmlvOgogICAgaW1hZ2U6IG1pbmlvL21pbmlvCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgY29tbWFuZDogJ3NlcnZlciAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiIC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdzbGVlcCA1ICYmIGV4aXQgMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiA1CiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvc3RvcmFnZTovZGF0YScKICBtaW5pby1jcmVhdGVidWNrZXQ6CiAgICBpbWFnZTogbWluaW8vbWMKICAgIHJlc3RhcnQ6ICdubycKICAgIGVudmlyb25tZW50OgogICAgICAtICdNSU5JT19ST09UX1VTRVI9JHtTRVJWSUNFX1VTRVJfTUlOSU99JwogICAgICAtICdNSU5JT19ST09UX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NSU5JT30nCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1taW5pbzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW50cnlwb2ludDoKICAgICAgLSAvZW50cnlwb2ludC5zaAogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZW50cnlwb2ludC5zaAogICAgICAgIHRhcmdldDogL2VudHJ5cG9pbnQuc2gKICAgICAgICBjb250ZW50OiAiIyEvYmluL3NoXG4vdXNyL2Jpbi9tYyBhbGlhcyBzZXQgc3VwYWJhc2UtbWluaW8gaHR0cDovL3N1cGFiYXNlLW1pbmlvOjkwMDAgJHtNSU5JT19ST09UX1VTRVJ9ICR7TUlOSU9fUk9PVF9QQVNTV09SRH07XG4vdXNyL2Jpbi9tYyBtYiAtLWlnbm9yZS1leGlzdGluZyBzdXBhYmFzZS1taW5pby9zdHViO1xuZXhpdCAwXG4iCiAgc3VwYWJhc2Utc3RvcmFnZToKICAgIGltYWdlOiAnc3VwYWJhc2Uvc3RvcmFnZS1hcGk6djEuMTAuMScKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLXJlc3Q6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX3N0YXJ0ZWQKICAgICAgaW1ncHJveHk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX3N0YXJ0ZWQKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjUwMDAvc3RhdHVzJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVkVSX1BPUlQ9NTAwMAogICAgICAtIFNFUlZFUl9SRUdJT049bG9jYWwKICAgICAgLSBNVUxUSV9URU5BTlQ9ZmFsc2UKICAgICAgLSAnQVVUSF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly9zdXBhYmFzZV9zdG9yYWdlX2FkbWluOiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AJHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9OiR7UE9TVEdSRVNfUE9SVDotNTQzMn0vJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtIERCX0lOU1RBTExfUk9MRVM9ZmFsc2UKICAgICAgLSBTVE9SQUdFX0JBQ0tFTkQ9czMKICAgICAgLSBTVE9SQUdFX1MzX0JVQ0tFVD1zdHViCiAgICAgIC0gJ1NUT1JBR0VfUzNfRU5EUE9JTlQ9aHR0cDovL3N1cGFiYXNlLW1pbmlvOjkwMDAnCiAgICAgIC0gU1RPUkFHRV9TM19GT1JDRV9QQVRIX1NUWUxFPXRydWUKICAgICAgLSBTVE9SQUdFX1MzX1JFR0lPTj11cy1lYXN0LTEKICAgICAgLSAnQVdTX0FDQ0VTU19LRVlfSUQ9JHtTRVJWSUNFX1VTRVJfTUlOSU99JwogICAgICAtICdBV1NfU0VDUkVUX0FDQ0VTU19LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX01JTklPfScKICAgICAgLSBVUExPQURfRklMRV9TSVpFX0xJTUlUPTUyNDI4ODAwMAogICAgICAtIFVQTE9BRF9GSUxFX1NJWkVfTElNSVRfU1RBTkRBUkQ9NTI0Mjg4MDAwCiAgICAgIC0gVVBMT0FEX1NJR05FRF9VUkxfRVhQSVJBVElPTl9USU1FPTEyMAogICAgICAtIFRVU19VUkxfUEFUSD0vdXBsb2FkL3Jlc3VtYWJsZQogICAgICAtIFRVU19NQVhfU0laRT0zNjAwMDAwCiAgICAgIC0gSU1BR0VfVFJBTlNGT1JNQVRJT05fRU5BQkxFRD10cnVlCiAgICAgIC0gJ0lNR1BST1hZX1VSTD1odHRwOi8vaW1ncHJveHk6ODA4MCcKICAgICAgLSBJTUdQUk9YWV9SRVFVRVNUX1RJTUVPVVQ9MTUKICAgICAgLSBEQVRBQkFTRV9TRUFSQ0hfUEFUSD1zdG9yYWdlCiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvc3RvcmFnZTovdmFyL2xpYi9zdG9yYWdlJwogIGltZ3Byb3h5OgogICAgaW1hZ2U6ICdkYXJ0aHNpbS9pbWdwcm94eTp2My44LjAnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gaW1ncHJveHkKICAgICAgICAtIGhlYWx0aAogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSU1HUFJPWFlfTE9DQUxfRklMRVNZU1RFTV9ST09UPS8KICAgICAgLSBJTUdQUk9YWV9VU0VfRVRBRz10cnVlCiAgICAgIC0gJ0lNR1BST1hZX0VOQUJMRV9XRUJQX0RFVEVDVElPTj0ke0lNR1BST1hZX0VOQUJMRV9XRUJQX0RFVEVDVElPTjotdHJ1ZX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvc3RvcmFnZTovdmFyL2xpYi9zdG9yYWdlJwogIHN1cGFiYXNlLW1ldGE6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3Bvc3RncmVzLW1ldGE6djAuODMuMicKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEdfTUVUQV9QT1JUPTgwODAKICAgICAgLSAnUEdfTUVUQV9EQl9IT1NUPSR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifScKICAgICAgLSAnUEdfTUVUQV9EQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ1BHX01FVEFfREJfTkFNRT0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gUEdfTUVUQV9EQl9VU0VSPXN1cGFiYXNlX2FkbWluCiAgICAgIC0gJ1BHX01FVEFfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICBzdXBhYmFzZS1lZGdlLWZ1bmN0aW9uczoKICAgIGltYWdlOiAnc3VwYWJhc2UvZWRnZS1ydW50aW1lOnYxLjU4LjMnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSAnRWRnZSBGdW5jdGlvbnMgaXMgaGVhbHRoeScKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdTVVBBQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fU1VQQUJBU0VLT05HfScKICAgICAgLSAnU1VQQUJBU0VfQU5PTl9LRVk9JHtTRVJWSUNFX1NVUEFCQVNFQU5PTl9LRVl9JwogICAgICAtICdTVVBBQkFTRV9TRVJWSUNFX1JPTEVfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnU1VQQUJBU0VfREJfVVJMPXBvc3RncmVzcWw6Ly9wb3N0Z3Jlczoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnVkVSSUZZX0pXVD0ke0ZVTkNUSU9OU19WRVJJRllfSldUOi1mYWxzZX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvZnVuY3Rpb25zOi9ob21lL2Rlbm8vZnVuY3Rpb25zJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2Z1bmN0aW9ucy9tYWluL2luZGV4LnRzCiAgICAgICAgdGFyZ2V0OiAvaG9tZS9kZW5vL2Z1bmN0aW9ucy9tYWluL2luZGV4LnRzCiAgICAgICAgY29udGVudDogImltcG9ydCB7IHNlcnZlIH0gZnJvbSAnaHR0cHM6Ly9kZW5vLmxhbmQvc3RkQDAuMTMxLjAvaHR0cC9zZXJ2ZXIudHMnXG5pbXBvcnQgKiBhcyBqb3NlIGZyb20gJ2h0dHBzOi8vZGVuby5sYW5kL3gvam9zZUB2NC4xNC40L2luZGV4LnRzJ1xuXG5jb25zb2xlLmxvZygnbWFpbiBmdW5jdGlvbiBzdGFydGVkJylcblxuY29uc3QgSldUX1NFQ1JFVCA9IERlbm8uZW52LmdldCgnSldUX1NFQ1JFVCcpXG5jb25zdCBWRVJJRllfSldUID0gRGVuby5lbnYuZ2V0KCdWRVJJRllfSldUJykgPT09ICd0cnVlJ1xuXG5mdW5jdGlvbiBnZXRBdXRoVG9rZW4ocmVxOiBSZXF1ZXN0KSB7XG4gIGNvbnN0IGF1dGhIZWFkZXIgPSByZXEuaGVhZGVycy5nZXQoJ2F1dGhvcml6YXRpb24nKVxuICBpZiAoIWF1dGhIZWFkZXIpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ01pc3NpbmcgYXV0aG9yaXphdGlvbiBoZWFkZXInKVxuICB9XG4gIGNvbnN0IFtiZWFyZXIsIHRva2VuXSA9IGF1dGhIZWFkZXIuc3BsaXQoJyAnKVxuICBpZiAoYmVhcmVyICE9PSAnQmVhcmVyJykge1xuICAgIHRocm93IG5ldyBFcnJvcihgQXV0aCBoZWFkZXIgaXMgbm90ICdCZWFyZXIge3Rva2VufSdgKVxuICB9XG4gIHJldHVybiB0b2tlblxufVxuXG5hc3luYyBmdW5jdGlvbiB2ZXJpZnlKV1Qoand0OiBzdHJpbmcpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgY29uc3QgZW5jb2RlciA9IG5ldyBUZXh0RW5jb2RlcigpXG4gIGNvbnN0IHNlY3JldEtleSA9IGVuY29kZXIuZW5jb2RlKEpXVF9TRUNSRVQpXG4gIHRyeSB7XG4gICAgYXdhaXQgam9zZS5qd3RWZXJpZnkoand0LCBzZWNyZXRLZXkpXG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGNvbnNvbGUuZXJyb3IoZXJyKVxuICAgIHJldHVybiBmYWxzZVxuICB9XG4gIHJldHVybiB0cnVlXG59XG5cbnNlcnZlKGFzeW5jIChyZXE6IFJlcXVlc3QpID0+IHtcbiAgaWYgKHJlcS5tZXRob2QgIT09ICdPUFRJT05TJyAmJiBWRVJJRllfSldUKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHRva2VuID0gZ2V0QXV0aFRva2VuKHJlcSlcbiAgICAgIGNvbnN0IGlzVmFsaWRKV1QgPSBhd2FpdCB2ZXJpZnlKV1QodG9rZW4pXG5cbiAgICAgIGlmICghaXNWYWxpZEpXVCkge1xuICAgICAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KHsgbXNnOiAnSW52YWxpZCBKV1QnIH0pLCB7XG4gICAgICAgICAgc3RhdHVzOiA0MDEsXG4gICAgICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG4gICAgICAgIH0pXG4gICAgICB9XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgY29uc29sZS5lcnJvcihlKVxuICAgICAgcmV0dXJuIG5ldyBSZXNwb25zZShKU09OLnN0cmluZ2lmeSh7IG1zZzogZS50b1N0cmluZygpIH0pLCB7XG4gICAgICAgIHN0YXR1czogNDAxLFxuICAgICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICAgIH0pXG4gICAgfVxuICB9XG5cbiAgY29uc3QgdXJsID0gbmV3IFVSTChyZXEudXJsKVxuICBjb25zdCB7IHBhdGhuYW1lIH0gPSB1cmxcbiAgY29uc3QgcGF0aF9wYXJ0cyA9IHBhdGhuYW1lLnNwbGl0KCcvJylcbiAgY29uc3Qgc2VydmljZV9uYW1lID0gcGF0aF9wYXJ0c1sxXVxuXG4gIGlmICghc2VydmljZV9uYW1lIHx8IHNlcnZpY2VfbmFtZSA9PT0gJycpIHtcbiAgICBjb25zdCBlcnJvciA9IHsgbXNnOiAnbWlzc2luZyBmdW5jdGlvbiBuYW1lIGluIHJlcXVlc3QnIH1cbiAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KGVycm9yKSwge1xuICAgICAgc3RhdHVzOiA0MDAsXG4gICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICB9KVxuICB9XG5cbiAgY29uc3Qgc2VydmljZVBhdGggPSBgL2hvbWUvZGVuby9mdW5jdGlvbnMvJHtzZXJ2aWNlX25hbWV9YFxuICBjb25zb2xlLmVycm9yKGBzZXJ2aW5nIHRoZSByZXF1ZXN0IHdpdGggJHtzZXJ2aWNlUGF0aH1gKVxuXG4gIGNvbnN0IG1lbW9yeUxpbWl0TWIgPSAxNTBcbiAgY29uc3Qgd29ya2VyVGltZW91dE1zID0gMSAqIDYwICogMTAwMFxuICBjb25zdCBub01vZHVsZUNhY2hlID0gZmFsc2VcbiAgY29uc3QgaW1wb3J0TWFwUGF0aCA9IG51bGxcbiAgY29uc3QgZW52VmFyc09iaiA9IERlbm8uZW52LnRvT2JqZWN0KClcbiAgY29uc3QgZW52VmFycyA9IE9iamVjdC5rZXlzKGVudlZhcnNPYmopLm1hcCgoaykgPT4gW2ssIGVudlZhcnNPYmpba11dKVxuXG4gIHRyeSB7XG4gICAgY29uc3Qgd29ya2VyID0gYXdhaXQgRWRnZVJ1bnRpbWUudXNlcldvcmtlcnMuY3JlYXRlKHtcbiAgICAgIHNlcnZpY2VQYXRoLFxuICAgICAgbWVtb3J5TGltaXRNYixcbiAgICAgIHdvcmtlclRpbWVvdXRNcyxcbiAgICAgIG5vTW9kdWxlQ2FjaGUsXG4gICAgICBpbXBvcnRNYXBQYXRoLFxuICAgICAgZW52VmFycyxcbiAgICB9KVxuICAgIHJldHVybiBhd2FpdCB3b3JrZXIuZmV0Y2gocmVxKVxuICB9IGNhdGNoIChlKSB7XG4gICAgY29uc3QgZXJyb3IgPSB7IG1zZzogZS50b1N0cmluZygpIH1cbiAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KGVycm9yKSwge1xuICAgICAgc3RhdHVzOiA1MDAsXG4gICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICB9KVxuICB9XG59KVxuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2Z1bmN0aW9ucy9oZWxsby9pbmRleC50cwogICAgICAgIHRhcmdldDogL2hvbWUvZGVuby9mdW5jdGlvbnMvaGVsbG8vaW5kZXgudHMKICAgICAgICBjb250ZW50OiAiLy8gRm9sbG93IHRoaXMgc2V0dXAgZ3VpZGUgdG8gaW50ZWdyYXRlIHRoZSBEZW5vIGxhbmd1YWdlIHNlcnZlciB3aXRoIHlvdXIgZWRpdG9yOlxuLy8gaHR0cHM6Ly9kZW5vLmxhbmQvbWFudWFsL2dldHRpbmdfc3RhcnRlZC9zZXR1cF95b3VyX2Vudmlyb25tZW50XG4vLyBUaGlzIGVuYWJsZXMgYXV0b2NvbXBsZXRlLCBnbyB0byBkZWZpbml0aW9uLCBldGMuXG5cbmltcG9ydCB7IHNlcnZlIH0gZnJvbSBcImh0dHBzOi8vZGVuby5sYW5kL3N0ZEAwLjE3Ny4xL2h0dHAvc2VydmVyLnRzXCJcblxuc2VydmUoYXN5bmMgKCkgPT4ge1xuICByZXR1cm4gbmV3IFJlc3BvbnNlKFxuICAgIGBcIkhlbGxvIGZyb20gRWRnZSBGdW5jdGlvbnMhXCJgLFxuICAgIHsgaGVhZGVyczogeyBcIkNvbnRlbnQtVHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIiB9IH0sXG4gIClcbn0pXG5cbi8vIFRvIGludm9rZTpcbi8vIGN1cmwgJ2h0dHA6Ly9sb2NhbGhvc3Q6PEtPTkdfSFRUUF9QT1JUPi9mdW5jdGlvbnMvdjEvaGVsbG8nIFxcXG4vLyAgIC0taGVhZGVyICdBdXRob3JpemF0aW9uOiBCZWFyZXIgPGFub24vc2VydmljZV9yb2xlIEFQSSBrZXk+J1xuIgogICAgY29tbWFuZDoKICAgICAgLSBzdGFydAogICAgICAtICctLW1haW4tc2VydmljZScKICAgICAgLSAvaG9tZS9kZW5vL2Z1bmN0aW9ucy9tYWluCiAgc3VwYWJhc2Utc3VwYXZpc29yOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9zdXBhdmlzb3I6MS4xLjU2JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctc1NmTCcKICAgICAgICAtICctbycKICAgICAgICAtIC9kZXYvbnVsbAogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NDAwMC9hcGkvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9PTEVSX1RFTkFOVF9JRD1kZXZfdGVuYW50CiAgICAgIC0gUE9PTEVSX1BPT0xfTU9ERT10cmFuc2FjdGlvbgogICAgICAtICdQT09MRVJfREVGQVVMVF9QT09MX1NJWkU9JHtQT09MRVJfREVGQVVMVF9QT09MX1NJWkU6LTIwfScKICAgICAgLSAnUE9PTEVSX01BWF9DTElFTlRfQ09OTj0ke1BPT0xFUl9NQVhfQ0xJRU5UX0NPTk46LTEwMH0nCiAgICAgIC0gUE9SVD00MDAwCiAgICAgIC0gJ1BPU1RHUkVTX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUE9TVEdSRVNfSE9TVE5BTUU9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1lY3RvOi8vc3VwYWJhc2VfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS9fc3VwYWJhc2UnCiAgICAgIC0gQ0xVU1RFUl9QT1NUR1JFUz10cnVlCiAgICAgIC0gJ1NFQ1JFVF9LRVlfQkFTRT0ke1NFUlZJQ0VfUEFTU1dPUkRfU1VQQVZJU09SU0VDUkVUfScKICAgICAgLSAnVkFVTFRfRU5DX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfVkFVTFRFTkN9JwogICAgICAtICdBUElfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnTUVUUklDU19KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIFJFR0lPTj1sb2NhbAogICAgICAtICdFUkxfQUZMQUdTPS1wcm90b19kaXN0IGluZXRfdGNwJwogICAgY29tbWFuZDoKICAgICAgLSAvYmluL3NoCiAgICAgIC0gJy1jJwogICAgICAtICcvYXBwL2Jpbi9taWdyYXRlICYmIC9hcHAvYmluL3N1cGF2aXNvciBldmFsICIkJChjYXQgL2V0Yy9wb29sZXIvcG9vbGVyLmV4cykiICYmIC9hcHAvYmluL3NlcnZlcicKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvcG9vbGVyL3Bvb2xlci5leHMKICAgICAgICB0YXJnZXQ6IC9ldGMvcG9vbGVyL3Bvb2xlci5leHMKICAgICAgICBjb250ZW50OiAiezpvaywgX30gPSBBcHBsaWNhdGlvbi5lbnN1cmVfYWxsX3N0YXJ0ZWQoOnN1cGF2aXNvcilcbns6b2ssIHZlcnNpb259ID1cbiAgICBjYXNlIFN1cGF2aXNvci5SZXBvLnF1ZXJ5IShcInNlbGVjdCB2ZXJzaW9uKClcIikgZG9cbiAgICAle3Jvd3M6IFtbdmVyXV19IC0+IFN1cGF2aXNvci5IZWxwZXJzLnBhcnNlX3BnX3ZlcnNpb24odmVyKVxuICAgIF8gLT4gbmlsXG4gICAgZW5kXG5wYXJhbXMgPSAle1xuICAgIFwiZXh0ZXJuYWxfaWRcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPT0xFUl9URU5BTlRfSURcIiksXG4gICAgXCJkYl9ob3N0XCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19IT1NUTkFNRVwiKSxcbiAgICBcImRiX3BvcnRcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPU1RHUkVTX1BPUlRcIikgfD4gU3RyaW5nLnRvX2ludGVnZXIoKSxcbiAgICBcImRiX2RhdGFiYXNlXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19EQlwiKSxcbiAgICBcInJlcXVpcmVfdXNlclwiID0+IGZhbHNlLFxuICAgIFwiYXV0aF9xdWVyeVwiID0+IFwiU0VMRUNUICogRlJPTSBwZ2JvdW5jZXIuZ2V0X2F1dGgoJDEpXCIsXG4gICAgXCJkZWZhdWx0X21heF9jbGllbnRzXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfTUFYX0NMSUVOVF9DT05OXCIpLFxuICAgIFwiZGVmYXVsdF9wb29sX3NpemVcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPT0xFUl9ERUZBVUxUX1BPT0xfU0laRVwiKSxcbiAgICBcImRlZmF1bHRfcGFyYW1ldGVyX3N0YXR1c1wiID0+ICV7XCJzZXJ2ZXJfdmVyc2lvblwiID0+IHZlcnNpb259LFxuICAgIFwidXNlcnNcIiA9PiBbJXtcbiAgICBcImRiX3VzZXJcIiA9PiBcInBnYm91bmNlclwiLFxuICAgIFwiZGJfcGFzc3dvcmRcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPU1RHUkVTX1BBU1NXT1JEXCIpLFxuICAgIFwibW9kZV90eXBlXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfUE9PTF9NT0RFXCIpLFxuICAgIFwicG9vbF9zaXplXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfREVGQVVMVF9QT09MX1NJWkVcIiksXG4gICAgXCJpc19tYW5hZ2VyXCIgPT4gdHJ1ZVxuICAgIH1dXG59XG5cbnRlbmFudCA9IFN1cGF2aXNvci5UZW5hbnRzLmdldF90ZW5hbnRfYnlfZXh0ZXJuYWxfaWQocGFyYW1zW1wiZXh0ZXJuYWxfaWRcIl0pXG5cbmlmIHRlbmFudCBkb1xuICB7Om9rLCBffSA9IFN1cGF2aXNvci5UZW5hbnRzLnVwZGF0ZV90ZW5hbnQodGVuYW50LCBwYXJhbXMpXG5lbHNlXG4gIHs6b2ssIF99ID0gU3VwYXZpc29yLlRlbmFudHMuY3JlYXRlX3RlbmFudChwYXJhbXMpXG5lbmRcbiIK", + "compose": "c2VydmljZXM6CiAgc3VwYWJhc2Uta29uZzoKICAgIGltYWdlOiAna29uZzoyLjguMScKICAgIGVudHJ5cG9pbnQ6ICdiYXNoIC1jICcnZXZhbCAiZWNobyBcIiQkKGNhdCB+L3RlbXAueW1sKVwiIiA+IH4va29uZy55bWwgJiYgL2RvY2tlci1lbnRyeXBvaW50LnNoIGtvbmcgZG9ja2VyLXN0YXJ0JycnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkdfODAwMAogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIEtPTkdfREFUQUJBU0U9b2ZmCiAgICAgIC0gS09OR19ERUNMQVJBVElWRV9DT05GSUc9L2hvbWUva29uZy9rb25nLnltbAogICAgICAtICdLT05HX0ROU19PUkRFUj1MQVNULEEsQ05BTUUnCiAgICAgIC0gJ0tPTkdfUExVR0lOUz1yZXF1ZXN0LXRyYW5zZm9ybWVyLGNvcnMsa2V5LWF1dGgsYWNsLGJhc2ljLWF1dGgnCiAgICAgIC0gS09OR19OR0lOWF9QUk9YWV9QUk9YWV9CVUZGRVJfU0laRT0xNjBrCiAgICAgIC0gJ0tPTkdfTkdJTlhfUFJPWFlfUFJPWFlfQlVGRkVSUz02NCAxNjBrJwogICAgICAtICdTVVBBQkFTRV9BTk9OX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX1NFUlZJQ0VfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnREFTSEJPQVJEX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX0FETUlOfScKICAgICAgLSAnREFTSEJPQVJEX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0nCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2FwaS9rb25nLnltbAogICAgICAgIHRhcmdldDogL2hvbWUva29uZy90ZW1wLnltbAogICAgICAgIGNvbnRlbnQ6ICJfZm9ybWF0X3ZlcnNpb246ICcyLjEnXG5fdHJhbnNmb3JtOiB0cnVlXG5cbiMjI1xuIyMjIENvbnN1bWVycyAvIFVzZXJzXG4jIyNcbmNvbnN1bWVyczpcbiAgLSB1c2VybmFtZTogREFTSEJPQVJEXG4gIC0gdXNlcm5hbWU6IGFub25cbiAgICBrZXlhdXRoX2NyZWRlbnRpYWxzOlxuICAgICAgLSBrZXk6ICRTVVBBQkFTRV9BTk9OX0tFWVxuICAtIHVzZXJuYW1lOiBzZXJ2aWNlX3JvbGVcbiAgICBrZXlhdXRoX2NyZWRlbnRpYWxzOlxuICAgICAgLSBrZXk6ICRTVVBBQkFTRV9TRVJWSUNFX0tFWVxuXG4jIyNcbiMjIyBBY2Nlc3MgQ29udHJvbCBMaXN0XG4jIyNcbmFjbHM6XG4gIC0gY29uc3VtZXI6IGFub25cbiAgICBncm91cDogYW5vblxuICAtIGNvbnN1bWVyOiBzZXJ2aWNlX3JvbGVcbiAgICBncm91cDogYWRtaW5cblxuIyMjXG4jIyMgRGFzaGJvYXJkIGNyZWRlbnRpYWxzXG4jIyNcbmJhc2ljYXV0aF9jcmVkZW50aWFsczpcbi0gY29uc3VtZXI6IERBU0hCT0FSRFxuICB1c2VybmFtZTogJERBU0hCT0FSRF9VU0VSTkFNRVxuICBwYXNzd29yZDogJERBU0hCT0FSRF9QQVNTV09SRFxuXG5cbiMjI1xuIyMjIEFQSSBSb3V0ZXNcbiMjI1xuc2VydmljZXM6XG5cbiAgIyMgT3BlbiBBdXRoIHJvdXRlc1xuICAtIG5hbWU6IGF1dGgtdjEtb3BlblxuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS92ZXJpZnlcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtb3BlblxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvdmVyaWZ5XG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAtIG5hbWU6IGF1dGgtdjEtb3Blbi1jYWxsYmFja1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS9jYWxsYmFja1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1vcGVuLWNhbGxiYWNrXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYXV0aC92MS9jYWxsYmFja1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgLSBuYW1lOiBhdXRoLXYxLW9wZW4tYXV0aG9yaXplXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5L2F1dGhvcml6ZVxuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1vcGVuLWF1dGhvcml6ZVxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvYXV0aG9yaXplXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIFNlY3VyZSBBdXRoIHJvdXRlc1xuICAtIG5hbWU6IGF1dGgtdjFcbiAgICBfY29tbWVudDogJ0dvVHJ1ZTogL2F1dGgvdjEvKiAtPiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5LyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5L1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9hdXRoL3YxL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IGZhbHNlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgUkVTVCByb3V0ZXNcbiAgLSBuYW1lOiByZXN0LXYxXG4gICAgX2NvbW1lbnQ6ICdQb3N0Z1JFU1Q6IC9yZXN0L3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlc3QtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVzdC92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgR3JhcGhRTCByb3V0ZXNcbiAgLSBuYW1lOiBncmFwaHFsLXYxXG4gICAgX2NvbW1lbnQ6ICdQb3N0Z1JFU1Q6IC9ncmFwaHFsL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC9ycGMvZ3JhcGhxbCdcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvcnBjL2dyYXBocWxcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGdyYXBocWwtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvZ3JhcGhxbC92MVxuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IHRydWVcbiAgICAgIC0gbmFtZTogcmVxdWVzdC10cmFuc2Zvcm1lclxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgYWRkOlxuICAgICAgICAgICAgaGVhZGVyczpcbiAgICAgICAgICAgICAgLSBDb250ZW50LVByb2ZpbGU6Z3JhcGhxbF9wdWJsaWNcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuXG4gICMjIFNlY3VyZSBSZWFsdGltZSByb3V0ZXNcbiAgLSBuYW1lOiByZWFsdGltZS12MS13c1xuICAgIF9jb21tZW50OiAnUmVhbHRpbWU6IC9yZWFsdGltZS92MS8qIC0+IHdzOi8vcmVhbHRpbWU6NDAwMC9zb2NrZXQvKidcbiAgICB1cmw6IGh0dHA6Ly9yZWFsdGltZS1kZXY6NDAwMC9zb2NrZXRcbiAgICBwcm90b2NvbDogd3NcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlYWx0aW1lLXYxLXdzXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVhbHRpbWUvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuICAtIG5hbWU6IHJlYWx0aW1lLXYxLXJlc3RcbiAgICBfY29tbWVudDogJ1JlYWx0aW1lOiAvcmVhbHRpbWUvdjEvKiAtPiB3czovL3JlYWx0aW1lOjQwMDAvc29ja2V0LyonXG4gICAgdXJsOiBodHRwOi8vcmVhbHRpbWUtZGV2OjQwMDAvYXBpXG4gICAgcHJvdG9jb2w6IGh0dHBcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlYWx0aW1lLXYxLXJlc3RcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9yZWFsdGltZS92MS9hcGlcbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiBmYWxzZVxuICAgICAgLSBuYW1lOiBhY2xcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfZ3JvdXBzX2hlYWRlcjogdHJ1ZVxuICAgICAgICAgIGFsbG93OlxuICAgICAgICAgICAgLSBhZG1pblxuICAgICAgICAgICAgLSBhbm9uXG5cbiAgIyMgU3RvcmFnZSByb3V0ZXM6IHRoZSBzdG9yYWdlIHNlcnZlciBtYW5hZ2VzIGl0cyBvd24gYXV0aFxuICAtIG5hbWU6IHN0b3JhZ2UtdjFcbiAgICBfY29tbWVudDogJ1N0b3JhZ2U6IC9zdG9yYWdlL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXN0b3JhZ2U6NTAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXN0b3JhZ2U6NTAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHN0b3JhZ2UtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvc3RvcmFnZS92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG5cbiAgIyMgRWRnZSBGdW5jdGlvbnMgcm91dGVzXG4gIC0gbmFtZTogZnVuY3Rpb25zLXYxXG4gICAgX2NvbW1lbnQ6ICdFZGdlIEZ1bmN0aW9uczogL2Z1bmN0aW9ucy92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1lZGdlLWZ1bmN0aW9uczo5MDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtZWRnZS1mdW5jdGlvbnM6OTAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGZ1bmN0aW9ucy12MS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9mdW5jdGlvbnMvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIEFuYWx5dGljcyByb3V0ZXNcbiAgLSBuYW1lOiBhbmFseXRpY3MtdjFcbiAgICBfY29tbWVudDogJ0FuYWx5dGljczogL2FuYWx5dGljcy92MS8qIC0+IGh0dHA6Ly9sb2dmbGFyZTo0MDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBhbmFseXRpY3MtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYW5hbHl0aWNzL3YxL1xuXG4gICMjIFNlY3VyZSBEYXRhYmFzZSByb3V0ZXNcbiAgLSBuYW1lOiBtZXRhXG4gICAgX2NvbW1lbnQ6ICdwZy1tZXRhOiAvcGcvKiAtPiBodHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwL1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogbWV0YS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9wZy9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cblxuICAjIyBQcm90ZWN0ZWQgRGFzaGJvYXJkIC0gY2F0Y2ggYWxsIHJlbWFpbmluZyByb3V0ZXNcbiAgLSBuYW1lOiBkYXNoYm9hcmRcbiAgICBfY29tbWVudDogJ1N0dWRpbzogLyogLT4gaHR0cDovL3N0dWRpbzozMDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2Utc3R1ZGlvOjMwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBkYXNoYm9hcmQtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBiYXNpYy1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4iCiAgc3VwYWJhc2Utc3R1ZGlvOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9zdHVkaW86MjAyNDA5MjMtMmUzZTkwYycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBub2RlCiAgICAgICAgLSAnLWUnCiAgICAgICAgLSAicmVxdWlyZSgnaHR0cCcpLmdldCgnaHR0cDovLzEyNy4wLjAuMTozMDAwL2FwaS9wcm9maWxlJywgKHIpID0+IHtpZiAoci5zdGF0dXNDb2RlICE9PSAyMDApIHByb2Nlc3MuZXhpdCgxKTsgZWxzZSBwcm9jZXNzLmV4aXQoMCk7IH0pLm9uKCdlcnJvcicsICgpID0+IHByb2Nlc3MuZXhpdCgxKSkiCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAzCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIEhPU1ROQU1FPTAuMC4wLjAKICAgICAgLSAnU1RVRElPX1BHX01FVEFfVVJMPWh0dHA6Ly9zdXBhYmFzZS1tZXRhOjgwODAnCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RFRkFVTFRfT1JHQU5JWkFUSU9OX05BTUU9JHtTVFVESU9fREVGQVVMVF9PUkdBTklaQVRJT046LURlZmF1bHQgT3JnYW5pemF0aW9ufScKICAgICAgLSAnREVGQVVMVF9QUk9KRUNUX05BTUU9JHtTVFVESU9fREVGQVVMVF9QUk9KRUNUOi1EZWZhdWx0IFByb2plY3R9JwogICAgICAtICdTVVBBQkFTRV9VUkw9aHR0cDovL3N1cGFiYXNlLWtvbmc6ODAwMCcKICAgICAgLSAnU1VQQUJBU0VfUFVCTElDX1VSTD0ke1NFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkd9JwogICAgICAtICdTVVBBQkFTRV9BTk9OX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX1NFUlZJQ0VfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnQVVUSF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdMT0dGTEFSRV9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9MT0dGTEFSRX0nCiAgICAgIC0gJ0xPR0ZMQVJFX1VSTD1odHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAnCiAgICAgIC0gJ1NVUEFCQVNFX1BVQkxJQ19BUEk9JHtTRVJWSUNFX0ZRRE5fU1VQQUJBU0VLT05HfScKICAgICAgLSBORVhUX1BVQkxJQ19FTkFCTEVfTE9HUz10cnVlCiAgICAgIC0gTkVYVF9BTkFMWVRJQ1NfQkFDS0VORF9QUk9WSURFUj1wb3N0Z3JlcwogIHN1cGFiYXNlLWRiOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9wb3N0Z3JlczoxNS4xLjEuNzgnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogJ3BnX2lzcmVhZHkgLVUgcG9zdGdyZXMgLWggMTI3LjAuMC4xJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLXZlY3RvcjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgY29tbWFuZDoKICAgICAgLSBwb3N0Z3JlcwogICAgICAtICctYycKICAgICAgLSBjb25maWdfZmlsZT0vZXRjL3Bvc3RncmVzcWwvcG9zdGdyZXNxbC5jb25mCiAgICAgIC0gJy1jJwogICAgICAtIGxvZ19taW5fbWVzc2FnZXM9ZmF0YWwKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0hPU1Q9L3Zhci9ydW4vcG9zdGdyZXNxbAogICAgICAtICdQR1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUE9TVEdSRVNfUE9SVD0ke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9JwogICAgICAtICdQR1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BHREFUQUJBU0U9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3N1cGFiYXNlLWRiLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2RiL3JlYWx0aW1lLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktcmVhbHRpbWUuc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IHBndXNlciBgZWNobyBcInN1cGFiYXNlX2FkbWluXCJgXG5cbmNyZWF0ZSBzY2hlbWEgaWYgbm90IGV4aXN0cyBfcmVhbHRpbWU7XG5hbHRlciBzY2hlbWEgX3JlYWx0aW1lIG93bmVyIHRvIDpwZ3VzZXI7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvX3N1cGFiYXNlLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTctX3N1cGFiYXNlLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCIkUE9TVEdSRVNfVVNFUlwiYFxuXG5DUkVBVEUgREFUQUJBU0UgX3N1cGFiYXNlIFdJVEggT1dORVIgOnBndXNlcjtcbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9wb29sZXIuc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvbWlncmF0aW9ucy85OS1wb29sZXIuc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IHBndXNlciBgZWNobyBcInN1cGFiYXNlX2FkbWluXCJgXG5cXGMgX3N1cGFiYXNlXG5jcmVhdGUgc2NoZW1hIGlmIG5vdCBleGlzdHMgX3N1cGF2aXNvcjtcbmFsdGVyIHNjaGVtYSBfc3VwYXZpc29yIG93bmVyIHRvIDpwZ3VzZXI7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvd2ViaG9va3Muc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk4LXdlYmhvb2tzLnNxbAogICAgICAgIGNvbnRlbnQ6ICJCRUdJTjtcbi0tIENyZWF0ZSBwZ19uZXQgZXh0ZW5zaW9uXG5DUkVBVEUgRVhURU5TSU9OIElGIE5PVCBFWElTVFMgcGdfbmV0IFNDSEVNQSBleHRlbnNpb25zO1xuLS0gQ3JlYXRlIHN1cGFiYXNlX2Z1bmN0aW9ucyBzY2hlbWFcbkNSRUFURSBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIEFVVEhPUklaQVRJT04gc3VwYWJhc2VfYWRtaW47XG5HUkFOVCBVU0FHRSBPTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gVEFCTEVTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gRlVOQ1RJT05TIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gU0VRVUVOQ0VTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMubWlncmF0aW9ucyBkZWZpbml0aW9uXG5DUkVBVEUgVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLm1pZ3JhdGlvbnMgKFxuICB2ZXJzaW9uIHRleHQgUFJJTUFSWSBLRVksXG4gIGluc2VydGVkX2F0IHRpbWVzdGFtcHR6IE5PVCBOVUxMIERFRkFVTFQgTk9XKClcbik7XG4tLSBJbml0aWFsIHN1cGFiYXNlX2Z1bmN0aW9ucyBtaWdyYXRpb25cbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCdpbml0aWFsJyk7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMuaG9va3MgZGVmaW5pdGlvblxuQ1JFQVRFIFRBQkxFIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyAoXG4gIGlkIGJpZ3NlcmlhbCBQUklNQVJZIEtFWSxcbiAgaG9va190YWJsZV9pZCBpbnRlZ2VyIE5PVCBOVUxMLFxuICBob29rX25hbWUgdGV4dCBOT1QgTlVMTCxcbiAgY3JlYXRlZF9hdCB0aW1lc3RhbXB0eiBOT1QgTlVMTCBERUZBVUxUIE5PVygpLFxuICByZXF1ZXN0X2lkIGJpZ2ludFxuKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfcmVxdWVzdF9pZF9pZHggT04gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIFVTSU5HIGJ0cmVlIChyZXF1ZXN0X2lkKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfaF90YWJsZV9pZF9oX25hbWVfaWR4IE9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyBVU0lORyBidHJlZSAoaG9va190YWJsZV9pZCwgaG9va19uYW1lKTtcbkNPTU1FTlQgT04gVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIElTICdTdXBhYmFzZSBGdW5jdGlvbnMgSG9va3M6IEF1ZGl0IHRyYWlsIGZvciB0cmlnZ2VyZWQgaG9va3MuJztcbkNSRUFURSBGVU5DVElPTiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KClcbiAgUkVUVVJOUyB0cmlnZ2VyXG4gIExBTkdVQUdFIHBscGdzcWxcbiAgQVMgJGZ1bmN0aW9uJFxuICBERUNMQVJFXG4gICAgcmVxdWVzdF9pZCBiaWdpbnQ7XG4gICAgcGF5bG9hZCBqc29uYjtcbiAgICB1cmwgdGV4dCA6PSBUR19BUkdWWzBdOjp0ZXh0O1xuICAgIG1ldGhvZCB0ZXh0IDo9IFRHX0FSR1ZbMV06OnRleHQ7XG4gICAgaGVhZGVycyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHBhcmFtcyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHRpbWVvdXRfbXMgaW50ZWdlciBERUZBVUxUIDEwMDA7XG4gIEJFR0lOXG4gICAgSUYgdXJsIElTIE5VTEwgT1IgdXJsID0gJ251bGwnIFRIRU5cbiAgICAgIFJBSVNFIEVYQ0VQVElPTiAndXJsIGFyZ3VtZW50IGlzIG1pc3NpbmcnO1xuICAgIEVORCBJRjtcblxuICAgIElGIG1ldGhvZCBJUyBOVUxMIE9SIG1ldGhvZCA9ICdudWxsJyBUSEVOXG4gICAgICBSQUlTRSBFWENFUFRJT04gJ21ldGhvZCBhcmd1bWVudCBpcyBtaXNzaW5nJztcbiAgICBFTkQgSUY7XG5cbiAgICBJRiBUR19BUkdWWzJdIElTIE5VTEwgT1IgVEdfQVJHVlsyXSA9ICdudWxsJyBUSEVOXG4gICAgICBoZWFkZXJzID0gJ3tcIkNvbnRlbnQtVHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIn0nOjpqc29uYjtcbiAgICBFTFNFXG4gICAgICBoZWFkZXJzID0gVEdfQVJHVlsyXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVlszXSBJUyBOVUxMIE9SIFRHX0FSR1ZbM10gPSAnbnVsbCcgVEhFTlxuICAgICAgcGFyYW1zID0gJ3t9Jzo6anNvbmI7XG4gICAgRUxTRVxuICAgICAgcGFyYW1zID0gVEdfQVJHVlszXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVls0XSBJUyBOVUxMIE9SIFRHX0FSR1ZbNF0gPSAnbnVsbCcgVEhFTlxuICAgICAgdGltZW91dF9tcyA9IDEwMDA7XG4gICAgRUxTRVxuICAgICAgdGltZW91dF9tcyA9IFRHX0FSR1ZbNF06OmludGVnZXI7XG4gICAgRU5EIElGO1xuXG4gICAgQ0FTRVxuICAgICAgV0hFTiBtZXRob2QgPSAnR0VUJyBUSEVOXG4gICAgICAgIFNFTEVDVCBodHRwX2dldCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9nZXQoXG4gICAgICAgICAgdXJsLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIFdIRU4gbWV0aG9kID0gJ1BPU1QnIFRIRU5cbiAgICAgICAgcGF5bG9hZCA9IGpzb25iX2J1aWxkX29iamVjdChcbiAgICAgICAgICAnb2xkX3JlY29yZCcsIE9MRCxcbiAgICAgICAgICAncmVjb3JkJywgTkVXLFxuICAgICAgICAgICd0eXBlJywgVEdfT1AsXG4gICAgICAgICAgJ3RhYmxlJywgVEdfVEFCTEVfTkFNRSxcbiAgICAgICAgICAnc2NoZW1hJywgVEdfVEFCTEVfU0NIRU1BXG4gICAgICAgICk7XG5cbiAgICAgICAgU0VMRUNUIGh0dHBfcG9zdCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9wb3N0KFxuICAgICAgICAgIHVybCxcbiAgICAgICAgICBwYXlsb2FkLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIEVMU0VcbiAgICAgICAgUkFJU0UgRVhDRVBUSU9OICdtZXRob2QgYXJndW1lbnQgJSBpcyBpbnZhbGlkJywgbWV0aG9kO1xuICAgIEVORCBDQVNFO1xuXG4gICAgSU5TRVJUIElOVE8gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzXG4gICAgICAoaG9va190YWJsZV9pZCwgaG9va19uYW1lLCByZXF1ZXN0X2lkKVxuICAgIFZBTFVFU1xuICAgICAgKFRHX1JFTElELCBUR19OQU1FLCByZXF1ZXN0X2lkKTtcblxuICAgIFJFVFVSTiBORVc7XG4gIEVORFxuJGZ1bmN0aW9uJDtcbi0tIFN1cGFiYXNlIHN1cGVyIGFkbWluXG5ET1xuJCRcbkJFR0lOXG4gIElGIE5PVCBFWElTVFMgKFxuICAgIFNFTEVDVCAxXG4gICAgRlJPTSBwZ19yb2xlc1xuICAgIFdIRVJFIHJvbG5hbWUgPSAnc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluJ1xuICApXG4gIFRIRU5cbiAgICBDUkVBVEUgVVNFUiBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gTk9JTkhFUklUIENSRUFURVJPTEUgTE9HSU4gTk9SRVBMSUNBVElPTjtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIFNDSEVNQSBzdXBhYmFzZV9mdW5jdGlvbnMgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuR1JBTlQgQUxMIFBSSVZJTEVHRVMgT04gQUxMIFRBQkxFUyBJTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIEFMTCBTRVFVRU5DRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5BTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBTRVQgc2VhcmNoX3BhdGggPSBcInN1cGFiYXNlX2Z1bmN0aW9uc1wiO1xuQUxURVIgdGFibGUgXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5taWdyYXRpb25zIE9XTkVSIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkFMVEVSIHRhYmxlIFwic3VwYWJhc2VfZnVuY3Rpb25zXCIuaG9va3MgT1dORVIgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuQUxURVIgZnVuY3Rpb24gXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5odHRwX3JlcXVlc3QoKSBPV05FUiBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5HUkFOVCBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gVE8gcG9zdGdyZXM7XG4tLSBSZW1vdmUgdW51c2VkIHN1cGFiYXNlX3BnX25ldF9hZG1pbiByb2xlXG5ET1xuJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX3JvbGVzXG4gICAgV0hFUkUgcm9sbmFtZSA9ICdzdXBhYmFzZV9wZ19uZXRfYWRtaW4nXG4gIClcbiAgVEhFTlxuICAgIFJFQVNTSUdOIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbiBUTyBzdXBhYmFzZV9hZG1pbjtcbiAgICBEUk9QIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbjtcbiAgICBEUk9QIFJPTEUgc3VwYWJhc2VfcGdfbmV0X2FkbWluO1xuICBFTkQgSUY7XG5FTkRcbiQkO1xuLS0gcGdfbmV0IGdyYW50cyB3aGVuIGV4dGVuc2lvbiBpcyBhbHJlYWR5IGVuYWJsZWRcbkRPXG4kJFxuQkVHSU5cbiAgSUYgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXh0ZW5zaW9uXG4gICAgV0hFUkUgZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbi0tIEV2ZW50IHRyaWdnZXIgZm9yIHBnX25ldFxuQ1JFQVRFIE9SIFJFUExBQ0UgRlVOQ1RJT04gZXh0ZW5zaW9ucy5ncmFudF9wZ19uZXRfYWNjZXNzKClcblJFVFVSTlMgZXZlbnRfdHJpZ2dlclxuTEFOR1VBR0UgcGxwZ3NxbFxuQVMgJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX2V2ZW50X3RyaWdnZXJfZGRsX2NvbW1hbmRzKCkgQVMgZXZcbiAgICBKT0lOIHBnX2V4dGVuc2lvbiBBUyBleHRcbiAgICBPTiBldi5vYmppZCA9IGV4dC5vaWRcbiAgICBXSEVSRSBleHQuZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EO1xuJCQ7XG5DT01NRU5UIE9OIEZVTkNUSU9OIGV4dGVuc2lvbnMuZ3JhbnRfcGdfbmV0X2FjY2VzcyBJUyAnR3JhbnRzIGFjY2VzcyB0byBwZ19uZXQnO1xuRE9cbiQkXG5CRUdJTlxuICBJRiBOT1QgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXZlbnRfdHJpZ2dlclxuICAgIFdIRVJFIGV2dG5hbWUgPSAnaXNzdWVfcGdfbmV0X2FjY2VzcydcbiAgKSBUSEVOXG4gICAgQ1JFQVRFIEVWRU5UIFRSSUdHRVIgaXNzdWVfcGdfbmV0X2FjY2VzcyBPTiBkZGxfY29tbWFuZF9lbmQgV0hFTiBUQUcgSU4gKCdDUkVBVEUgRVhURU5TSU9OJylcbiAgICBFWEVDVVRFIFBST0NFRFVSRSBleHRlbnNpb25zLmdyYW50X3BnX25ldF9hY2Nlc3MoKTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCcyMDIxMDgwOTE4MzQyM191cGRhdGVfZ3JhbnRzJyk7XG5BTFRFUiBmdW5jdGlvbiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KCkgU0VDVVJJVFkgREVGSU5FUjtcbkFMVEVSIGZ1bmN0aW9uIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBTRVQgc2VhcmNoX3BhdGggPSBzdXBhYmFzZV9mdW5jdGlvbnM7XG5SRVZPS0UgQUxMIE9OIEZVTkNUSU9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBGUk9NIFBVQkxJQztcbkdSQU5UIEVYRUNVVEUgT04gRlVOQ1RJT04gc3VwYWJhc2VfZnVuY3Rpb25zLmh0dHBfcmVxdWVzdCgpIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5DT01NSVQ7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcm9sZXMuc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk5LXJvbGVzLnNxbAogICAgICAgIGNvbnRlbnQ6ICItLSBOT1RFOiBjaGFuZ2UgdG8geW91ciBvd24gcGFzc3dvcmRzIGZvciBwcm9kdWN0aW9uIGVudmlyb25tZW50c1xuIFxcc2V0IHBncGFzcyBgZWNobyBcIiRQT1NUR1JFU19QQVNTV09SRFwiYFxuXG4gQUxURVIgVVNFUiBhdXRoZW50aWNhdG9yIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgcGdib3VuY2VyIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgc3VwYWJhc2VfYXV0aF9hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX3N0b3JhZ2VfYWRtaW4gV0lUSCBQQVNTV09SRCA6J3BncGFzcyc7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvand0LnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL2luaXQtc2NyaXB0cy85OS1qd3Quc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IGp3dF9zZWNyZXQgYGVjaG8gXCIkSldUX1NFQ1JFVFwiYFxuXFxzZXQgand0X2V4cCBgZWNobyBcIiRKV1RfRVhQXCJgXG5cXHNldCBkYl9uYW1lIGBlY2hvIFwiJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9XCJgXG5cbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3Rfc2VjcmV0XCIgVE8gOidqd3Rfc2VjcmV0JztcbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3RfZXhwXCIgVE8gOidqd3RfZXhwJztcbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9sb2dzLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktbG9ncy5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwic3VwYWJhc2VfYWRtaW5cImBcblxcYyBfc3VwYWJhc2VcbmNyZWF0ZSBzY2hlbWEgaWYgbm90IGV4aXN0cyBfYW5hbHl0aWNzO1xuYWx0ZXIgc2NoZW1hIF9hbmFseXRpY3Mgb3duZXIgdG8gOnBndXNlcjtcbiIKICAgICAgLSAnc3VwYWJhc2UtZGItY29uZmlnOi9ldGMvcG9zdGdyZXNxbC1jdXN0b20nCiAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9sb2dmbGFyZToxLjQuMCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo0MDAwL2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1kYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTE9HRkxBUkVfTk9ERV9IT1NUPTEyNy4wLjAuMQogICAgICAtIERCX1VTRVJOQU1FPXN1cGFiYXNlX2FkbWluCiAgICAgIC0gREJfREFUQUJBU0U9X3N1cGFiYXNlCiAgICAgIC0gJ0RCX0hPU1ROQU1FPSR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifScKICAgICAgLSAnREJfUE9SVD0ke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9JwogICAgICAtICdEQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtIERCX1NDSEVNQT1fYW5hbHl0aWNzCiAgICAgIC0gJ0xPR0ZMQVJFX0FQSV9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX0xPR0ZMQVJFfScKICAgICAgLSBMT0dGTEFSRV9TSU5HTEVfVEVOQU5UPXRydWUKICAgICAgLSBMT0dGTEFSRV9TSU5HTEVfVEVOQU5UX01PREU9dHJ1ZQogICAgICAtIExPR0ZMQVJFX1NVUEFCQVNFX01PREU9dHJ1ZQogICAgICAtIExPR0ZMQVJFX01JTl9DTFVTVEVSX1NJWkU9MQogICAgICAtICdQT1NUR1JFU19CQUNLRU5EX1VSTD1wb3N0Z3Jlc3FsOi8vc3VwYWJhc2VfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS9fc3VwYWJhc2UnCiAgICAgIC0gUE9TVEdSRVNfQkFDS0VORF9TQ0hFTUE9X2FuYWx5dGljcwogICAgICAtIExPR0ZMQVJFX0ZFQVRVUkVfRkxBR19PVkVSUklERT1tdWx0aWJhY2tlbmQ9dHJ1ZQogIHN1cGFiYXNlLXZlY3RvcjoKICAgIGltYWdlOiAndGltYmVyaW8vdmVjdG9yOjAuMjguMS1hbHBpbmUnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovL3N1cGFiYXNlLXZlY3Rvcjo5MDAxL2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvbG9ncy92ZWN0b3IueW1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL3ZlY3Rvci92ZWN0b3IueW1sCiAgICAgICAgcmVhZF9vbmx5OiB0cnVlCiAgICAgICAgY29udGVudDogImFwaTpcbiAgZW5hYmxlZDogdHJ1ZVxuICBhZGRyZXNzOiAwLjAuMC4wOjkwMDFcblxuc291cmNlczpcbiAgZG9ja2VyX2hvc3Q6XG4gICAgdHlwZTogZG9ja2VyX2xvZ3NcbiAgICBleGNsdWRlX2NvbnRhaW5lcnM6XG4gICAgICAtIHN1cGFiYXNlLXZlY3RvclxuXG50cmFuc2Zvcm1zOlxuICBwcm9qZWN0X2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIGRvY2tlcl9ob3N0XG4gICAgc291cmNlOiB8LVxuICAgICAgLnByb2plY3QgPSBcImRlZmF1bHRcIlxuICAgICAgLmV2ZW50X21lc3NhZ2UgPSBkZWwoLm1lc3NhZ2UpXG4gICAgICAuYXBwbmFtZSA9IGRlbCguY29udGFpbmVyX25hbWUpXG4gICAgICBkZWwoLmNvbnRhaW5lcl9jcmVhdGVkX2F0KVxuICAgICAgZGVsKC5jb250YWluZXJfaWQpXG4gICAgICBkZWwoLnNvdXJjZV90eXBlKVxuICAgICAgZGVsKC5zdHJlYW0pXG4gICAgICBkZWwoLmxhYmVsKVxuICAgICAgZGVsKC5pbWFnZSlcbiAgICAgIGRlbCguaG9zdClcbiAgICAgIGRlbCguc3RyZWFtKVxuICByb3V0ZXI6XG4gICAgdHlwZTogcm91dGVcbiAgICBpbnB1dHM6XG4gICAgICAtIHByb2plY3RfbG9nc1xuICAgIHJvdXRlOlxuICAgICAga29uZzogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInN1cGFiYXNlLWtvbmdcIiknXG4gICAgICBhdXRoOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2UtYXV0aFwiKSdcbiAgICAgIHJlc3Q6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1yZXN0XCIpJ1xuICAgICAgcmVhbHRpbWU6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJyZWFsdGltZS1kZXZcIiknXG4gICAgICBzdG9yYWdlOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2Utc3RvcmFnZVwiKSdcbiAgICAgIGZ1bmN0aW9uczogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInN1cGFiYXNlLWZ1bmN0aW9uc1wiKSdcbiAgICAgIGRiOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2UtZGJcIiknXG4gICMgSWdub3JlcyBub24gbmdpbnggZXJyb3JzIHNpbmNlIHRoZXkgYXJlIHJlbGF0ZWQgd2l0aCBrb25nIGJvb3RpbmcgdXBcbiAga29uZ19sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIua29uZ1xuICAgIHNvdXJjZTogfC1cbiAgICAgIHJlcSwgZXJyID0gcGFyc2VfbmdpbnhfbG9nKC5ldmVudF9tZXNzYWdlLCBcImNvbWJpbmVkXCIpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLnRpbWVzdGFtcCA9IHJlcS50aW1lc3RhbXBcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5oZWFkZXJzLnJlZmVyZXIgPSByZXEucmVmZXJlclxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LmhlYWRlcnMudXNlcl9hZ2VudCA9IHJlcS5hZ2VudFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LmhlYWRlcnMuY2ZfY29ubmVjdGluZ19pcCA9IHJlcS5jbGllbnRcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSByZXEubWV0aG9kXG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucGF0aCA9IHJlcS5wYXRoXG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucHJvdG9jb2wgPSByZXEucHJvdG9jb2xcbiAgICAgICAgICAubWV0YWRhdGEucmVzcG9uc2Uuc3RhdHVzX2NvZGUgPSByZXEuc3RhdHVzXG4gICAgICB9XG4gICAgICBpZiBlcnIgIT0gbnVsbCB7XG4gICAgICAgIGFib3J0XG4gICAgICB9XG4gICMgSWdub3JlcyBub24gbmdpbnggZXJyb3JzIHNpbmNlIHRoZXkgYXJlIHJlbGF0ZWQgd2l0aCBrb25nIGJvb3RpbmcgdXBcbiAga29uZ19lcnI6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5rb25nXG4gICAgc291cmNlOiB8LVxuICAgICAgLm1ldGFkYXRhLnJlcXVlc3QubWV0aG9kID0gXCJHRVRcIlxuICAgICAgLm1ldGFkYXRhLnJlc3BvbnNlLnN0YXR1c19jb2RlID0gMjAwXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX25naW54X2xvZyguZXZlbnRfbWVzc2FnZSwgXCJlcnJvclwiKVxuICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgIC50aW1lc3RhbXAgPSBwYXJzZWQudGltZXN0YW1wXG4gICAgICAgICAgLnNldmVyaXR5ID0gcGFyc2VkLnNldmVyaXR5XG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaG9zdCA9IHBhcnNlZC5ob3N0XG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy5jZl9jb25uZWN0aW5nX2lwID0gcGFyc2VkLmNsaWVudFxuICAgICAgICAgIHVybCwgZXJyID0gc3BsaXQocGFyc2VkLnJlcXVlc3QsIFwiIFwiKVxuICAgICAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QubWV0aG9kID0gdXJsWzBdXG4gICAgICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LnBhdGggPSB1cmxbMV1cbiAgICAgICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucHJvdG9jb2wgPSB1cmxbMl1cbiAgICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiBlcnIgIT0gbnVsbCB7XG4gICAgICAgIGFib3J0XG4gICAgICB9XG4gICMgR290cnVlIGxvZ3MgYXJlIHN0cnVjdHVyZWQganNvbiBzdHJpbmdzIHdoaWNoIGZyb250ZW5kIHBhcnNlcyBkaXJlY3RseS4gQnV0IHdlIGtlZXAgbWV0YWRhdGEgZm9yIGNvbnNpc3RlbmN5LlxuICBhdXRoX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5hdXRoXG4gICAgc291cmNlOiB8LVxuICAgICAgcGFyc2VkLCBlcnIgPSBwYXJzZV9qc29uKC5ldmVudF9tZXNzYWdlKVxuICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgIC5tZXRhZGF0YS50aW1lc3RhbXAgPSBwYXJzZWQudGltZVxuICAgICAgICAgIC5tZXRhZGF0YSA9IG1lcmdlISgubWV0YWRhdGEsIHBhcnNlZClcbiAgICAgIH1cbiAgIyBQb3N0Z1JFU1QgbG9ncyBhcmUgc3RydWN0dXJlZCBzbyB3ZSBzZXBhcmF0ZSB0aW1lc3RhbXAgZnJvbSBtZXNzYWdlIHVzaW5nIHJlZ2V4XG4gIHJlc3RfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLnJlc3RcbiAgICBzb3VyY2U6IHwtXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX3JlZ2V4KC5ldmVudF9tZXNzYWdlLCByJ14oP1A8dGltZT4uKik6ICg/UDxtc2c+LiopJCcpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLmV2ZW50X21lc3NhZ2UgPSBwYXJzZWQubXNnXG4gICAgICAgICAgLnRpbWVzdGFtcCA9IHRvX3RpbWVzdGFtcCEocGFyc2VkLnRpbWUpXG4gICAgICAgICAgLm1ldGFkYXRhLmhvc3QgPSAucHJvamVjdFxuICAgICAgfVxuICAjIFJlYWx0aW1lIGxvZ3MgYXJlIHN0cnVjdHVyZWQgc28gd2UgcGFyc2UgdGhlIHNldmVyaXR5IGxldmVsIHVzaW5nIHJlZ2V4IChpZ25vcmUgdGltZSBiZWNhdXNlIGl0IGhhcyBubyBkYXRlKVxuICByZWFsdGltZV9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIucmVhbHRpbWVcbiAgICBzb3VyY2U6IHwtXG4gICAgICAubWV0YWRhdGEucHJvamVjdCA9IGRlbCgucHJvamVjdClcbiAgICAgIC5tZXRhZGF0YS5leHRlcm5hbF9pZCA9IC5tZXRhZGF0YS5wcm9qZWN0XG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX3JlZ2V4KC5ldmVudF9tZXNzYWdlLCByJ14oP1A8dGltZT5cXGQrOlxcZCs6XFxkK1xcLlxcZCspIFxcWyg/UDxsZXZlbD5cXHcrKVxcXSAoP1A8bXNnPi4qKSQnKVxuICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgIC5ldmVudF9tZXNzYWdlID0gcGFyc2VkLm1zZ1xuICAgICAgICAgIC5tZXRhZGF0YS5sZXZlbCA9IHBhcnNlZC5sZXZlbFxuICAgICAgfVxuICAjIFN0b3JhZ2UgbG9ncyBtYXkgY29udGFpbiBqc29uIG9iamVjdHMgc28gd2UgcGFyc2UgdGhlbSBmb3IgY29tcGxldGVuZXNzXG4gIHN0b3JhZ2VfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLnN0b3JhZ2VcbiAgICBzb3VyY2U6IHwtXG4gICAgICAubWV0YWRhdGEucHJvamVjdCA9IGRlbCgucHJvamVjdClcbiAgICAgIC5tZXRhZGF0YS50ZW5hbnRJZCA9IC5tZXRhZGF0YS5wcm9qZWN0XG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX2pzb24oLmV2ZW50X21lc3NhZ2UpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLmV2ZW50X21lc3NhZ2UgPSBwYXJzZWQubXNnXG4gICAgICAgICAgLm1ldGFkYXRhLmxldmVsID0gcGFyc2VkLmxldmVsXG4gICAgICAgICAgLm1ldGFkYXRhLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lXG4gICAgICAgICAgLm1ldGFkYXRhLmNvbnRleHRbMF0uaG9zdCA9IHBhcnNlZC5ob3N0bmFtZVxuICAgICAgICAgIC5tZXRhZGF0YS5jb250ZXh0WzBdLnBpZCA9IHBhcnNlZC5waWRcbiAgICAgIH1cbiAgIyBQb3N0Z3JlcyBsb2dzIHNvbWUgbWVzc2FnZXMgdG8gc3RkZXJyIHdoaWNoIHdlIG1hcCB0byB3YXJuaW5nIHNldmVyaXR5IGxldmVsXG4gIGRiX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5kYlxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5ob3N0ID0gXCJkYi1kZWZhdWx0XCJcbiAgICAgIC5tZXRhZGF0YS5wYXJzZWQudGltZXN0YW1wID0gLnRpbWVzdGFtcFxuXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX3JlZ2V4KC5ldmVudF9tZXNzYWdlLCByJy4qKD9QPGxldmVsPklORk98Tk9USUNFfFdBUk5JTkd8RVJST1J8TE9HfEZBVEFMfFBBTklDPyk6LionLCBudW1lcmljX2dyb3VwczogdHJ1ZSlcblxuICAgICAgaWYgZXJyICE9IG51bGwgfHwgcGFyc2VkID09IG51bGwge1xuICAgICAgICAubWV0YWRhdGEucGFyc2VkLmVycm9yX3NldmVyaXR5ID0gXCJpbmZvXCJcbiAgICAgIH1cbiAgICAgIGlmIHBhcnNlZCAhPSBudWxsIHtcbiAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSBwYXJzZWQubGV2ZWxcbiAgICAgIH1cbiAgICAgIGlmIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPT0gXCJpbmZvXCIge1xuICAgICAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSBcImxvZ1wiXG4gICAgICB9XG4gICAgICAubWV0YWRhdGEucGFyc2VkLmVycm9yX3NldmVyaXR5ID0gdXBjYXNlISgubWV0YWRhdGEucGFyc2VkLmVycm9yX3NldmVyaXR5KVxuXG5zaW5rczpcbiAgbG9nZmxhcmVfYXV0aDpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIGF1dGhfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1nb3RydWUubG9ncy5wcm9kJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9yZWFsdGltZTpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIHJlYWx0aW1lX2xvZ3NcbiAgICBlbmNvZGluZzpcbiAgICAgIGNvZGVjOiAnanNvbidcbiAgICBtZXRob2Q6ICdwb3N0J1xuICAgIHJlcXVlc3Q6XG4gICAgICByZXRyeV9tYXhfZHVyYXRpb25fc2VjczogMTBcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvYXBpL2xvZ3M/c291cmNlX25hbWU9cmVhbHRpbWUubG9ncy5wcm9kJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9yZXN0OlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gcmVzdF9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPXBvc3RnUkVTVC5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX2RiOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gZGJfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgICMgV2UgbXVzdCByb3V0ZSB0aGUgc2luayB0aHJvdWdoIGtvbmcgYmVjYXVzZSBpbmdlc3RpbmcgbG9ncyBiZWZvcmUgbG9nZmxhcmUgaXMgZnVsbHkgaW5pdGlhbGlzZWQgd2lsbFxuICAgICMgbGVhZCB0byBicm9rZW4gcXVlcmllcyBmcm9tIHN0dWRpby4gVGhpcyB3b3JrcyBieSB0aGUgYXNzdW1wdGlvbiB0aGF0IGNvbnRhaW5lcnMgYXJlIHN0YXJ0ZWQgaW4gdGhlXG4gICAgIyBmb2xsb3dpbmcgb3JkZXI6IHZlY3RvciA+IGRiID4gbG9nZmxhcmUgPiBrb25nXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWtvbmc6ODAwMC9hbmFseXRpY3MvdjEvYXBpL2xvZ3M/c291cmNlX25hbWU9cG9zdGdyZXMubG9ncyZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfZnVuY3Rpb25zOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmZ1bmN0aW9uc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1kZW5vLXJlbGF5LWxvZ3MmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3N0b3JhZ2U6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSBzdG9yYWdlX2xvZ3NcbiAgICBlbmNvZGluZzpcbiAgICAgIGNvZGVjOiAnanNvbidcbiAgICBtZXRob2Q6ICdwb3N0J1xuICAgIHJlcXVlc3Q6XG4gICAgICByZXRyeV9tYXhfZHVyYXRpb25fc2VjczogMTBcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvYXBpL2xvZ3M/c291cmNlX25hbWU9c3RvcmFnZS5sb2dzLnByb2QuMiZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfa29uZzpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIGtvbmdfbG9nc1xuICAgICAgLSBrb25nX2VyclxuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1jbG91ZGZsYXJlLmxvZ3MucHJvZCZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiIKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2s6cm8nCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTE9HRkxBUkVfQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTE9HRkxBUkV9JwogICAgY29tbWFuZDoKICAgICAgLSAnLS1jb25maWcnCiAgICAgIC0gZXRjL3ZlY3Rvci92ZWN0b3IueW1sCiAgc3VwYWJhc2UtcmVzdDoKICAgIGltYWdlOiAncG9zdGdyZXN0L3Bvc3RncmVzdDp2MTIuMi4wJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUEdSU1RfREJfVVJJPXBvc3RncmVzOi8vYXV0aGVudGljYXRvcjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUEdSU1RfREJfU0NIRU1BUz0ke1BHUlNUX0RCX1NDSEVNQVM6LXB1YmxpYyxzdG9yYWdlLGdyYXBocWxfcHVibGljfScKICAgICAgLSBQR1JTVF9EQl9BTk9OX1JPTEU9YW5vbgogICAgICAtICdQR1JTVF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIFBHUlNUX0RCX1VTRV9MRUdBQ1lfR1VDUz1mYWxzZQogICAgICAtICdQR1JTVF9BUFBfU0VUVElOR1NfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnUEdSU1RfQVBQX1NFVFRJTkdTX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgIGNvbW1hbmQ6IHBvc3RncmVzdAogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgc3VwYWJhc2UtYXV0aDoKICAgIGltYWdlOiAnc3VwYWJhc2UvZ290cnVlOnYyLjE1OC4xJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo5OTk5L2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPVFJVRV9BUElfSE9TVD0wLjAuMC4wCiAgICAgIC0gR09UUlVFX0FQSV9QT1JUPTk5OTkKICAgICAgLSAnQVBJX0VYVEVSTkFMX1VSTD0ke0FQSV9FWFRFUk5BTF9VUkw6LWh0dHA6Ly9zdXBhYmFzZS1rb25nOjgwMDB9JwogICAgICAtIEdPVFJVRV9EQl9EUklWRVI9cG9zdGdyZXMKICAgICAgLSAnR09UUlVFX0RCX0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovL3N1cGFiYXNlX2F1dGhfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0dPVFJVRV9TSVRFX1VSTD0ke1NFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkd9JwogICAgICAtICdHT1RSVUVfVVJJX0FMTE9XX0xJU1Q9JHtBRERJVElPTkFMX1JFRElSRUNUX1VSTFN9JwogICAgICAtICdHT1RSVUVfRElTQUJMRV9TSUdOVVA9JHtESVNBQkxFX1NJR05VUDotZmFsc2V9JwogICAgICAtIEdPVFJVRV9KV1RfQURNSU5fUk9MRVM9c2VydmljZV9yb2xlCiAgICAgIC0gR09UUlVFX0pXVF9BVUQ9YXV0aGVudGljYXRlZAogICAgICAtIEdPVFJVRV9KV1RfREVGQVVMVF9HUk9VUF9OQU1FPWF1dGhlbnRpY2F0ZWQKICAgICAgLSAnR09UUlVFX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgICAgLSAnR09UUlVFX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0dPVFJVRV9FWFRFUk5BTF9FTUFJTF9FTkFCTEVEPSR7RU5BQkxFX0VNQUlMX1NJR05VUDotdHJ1ZX0nCiAgICAgIC0gJ0dPVFJVRV9FWFRFUk5BTF9BTk9OWU1PVVNfVVNFUlNfRU5BQkxFRD0ke0VOQUJMRV9BTk9OWU1PVVNfVVNFUlM6LWZhbHNlfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9BVVRPQ09ORklSTT0ke0VOQUJMRV9FTUFJTF9BVVRPQ09ORklSTTotZmFsc2V9JwogICAgICAtICdHT1RSVUVfU01UUF9BRE1JTl9FTUFJTD0ke1NNVFBfQURNSU5fRU1BSUx9JwogICAgICAtICdHT1RSVUVfU01UUF9IT1NUPSR7U01UUF9IT1NUfScKICAgICAgLSAnR09UUlVFX1NNVFBfUE9SVD0ke1NNVFBfUE9SVDotNTg3fScKICAgICAgLSAnR09UUlVFX1NNVFBfVVNFUj0ke1NNVFBfVVNFUn0nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX1BBU1M9JHtTTVRQX1BBU1N9JwogICAgICAtICdHT1RSVUVfU01UUF9TRU5ERVJfTkFNRT0ke1NNVFBfU0VOREVSX05BTUV9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1VSTFBBVEhTX0lOVklURT0ke01BSUxFUl9VUkxQQVRIU19JTlZJVEU6LS9hdXRoL3YxL3ZlcmlmeX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVVJMUEFUSFNfQ09ORklSTUFUSU9OPSR7TUFJTEVSX1VSTFBBVEhTX0NPTkZJUk1BVElPTjotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19SRUNPVkVSWT0ke01BSUxFUl9VUkxQQVRIU19SRUNPVkVSWTotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19FTUFJTF9DSEFOR0U9JHtNQUlMRVJfVVJMUEFUSFNfRU1BSUxfQ0hBTkdFOi0vYXV0aC92MS92ZXJpZnl9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1RFTVBMQVRFU19JTlZJVEU9JHtNQUlMRVJfVEVNUExBVEVTX0lOVklURX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0NPTkZJUk1BVElPTj0ke01BSUxFUl9URU1QTEFURVNfQ09ORklSTUFUSU9OfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfUkVDT1ZFUlk9JHtNQUlMRVJfVEVNUExBVEVTX1JFQ09WRVJZfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfTUFHSUNfTElOSz0ke01BSUxFUl9URU1QTEFURVNfTUFHSUNfTElOS30nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0VNQUlMX0NIQU5HRT0ke01BSUxFUl9URU1QTEFURVNfRU1BSUxfQ0hBTkdFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19DT05GSVJNQVRJT049JHtNQUlMRVJfU1VCSkVDVFNfQ09ORklSTUFUSU9OfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19SRUNPVkVSWT0ke01BSUxFUl9TVUJKRUNUU19SRUNPVkVSWX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfU1VCSkVDVFNfTUFHSUNfTElOSz0ke01BSUxFUl9TVUJKRUNUU19NQUdJQ19MSU5LfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19FTUFJTF9DSEFOR0U9JHtNQUlMRVJfU1VCSkVDVFNfRU1BSUxfQ0hBTkdFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19JTlZJVEU9JHtNQUlMRVJfU1VCSkVDVFNfSU5WSVRFfScKICAgICAgLSAnR09UUlVFX0VYVEVSTkFMX1BIT05FX0VOQUJMRUQ9JHtFTkFCTEVfUEhPTkVfU0lHTlVQOi10cnVlfScKICAgICAgLSAnR09UUlVFX1NNU19BVVRPQ09ORklSTT0ke0VOQUJMRV9QSE9ORV9BVVRPQ09ORklSTTotdHJ1ZX0nCiAgcmVhbHRpbWUtZGV2OgogICAgaW1hZ2U6ICdzdXBhYmFzZS9yZWFsdGltZTp2Mi4zMC4zNCcKICAgIGNvbnRhaW5lcl9uYW1lOiByZWFsdGltZS1kZXYuc3VwYWJhc2UtcmVhbHRpbWUKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctc1NmTCcKICAgICAgICAtICctLWhlYWQnCiAgICAgICAgLSAnLW8nCiAgICAgICAgLSAvZGV2L251bGwKICAgICAgICAtICctSCcKICAgICAgICAtICdBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtTRVJWSUNFX1NVUEFCQVNFQU5PTl9LRVl9JwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NDAwMC9hcGkvdGVuYW50cy9yZWFsdGltZS1kZXYvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9SVD00MDAwCiAgICAgIC0gJ0RCX0hPU1Q9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdEQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gREJfVVNFUj1zdXBhYmFzZV9hZG1pbgogICAgICAtICdEQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdEQl9OQU1FPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnREJfQUZURVJfQ09OTkVDVF9RVUVSWT1TRVQgc2VhcmNoX3BhdGggVE8gX3JlYWx0aW1lJwogICAgICAtIERCX0VOQ19LRVk9c3VwYWJhc2VyZWFsdGltZQogICAgICAtICdBUElfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSBGTFlfQUxMT0NfSUQ9Zmx5MTIzCiAgICAgIC0gRkxZX0FQUF9OQU1FPXJlYWx0aW1lCiAgICAgIC0gJ1NFQ1JFVF9LRVlfQkFTRT0ke1NFQ1JFVF9QQVNTV09SRF9SRUFMVElNRX0nCiAgICAgIC0gJ0VSTF9BRkxBR1M9LXByb3RvX2Rpc3QgaW5ldF90Y3AnCiAgICAgIC0gRU5BQkxFX1RBSUxTQ0FMRT1mYWxzZQogICAgICAtICJETlNfTk9ERVM9JyciCiAgICAgIC0gUkxJTUlUX05PRklMRT0xMDAwMAogICAgICAtIEFQUF9OQU1FPXJlYWx0aW1lCiAgICAgIC0gU0VFRF9TRUxGX0hPU1Q9dHJ1ZQogICAgY29tbWFuZDogInNoIC1jIFwiL2FwcC9iaW4vbWlncmF0ZSAmJiAvYXBwL2Jpbi9yZWFsdGltZSBldmFsICdSZWFsdGltZS5SZWxlYXNlLnNlZWRzKFJlYWx0aW1lLlJlcG8pJyAmJiAvYXBwL2Jpbi9zZXJ2ZXJcIlxuIgogIHN1cGFiYXNlLW1pbmlvOgogICAgaW1hZ2U6IG1pbmlvL21pbmlvCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgY29tbWFuZDogJ3NlcnZlciAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiIC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdzbGVlcCA1ICYmIGV4aXQgMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiA1CiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvc3RvcmFnZTovZGF0YScKICBtaW5pby1jcmVhdGVidWNrZXQ6CiAgICBpbWFnZTogbWluaW8vbWMKICAgIHJlc3RhcnQ6ICdubycKICAgIGVudmlyb25tZW50OgogICAgICAtICdNSU5JT19ST09UX1VTRVI9JHtTRVJWSUNFX1VTRVJfTUlOSU99JwogICAgICAtICdNSU5JT19ST09UX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NSU5JT30nCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1taW5pbzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW50cnlwb2ludDoKICAgICAgLSAvZW50cnlwb2ludC5zaAogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZW50cnlwb2ludC5zaAogICAgICAgIHRhcmdldDogL2VudHJ5cG9pbnQuc2gKICAgICAgICBjb250ZW50OiAiIyEvYmluL3NoXG4vdXNyL2Jpbi9tYyBhbGlhcyBzZXQgc3VwYWJhc2UtbWluaW8gaHR0cDovL3N1cGFiYXNlLW1pbmlvOjkwMDAgJHtNSU5JT19ST09UX1VTRVJ9ICR7TUlOSU9fUk9PVF9QQVNTV09SRH07XG4vdXNyL2Jpbi9tYyBtYiAtLWlnbm9yZS1leGlzdGluZyBzdXBhYmFzZS1taW5pby9zdHViO1xuZXhpdCAwXG4iCiAgc3VwYWJhc2Utc3RvcmFnZToKICAgIGltYWdlOiAnc3VwYWJhc2Uvc3RvcmFnZS1hcGk6djEuMTAuMScKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLXJlc3Q6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX3N0YXJ0ZWQKICAgICAgaW1ncHJveHk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX3N0YXJ0ZWQKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjUwMDAvc3RhdHVzJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVkVSX1BPUlQ9NTAwMAogICAgICAtIFNFUlZFUl9SRUdJT049bG9jYWwKICAgICAgLSBNVUxUSV9URU5BTlQ9ZmFsc2UKICAgICAgLSAnQVVUSF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly9zdXBhYmFzZV9zdG9yYWdlX2FkbWluOiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AJHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9OiR7UE9TVEdSRVNfUE9SVDotNTQzMn0vJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtIERCX0lOU1RBTExfUk9MRVM9ZmFsc2UKICAgICAgLSBTVE9SQUdFX0JBQ0tFTkQ9czMKICAgICAgLSBTVE9SQUdFX1MzX0JVQ0tFVD1zdHViCiAgICAgIC0gJ1NUT1JBR0VfUzNfRU5EUE9JTlQ9aHR0cDovL3N1cGFiYXNlLW1pbmlvOjkwMDAnCiAgICAgIC0gU1RPUkFHRV9TM19GT1JDRV9QQVRIX1NUWUxFPXRydWUKICAgICAgLSBTVE9SQUdFX1MzX1JFR0lPTj11cy1lYXN0LTEKICAgICAgLSAnQVdTX0FDQ0VTU19LRVlfSUQ9JHtTRVJWSUNFX1VTRVJfTUlOSU99JwogICAgICAtICdBV1NfU0VDUkVUX0FDQ0VTU19LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX01JTklPfScKICAgICAgLSBVUExPQURfRklMRV9TSVpFX0xJTUlUPTUyNDI4ODAwMAogICAgICAtIFVQTE9BRF9GSUxFX1NJWkVfTElNSVRfU1RBTkRBUkQ9NTI0Mjg4MDAwCiAgICAgIC0gVVBMT0FEX1NJR05FRF9VUkxfRVhQSVJBVElPTl9USU1FPTEyMAogICAgICAtIFRVU19VUkxfUEFUSD0vdXBsb2FkL3Jlc3VtYWJsZQogICAgICAtIFRVU19NQVhfU0laRT0zNjAwMDAwCiAgICAgIC0gRU5BQkxFX0lNQUdFX1RSQU5TRk9STUFUSU9OPXRydWUKICAgICAgLSAnSU1HUFJPWFlfVVJMPWh0dHA6Ly9pbWdwcm94eTo4MDgwJwogICAgICAtIElNR1BST1hZX1JFUVVFU1RfVElNRU9VVD0xNQogICAgICAtIERBVEFCQVNFX1NFQVJDSF9QQVRIPXN0b3JhZ2UKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vdm9sdW1lcy9zdG9yYWdlOi92YXIvbGliL3N0b3JhZ2UnCiAgaW1ncHJveHk6CiAgICBpbWFnZTogJ2RhcnRoc2ltL2ltZ3Byb3h5OnYzLjguMCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBpbWdwcm94eQogICAgICAgIC0gaGVhbHRoCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBJTUdQUk9YWV9MT0NBTF9GSUxFU1lTVEVNX1JPT1Q9LwogICAgICAtIElNR1BST1hZX1VTRV9FVEFHPXRydWUKICAgICAgLSAnSU1HUFJPWFlfRU5BQkxFX1dFQlBfREVURUNUSU9OPSR7SU1HUFJPWFlfRU5BQkxFX1dFQlBfREVURUNUSU9OOi10cnVlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vdm9sdW1lcy9zdG9yYWdlOi92YXIvbGliL3N0b3JhZ2UnCiAgc3VwYWJhc2UtbWV0YToKICAgIGltYWdlOiAnc3VwYWJhc2UvcG9zdGdyZXMtbWV0YTp2MC44My4yJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQR19NRVRBX1BPUlQ9ODA4MAogICAgICAtICdQR19NRVRBX0RCX0hPU1Q9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdQR19NRVRBX0RCX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUEdfTUVUQV9EQl9OQU1FPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSBQR19NRVRBX0RCX1VTRVI9c3VwYWJhc2VfYWRtaW4KICAgICAgLSAnUEdfTUVUQV9EQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogIHN1cGFiYXNlLWVkZ2UtZnVuY3Rpb25zOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9lZGdlLXJ1bnRpbWU6djEuNTguMycKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGVjaG8KICAgICAgICAtICdFZGdlIEZ1bmN0aW9ucyBpcyBoZWFsdGh5JwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ1NVUEFCQVNFX1VSTD0ke1NFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkd9JwogICAgICAtICdTVVBBQkFTRV9BTk9OX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX1NFUlZJQ0VfUk9MRV9LRVk9JHtTRVJWSUNFX1NVUEFCQVNFU0VSVklDRV9LRVl9JwogICAgICAtICdTVVBBQkFTRV9EQl9VUkw9cG9zdGdyZXNxbDovL3Bvc3RncmVzOiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AJHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9OiR7UE9TVEdSRVNfUE9SVDotNTQzMn0vJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdWRVJJRllfSldUPSR7RlVOQ1RJT05TX1ZFUklGWV9KV1Q6LWZhbHNlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vdm9sdW1lcy9mdW5jdGlvbnM6L2hvbWUvZGVuby9mdW5jdGlvbnMnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZnVuY3Rpb25zL21haW4vaW5kZXgudHMKICAgICAgICB0YXJnZXQ6IC9ob21lL2Rlbm8vZnVuY3Rpb25zL21haW4vaW5kZXgudHMKICAgICAgICBjb250ZW50OiAiaW1wb3J0IHsgc2VydmUgfSBmcm9tICdodHRwczovL2Rlbm8ubGFuZC9zdGRAMC4xMzEuMC9odHRwL3NlcnZlci50cydcbmltcG9ydCAqIGFzIGpvc2UgZnJvbSAnaHR0cHM6Ly9kZW5vLmxhbmQveC9qb3NlQHY0LjE0LjQvaW5kZXgudHMnXG5cbmNvbnNvbGUubG9nKCdtYWluIGZ1bmN0aW9uIHN0YXJ0ZWQnKVxuXG5jb25zdCBKV1RfU0VDUkVUID0gRGVuby5lbnYuZ2V0KCdKV1RfU0VDUkVUJylcbmNvbnN0IFZFUklGWV9KV1QgPSBEZW5vLmVudi5nZXQoJ1ZFUklGWV9KV1QnKSA9PT0gJ3RydWUnXG5cbmZ1bmN0aW9uIGdldEF1dGhUb2tlbihyZXE6IFJlcXVlc3QpIHtcbiAgY29uc3QgYXV0aEhlYWRlciA9IHJlcS5oZWFkZXJzLmdldCgnYXV0aG9yaXphdGlvbicpXG4gIGlmICghYXV0aEhlYWRlcikge1xuICAgIHRocm93IG5ldyBFcnJvcignTWlzc2luZyBhdXRob3JpemF0aW9uIGhlYWRlcicpXG4gIH1cbiAgY29uc3QgW2JlYXJlciwgdG9rZW5dID0gYXV0aEhlYWRlci5zcGxpdCgnICcpXG4gIGlmIChiZWFyZXIgIT09ICdCZWFyZXInKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBBdXRoIGhlYWRlciBpcyBub3QgJ0JlYXJlciB7dG9rZW59J2ApXG4gIH1cbiAgcmV0dXJuIHRva2VuXG59XG5cbmFzeW5jIGZ1bmN0aW9uIHZlcmlmeUpXVChqd3Q6IHN0cmluZyk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICBjb25zdCBlbmNvZGVyID0gbmV3IFRleHRFbmNvZGVyKClcbiAgY29uc3Qgc2VjcmV0S2V5ID0gZW5jb2Rlci5lbmNvZGUoSldUX1NFQ1JFVClcbiAgdHJ5IHtcbiAgICBhd2FpdCBqb3NlLmp3dFZlcmlmeShqd3QsIHNlY3JldEtleSlcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgY29uc29sZS5lcnJvcihlcnIpXG4gICAgcmV0dXJuIGZhbHNlXG4gIH1cbiAgcmV0dXJuIHRydWVcbn1cblxuc2VydmUoYXN5bmMgKHJlcTogUmVxdWVzdCkgPT4ge1xuICBpZiAocmVxLm1ldGhvZCAhPT0gJ09QVElPTlMnICYmIFZFUklGWV9KV1QpIHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgdG9rZW4gPSBnZXRBdXRoVG9rZW4ocmVxKVxuICAgICAgY29uc3QgaXNWYWxpZEpXVCA9IGF3YWl0IHZlcmlmeUpXVCh0b2tlbilcblxuICAgICAgaWYgKCFpc1ZhbGlkSldUKSB7XG4gICAgICAgIHJldHVybiBuZXcgUmVzcG9uc2UoSlNPTi5zdHJpbmdpZnkoeyBtc2c6ICdJbnZhbGlkIEpXVCcgfSksIHtcbiAgICAgICAgICBzdGF0dXM6IDQwMSxcbiAgICAgICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICAgICAgfSlcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBjb25zb2xlLmVycm9yKGUpXG4gICAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KHsgbXNnOiBlLnRvU3RyaW5nKCkgfSksIHtcbiAgICAgICAgc3RhdHVzOiA0MDEsXG4gICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LFxuICAgICAgfSlcbiAgICB9XG4gIH1cblxuICBjb25zdCB1cmwgPSBuZXcgVVJMKHJlcS51cmwpXG4gIGNvbnN0IHsgcGF0aG5hbWUgfSA9IHVybFxuICBjb25zdCBwYXRoX3BhcnRzID0gcGF0aG5hbWUuc3BsaXQoJy8nKVxuICBjb25zdCBzZXJ2aWNlX25hbWUgPSBwYXRoX3BhcnRzWzFdXG5cbiAgaWYgKCFzZXJ2aWNlX25hbWUgfHwgc2VydmljZV9uYW1lID09PSAnJykge1xuICAgIGNvbnN0IGVycm9yID0geyBtc2c6ICdtaXNzaW5nIGZ1bmN0aW9uIG5hbWUgaW4gcmVxdWVzdCcgfVxuICAgIHJldHVybiBuZXcgUmVzcG9uc2UoSlNPTi5zdHJpbmdpZnkoZXJyb3IpLCB7XG4gICAgICBzdGF0dXM6IDQwMCxcbiAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LFxuICAgIH0pXG4gIH1cblxuICBjb25zdCBzZXJ2aWNlUGF0aCA9IGAvaG9tZS9kZW5vL2Z1bmN0aW9ucy8ke3NlcnZpY2VfbmFtZX1gXG4gIGNvbnNvbGUuZXJyb3IoYHNlcnZpbmcgdGhlIHJlcXVlc3Qgd2l0aCAke3NlcnZpY2VQYXRofWApXG5cbiAgY29uc3QgbWVtb3J5TGltaXRNYiA9IDE1MFxuICBjb25zdCB3b3JrZXJUaW1lb3V0TXMgPSAxICogNjAgKiAxMDAwXG4gIGNvbnN0IG5vTW9kdWxlQ2FjaGUgPSBmYWxzZVxuICBjb25zdCBpbXBvcnRNYXBQYXRoID0gbnVsbFxuICBjb25zdCBlbnZWYXJzT2JqID0gRGVuby5lbnYudG9PYmplY3QoKVxuICBjb25zdCBlbnZWYXJzID0gT2JqZWN0LmtleXMoZW52VmFyc09iaikubWFwKChrKSA9PiBbaywgZW52VmFyc09ialtrXV0pXG5cbiAgdHJ5IHtcbiAgICBjb25zdCB3b3JrZXIgPSBhd2FpdCBFZGdlUnVudGltZS51c2VyV29ya2Vycy5jcmVhdGUoe1xuICAgICAgc2VydmljZVBhdGgsXG4gICAgICBtZW1vcnlMaW1pdE1iLFxuICAgICAgd29ya2VyVGltZW91dE1zLFxuICAgICAgbm9Nb2R1bGVDYWNoZSxcbiAgICAgIGltcG9ydE1hcFBhdGgsXG4gICAgICBlbnZWYXJzLFxuICAgIH0pXG4gICAgcmV0dXJuIGF3YWl0IHdvcmtlci5mZXRjaChyZXEpXG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zdCBlcnJvciA9IHsgbXNnOiBlLnRvU3RyaW5nKCkgfVxuICAgIHJldHVybiBuZXcgUmVzcG9uc2UoSlNPTi5zdHJpbmdpZnkoZXJyb3IpLCB7XG4gICAgICBzdGF0dXM6IDUwMCxcbiAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LFxuICAgIH0pXG4gIH1cbn0pXG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZnVuY3Rpb25zL2hlbGxvL2luZGV4LnRzCiAgICAgICAgdGFyZ2V0OiAvaG9tZS9kZW5vL2Z1bmN0aW9ucy9oZWxsby9pbmRleC50cwogICAgICAgIGNvbnRlbnQ6ICIvLyBGb2xsb3cgdGhpcyBzZXR1cCBndWlkZSB0byBpbnRlZ3JhdGUgdGhlIERlbm8gbGFuZ3VhZ2Ugc2VydmVyIHdpdGggeW91ciBlZGl0b3I6XG4vLyBodHRwczovL2Rlbm8ubGFuZC9tYW51YWwvZ2V0dGluZ19zdGFydGVkL3NldHVwX3lvdXJfZW52aXJvbm1lbnRcbi8vIFRoaXMgZW5hYmxlcyBhdXRvY29tcGxldGUsIGdvIHRvIGRlZmluaXRpb24sIGV0Yy5cblxuaW1wb3J0IHsgc2VydmUgfSBmcm9tIFwiaHR0cHM6Ly9kZW5vLmxhbmQvc3RkQDAuMTc3LjEvaHR0cC9zZXJ2ZXIudHNcIlxuXG5zZXJ2ZShhc3luYyAoKSA9PiB7XG4gIHJldHVybiBuZXcgUmVzcG9uc2UoXG4gICAgYFwiSGVsbG8gZnJvbSBFZGdlIEZ1bmN0aW9ucyFcImAsXG4gICAgeyBoZWFkZXJzOiB7IFwiQ29udGVudC1UeXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiIH0gfSxcbiAgKVxufSlcblxuLy8gVG8gaW52b2tlOlxuLy8gY3VybCAnaHR0cDovL2xvY2FsaG9zdDo8S09OR19IVFRQX1BPUlQ+L2Z1bmN0aW9ucy92MS9oZWxsbycgXFxcbi8vICAgLS1oZWFkZXIgJ0F1dGhvcml6YXRpb246IEJlYXJlciA8YW5vbi9zZXJ2aWNlX3JvbGUgQVBJIGtleT4nXG4iCiAgICBjb21tYW5kOgogICAgICAtIHN0YXJ0CiAgICAgIC0gJy0tbWFpbi1zZXJ2aWNlJwogICAgICAtIC9ob21lL2Rlbm8vZnVuY3Rpb25zL21haW4KICBzdXBhYmFzZS1zdXBhdmlzb3I6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3N1cGF2aXNvcjoxLjEuNTYnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1zU2ZMJwogICAgICAgIC0gJy1vJwogICAgICAgIC0gL2Rldi9udWxsCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo0MDAwL2FwaS9oZWFsdGgnCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAxMAogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT09MRVJfVEVOQU5UX0lEPWRldl90ZW5hbnQKICAgICAgLSBQT09MRVJfUE9PTF9NT0RFPXRyYW5zYWN0aW9uCiAgICAgIC0gJ1BPT0xFUl9ERUZBVUxUX1BPT0xfU0laRT0ke1BPT0xFUl9ERUZBVUxUX1BPT0xfU0laRTotMjB9JwogICAgICAtICdQT09MRVJfTUFYX0NMSUVOVF9DT05OPSR7UE9PTEVSX01BWF9DTElFTlRfQ09OTjotMTAwfScKICAgICAgLSBQT1JUPTQwMDAKICAgICAgLSAnUE9TVEdSRVNfUE9SVD0ke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9JwogICAgICAtICdQT1NUR1JFU19IT1NUTkFNRT0ke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnREFUQUJBU0VfVVJMPWVjdG86Ly9zdXBhYmFzZV9hZG1pbjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9L19zdXBhYmFzZScKICAgICAgLSBDTFVTVEVSX1BPU1RHUkVTPXRydWUKICAgICAgLSAnU0VDUkVUX0tFWV9CQVNFPSR7U0VSVklDRV9QQVNTV09SRF9TVVBBVklTT1JTRUNSRVR9JwogICAgICAtICdWQVVMVF9FTkNfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9WQVVMVEVOQ30nCiAgICAgIC0gJ0FQSV9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdNRVRSSUNTX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gUkVHSU9OPWxvY2FsCiAgICAgIC0gJ0VSTF9BRkxBR1M9LXByb3RvX2Rpc3QgaW5ldF90Y3AnCiAgICBjb21tYW5kOgogICAgICAtIC9iaW4vc2gKICAgICAgLSAnLWMnCiAgICAgIC0gJy9hcHAvYmluL21pZ3JhdGUgJiYgL2FwcC9iaW4vc3VwYXZpc29yIGV2YWwgIiQkKGNhdCAvZXRjL3Bvb2xlci9wb29sZXIuZXhzKSIgJiYgL2FwcC9iaW4vc2VydmVyJwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9wb29sZXIvcG9vbGVyLmV4cwogICAgICAgIHRhcmdldDogL2V0Yy9wb29sZXIvcG9vbGVyLmV4cwogICAgICAgIGNvbnRlbnQ6ICJ7Om9rLCBffSA9IEFwcGxpY2F0aW9uLmVuc3VyZV9hbGxfc3RhcnRlZCg6c3VwYXZpc29yKVxuezpvaywgdmVyc2lvbn0gPVxuICAgIGNhc2UgU3VwYXZpc29yLlJlcG8ucXVlcnkhKFwic2VsZWN0IHZlcnNpb24oKVwiKSBkb1xuICAgICV7cm93czogW1t2ZXJdXX0gLT4gU3VwYXZpc29yLkhlbHBlcnMucGFyc2VfcGdfdmVyc2lvbih2ZXIpXG4gICAgXyAtPiBuaWxcbiAgICBlbmRcbnBhcmFtcyA9ICV7XG4gICAgXCJleHRlcm5hbF9pZFwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9PTEVSX1RFTkFOVF9JRFwiKSxcbiAgICBcImRiX2hvc3RcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPU1RHUkVTX0hPU1ROQU1FXCIpLFxuICAgIFwiZGJfcG9ydFwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9TVEdSRVNfUE9SVFwiKSB8PiBTdHJpbmcudG9faW50ZWdlcigpLFxuICAgIFwiZGJfZGF0YWJhc2VcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPU1RHUkVTX0RCXCIpLFxuICAgIFwicmVxdWlyZV91c2VyXCIgPT4gZmFsc2UsXG4gICAgXCJhdXRoX3F1ZXJ5XCIgPT4gXCJTRUxFQ1QgKiBGUk9NIHBnYm91bmNlci5nZXRfYXV0aCgkMSlcIixcbiAgICBcImRlZmF1bHRfbWF4X2NsaWVudHNcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPT0xFUl9NQVhfQ0xJRU5UX0NPTk5cIiksXG4gICAgXCJkZWZhdWx0X3Bvb2xfc2l6ZVwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9PTEVSX0RFRkFVTFRfUE9PTF9TSVpFXCIpLFxuICAgIFwiZGVmYXVsdF9wYXJhbWV0ZXJfc3RhdHVzXCIgPT4gJXtcInNlcnZlcl92ZXJzaW9uXCIgPT4gdmVyc2lvbn0sXG4gICAgXCJ1c2Vyc1wiID0+IFsle1xuICAgIFwiZGJfdXNlclwiID0+IFwicGdib3VuY2VyXCIsXG4gICAgXCJkYl9wYXNzd29yZFwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9TVEdSRVNfUEFTU1dPUkRcIiksXG4gICAgXCJtb2RlX3R5cGVcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPT0xFUl9QT09MX01PREVcIiksXG4gICAgXCJwb29sX3NpemVcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPT0xFUl9ERUZBVUxUX1BPT0xfU0laRVwiKSxcbiAgICBcImlzX21hbmFnZXJcIiA9PiB0cnVlXG4gICAgfV1cbn1cblxudGVuYW50ID0gU3VwYXZpc29yLlRlbmFudHMuZ2V0X3RlbmFudF9ieV9leHRlcm5hbF9pZChwYXJhbXNbXCJleHRlcm5hbF9pZFwiXSlcblxuaWYgdGVuYW50IGRvXG4gIHs6b2ssIF99ID0gU3VwYXZpc29yLlRlbmFudHMudXBkYXRlX3RlbmFudCh0ZW5hbnQsIHBhcmFtcylcbmVsc2VcbiAgezpvaywgX30gPSBTdXBhdmlzb3IuVGVuYW50cy5jcmVhdGVfdGVuYW50KHBhcmFtcylcbmVuZFxuIgo=", "tags": [ "firebase", "alternative", From a836d78f0bb7716134f2424d917636046b41e91a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 14:07:50 +0100 Subject: [PATCH 084/395] remove unnecessary function --- app/Livewire/Help.php | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/app/Livewire/Help.php b/app/Livewire/Help.php index aa354e94e..f51527fbe 100644 --- a/app/Livewire/Help.php +++ b/app/Livewire/Help.php @@ -56,35 +56,3 @@ class Help extends Component return view('livewire.help')->layout('layouts.app'); } } - -function set_transanctional_email_settings($settings = null) -{ - if (is_null($settings)) { - $settings = instanceSettings(); - } - - if ($settings->resend_enabled) { - config()->set('mail.default', 'resend'); - config()->set('resend.api_key', $settings->resend_api_key); - - return 'resend'; - } - - if ($settings->smtp_enabled) { - config()->set('mail.default', 'smtp'); - config()->set('mail.mailers.smtp', [ - 'transport' => 'smtp', - 'host' => $settings->smtp_host, - 'port' => $settings->smtp_port, - 'encryption' => $settings->smtp_encryption === 'none' ? null : $settings->smtp_encryption, - 'username' => $settings->smtp_username, - 'password' => $settings->smtp_password, - 'timeout' => $settings->smtp_timeout, - 'local_domain' => null, - ]); - - return 'smtp'; - } - - return false; -} From 11fb5a9e32a86da4f77a654bd2e91cd8dcc47dc6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 14:34:18 +0100 Subject: [PATCH 085/395] fix: service generate includes yml files as well (haha) --- app/Console/Commands/ServicesGenerate.php | 5 ++++- templates/service-templates.json | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/ServicesGenerate.php b/app/Console/Commands/ServicesGenerate.php index 1559e5f6d..06287c1b8 100644 --- a/app/Console/Commands/ServicesGenerate.php +++ b/app/Console/Commands/ServicesGenerate.php @@ -20,7 +20,10 @@ class ServicesGenerate extends Command public function handle(): int { - $serviceTemplatesJson = collect(glob(base_path('templates/compose/*.yaml'))) + $serviceTemplatesJson = collect(array_merge( + glob(base_path('templates/compose/*.yaml')), + glob(base_path('templates/compose/*.yml')) + )) ->mapWithKeys(function ($file): array { $file = basename($file); $parsed = $this->processFile($file); diff --git a/templates/service-templates.json b/templates/service-templates.json index 4f1568613..faa4a6297 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -3040,5 +3040,20 @@ "logo": "svgs/zipline.png", "minversion": "0.0.0", "port": "3000" + }, + "convertx": { + "documentation": "https://github.com/C4illin/ConvertX?utm_source=coolify.io", + "slogan": "A self-hosted online file converter. Supports over a thousand different formats.", + "compose": "c2VydmljZXM6CiAgY29udmVydHg6CiAgICBpbWFnZTogJ2doY3IuaW8vYzRpbGxpbi9jb252ZXJ0eDpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQ09OVkVSVFgKICAgICAgLSAnQUNDT1VOVF9SRUdJU1RSQVRJT049JHtBQ0NPVU5UX1JFR0lTVFJBVElPTjotZmFsc2V9JwogICAgICAtICdIVFRQX0FMTE9XRUQ9JHtIVFRQX0FMTE9XRUQ6LXRydWV9JwogICAgICAtICdBTExPV19VTkFVVEhFTlRJQ0FURUQ9JHtBTExPV19VTkFVVEhFTlRJQ0FURUQ6LWZhbHNlfScKICAgICAgLSAnQVVUT19ERUxFVEVfRVZFUllfTl9IT1VSUz0ke0FVVE9fREVMRVRFX0VWRVJZX05fSE9VUlM6LTI0fScKICAgICAgLSAnSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfQ09OVkVSVFhKV1RTRUNSRVR9JwogICAgdm9sdW1lczoKICAgICAgLSAnY29udmVydHhfZGF0YTovYXBwL2RhdGEnCg==", + "tags": [ + "converter", + "file", + "documents", + "files", + "directories" + ], + "logo": "svgs/convertx.png", + "minversion": "0.0.0", + "port": "3000" } } From f0a44f4022d7ed9609e9cebc85211e0103ff66a9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 14:37:44 +0100 Subject: [PATCH 086/395] fix: servercheckJob should run every 5 minutes on cloud --- app/Console/Kernel.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 832dcf58b..fe5e2c407 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -141,8 +141,12 @@ class Kernel extends ConsoleKernel if (validate_timezone($serverTimezone) === false) { $serverTimezone = config('app.timezone'); } - $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer(); - // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer(); + if (isCloud()) { + $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer(); + } else { + $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer(); + } + // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer(); // Check storage usage every 10 minutes if Sentinel does not activated $this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer(); From d87cb67229f08c584a23aba8045e1631148b26fb Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:10:21 +0100 Subject: [PATCH 087/395] fix body section --- resources/views/layouts/base.blade.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 5213479c8..b581fb3c5 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -43,9 +43,8 @@ @endauth -@section('body') - - + + @section('body') - -@show + @show + From 504caefb8c82a2e9185aee80695ec81dbd647e5a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 15:33:38 +0100 Subject: [PATCH 088/395] fix: new resource icons --- app/Livewire/Project/New/Select.php | 16 ++++++++-------- .../views/livewire/project/new/select.blade.php | 10 ++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index 3dedc11af..f0a484d47 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -144,14 +144,14 @@ class Select extends Component 'id' => 'postgresql', 'name' => 'PostgreSQL', 'description' => 'PostgreSQL is an object-relational database known for its robustness, advanced features, and strong standards compliance.', - 'logo' => ' + 'logo' => ' ', ], [ 'id' => 'mysql', 'name' => 'MySQL', 'description' => 'MySQL is an open-source relational database management system. ', - 'logo' => ' + 'logo' => ' @@ -162,37 +162,37 @@ class Select extends Component 'id' => 'mariadb', 'name' => 'MariaDB', 'description' => 'MariaDB is a community-developed, commercially supported fork of the MySQL relational database management system, intended to remain free and open-source.', - 'logo' => '', + 'logo' => '', ], [ 'id' => 'redis', 'name' => 'Redis', 'description' => 'Redis is a source-available, in-memory storage, used as a distributed, in-memory key–value database, cache and message broker, with optional durability.', - 'logo' => '', + 'logo' => '', ], [ 'id' => 'keydb', 'name' => 'KeyDB', 'description' => 'KeyDB is a database that offers high performance, low latency, and scalability for various data structures and workloads.', - 'logo' => '
      ', + 'logo' => '
      ', ], [ 'id' => 'dragonfly', 'name' => 'Dragonfly', 'description' => 'Dragonfly DB is a drop-in Redis replacement that delivers 25x more throughput and 12x faster snapshotting than Redis.', - 'logo' => '
      ', + 'logo' => '
      ', ], [ 'id' => 'mongodb', 'name' => 'MongoDB', 'description' => 'MongoDB is a source-available, cross-platform, document-oriented database program.', - 'logo' => '', + 'logo' => '', ], [ 'id' => 'clickhouse', 'name' => 'ClickHouse', 'description' => 'ClickHouse is a column-oriented database that supports real-time analytics, business intelligence, observability, ML and GenAI, and more.', - 'logo' => '
      ', + 'logo' => '
      ', ], ]; diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index 78d3a3443..0a9614ea0 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -30,8 +30,7 @@ - @@ -47,8 +46,7 @@
    @@ -100,7 +98,7 @@