From 342e8e765d8b4e27633da70539bdf95f7099713d Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Thu, 25 Dec 2025 07:56:19 +0000 Subject: [PATCH 001/434] feat: add command healthcheck type --- app/Jobs/ApplicationDeploymentJob.php | 11 +++- app/Livewire/Project/Shared/HealthChecks.php | 18 ++++++ app/Models/Application.php | 2 + ..._cmd_healthcheck_to_applications_table.php | 44 ++++++++++++++ openapi.json | 13 +++++ openapi.yaml | 10 ++++ .../project/shared/health-checks.blade.php | 57 +++++++++++++------ templates/service-templates-latest.json | 6 +- templates/service-templates.json | 6 +- 9 files changed, 143 insertions(+), 24 deletions(-) create mode 100644 database/migrations/2025_12_25_072315_add_cmd_healthcheck_to_applications_table.php diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 56a29276b..7ee7fe0e2 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1798,7 +1798,8 @@ private function health_check() $counter = 1; $this->application_deployment_queue->addLogEntry('Waiting for healthcheck to pass on the new container.'); if ($this->full_healthcheck_url && ! $this->application->custom_healthcheck_found) { - $this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}"); + $healthcheckLabel = $this->application->health_check_type === 'cmd' ? 'Healthcheck command' : 'Healthcheck URL'; + $this->application_deployment_queue->addLogEntry("{$healthcheckLabel} (inside the container): {$this->full_healthcheck_url}"); } $this->application_deployment_queue->addLogEntry("Waiting for the start period ({$this->application->health_check_start_period} seconds) before starting healthcheck."); $sleeptime = 0; @@ -2749,6 +2750,14 @@ private function generate_local_persistent_volumes_only_volume_names() private function generate_healthcheck_commands() { + // Handle CMD type healthcheck + if ($this->application->health_check_type === 'cmd' && ! empty($this->application->health_check_command)) { + $this->full_healthcheck_url = $this->application->health_check_command; + + return $this->application->health_check_command; + } + + // HTTP type healthcheck (default) if (! $this->application->health_check_port) { $health_check_port = $this->application->ports_exposes_array[0]; } else { diff --git a/app/Livewire/Project/Shared/HealthChecks.php b/app/Livewire/Project/Shared/HealthChecks.php index 05f786690..ec4f494f8 100644 --- a/app/Livewire/Project/Shared/HealthChecks.php +++ b/app/Livewire/Project/Shared/HealthChecks.php @@ -16,6 +16,12 @@ class HealthChecks extends Component #[Validate(['boolean'])] public bool $healthCheckEnabled = false; + #[Validate(['string', 'in:http,cmd'])] + public string $healthCheckType = 'http'; + + #[Validate(['nullable', 'string'])] + public ?string $healthCheckCommand = null; + #[Validate(['string'])] public string $healthCheckMethod; @@ -54,6 +60,8 @@ class HealthChecks extends Component protected $rules = [ 'healthCheckEnabled' => 'boolean', + 'healthCheckType' => 'string|in:http,cmd', + 'healthCheckCommand' => 'nullable|string', 'healthCheckPath' => 'string', 'healthCheckPort' => 'nullable|string', 'healthCheckHost' => 'string', @@ -81,6 +89,8 @@ public function syncData(bool $toModel = false): void // Sync to model $this->resource->health_check_enabled = $this->healthCheckEnabled; + $this->resource->health_check_type = $this->healthCheckType; + $this->resource->health_check_command = $this->healthCheckCommand; $this->resource->health_check_method = $this->healthCheckMethod; $this->resource->health_check_scheme = $this->healthCheckScheme; $this->resource->health_check_host = $this->healthCheckHost; @@ -98,6 +108,8 @@ public function syncData(bool $toModel = false): void } else { // Sync from model $this->healthCheckEnabled = $this->resource->health_check_enabled; + $this->healthCheckType = $this->resource->health_check_type ?? 'http'; + $this->healthCheckCommand = $this->resource->health_check_command; $this->healthCheckMethod = $this->resource->health_check_method; $this->healthCheckScheme = $this->resource->health_check_scheme; $this->healthCheckHost = $this->resource->health_check_host; @@ -119,6 +131,8 @@ public function instantSave() // Sync component properties to model $this->resource->health_check_enabled = $this->healthCheckEnabled; + $this->resource->health_check_type = $this->healthCheckType; + $this->resource->health_check_command = $this->healthCheckCommand; $this->resource->health_check_method = $this->healthCheckMethod; $this->resource->health_check_scheme = $this->healthCheckScheme; $this->resource->health_check_host = $this->healthCheckHost; @@ -143,6 +157,8 @@ public function submit() // Sync component properties to model $this->resource->health_check_enabled = $this->healthCheckEnabled; + $this->resource->health_check_type = $this->healthCheckType; + $this->resource->health_check_command = $this->healthCheckCommand; $this->resource->health_check_method = $this->healthCheckMethod; $this->resource->health_check_scheme = $this->healthCheckScheme; $this->resource->health_check_host = $this->healthCheckHost; @@ -171,6 +187,8 @@ public function toggleHealthcheck() // Sync component properties to model $this->resource->health_check_enabled = $this->healthCheckEnabled; + $this->resource->health_check_type = $this->healthCheckType; + $this->resource->health_check_command = $this->healthCheckCommand; $this->resource->health_check_method = $this->healthCheckMethod; $this->resource->health_check_scheme = $this->healthCheckScheme; $this->resource->health_check_host = $this->healthCheckHost; diff --git a/app/Models/Application.php b/app/Models/Application.php index 5006d0ff8..ce8a10dd0 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -60,6 +60,8 @@ 'health_check_timeout' => ['type' => 'integer', 'description' => 'Health check timeout in seconds.'], 'health_check_retries' => ['type' => 'integer', 'description' => 'Health check retries count.'], 'health_check_start_period' => ['type' => 'integer', 'description' => 'Health check start period in seconds.'], + 'health_check_type' => ['type' => 'string', 'description' => 'Health check type: http or cmd.', 'enum' => ['http', 'cmd']], + 'health_check_command' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check command for CMD type.'], 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit.'], 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit.'], 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness.'], diff --git a/database/migrations/2025_12_25_072315_add_cmd_healthcheck_to_applications_table.php b/database/migrations/2025_12_25_072315_add_cmd_healthcheck_to_applications_table.php new file mode 100644 index 000000000..cd9d98a1c --- /dev/null +++ b/database/migrations/2025_12_25_072315_add_cmd_healthcheck_to_applications_table.php @@ -0,0 +1,44 @@ +text('health_check_type')->default('http')->after('health_check_enabled'); + }); + } + + if (! Schema::hasColumn('applications', 'health_check_command')) { + Schema::table('applications', function (Blueprint $table) { + $table->text('health_check_command')->nullable()->after('health_check_type'); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (Schema::hasColumn('applications', 'health_check_type')) { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('health_check_type'); + }); + } + + if (Schema::hasColumn('applications', 'health_check_command')) { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('health_check_command'); + }); + } + } +}; diff --git a/openapi.json b/openapi.json index fe8ca863e..3270e13b2 100644 --- a/openapi.json +++ b/openapi.json @@ -10173,6 +10173,19 @@ "type": "integer", "description": "Health check start period in seconds." }, + "health_check_type": { + "type": "string", + "description": "Health check type: http or cmd.", + "enum": [ + "http", + "cmd" + ] + }, + "health_check_command": { + "type": "string", + "nullable": true, + "description": "Health check command for CMD type." + }, "limits_memory": { "type": "string", "description": "Memory limit." diff --git a/openapi.yaml b/openapi.yaml index a7faa8c72..7511e0c91 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -6416,6 +6416,16 @@ components: health_check_start_period: type: integer description: 'Health check start period in seconds.' + health_check_type: + type: string + description: 'Health check type: http or cmd.' + enum: + - http + - cmd + health_check_command: + type: string + nullable: true + description: 'Health check command for CMD type.' limits_memory: type: string description: 'Memory limit.' diff --git a/resources/views/livewire/project/shared/health-checks.blade.php b/resources/views/livewire/project/shared/health-checks.blade.php index 730353c87..a0027c62c 100644 --- a/resources/views/livewire/project/shared/health-checks.blade.php +++ b/resources/views/livewire/project/shared/health-checks.blade.php @@ -20,25 +20,48 @@

A custom health check has been detected. If you enable this health check, it will disable the custom one and use this instead.

@endif + + {{-- Healthcheck Type Selector --}}
- - - + + + - - - - - - - -
-
- -
+ + @if ($healthCheckType === 'http') + {{-- HTTP Healthcheck Fields --}} +
+ + + + + + + + + + + +
+
+ + +
+ @else + {{-- CMD Healthcheck Fields --}} +
+ +
+ @endif + + {{-- Common timing fields (used by both types) --}}
@@ -49,4 +72,4 @@ label="Start Period (s)" required />
- \ No newline at end of file + diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json index c3e33b582..1986e17d3 100644 --- a/templates/service-templates-latest.json +++ b/templates/service-templates-latest.json @@ -851,7 +851,7 @@ "dolibarr": { "documentation": "https://www.dolibarr.org/documentation-home.php?utm_source=coolify.io", "slogan": "Dolibarr is a modern software package to manage your organization's activity (contacts, quotes, invoices, orders, stocks, agenda, hr, expense reports, accountancy, ecm, manufacturing, ...).", - "compose": "c2VydmljZXM6CiAgZG9saWJhcnI6CiAgICBpbWFnZTogJ2RvbGliYXJyL2RvbGliYXJyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX0RPTElCQVJSXzgwCiAgICAgIC0gJ1dXV19VU0VSX0lEPSR7V1dXX1VTRVJfSUQ6LTEwMDB9JwogICAgICAtICdXV1dfR1JPVVBfSUQ9JHtXV1dfR1JPVVBfSUQ6LTEwMDB9JwogICAgICAtIERPTElfREJfSE9TVD1tYXJpYWRiCiAgICAgIC0gJ0RPTElfREJfTkFNRT0ke01ZU1FMX0RBVEFCQVNFOi1kb2xpYmFyci1kYn0nCiAgICAgIC0gJ0RPTElfREJfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NWVNRTH0nCiAgICAgIC0gJ0RPTElfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMfScKICAgICAgLSAnRE9MSV9VUkxfUk9PVD0ke1NFUlZJQ0VfVVJMX0RPTElCQVJSfScKICAgICAgLSAnRE9MSV9BRE1JTl9MT0dJTj0ke1NFUlZJQ0VfVVNFUl9ET0xJQkFSUn0nCiAgICAgIC0gJ0RPTElfQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0RPTElCQVJSfScKICAgICAgLSAnRE9MSV9DUk9OPSR7RE9MSV9DUk9OOi0wfScKICAgICAgLSAnRE9MSV9JTklUX0RFTU89JHtET0xJX0lOSVRfREVNTzotMH0nCiAgICAgIC0gJ0RPTElfQ09NUEFOWV9OQU1FPSR7RE9MSV9DT01QQU5ZX05BTUU6LU15QmlnQ29tcGFueX0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBtYXJpYWRiOgogICAgaW1hZ2U6ICdtYXJpYWRiOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtICdNWVNRTF9EQVRBQkFTRT0ke01ZU1FMX0RBVEFCQVNFOi1kb2xpYmFyci1kYn0nCiAgICAgIC0gJ01ZU1FMX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUx9JwogICAgICAtICdNWVNRTF9ST09UX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9ST09UfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RvbGliYXJyX21hcmlhZGJfZGF0YTovdmFyL2xpYi9teXNxbCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBoZWFsdGhjaGVjay5zaAogICAgICAgIC0gJy0tY29ubmVjdCcKICAgICAgICAtICctLWlubm9kYl9pbml0aWFsaXplZCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=", + "compose": "c2VydmljZXM6CiAgZG9saWJhcnI6CiAgICBpbWFnZTogJ2RvbGliYXJyL2RvbGliYXJyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX0RPTElCQVJSXzgwCiAgICAgIC0gJ1dXV19VU0VSX0lEPSR7V1dXX1VTRVJfSUQ6LTEwMDB9JwogICAgICAtICdXV1dfR1JPVVBfSUQ9JHtXV1dfR1JPVVBfSUQ6LTEwMDB9JwogICAgICAtIERPTElfREJfSE9TVD1tYXJpYWRiCiAgICAgIC0gJ0RPTElfREJfTkFNRT0ke01ZU1FMX0RBVEFCQVNFOi1kb2xpYmFyci1kYn0nCiAgICAgIC0gJ0RPTElfREJfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NWVNRTH0nCiAgICAgIC0gJ0RPTElfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMfScKICAgICAgLSAnRE9MSV9VUkxfUk9PVD0ke1NFUlZJQ0VfVVJMX0RPTElCQVJSfScKICAgICAgLSAnRE9MSV9BRE1JTl9MT0dJTj0ke1NFUlZJQ0VfVVNFUl9ET0xJQkFSUn0nCiAgICAgIC0gJ0RPTElfQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0RPTElCQVJSfScKICAgICAgLSAnRE9MSV9DUk9OPSR7RE9MSV9DUk9OOi0wfScKICAgICAgLSAnRE9MSV9JTklUX0RFTU89JHtET0xJX0lOSVRfREVNTzotMH0nCiAgICAgIC0gJ0RPTElfQ09NUEFOWV9OQU1FPSR7RE9MSV9DT01QQU5ZX05BTUU6LU15QmlnQ29tcGFueX0nCiAgICB2b2x1bWVzOgogICAgICAtICdkb2xpYmFycl9kb2NzOi92YXIvd3d3L2RvY3VtZW50cycKICAgICAgLSAnZG9saWJhcnJfY3VzdG9tOi92YXIvd3d3L2h0bWwvY3VzdG9tJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTVlTUUxfREFUQUJBU0U9JHtNWVNRTF9EQVRBQkFTRTotZG9saWJhcnItZGJ9JwogICAgICAtICdNWVNRTF9VU0VSPSR7U0VSVklDRV9VU0VSX01ZU1FMfScKICAgICAgLSAnTVlTUUxfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMfScKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUk9PVH0nCiAgICB2b2x1bWVzOgogICAgICAtICdkb2xpYmFycl9tYXJpYWRiX2RhdGE6L3Zhci9saWIvbXlzcWwnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gaGVhbHRoY2hlY2suc2gKICAgICAgICAtICctLWNvbm5lY3QnCiAgICAgICAgLSAnLS1pbm5vZGJfaW5pdGlhbGl6ZWQnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", "tags": [ "crm", "erp" @@ -4088,7 +4088,7 @@ "supabase": { "documentation": "https://supabase.io?utm_source=coolify.io", "slogan": "The open source Firebase alternative.", - "compose": "c2VydmljZXM6CiAgc3VwYWJhc2Uta29uZzoKICAgIGltYWdlOiAna29uZzoyLjguMScKICAgIGVudHJ5cG9pbnQ6ICdiYXNoIC1jICcnZXZhbCAiZWNobyBcIiQkKGNhdCB+L3RlbXAueW1sKVwiIiA+IH4va29uZy55bWwgJiYgL2RvY2tlci1lbnRyeXBvaW50LnNoIGtvbmcgZG9ja2VyLXN0YXJ0JycnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX1NVUEFCQVNFS09OR184MDAwCiAgICAgIC0gJ0tPTkdfUE9SVF9NQVBTPTQ0Mzo4MDAwJwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIEtPTkdfREFUQUJBU0U9b2ZmCiAgICAgIC0gS09OR19ERUNMQVJBVElWRV9DT05GSUc9L2hvbWUva29uZy9rb25nLnltbAogICAgICAtICdLT05HX0ROU19PUkRFUj1MQVNULEEsQ05BTUUnCiAgICAgIC0gJ0tPTkdfUExVR0lOUz1yZXF1ZXN0LXRyYW5zZm9ybWVyLGNvcnMsa2V5LWF1dGgsYWNsLGJhc2ljLWF1dGgnCiAgICAgIC0gS09OR19OR0lOWF9QUk9YWV9QUk9YWV9CVUZGRVJfU0laRT0xNjBrCiAgICAgIC0gJ0tPTkdfTkdJTlhfUFJPWFlfUFJPWFlfQlVGRkVSUz02NCAxNjBrJwogICAgICAtICdTVVBBQkFTRV9BTk9OX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX1NFUlZJQ0VfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnREFTSEJPQVJEX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX0FETUlOfScKICAgICAgLSAnREFTSEJPQVJEX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0nCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2FwaS9rb25nLnltbAogICAgICAgIHRhcmdldDogL2hvbWUva29uZy90ZW1wLnltbAogICAgICAgIGNvbnRlbnQ6ICJfZm9ybWF0X3ZlcnNpb246ICcyLjEnXG5fdHJhbnNmb3JtOiB0cnVlXG5cbiMjI1xuIyMjIENvbnN1bWVycyAvIFVzZXJzXG4jIyNcbmNvbnN1bWVyczpcbiAgLSB1c2VybmFtZTogREFTSEJPQVJEXG4gIC0gdXNlcm5hbWU6IGFub25cbiAgICBrZXlhdXRoX2NyZWRlbnRpYWxzOlxuICAgICAgLSBrZXk6ICRTVVBBQkFTRV9BTk9OX0tFWVxuICAtIHVzZXJuYW1lOiBzZXJ2aWNlX3JvbGVcbiAgICBrZXlhdXRoX2NyZWRlbnRpYWxzOlxuICAgICAgLSBrZXk6ICRTVVBBQkFTRV9TRVJWSUNFX0tFWVxuXG4jIyNcbiMjIyBBY2Nlc3MgQ29udHJvbCBMaXN0XG4jIyNcbmFjbHM6XG4gIC0gY29uc3VtZXI6IGFub25cbiAgICBncm91cDogYW5vblxuICAtIGNvbnN1bWVyOiBzZXJ2aWNlX3JvbGVcbiAgICBncm91cDogYWRtaW5cblxuIyMjXG4jIyMgRGFzaGJvYXJkIGNyZWRlbnRpYWxzXG4jIyNcbmJhc2ljYXV0aF9jcmVkZW50aWFsczpcbi0gY29uc3VtZXI6IERBU0hCT0FSRFxuICB1c2VybmFtZTogJERBU0hCT0FSRF9VU0VSTkFNRVxuICBwYXNzd29yZDogJERBU0hCT0FSRF9QQVNTV09SRFxuXG5cbiMjI1xuIyMjIEFQSSBSb3V0ZXNcbiMjI1xuc2VydmljZXM6XG5cbiAgIyMgT3BlbiBBdXRoIHJvdXRlc1xuICAtIG5hbWU6IGF1dGgtdjEtb3BlblxuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS92ZXJpZnlcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtb3BlblxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvdmVyaWZ5XG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAtIG5hbWU6IGF1dGgtdjEtb3Blbi1jYWxsYmFja1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS9jYWxsYmFja1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1vcGVuLWNhbGxiYWNrXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYXV0aC92MS9jYWxsYmFja1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgLSBuYW1lOiBhdXRoLXYxLW9wZW4tYXV0aG9yaXplXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5L2F1dGhvcml6ZVxuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1vcGVuLWF1dGhvcml6ZVxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvYXV0aG9yaXplXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIFNlY3VyZSBBdXRoIHJvdXRlc1xuICAtIG5hbWU6IGF1dGgtdjFcbiAgICBfY29tbWVudDogJ0dvVHJ1ZTogL2F1dGgvdjEvKiAtPiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5LyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5L1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9hdXRoL3YxL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IGZhbHNlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgUkVTVCByb3V0ZXNcbiAgLSBuYW1lOiByZXN0LXYxXG4gICAgX2NvbW1lbnQ6ICdQb3N0Z1JFU1Q6IC9yZXN0L3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlc3QtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVzdC92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgR3JhcGhRTCByb3V0ZXNcbiAgLSBuYW1lOiBncmFwaHFsLXYxXG4gICAgX2NvbW1lbnQ6ICdQb3N0Z1JFU1Q6IC9ncmFwaHFsL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC9ycGMvZ3JhcGhxbCdcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvcnBjL2dyYXBocWxcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGdyYXBocWwtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvZ3JhcGhxbC92MVxuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IHRydWVcbiAgICAgIC0gbmFtZTogcmVxdWVzdC10cmFuc2Zvcm1lclxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgYWRkOlxuICAgICAgICAgICAgaGVhZGVyczpcbiAgICAgICAgICAgICAgLSBDb250ZW50LVByb2ZpbGU6Z3JhcGhxbF9wdWJsaWNcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuXG4gICMjIFNlY3VyZSBSZWFsdGltZSByb3V0ZXNcbiAgLSBuYW1lOiByZWFsdGltZS12MS13c1xuICAgIF9jb21tZW50OiAnUmVhbHRpbWU6IC9yZWFsdGltZS92MS8qIC0+IHdzOi8vcmVhbHRpbWU6NDAwMC9zb2NrZXQvKidcbiAgICB1cmw6IGh0dHA6Ly9yZWFsdGltZS1kZXY6NDAwMC9zb2NrZXRcbiAgICBwcm90b2NvbDogd3NcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlYWx0aW1lLXYxLXdzXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVhbHRpbWUvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuICAtIG5hbWU6IHJlYWx0aW1lLXYxLXJlc3RcbiAgICBfY29tbWVudDogJ1JlYWx0aW1lOiAvcmVhbHRpbWUvdjEvKiAtPiB3czovL3JlYWx0aW1lOjQwMDAvc29ja2V0LyonXG4gICAgdXJsOiBodHRwOi8vcmVhbHRpbWUtZGV2OjQwMDAvYXBpXG4gICAgcHJvdG9jb2w6IGh0dHBcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlYWx0aW1lLXYxLXJlc3RcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9yZWFsdGltZS92MS9hcGlcbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiBmYWxzZVxuICAgICAgLSBuYW1lOiBhY2xcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfZ3JvdXBzX2hlYWRlcjogdHJ1ZVxuICAgICAgICAgIGFsbG93OlxuICAgICAgICAgICAgLSBhZG1pblxuICAgICAgICAgICAgLSBhbm9uXG5cbiAgIyMgU3RvcmFnZSByb3V0ZXM6IHRoZSBzdG9yYWdlIHNlcnZlciBtYW5hZ2VzIGl0cyBvd24gYXV0aFxuICAtIG5hbWU6IHN0b3JhZ2UtdjFcbiAgICBfY29tbWVudDogJ1N0b3JhZ2U6IC9zdG9yYWdlL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXN0b3JhZ2U6NTAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXN0b3JhZ2U6NTAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHN0b3JhZ2UtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvc3RvcmFnZS92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG5cbiAgIyMgRWRnZSBGdW5jdGlvbnMgcm91dGVzXG4gIC0gbmFtZTogZnVuY3Rpb25zLXYxXG4gICAgX2NvbW1lbnQ6ICdFZGdlIEZ1bmN0aW9uczogL2Z1bmN0aW9ucy92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1lZGdlLWZ1bmN0aW9uczo5MDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtZWRnZS1mdW5jdGlvbnM6OTAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGZ1bmN0aW9ucy12MS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9mdW5jdGlvbnMvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIEFuYWx5dGljcyByb3V0ZXNcbiAgLSBuYW1lOiBhbmFseXRpY3MtdjFcbiAgICBfY29tbWVudDogJ0FuYWx5dGljczogL2FuYWx5dGljcy92MS8qIC0+IGh0dHA6Ly9sb2dmbGFyZTo0MDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBhbmFseXRpY3MtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYW5hbHl0aWNzL3YxL1xuXG4gICMjIFNlY3VyZSBEYXRhYmFzZSByb3V0ZXNcbiAgLSBuYW1lOiBtZXRhXG4gICAgX2NvbW1lbnQ6ICdwZy1tZXRhOiAvcGcvKiAtPiBodHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwL1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogbWV0YS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9wZy9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cblxuICAjIyBQcm90ZWN0ZWQgRGFzaGJvYXJkIC0gY2F0Y2ggYWxsIHJlbWFpbmluZyByb3V0ZXNcbiAgLSBuYW1lOiBkYXNoYm9hcmRcbiAgICBfY29tbWVudDogJ1N0dWRpbzogLyogLT4gaHR0cDovL3N0dWRpbzozMDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2Utc3R1ZGlvOjMwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBkYXNoYm9hcmQtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBiYXNpYy1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4iCiAgc3VwYWJhc2Utc3R1ZGlvOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9zdHVkaW86MjAyNS4wNi4wMi1zaGEtOGYyOTkzZCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBub2RlCiAgICAgICAgLSAnLWUnCiAgICAgICAgLSAiZmV0Y2goJ2h0dHA6Ly8xMjcuMC4wLjE6MzAwMC9hcGkvcGxhdGZvcm0vcHJvZmlsZScpLnRoZW4oKHIpID0+IHtpZiAoci5zdGF0dXMgIT09IDIwMCkgdGhyb3cgbmV3IEVycm9yKHIuc3RhdHVzKX0pIgogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBIT1NUTkFNRT0wLjAuMC4wCiAgICAgIC0gJ1NUVURJT19QR19NRVRBX1VSTD1odHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwJwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdERUZBVUxUX09SR0FOSVpBVElPTl9OQU1FPSR7U1RVRElPX0RFRkFVTFRfT1JHQU5JWkFUSU9OOi1EZWZhdWx0IE9yZ2FuaXphdGlvbn0nCiAgICAgIC0gJ0RFRkFVTFRfUFJPSkVDVF9OQU1FPSR7U1RVRElPX0RFRkFVTFRfUFJPSkVDVDotRGVmYXVsdCBQcm9qZWN0fScKICAgICAgLSAnU1VQQUJBU0VfVVJMPWh0dHA6Ly9zdXBhYmFzZS1rb25nOjgwMDAnCiAgICAgIC0gJ1NVUEFCQVNFX1BVQkxJQ19VUkw9JHtTRVJWSUNFX1VSTF9TVVBBQkFTRUtPTkd9JwogICAgICAtICdTVVBBQkFTRV9BTk9OX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX1NFUlZJQ0VfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnQVVUSF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdMT0dGTEFSRV9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9MT0dGTEFSRX0nCiAgICAgIC0gJ0xPR0ZMQVJFX1VSTD1odHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAnCiAgICAgIC0gJ1NVUEFCQVNFX1BVQkxJQ19BUEk9JHtTRVJWSUNFX1VSTF9TVVBBQkFTRUtPTkd9JwogICAgICAtIE5FWFRfUFVCTElDX0VOQUJMRV9MT0dTPXRydWUKICAgICAgLSBORVhUX0FOQUxZVElDU19CQUNLRU5EX1BST1ZJREVSPXBvc3RncmVzCiAgICAgIC0gJ09QRU5BSV9BUElfS0VZPSR7T1BFTkFJX0FQSV9LRVl9JwogIHN1cGFiYXNlLWRiOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9wb3N0Z3JlczoxNS44LjEuMDQ4JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdwZ19pc3JlYWR5IC1VIHBvc3RncmVzIC1oIDEyNy4wLjAuMScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS12ZWN0b3I6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGNvbW1hbmQ6CiAgICAgIC0gcG9zdGdyZXMKICAgICAgLSAnLWMnCiAgICAgIC0gY29uZmlnX2ZpbGU9L2V0Yy9wb3N0Z3Jlc3FsL3Bvc3RncmVzcWwuY29uZgogICAgICAtICctYycKICAgICAgLSBsb2dfbWluX21lc3NhZ2VzPWZhdGFsCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19IT1NUPS92YXIvcnVuL3Bvc3RncmVzcWwKICAgICAgLSAnUEdQT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ1BPU1RHUkVTX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUEdQQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQR0RBVEFCQVNFPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdKV1RfRVhQPSR7SldUX0VYUElSWTotMzYwMH0nCiAgICB2b2x1bWVzOgogICAgICAtICdzdXBhYmFzZS1kYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9yZWFsdGltZS5zcWwKICAgICAgICB0YXJnZXQ6IC9kb2NrZXItZW50cnlwb2ludC1pbml0ZGIuZC9taWdyYXRpb25zLzk5LXJlYWx0aW1lLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCJzdXBhYmFzZV9hZG1pblwiYFxuXG5jcmVhdGUgc2NoZW1hIGlmIG5vdCBleGlzdHMgX3JlYWx0aW1lO1xuYWx0ZXIgc2NoZW1hIF9yZWFsdGltZSBvd25lciB0byA6cGd1c2VyO1xuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2RiL19zdXBhYmFzZS5zcWwKICAgICAgICB0YXJnZXQ6IC9kb2NrZXItZW50cnlwb2ludC1pbml0ZGIuZC9taWdyYXRpb25zLzk3LV9zdXBhYmFzZS5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwiJFBPU1RHUkVTX1VTRVJcImBcblxuQ1JFQVRFIERBVEFCQVNFIF9zdXBhYmFzZSBXSVRIIE9XTkVSIDpwZ3VzZXI7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcG9vbGVyLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktcG9vbGVyLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCJzdXBhYmFzZV9hZG1pblwiYFxuXFxjIF9zdXBhYmFzZVxuY3JlYXRlIHNjaGVtYSBpZiBub3QgZXhpc3RzIF9zdXBhdmlzb3I7XG5hbHRlciBzY2hlbWEgX3N1cGF2aXNvciBvd25lciB0byA6cGd1c2VyO1xuXFxjIHBvc3RncmVzXG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvd2ViaG9va3Muc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk4LXdlYmhvb2tzLnNxbAogICAgICAgIGNvbnRlbnQ6ICJCRUdJTjtcbi0tIENyZWF0ZSBwZ19uZXQgZXh0ZW5zaW9uXG5DUkVBVEUgRVhURU5TSU9OIElGIE5PVCBFWElTVFMgcGdfbmV0IFNDSEVNQSBleHRlbnNpb25zO1xuLS0gQ3JlYXRlIHN1cGFiYXNlX2Z1bmN0aW9ucyBzY2hlbWFcbkNSRUFURSBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIEFVVEhPUklaQVRJT04gc3VwYWJhc2VfYWRtaW47XG5HUkFOVCBVU0FHRSBPTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gVEFCTEVTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gRlVOQ1RJT05TIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gU0VRVUVOQ0VTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMubWlncmF0aW9ucyBkZWZpbml0aW9uXG5DUkVBVEUgVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLm1pZ3JhdGlvbnMgKFxuICB2ZXJzaW9uIHRleHQgUFJJTUFSWSBLRVksXG4gIGluc2VydGVkX2F0IHRpbWVzdGFtcHR6IE5PVCBOVUxMIERFRkFVTFQgTk9XKClcbik7XG4tLSBJbml0aWFsIHN1cGFiYXNlX2Z1bmN0aW9ucyBtaWdyYXRpb25cbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCdpbml0aWFsJyk7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMuaG9va3MgZGVmaW5pdGlvblxuQ1JFQVRFIFRBQkxFIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyAoXG4gIGlkIGJpZ3NlcmlhbCBQUklNQVJZIEtFWSxcbiAgaG9va190YWJsZV9pZCBpbnRlZ2VyIE5PVCBOVUxMLFxuICBob29rX25hbWUgdGV4dCBOT1QgTlVMTCxcbiAgY3JlYXRlZF9hdCB0aW1lc3RhbXB0eiBOT1QgTlVMTCBERUZBVUxUIE5PVygpLFxuICByZXF1ZXN0X2lkIGJpZ2ludFxuKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfcmVxdWVzdF9pZF9pZHggT04gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIFVTSU5HIGJ0cmVlIChyZXF1ZXN0X2lkKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfaF90YWJsZV9pZF9oX25hbWVfaWR4IE9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyBVU0lORyBidHJlZSAoaG9va190YWJsZV9pZCwgaG9va19uYW1lKTtcbkNPTU1FTlQgT04gVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIElTICdTdXBhYmFzZSBGdW5jdGlvbnMgSG9va3M6IEF1ZGl0IHRyYWlsIGZvciB0cmlnZ2VyZWQgaG9va3MuJztcbkNSRUFURSBGVU5DVElPTiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KClcbiAgUkVUVVJOUyB0cmlnZ2VyXG4gIExBTkdVQUdFIHBscGdzcWxcbiAgQVMgJGZ1bmN0aW9uJFxuICBERUNMQVJFXG4gICAgcmVxdWVzdF9pZCBiaWdpbnQ7XG4gICAgcGF5bG9hZCBqc29uYjtcbiAgICB1cmwgdGV4dCA6PSBUR19BUkdWWzBdOjp0ZXh0O1xuICAgIG1ldGhvZCB0ZXh0IDo9IFRHX0FSR1ZbMV06OnRleHQ7XG4gICAgaGVhZGVycyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHBhcmFtcyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHRpbWVvdXRfbXMgaW50ZWdlciBERUZBVUxUIDEwMDA7XG4gIEJFR0lOXG4gICAgSUYgdXJsIElTIE5VTEwgT1IgdXJsID0gJ251bGwnIFRIRU5cbiAgICAgIFJBSVNFIEVYQ0VQVElPTiAndXJsIGFyZ3VtZW50IGlzIG1pc3NpbmcnO1xuICAgIEVORCBJRjtcblxuICAgIElGIG1ldGhvZCBJUyBOVUxMIE9SIG1ldGhvZCA9ICdudWxsJyBUSEVOXG4gICAgICBSQUlTRSBFWENFUFRJT04gJ21ldGhvZCBhcmd1bWVudCBpcyBtaXNzaW5nJztcbiAgICBFTkQgSUY7XG5cbiAgICBJRiBUR19BUkdWWzJdIElTIE5VTEwgT1IgVEdfQVJHVlsyXSA9ICdudWxsJyBUSEVOXG4gICAgICBoZWFkZXJzID0gJ3tcIkNvbnRlbnQtVHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIn0nOjpqc29uYjtcbiAgICBFTFNFXG4gICAgICBoZWFkZXJzID0gVEdfQVJHVlsyXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVlszXSBJUyBOVUxMIE9SIFRHX0FSR1ZbM10gPSAnbnVsbCcgVEhFTlxuICAgICAgcGFyYW1zID0gJ3t9Jzo6anNvbmI7XG4gICAgRUxTRVxuICAgICAgcGFyYW1zID0gVEdfQVJHVlszXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVls0XSBJUyBOVUxMIE9SIFRHX0FSR1ZbNF0gPSAnbnVsbCcgVEhFTlxuICAgICAgdGltZW91dF9tcyA9IDEwMDA7XG4gICAgRUxTRVxuICAgICAgdGltZW91dF9tcyA9IFRHX0FSR1ZbNF06OmludGVnZXI7XG4gICAgRU5EIElGO1xuXG4gICAgQ0FTRVxuICAgICAgV0hFTiBtZXRob2QgPSAnR0VUJyBUSEVOXG4gICAgICAgIFNFTEVDVCBodHRwX2dldCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9nZXQoXG4gICAgICAgICAgdXJsLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIFdIRU4gbWV0aG9kID0gJ1BPU1QnIFRIRU5cbiAgICAgICAgcGF5bG9hZCA9IGpzb25iX2J1aWxkX29iamVjdChcbiAgICAgICAgICAnb2xkX3JlY29yZCcsIE9MRCxcbiAgICAgICAgICAncmVjb3JkJywgTkVXLFxuICAgICAgICAgICd0eXBlJywgVEdfT1AsXG4gICAgICAgICAgJ3RhYmxlJywgVEdfVEFCTEVfTkFNRSxcbiAgICAgICAgICAnc2NoZW1hJywgVEdfVEFCTEVfU0NIRU1BXG4gICAgICAgICk7XG5cbiAgICAgICAgU0VMRUNUIGh0dHBfcG9zdCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9wb3N0KFxuICAgICAgICAgIHVybCxcbiAgICAgICAgICBwYXlsb2FkLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIEVMU0VcbiAgICAgICAgUkFJU0UgRVhDRVBUSU9OICdtZXRob2QgYXJndW1lbnQgJSBpcyBpbnZhbGlkJywgbWV0aG9kO1xuICAgIEVORCBDQVNFO1xuXG4gICAgSU5TRVJUIElOVE8gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzXG4gICAgICAoaG9va190YWJsZV9pZCwgaG9va19uYW1lLCByZXF1ZXN0X2lkKVxuICAgIFZBTFVFU1xuICAgICAgKFRHX1JFTElELCBUR19OQU1FLCByZXF1ZXN0X2lkKTtcblxuICAgIFJFVFVSTiBORVc7XG4gIEVORFxuJGZ1bmN0aW9uJDtcbi0tIFN1cGFiYXNlIHN1cGVyIGFkbWluXG5ET1xuJCRcbkJFR0lOXG4gIElGIE5PVCBFWElTVFMgKFxuICAgIFNFTEVDVCAxXG4gICAgRlJPTSBwZ19yb2xlc1xuICAgIFdIRVJFIHJvbG5hbWUgPSAnc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluJ1xuICApXG4gIFRIRU5cbiAgICBDUkVBVEUgVVNFUiBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gTk9JTkhFUklUIENSRUFURVJPTEUgTE9HSU4gTk9SRVBMSUNBVElPTjtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIFNDSEVNQSBzdXBhYmFzZV9mdW5jdGlvbnMgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuR1JBTlQgQUxMIFBSSVZJTEVHRVMgT04gQUxMIFRBQkxFUyBJTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIEFMTCBTRVFVRU5DRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5BTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBTRVQgc2VhcmNoX3BhdGggPSBcInN1cGFiYXNlX2Z1bmN0aW9uc1wiO1xuQUxURVIgdGFibGUgXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5taWdyYXRpb25zIE9XTkVSIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkFMVEVSIHRhYmxlIFwic3VwYWJhc2VfZnVuY3Rpb25zXCIuaG9va3MgT1dORVIgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuQUxURVIgZnVuY3Rpb24gXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5odHRwX3JlcXVlc3QoKSBPV05FUiBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5HUkFOVCBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gVE8gcG9zdGdyZXM7XG4tLSBSZW1vdmUgdW51c2VkIHN1cGFiYXNlX3BnX25ldF9hZG1pbiByb2xlXG5ET1xuJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX3JvbGVzXG4gICAgV0hFUkUgcm9sbmFtZSA9ICdzdXBhYmFzZV9wZ19uZXRfYWRtaW4nXG4gIClcbiAgVEhFTlxuICAgIFJFQVNTSUdOIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbiBUTyBzdXBhYmFzZV9hZG1pbjtcbiAgICBEUk9QIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbjtcbiAgICBEUk9QIFJPTEUgc3VwYWJhc2VfcGdfbmV0X2FkbWluO1xuICBFTkQgSUY7XG5FTkRcbiQkO1xuLS0gcGdfbmV0IGdyYW50cyB3aGVuIGV4dGVuc2lvbiBpcyBhbHJlYWR5IGVuYWJsZWRcbkRPXG4kJFxuQkVHSU5cbiAgSUYgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXh0ZW5zaW9uXG4gICAgV0hFUkUgZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbi0tIEV2ZW50IHRyaWdnZXIgZm9yIHBnX25ldFxuQ1JFQVRFIE9SIFJFUExBQ0UgRlVOQ1RJT04gZXh0ZW5zaW9ucy5ncmFudF9wZ19uZXRfYWNjZXNzKClcblJFVFVSTlMgZXZlbnRfdHJpZ2dlclxuTEFOR1VBR0UgcGxwZ3NxbFxuQVMgJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX2V2ZW50X3RyaWdnZXJfZGRsX2NvbW1hbmRzKCkgQVMgZXZcbiAgICBKT0lOIHBnX2V4dGVuc2lvbiBBUyBleHRcbiAgICBPTiBldi5vYmppZCA9IGV4dC5vaWRcbiAgICBXSEVSRSBleHQuZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EO1xuJCQ7XG5DT01NRU5UIE9OIEZVTkNUSU9OIGV4dGVuc2lvbnMuZ3JhbnRfcGdfbmV0X2FjY2VzcyBJUyAnR3JhbnRzIGFjY2VzcyB0byBwZ19uZXQnO1xuRE9cbiQkXG5CRUdJTlxuICBJRiBOT1QgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXZlbnRfdHJpZ2dlclxuICAgIFdIRVJFIGV2dG5hbWUgPSAnaXNzdWVfcGdfbmV0X2FjY2VzcydcbiAgKSBUSEVOXG4gICAgQ1JFQVRFIEVWRU5UIFRSSUdHRVIgaXNzdWVfcGdfbmV0X2FjY2VzcyBPTiBkZGxfY29tbWFuZF9lbmQgV0hFTiBUQUcgSU4gKCdDUkVBVEUgRVhURU5TSU9OJylcbiAgICBFWEVDVVRFIFBST0NFRFVSRSBleHRlbnNpb25zLmdyYW50X3BnX25ldF9hY2Nlc3MoKTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCcyMDIxMDgwOTE4MzQyM191cGRhdGVfZ3JhbnRzJyk7XG5BTFRFUiBmdW5jdGlvbiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KCkgU0VDVVJJVFkgREVGSU5FUjtcbkFMVEVSIGZ1bmN0aW9uIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBTRVQgc2VhcmNoX3BhdGggPSBzdXBhYmFzZV9mdW5jdGlvbnM7XG5SRVZPS0UgQUxMIE9OIEZVTkNUSU9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBGUk9NIFBVQkxJQztcbkdSQU5UIEVYRUNVVEUgT04gRlVOQ1RJT04gc3VwYWJhc2VfZnVuY3Rpb25zLmh0dHBfcmVxdWVzdCgpIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5DT01NSVQ7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcm9sZXMuc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk5LXJvbGVzLnNxbAogICAgICAgIGNvbnRlbnQ6ICItLSBOT1RFOiBjaGFuZ2UgdG8geW91ciBvd24gcGFzc3dvcmRzIGZvciBwcm9kdWN0aW9uIGVudmlyb25tZW50c1xuIFxcc2V0IHBncGFzcyBgZWNobyBcIiRQT1NUR1JFU19QQVNTV09SRFwiYFxuXG4gQUxURVIgVVNFUiBhdXRoZW50aWNhdG9yIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgcGdib3VuY2VyIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgc3VwYWJhc2VfYXV0aF9hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX3N0b3JhZ2VfYWRtaW4gV0lUSCBQQVNTV09SRCA6J3BncGFzcyc7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvand0LnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL2luaXQtc2NyaXB0cy85OS1qd3Quc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IGp3dF9zZWNyZXQgYGVjaG8gXCIkSldUX1NFQ1JFVFwiYFxuXFxzZXQgand0X2V4cCBgZWNobyBcIiRKV1RfRVhQXCJgXG5cXHNldCBkYl9uYW1lIGBlY2hvIFwiJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9XCJgXG5cbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3Rfc2VjcmV0XCIgVE8gOidqd3Rfc2VjcmV0JztcbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3RfZXhwXCIgVE8gOidqd3RfZXhwJztcbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9sb2dzLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktbG9ncy5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwic3VwYWJhc2VfYWRtaW5cImBcblxcYyBfc3VwYWJhc2VcbmNyZWF0ZSBzY2hlbWEgaWYgbm90IGV4aXN0cyBfYW5hbHl0aWNzO1xuYWx0ZXIgc2NoZW1hIF9hbmFseXRpY3Mgb3duZXIgdG8gOnBndXNlcjtcblxcYyBwb3N0Z3Jlc1xuIgogICAgICAtICdzdXBhYmFzZS1kYi1jb25maWc6L2V0Yy9wb3N0Z3Jlc3FsLWN1c3RvbScKICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICBpbWFnZTogJ3N1cGFiYXNlL2xvZ2ZsYXJlOjEuNC4wJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjQwMDAvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBMT0dGTEFSRV9OT0RFX0hPU1Q9MTI3LjAuMC4xCiAgICAgIC0gREJfVVNFUk5BTUU9c3VwYWJhc2VfYWRtaW4KICAgICAgLSBEQl9EQVRBQkFTRT1fc3VwYWJhc2UKICAgICAgLSAnREJfSE9TVE5BTUU9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdEQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gREJfU0NIRU1BPV9hbmFseXRpY3MKICAgICAgLSAnTE9HRkxBUkVfQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTE9HRkxBUkV9JwogICAgICAtIExPR0ZMQVJFX1NJTkdMRV9URU5BTlQ9dHJ1ZQogICAgICAtIExPR0ZMQVJFX1NJTkdMRV9URU5BTlRfTU9ERT10cnVlCiAgICAgIC0gTE9HRkxBUkVfU1VQQUJBU0VfTU9ERT10cnVlCiAgICAgIC0gTE9HRkxBUkVfTUlOX0NMVVNURVJfU0laRT0xCiAgICAgIC0gJ1BPU1RHUkVTX0JBQ0tFTkRfVVJMPXBvc3RncmVzcWw6Ly9zdXBhYmFzZV9hZG1pbjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9L19zdXBhYmFzZScKICAgICAgLSBQT1NUR1JFU19CQUNLRU5EX1NDSEVNQT1fYW5hbHl0aWNzCiAgICAgIC0gTE9HRkxBUkVfRkVBVFVSRV9GTEFHX09WRVJSSURFPW11bHRpYmFja2VuZD10cnVlCiAgc3VwYWJhc2UtdmVjdG9yOgogICAgaW1hZ2U6ICd0aW1iZXJpby92ZWN0b3I6MC4yOC4xLWFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vc3VwYWJhc2UtdmVjdG9yOjkwMDEvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9sb2dzL3ZlY3Rvci55bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvdmVjdG9yL3ZlY3Rvci55bWwKICAgICAgICByZWFkX29ubHk6IHRydWUKICAgICAgICBjb250ZW50OiAiYXBpOlxuICBlbmFibGVkOiB0cnVlXG4gIGFkZHJlc3M6IDAuMC4wLjA6OTAwMVxuXG5zb3VyY2VzOlxuICBkb2NrZXJfaG9zdDpcbiAgICB0eXBlOiBkb2NrZXJfbG9nc1xuICAgIGV4Y2x1ZGVfY29udGFpbmVyczpcbiAgICAgIC0gc3VwYWJhc2UtdmVjdG9yXG5cbnRyYW5zZm9ybXM6XG4gIHByb2plY3RfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gZG9ja2VyX2hvc3RcbiAgICBzb3VyY2U6IHwtXG4gICAgICAucHJvamVjdCA9IFwiZGVmYXVsdFwiXG4gICAgICAuZXZlbnRfbWVzc2FnZSA9IGRlbCgubWVzc2FnZSlcbiAgICAgIC5hcHBuYW1lID0gZGVsKC5jb250YWluZXJfbmFtZSlcbiAgICAgIGRlbCguY29udGFpbmVyX2NyZWF0ZWRfYXQpXG4gICAgICBkZWwoLmNvbnRhaW5lcl9pZClcbiAgICAgIGRlbCguc291cmNlX3R5cGUpXG4gICAgICBkZWwoLnN0cmVhbSlcbiAgICAgIGRlbCgubGFiZWwpXG4gICAgICBkZWwoLmltYWdlKVxuICAgICAgZGVsKC5ob3N0KVxuICAgICAgZGVsKC5zdHJlYW0pXG4gIHJvdXRlcjpcbiAgICB0eXBlOiByb3V0ZVxuICAgIGlucHV0czpcbiAgICAgIC0gcHJvamVjdF9sb2dzXG4gICAgcm91dGU6XG4gICAgICBrb25nOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2Uta29uZ1wiKSdcbiAgICAgIGF1dGg6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1hdXRoXCIpJ1xuICAgICAgcmVzdDogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInN1cGFiYXNlLXJlc3RcIiknXG4gICAgICByZWFsdGltZTogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInJlYWx0aW1lLWRldlwiKSdcbiAgICAgIHN0b3JhZ2U6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1zdG9yYWdlXCIpJ1xuICAgICAgZnVuY3Rpb25zOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2UtZnVuY3Rpb25zXCIpJ1xuICAgICAgZGI6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1kYlwiKSdcbiAgIyBJZ25vcmVzIG5vbiBuZ2lueCBlcnJvcnMgc2luY2UgdGhleSBhcmUgcmVsYXRlZCB3aXRoIGtvbmcgYm9vdGluZyB1cFxuICBrb25nX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5rb25nXG4gICAgc291cmNlOiB8LVxuICAgICAgcmVxLCBlcnIgPSBwYXJzZV9uZ2lueF9sb2coLmV2ZW50X21lc3NhZ2UsIFwiY29tYmluZWRcIilcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAudGltZXN0YW1wID0gcmVxLnRpbWVzdGFtcFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LmhlYWRlcnMucmVmZXJlciA9IHJlcS5yZWZlcmVyXG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy51c2VyX2FnZW50ID0gcmVxLmFnZW50XG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy5jZl9jb25uZWN0aW5nX2lwID0gcmVxLmNsaWVudFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0Lm1ldGhvZCA9IHJlcS5tZXRob2RcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wYXRoID0gcmVxLnBhdGhcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wcm90b2NvbCA9IHJlcS5wcm90b2NvbFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXNwb25zZS5zdGF0dXNfY29kZSA9IHJlcS5zdGF0dXNcbiAgICAgIH1cbiAgICAgIGlmIGVyciAhPSBudWxsIHtcbiAgICAgICAgYWJvcnRcbiAgICAgIH1cbiAgIyBJZ25vcmVzIG5vbiBuZ2lueCBlcnJvcnMgc2luY2UgdGhleSBhcmUgcmVsYXRlZCB3aXRoIGtvbmcgYm9vdGluZyB1cFxuICBrb25nX2VycjpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmtvbmdcbiAgICBzb3VyY2U6IHwtXG4gICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSBcIkdFVFwiXG4gICAgICAubWV0YWRhdGEucmVzcG9uc2Uuc3RhdHVzX2NvZGUgPSAyMDBcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfbmdpbnhfbG9nKC5ldmVudF9tZXNzYWdlLCBcImVycm9yXCIpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lc3RhbXBcbiAgICAgICAgICAuc2V2ZXJpdHkgPSBwYXJzZWQuc2V2ZXJpdHlcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5ob3N0ID0gcGFyc2VkLmhvc3RcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5oZWFkZXJzLmNmX2Nvbm5lY3RpbmdfaXAgPSBwYXJzZWQuY2xpZW50XG4gICAgICAgICAgdXJsLCBlcnIgPSBzcGxpdChwYXJzZWQucmVxdWVzdCwgXCIgXCIpXG4gICAgICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSB1cmxbMF1cbiAgICAgICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucGF0aCA9IHVybFsxXVxuICAgICAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wcm90b2NvbCA9IHVybFsyXVxuICAgICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIGVyciAhPSBudWxsIHtcbiAgICAgICAgYWJvcnRcbiAgICAgIH1cbiAgIyBHb3RydWUgbG9ncyBhcmUgc3RydWN0dXJlZCBqc29uIHN0cmluZ3Mgd2hpY2ggZnJvbnRlbmQgcGFyc2VzIGRpcmVjdGx5LiBCdXQgd2Uga2VlcCBtZXRhZGF0YSBmb3IgY29uc2lzdGVuY3kuXG4gIGF1dGhfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmF1dGhcbiAgICBzb3VyY2U6IHwtXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX2pzb24oLmV2ZW50X21lc3NhZ2UpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLm1ldGFkYXRhLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lXG4gICAgICAgICAgLm1ldGFkYXRhID0gbWVyZ2UhKC5tZXRhZGF0YSwgcGFyc2VkKVxuICAgICAgfVxuICAjIFBvc3RnUkVTVCBsb2dzIGFyZSBzdHJ1Y3R1cmVkIHNvIHdlIHNlcGFyYXRlIHRpbWVzdGFtcCBmcm9tIG1lc3NhZ2UgdXNpbmcgcmVnZXhcbiAgcmVzdF9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIucmVzdFxuICAgIHNvdXJjZTogfC1cbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInXig/UDx0aW1lPi4qKTogKD9QPG1zZz4uKikkJylcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAuZXZlbnRfbWVzc2FnZSA9IHBhcnNlZC5tc2dcbiAgICAgICAgICAudGltZXN0YW1wID0gdG9fdGltZXN0YW1wIShwYXJzZWQudGltZSlcbiAgICAgICAgICAubWV0YWRhdGEuaG9zdCA9IC5wcm9qZWN0XG4gICAgICB9XG4gICMgUmVhbHRpbWUgbG9ncyBhcmUgc3RydWN0dXJlZCBzbyB3ZSBwYXJzZSB0aGUgc2V2ZXJpdHkgbGV2ZWwgdXNpbmcgcmVnZXggKGlnbm9yZSB0aW1lIGJlY2F1c2UgaXQgaGFzIG5vIGRhdGUpXG4gIHJlYWx0aW1lX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5yZWFsdGltZVxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5wcm9qZWN0ID0gZGVsKC5wcm9qZWN0KVxuICAgICAgLm1ldGFkYXRhLmV4dGVybmFsX2lkID0gLm1ldGFkYXRhLnByb2plY3RcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInXig/UDx0aW1lPlxcZCs6XFxkKzpcXGQrXFwuXFxkKykgXFxbKD9QPGxldmVsPlxcdyspXFxdICg/UDxtc2c+LiopJCcpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLmV2ZW50X21lc3NhZ2UgPSBwYXJzZWQubXNnXG4gICAgICAgICAgLm1ldGFkYXRhLmxldmVsID0gcGFyc2VkLmxldmVsXG4gICAgICB9XG4gICMgU3RvcmFnZSBsb2dzIG1heSBjb250YWluIGpzb24gb2JqZWN0cyBzbyB3ZSBwYXJzZSB0aGVtIGZvciBjb21wbGV0ZW5lc3NcbiAgc3RvcmFnZV9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIuc3RvcmFnZVxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5wcm9qZWN0ID0gZGVsKC5wcm9qZWN0KVxuICAgICAgLm1ldGFkYXRhLnRlbmFudElkID0gLm1ldGFkYXRhLnByb2plY3RcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfanNvbiguZXZlbnRfbWVzc2FnZSlcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAuZXZlbnRfbWVzc2FnZSA9IHBhcnNlZC5tc2dcbiAgICAgICAgICAubWV0YWRhdGEubGV2ZWwgPSBwYXJzZWQubGV2ZWxcbiAgICAgICAgICAubWV0YWRhdGEudGltZXN0YW1wID0gcGFyc2VkLnRpbWVcbiAgICAgICAgICAubWV0YWRhdGEuY29udGV4dFswXS5ob3N0ID0gcGFyc2VkLmhvc3RuYW1lXG4gICAgICAgICAgLm1ldGFkYXRhLmNvbnRleHRbMF0ucGlkID0gcGFyc2VkLnBpZFxuICAgICAgfVxuICAjIFBvc3RncmVzIGxvZ3Mgc29tZSBtZXNzYWdlcyB0byBzdGRlcnIgd2hpY2ggd2UgbWFwIHRvIHdhcm5pbmcgc2V2ZXJpdHkgbGV2ZWxcbiAgZGJfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmRiXG4gICAgc291cmNlOiB8LVxuICAgICAgLm1ldGFkYXRhLmhvc3QgPSBcImRiLWRlZmF1bHRcIlxuICAgICAgLm1ldGFkYXRhLnBhcnNlZC50aW1lc3RhbXAgPSAudGltZXN0YW1wXG5cbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInLiooP1A8bGV2ZWw+SU5GT3xOT1RJQ0V8V0FSTklOR3xFUlJPUnxMT0d8RkFUQUx8UEFOSUM/KTouKicsIG51bWVyaWNfZ3JvdXBzOiB0cnVlKVxuXG4gICAgICBpZiBlcnIgIT0gbnVsbCB8fCBwYXJzZWQgPT0gbnVsbCB7XG4gICAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSBcImluZm9cIlxuICAgICAgfVxuICAgICAgaWYgcGFyc2VkICE9IG51bGwge1xuICAgICAgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9IHBhcnNlZC5sZXZlbFxuICAgICAgfVxuICAgICAgaWYgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9PSBcImluZm9cIiB7XG4gICAgICAgICAgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9IFwibG9nXCJcbiAgICAgIH1cbiAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSB1cGNhc2UhKC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkpXG5cbnNpbmtzOlxuICBsb2dmbGFyZV9hdXRoOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gYXV0aF9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWdvdHJ1ZS5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3JlYWx0aW1lOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gcmVhbHRpbWVfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1yZWFsdGltZS5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3Jlc3Q6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSByZXN0X2xvZ3NcbiAgICBlbmNvZGluZzpcbiAgICAgIGNvZGVjOiAnanNvbidcbiAgICBtZXRob2Q6ICdwb3N0J1xuICAgIHJlcXVlc3Q6XG4gICAgICByZXRyeV9tYXhfZHVyYXRpb25fc2VjczogMTBcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvYXBpL2xvZ3M/c291cmNlX25hbWU9cG9zdGdSRVNULmxvZ3MucHJvZCZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfZGI6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSBkYl9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgIyBXZSBtdXN0IHJvdXRlIHRoZSBzaW5rIHRocm91Z2gga29uZyBiZWNhdXNlIGluZ2VzdGluZyBsb2dzIGJlZm9yZSBsb2dmbGFyZSBpcyBmdWxseSBpbml0aWFsaXNlZCB3aWxsXG4gICAgIyBsZWFkIHRvIGJyb2tlbiBxdWVyaWVzIGZyb20gc3R1ZGlvLiBUaGlzIHdvcmtzIGJ5IHRoZSBhc3N1bXB0aW9uIHRoYXQgY29udGFpbmVycyBhcmUgc3RhcnRlZCBpbiB0aGVcbiAgICAjIGZvbGxvd2luZyBvcmRlcjogdmVjdG9yID4gZGIgPiBsb2dmbGFyZSA+IGtvbmdcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2Uta29uZzo4MDAwL2FuYWx5dGljcy92MS9hcGkvbG9ncz9zb3VyY2VfbmFtZT1wb3N0Z3Jlcy5sb2dzJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9mdW5jdGlvbnM6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIuZnVuY3Rpb25zXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWRlbm8tcmVsYXktbG9ncyZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfc3RvcmFnZTpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIHN0b3JhZ2VfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1zdG9yYWdlLmxvZ3MucHJvZC4yJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9rb25nOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0ga29uZ19sb2dzXG4gICAgICAtIGtvbmdfZXJyXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWNsb3VkZmxhcmUubG9ncy5wcm9kJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuIgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jazpybycKICAgIGVudmlyb25tZW50OgogICAgICAtICdMT0dGTEFSRV9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9MT0dGTEFSRX0nCiAgICBjb21tYW5kOgogICAgICAtICctLWNvbmZpZycKICAgICAgLSBldGMvdmVjdG9yL3ZlY3Rvci55bWwKICBzdXBhYmFzZS1yZXN0OgogICAgaW1hZ2U6ICdwb3N0Z3Jlc3QvcG9zdGdyZXN0OnYxMi4yLjEyJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUEdSU1RfREJfVVJJPXBvc3RncmVzOi8vYXV0aGVudGljYXRvcjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUEdSU1RfREJfU0NIRU1BUz0ke1BHUlNUX0RCX1NDSEVNQVM6LXB1YmxpYyxzdG9yYWdlLGdyYXBocWxfcHVibGljfScKICAgICAgLSBQR1JTVF9EQl9BTk9OX1JPTEU9YW5vbgogICAgICAtICdQR1JTVF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIFBHUlNUX0RCX1VTRV9MRUdBQ1lfR1VDUz1mYWxzZQogICAgICAtICdQR1JTVF9BUFBfU0VUVElOR1NfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnUEdSU1RfQVBQX1NFVFRJTkdTX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgIGNvbW1hbmQ6IHBvc3RncmVzdAogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgc3VwYWJhc2UtYXV0aDoKICAgIGltYWdlOiAnc3VwYWJhc2UvZ290cnVlOnYyLjE3NC4wJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo5OTk5L2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPVFJVRV9BUElfSE9TVD0wLjAuMC4wCiAgICAgIC0gR09UUlVFX0FQSV9QT1JUPTk5OTkKICAgICAgLSAnQVBJX0VYVEVSTkFMX1VSTD0ke0FQSV9FWFRFUk5BTF9VUkw6LWh0dHA6Ly9zdXBhYmFzZS1rb25nOjgwMDB9JwogICAgICAtIEdPVFJVRV9EQl9EUklWRVI9cG9zdGdyZXMKICAgICAgLSAnR09UUlVFX0RCX0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovL3N1cGFiYXNlX2F1dGhfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0dPVFJVRV9TSVRFX1VSTD0ke1NFUlZJQ0VfVVJMX1NVUEFCQVNFS09OR30nCiAgICAgIC0gJ0dPVFJVRV9VUklfQUxMT1dfTElTVD0ke0FERElUSU9OQUxfUkVESVJFQ1RfVVJMU30nCiAgICAgIC0gJ0dPVFJVRV9ESVNBQkxFX1NJR05VUD0ke0RJU0FCTEVfU0lHTlVQOi1mYWxzZX0nCiAgICAgIC0gR09UUlVFX0pXVF9BRE1JTl9ST0xFUz1zZXJ2aWNlX3JvbGUKICAgICAgLSBHT1RSVUVfSldUX0FVRD1hdXRoZW50aWNhdGVkCiAgICAgIC0gR09UUlVFX0pXVF9ERUZBVUxUX0dST1VQX05BTUU9YXV0aGVudGljYXRlZAogICAgICAtICdHT1RSVUVfSldUX0VYUD0ke0pXVF9FWFBJUlk6LTM2MDB9JwogICAgICAtICdHT1RSVUVfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnR09UUlVFX0VYVEVSTkFMX0VNQUlMX0VOQUJMRUQ9JHtFTkFCTEVfRU1BSUxfU0lHTlVQOi10cnVlfScKICAgICAgLSAnR09UUlVFX0VYVEVSTkFMX0FOT05ZTU9VU19VU0VSU19FTkFCTEVEPSR7RU5BQkxFX0FOT05ZTU9VU19VU0VSUzotZmFsc2V9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX0FVVE9DT05GSVJNPSR7RU5BQkxFX0VNQUlMX0FVVE9DT05GSVJNOi1mYWxzZX0nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX0FETUlOX0VNQUlMPSR7U01UUF9BRE1JTl9FTUFJTH0nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX0hPU1Q9JHtTTVRQX0hPU1R9JwogICAgICAtICdHT1RSVUVfU01UUF9QT1JUPSR7U01UUF9QT1JUOi01ODd9JwogICAgICAtICdHT1RSVUVfU01UUF9VU0VSPSR7U01UUF9VU0VSfScKICAgICAgLSAnR09UUlVFX1NNVFBfUEFTUz0ke1NNVFBfUEFTU30nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX1NFTkRFUl9OQU1FPSR7U01UUF9TRU5ERVJfTkFNRX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVVJMUEFUSFNfSU5WSVRFPSR7TUFJTEVSX1VSTFBBVEhTX0lOVklURTotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19DT05GSVJNQVRJT049JHtNQUlMRVJfVVJMUEFUSFNfQ09ORklSTUFUSU9OOi0vYXV0aC92MS92ZXJpZnl9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1VSTFBBVEhTX1JFQ09WRVJZPSR7TUFJTEVSX1VSTFBBVEhTX1JFQ09WRVJZOi0vYXV0aC92MS92ZXJpZnl9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1VSTFBBVEhTX0VNQUlMX0NIQU5HRT0ke01BSUxFUl9VUkxQQVRIU19FTUFJTF9DSEFOR0U6LS9hdXRoL3YxL3ZlcmlmeX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0lOVklURT0ke01BSUxFUl9URU1QTEFURVNfSU5WSVRFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfQ09ORklSTUFUSU9OPSR7TUFJTEVSX1RFTVBMQVRFU19DT05GSVJNQVRJT059JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1RFTVBMQVRFU19SRUNPVkVSWT0ke01BSUxFUl9URU1QTEFURVNfUkVDT1ZFUll9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1RFTVBMQVRFU19NQUdJQ19MSU5LPSR7TUFJTEVSX1RFTVBMQVRFU19NQUdJQ19MSU5LfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfRU1BSUxfQ0hBTkdFPSR7TUFJTEVSX1RFTVBMQVRFU19FTUFJTF9DSEFOR0V9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1NVQkpFQ1RTX0NPTkZJUk1BVElPTj0ke01BSUxFUl9TVUJKRUNUU19DT05GSVJNQVRJT059JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1NVQkpFQ1RTX1JFQ09WRVJZPSR7TUFJTEVSX1NVQkpFQ1RTX1JFQ09WRVJZfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19NQUdJQ19MSU5LPSR7TUFJTEVSX1NVQkpFQ1RTX01BR0lDX0xJTkt9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1NVQkpFQ1RTX0VNQUlMX0NIQU5HRT0ke01BSUxFUl9TVUJKRUNUU19FTUFJTF9DSEFOR0V9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1NVQkpFQ1RTX0lOVklURT0ke01BSUxFUl9TVUJKRUNUU19JTlZJVEV9JwogICAgICAtICdHT1RSVUVfRVhURVJOQUxfUEhPTkVfRU5BQkxFRD0ke0VOQUJMRV9QSE9ORV9TSUdOVVA6LXRydWV9JwogICAgICAtICdHT1RSVUVfU01TX0FVVE9DT05GSVJNPSR7RU5BQkxFX1BIT05FX0FVVE9DT05GSVJNOi10cnVlfScKICByZWFsdGltZS1kZXY6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3JlYWx0aW1lOnYyLjM0LjQ3JwogICAgY29udGFpbmVyX25hbWU6IHJlYWx0aW1lLWRldi5zdXBhYmFzZS1yZWFsdGltZQogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1zU2ZMJwogICAgICAgIC0gJy0taGVhZCcKICAgICAgICAtICctbycKICAgICAgICAtIC9kZXYvbnVsbAogICAgICAgIC0gJy1IJwogICAgICAgIC0gJ0F1dGhvcml6YXRpb246IEJlYXJlciAke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo0MDAwL2FwaS90ZW5hbnRzL3JlYWx0aW1lLWRldi9oZWFsdGgnCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1JUPTQwMDAKICAgICAgLSAnREJfSE9TVD0ke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn0nCiAgICAgIC0gJ0RCX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSBEQl9VU0VSPXN1cGFiYXNlX2FkbWluCiAgICAgIC0gJ0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RCX05BTUU9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdEQl9BRlRFUl9DT05ORUNUX1FVRVJZPVNFVCBzZWFyY2hfcGF0aCBUTyBfcmVhbHRpbWUnCiAgICAgIC0gREJfRU5DX0tFWT1zdXBhYmFzZXJlYWx0aW1lCiAgICAgIC0gJ0FQSV9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIEZMWV9BTExPQ19JRD1mbHkxMjMKICAgICAgLSBGTFlfQVBQX05BTUU9cmVhbHRpbWUKICAgICAgLSAnU0VDUkVUX0tFWV9CQVNFPSR7U0VDUkVUX1BBU1NXT1JEX1JFQUxUSU1FfScKICAgICAgLSAnRVJMX0FGTEFHUz0tcHJvdG9fZGlzdCBpbmV0X3RjcCcKICAgICAgLSBFTkFCTEVfVEFJTFNDQUxFPWZhbHNlCiAgICAgIC0gIkROU19OT0RFUz0nJyIKICAgICAgLSBSTElNSVRfTk9GSUxFPTEwMDAwCiAgICAgIC0gQVBQX05BTUU9cmVhbHRpbWUKICAgICAgLSBTRUVEX1NFTEZfSE9TVD10cnVlCiAgICAgIC0gTE9HX0xFVkVMPWVycm9yCiAgICAgIC0gUlVOX0pBTklUT1I9dHJ1ZQogICAgICAtIEpBTklUT1JfSU5URVJWQUw9NjAwMDAKICAgIGNvbW1hbmQ6ICJzaCAtYyBcIi9hcHAvYmluL21pZ3JhdGUgJiYgL2FwcC9iaW4vcmVhbHRpbWUgZXZhbCAnUmVhbHRpbWUuUmVsZWFzZS5zZWVkcyhSZWFsdGltZS5SZXBvKScgJiYgL2FwcC9iaW4vc2VydmVyXCJcbiIKICBzdXBhYmFzZS1taW5pbzoKICAgIGltYWdlOiAnZ2hjci5pby9jb29sbGFic2lvL21pbmlvOlJFTEVBU0UuMjAyNS0xMC0xNVQxNy0yOS01NVonCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgY29tbWFuZDogJ3NlcnZlciAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiIC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG1jCiAgICAgICAgLSByZWFkeQogICAgICAgIC0gbG9jYWwKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgdm9sdW1lczoKICAgICAgLSAnLi92b2x1bWVzL3N0b3JhZ2U6L2RhdGEnCiAgbWluaW8tY3JlYXRlYnVja2V0OgogICAgaW1hZ2U6IG1pbmlvL21jCiAgICByZXN0YXJ0OiAnbm8nCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtbWluaW86CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudHJ5cG9pbnQ6CiAgICAgIC0gL2VudHJ5cG9pbnQuc2gKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2VudHJ5cG9pbnQuc2gKICAgICAgICB0YXJnZXQ6IC9lbnRyeXBvaW50LnNoCiAgICAgICAgY29udGVudDogIiMhL2Jpbi9zaFxuL3Vzci9iaW4vbWMgYWxpYXMgc2V0IHN1cGFiYXNlLW1pbmlvIGh0dHA6Ly9zdXBhYmFzZS1taW5pbzo5MDAwICR7TUlOSU9fUk9PVF9VU0VSfSAke01JTklPX1JPT1RfUEFTU1dPUkR9O1xuL3Vzci9iaW4vbWMgbWIgLS1pZ25vcmUtZXhpc3Rpbmcgc3VwYWJhc2UtbWluaW8vc3R1YjtcbmV4aXQgMFxuIgogIHN1cGFiYXNlLXN0b3JhZ2U6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3N0b3JhZ2UtYXBpOnYxLjE0LjYnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1kYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzdXBhYmFzZS1yZXN0OgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9zdGFydGVkCiAgICAgIGltZ3Byb3h5OgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9zdGFydGVkCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo1MDAwL3N0YXR1cycKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZFUl9QT1JUPTUwMDAKICAgICAgLSBTRVJWRVJfUkVHSU9OPWxvY2FsCiAgICAgIC0gTVVMVElfVEVOQU5UPWZhbHNlCiAgICAgIC0gJ0FVVEhfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vc3VwYWJhc2Vfc3RvcmFnZV9hZG1pbjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSBEQl9JTlNUQUxMX1JPTEVTPWZhbHNlCiAgICAgIC0gU1RPUkFHRV9CQUNLRU5EPXMzCiAgICAgIC0gU1RPUkFHRV9TM19CVUNLRVQ9c3R1YgogICAgICAtICdTVE9SQUdFX1MzX0VORFBPSU5UPWh0dHA6Ly9zdXBhYmFzZS1taW5pbzo5MDAwJwogICAgICAtIFNUT1JBR0VfUzNfRk9SQ0VfUEFUSF9TVFlMRT10cnVlCiAgICAgIC0gU1RPUkFHRV9TM19SRUdJT049dXMtZWFzdC0xCiAgICAgIC0gJ0FXU19BQ0NFU1NfS0VZX0lEPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnQVdTX1NFQ1JFVF9BQ0NFU1NfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9NSU5JT30nCiAgICAgIC0gVVBMT0FEX0ZJTEVfU0laRV9MSU1JVD01MjQyODgwMDAKICAgICAgLSBVUExPQURfRklMRV9TSVpFX0xJTUlUX1NUQU5EQVJEPTUyNDI4ODAwMAogICAgICAtIFVQTE9BRF9TSUdORURfVVJMX0VYUElSQVRJT05fVElNRT0xMjAKICAgICAgLSBUVVNfVVJMX1BBVEg9dXBsb2FkL3Jlc3VtYWJsZQogICAgICAtIFRVU19NQVhfU0laRT0zNjAwMDAwCiAgICAgIC0gRU5BQkxFX0lNQUdFX1RSQU5TRk9STUFUSU9OPXRydWUKICAgICAgLSAnSU1HUFJPWFlfVVJMPWh0dHA6Ly9pbWdwcm94eTo4MDgwJwogICAgICAtIElNR1BST1hZX1JFUVVFU1RfVElNRU9VVD0xNQogICAgICAtIERBVEFCQVNFX1NFQVJDSF9QQVRIPXN0b3JhZ2UKICAgICAgLSBOT0RFX0VOVj1wcm9kdWN0aW9uCiAgICAgIC0gUkVRVUVTVF9BTExPV19YX0ZPUldBUkRFRF9QQVRIPXRydWUKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vdm9sdW1lcy9zdG9yYWdlOi92YXIvbGliL3N0b3JhZ2UnCiAgaW1ncHJveHk6CiAgICBpbWFnZTogJ2RhcnRoc2ltL2ltZ3Byb3h5OnYzLjguMCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBpbWdwcm94eQogICAgICAgIC0gaGVhbHRoCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBJTUdQUk9YWV9MT0NBTF9GSUxFU1lTVEVNX1JPT1Q9LwogICAgICAtIElNR1BST1hZX1VTRV9FVEFHPXRydWUKICAgICAgLSAnSU1HUFJPWFlfRU5BQkxFX1dFQlBfREVURUNUSU9OPSR7SU1HUFJPWFlfRU5BQkxFX1dFQlBfREVURUNUSU9OOi10cnVlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vdm9sdW1lcy9zdG9yYWdlOi92YXIvbGliL3N0b3JhZ2UnCiAgc3VwYWJhc2UtbWV0YToKICAgIGltYWdlOiAnc3VwYWJhc2UvcG9zdGdyZXMtbWV0YTp2MC44OS4zJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQR19NRVRBX1BPUlQ9ODA4MAogICAgICAtICdQR19NRVRBX0RCX0hPU1Q9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdQR19NRVRBX0RCX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUEdfTUVUQV9EQl9OQU1FPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSBQR19NRVRBX0RCX1VTRVI9c3VwYWJhc2VfYWRtaW4KICAgICAgLSAnUEdfTUVUQV9EQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogIHN1cGFiYXNlLWVkZ2UtZnVuY3Rpb25zOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9lZGdlLXJ1bnRpbWU6djEuNjcuNCcKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGVjaG8KICAgICAgICAtICdFZGdlIEZ1bmN0aW9ucyBpcyBoZWFsdGh5JwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ1NVUEFCQVNFX1VSTD0ke1NFUlZJQ0VfVVJMX1NVUEFCQVNFS09OR30nCiAgICAgIC0gJ1NVUEFCQVNFX0FOT05fS0VZPSR7U0VSVklDRV9TVVBBQkFTRUFOT05fS0VZfScKICAgICAgLSAnU1VQQUJBU0VfU0VSVklDRV9ST0xFX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VTRVJWSUNFX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX0RCX1VSTD1wb3N0Z3Jlc3FsOi8vcG9zdGdyZXM6JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ1ZFUklGWV9KV1Q9JHtGVU5DVElPTlNfVkVSSUZZX0pXVDotZmFsc2V9JwogICAgdm9sdW1lczoKICAgICAgLSAnLi92b2x1bWVzL2Z1bmN0aW9uczovaG9tZS9kZW5vL2Z1bmN0aW9ucycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9mdW5jdGlvbnMvbWFpbi9pbmRleC50cwogICAgICAgIHRhcmdldDogL2hvbWUvZGVuby9mdW5jdGlvbnMvbWFpbi9pbmRleC50cwogICAgICAgIGNvbnRlbnQ6ICJpbXBvcnQgeyBzZXJ2ZSB9IGZyb20gJ2h0dHBzOi8vZGVuby5sYW5kL3N0ZEAwLjEzMS4wL2h0dHAvc2VydmVyLnRzJ1xuaW1wb3J0ICogYXMgam9zZSBmcm9tICdodHRwczovL2Rlbm8ubGFuZC94L2pvc2VAdjQuMTQuNC9pbmRleC50cydcblxuY29uc29sZS5sb2coJ21haW4gZnVuY3Rpb24gc3RhcnRlZCcpXG5cbmNvbnN0IEpXVF9TRUNSRVQgPSBEZW5vLmVudi5nZXQoJ0pXVF9TRUNSRVQnKVxuY29uc3QgVkVSSUZZX0pXVCA9IERlbm8uZW52LmdldCgnVkVSSUZZX0pXVCcpID09PSAndHJ1ZSdcblxuZnVuY3Rpb24gZ2V0QXV0aFRva2VuKHJlcTogUmVxdWVzdCkge1xuICBjb25zdCBhdXRoSGVhZGVyID0gcmVxLmhlYWRlcnMuZ2V0KCdhdXRob3JpemF0aW9uJylcbiAgaWYgKCFhdXRoSGVhZGVyKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdNaXNzaW5nIGF1dGhvcml6YXRpb24gaGVhZGVyJylcbiAgfVxuICBjb25zdCBbYmVhcmVyLCB0b2tlbl0gPSBhdXRoSGVhZGVyLnNwbGl0KCcgJylcbiAgaWYgKGJlYXJlciAhPT0gJ0JlYXJlcicpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEF1dGggaGVhZGVyIGlzIG5vdCAnQmVhcmVyIHt0b2tlbn0nYClcbiAgfVxuICByZXR1cm4gdG9rZW5cbn1cblxuYXN5bmMgZnVuY3Rpb24gdmVyaWZ5SldUKGp3dDogc3RyaW5nKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGNvbnN0IGVuY29kZXIgPSBuZXcgVGV4dEVuY29kZXIoKVxuICBjb25zdCBzZWNyZXRLZXkgPSBlbmNvZGVyLmVuY29kZShKV1RfU0VDUkVUKVxuICB0cnkge1xuICAgIGF3YWl0IGpvc2Uuand0VmVyaWZ5KGp3dCwgc2VjcmV0S2V5KVxuICB9IGNhdGNoIChlcnIpIHtcbiAgICBjb25zb2xlLmVycm9yKGVycilcbiAgICByZXR1cm4gZmFsc2VcbiAgfVxuICByZXR1cm4gdHJ1ZVxufVxuXG5zZXJ2ZShhc3luYyAocmVxOiBSZXF1ZXN0KSA9PiB7XG4gIGlmIChyZXEubWV0aG9kICE9PSAnT1BUSU9OUycgJiYgVkVSSUZZX0pXVCkge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCB0b2tlbiA9IGdldEF1dGhUb2tlbihyZXEpXG4gICAgICBjb25zdCBpc1ZhbGlkSldUID0gYXdhaXQgdmVyaWZ5SldUKHRva2VuKVxuXG4gICAgICBpZiAoIWlzVmFsaWRKV1QpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBSZXNwb25zZShKU09OLnN0cmluZ2lmeSh7IG1zZzogJ0ludmFsaWQgSldUJyB9KSwge1xuICAgICAgICAgIHN0YXR1czogNDAxLFxuICAgICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LFxuICAgICAgICB9KVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoZSlcbiAgICAgIHJldHVybiBuZXcgUmVzcG9uc2UoSlNPTi5zdHJpbmdpZnkoeyBtc2c6IGUudG9TdHJpbmcoKSB9KSwge1xuICAgICAgICBzdGF0dXM6IDQwMSxcbiAgICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG4gICAgICB9KVxuICAgIH1cbiAgfVxuXG4gIGNvbnN0IHVybCA9IG5ldyBVUkwocmVxLnVybClcbiAgY29uc3QgeyBwYXRobmFtZSB9ID0gdXJsXG4gIGNvbnN0IHBhdGhfcGFydHMgPSBwYXRobmFtZS5zcGxpdCgnLycpXG4gIGNvbnN0IHNlcnZpY2VfbmFtZSA9IHBhdGhfcGFydHNbMV1cblxuICBpZiAoIXNlcnZpY2VfbmFtZSB8fCBzZXJ2aWNlX25hbWUgPT09ICcnKSB7XG4gICAgY29uc3QgZXJyb3IgPSB7IG1zZzogJ21pc3NpbmcgZnVuY3Rpb24gbmFtZSBpbiByZXF1ZXN0JyB9XG4gICAgcmV0dXJuIG5ldyBSZXNwb25zZShKU09OLnN0cmluZ2lmeShlcnJvciksIHtcbiAgICAgIHN0YXR1czogNDAwLFxuICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG4gICAgfSlcbiAgfVxuXG4gIGNvbnN0IHNlcnZpY2VQYXRoID0gYC9ob21lL2Rlbm8vZnVuY3Rpb25zLyR7c2VydmljZV9uYW1lfWBcbiAgY29uc29sZS5lcnJvcihgc2VydmluZyB0aGUgcmVxdWVzdCB3aXRoICR7c2VydmljZVBhdGh9YClcblxuICBjb25zdCBtZW1vcnlMaW1pdE1iID0gMTUwXG4gIGNvbnN0IHdvcmtlclRpbWVvdXRNcyA9IDEgKiA2MCAqIDEwMDBcbiAgY29uc3Qgbm9Nb2R1bGVDYWNoZSA9IGZhbHNlXG4gIGNvbnN0IGltcG9ydE1hcFBhdGggPSBudWxsXG4gIGNvbnN0IGVudlZhcnNPYmogPSBEZW5vLmVudi50b09iamVjdCgpXG4gIGNvbnN0IGVudlZhcnMgPSBPYmplY3Qua2V5cyhlbnZWYXJzT2JqKS5tYXAoKGspID0+IFtrLCBlbnZWYXJzT2JqW2tdXSlcblxuICB0cnkge1xuICAgIGNvbnN0IHdvcmtlciA9IGF3YWl0IEVkZ2VSdW50aW1lLnVzZXJXb3JrZXJzLmNyZWF0ZSh7XG4gICAgICBzZXJ2aWNlUGF0aCxcbiAgICAgIG1lbW9yeUxpbWl0TWIsXG4gICAgICB3b3JrZXJUaW1lb3V0TXMsXG4gICAgICBub01vZHVsZUNhY2hlLFxuICAgICAgaW1wb3J0TWFwUGF0aCxcbiAgICAgIGVudlZhcnMsXG4gICAgfSlcbiAgICByZXR1cm4gYXdhaXQgd29ya2VyLmZldGNoKHJlcSlcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnN0IGVycm9yID0geyBtc2c6IGUudG9TdHJpbmcoKSB9XG4gICAgcmV0dXJuIG5ldyBSZXNwb25zZShKU09OLnN0cmluZ2lmeShlcnJvciksIHtcbiAgICAgIHN0YXR1czogNTAwLFxuICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG4gICAgfSlcbiAgfVxufSkiCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZnVuY3Rpb25zL2hlbGxvL2luZGV4LnRzCiAgICAgICAgdGFyZ2V0OiAvaG9tZS9kZW5vL2Z1bmN0aW9ucy9oZWxsby9pbmRleC50cwogICAgICAgIGNvbnRlbnQ6ICIvLyBGb2xsb3cgdGhpcyBzZXR1cCBndWlkZSB0byBpbnRlZ3JhdGUgdGhlIERlbm8gbGFuZ3VhZ2Ugc2VydmVyIHdpdGggeW91ciBlZGl0b3I6XG4vLyBodHRwczovL2Rlbm8ubGFuZC9tYW51YWwvZ2V0dGluZ19zdGFydGVkL3NldHVwX3lvdXJfZW52aXJvbm1lbnRcbi8vIFRoaXMgZW5hYmxlcyBhdXRvY29tcGxldGUsIGdvIHRvIGRlZmluaXRpb24sIGV0Yy5cblxuaW1wb3J0IHsgc2VydmUgfSBmcm9tIFwiaHR0cHM6Ly9kZW5vLmxhbmQvc3RkQDAuMTc3LjEvaHR0cC9zZXJ2ZXIudHNcIlxuXG5zZXJ2ZShhc3luYyAoKSA9PiB7XG4gIHJldHVybiBuZXcgUmVzcG9uc2UoXG4gICAgYFwiSGVsbG8gZnJvbSBFZGdlIEZ1bmN0aW9ucyFcImAsXG4gICAgeyBoZWFkZXJzOiB7IFwiQ29udGVudC1UeXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiIH0gfSxcbiAgKVxufSlcblxuLy8gVG8gaW52b2tlOlxuLy8gY3VybCAnaHR0cDovL2xvY2FsaG9zdDo8S09OR19IVFRQX1BPUlQ+L2Z1bmN0aW9ucy92MS9oZWxsbycgXFxcbi8vICAgLS1oZWFkZXIgJ0F1dGhvcml6YXRpb246IEJlYXJlciA8YW5vbi9zZXJ2aWNlX3JvbGUgQVBJIGtleT4nXG4iCiAgICBjb21tYW5kOgogICAgICAtIHN0YXJ0CiAgICAgIC0gJy0tbWFpbi1zZXJ2aWNlJwogICAgICAtIC9ob21lL2Rlbm8vZnVuY3Rpb25zL21haW4KICBzdXBhYmFzZS1zdXBhdmlzb3I6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3N1cGF2aXNvcjoyLjUuMScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLXNTZkwnCiAgICAgICAgLSAnLW8nCiAgICAgICAgLSAvZGV2L251bGwKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjQwMDAvYXBpL2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1kYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPT0xFUl9URU5BTlRfSUQ9ZGV2X3RlbmFudAogICAgICAtIFBPT0xFUl9QT09MX01PREU9dHJhbnNhY3Rpb24KICAgICAgLSAnUE9PTEVSX0RFRkFVTFRfUE9PTF9TSVpFPSR7UE9PTEVSX0RFRkFVTFRfUE9PTF9TSVpFOi0yMH0nCiAgICAgIC0gJ1BPT0xFUl9NQVhfQ0xJRU5UX0NPTk49JHtQT09MRVJfTUFYX0NMSUVOVF9DT05OOi0xMDB9JwogICAgICAtIFBPUlQ9NDAwMAogICAgICAtICdQT1NUR1JFU19QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ1BPU1RHUkVTX0hPU1ROQU1FPSR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdEQVRBQkFTRV9VUkw9ZWN0bzovL3N1cGFiYXNlX2FkbWluOiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AJHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9OiR7UE9TVEdSRVNfUE9SVDotNTQzMn0vX3N1cGFiYXNlJwogICAgICAtIENMVVNURVJfUE9TVEdSRVM9dHJ1ZQogICAgICAtICdTRUNSRVRfS0VZX0JBU0U9JHtTRVJWSUNFX1BBU1NXT1JEX1NVUEFWSVNPUlNFQ1JFVH0nCiAgICAgIC0gJ1ZBVUxUX0VOQ19LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX1ZBVUxURU5DfScKICAgICAgLSAnQVBJX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ01FVFJJQ1NfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSBSRUdJT049bG9jYWwKICAgICAgLSAnRVJMX0FGTEFHUz0tcHJvdG9fZGlzdCBpbmV0X3RjcCcKICAgIGNvbW1hbmQ6CiAgICAgIC0gL2Jpbi9zaAogICAgICAtICctYycKICAgICAgLSAnL2FwcC9iaW4vbWlncmF0ZSAmJiAvYXBwL2Jpbi9zdXBhdmlzb3IgZXZhbCAiJCQoY2F0IC9ldGMvcG9vbGVyL3Bvb2xlci5leHMpIiAmJiAvYXBwL2Jpbi9zZXJ2ZXInCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL3Bvb2xlci9wb29sZXIuZXhzCiAgICAgICAgdGFyZ2V0OiAvZXRjL3Bvb2xlci9wb29sZXIuZXhzCiAgICAgICAgY29udGVudDogIns6b2ssIF99ID0gQXBwbGljYXRpb24uZW5zdXJlX2FsbF9zdGFydGVkKDpzdXBhdmlzb3IpXG57Om9rLCB2ZXJzaW9ufSA9XG4gICAgY2FzZSBTdXBhdmlzb3IuUmVwby5xdWVyeSEoXCJzZWxlY3QgdmVyc2lvbigpXCIpIGRvXG4gICAgJXtyb3dzOiBbW3Zlcl1dfSAtPiBTdXBhdmlzb3IuSGVscGVycy5wYXJzZV9wZ192ZXJzaW9uKHZlcilcbiAgICBfIC0+IG5pbFxuICAgIGVuZFxucGFyYW1zID0gJXtcbiAgICBcImV4dGVybmFsX2lkXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfVEVOQU5UX0lEXCIpLFxuICAgIFwiZGJfaG9zdFwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9TVEdSRVNfSE9TVE5BTUVcIiksXG4gICAgXCJkYl9wb3J0XCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19QT1JUXCIpIHw+IFN0cmluZy50b19pbnRlZ2VyKCksXG4gICAgXCJkYl9kYXRhYmFzZVwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9TVEdSRVNfREJcIiksXG4gICAgXCJyZXF1aXJlX3VzZXJcIiA9PiBmYWxzZSxcbiAgICBcImF1dGhfcXVlcnlcIiA9PiBcIlNFTEVDVCAqIEZST00gcGdib3VuY2VyLmdldF9hdXRoKCQxKVwiLFxuICAgIFwiZGVmYXVsdF9tYXhfY2xpZW50c1wiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9PTEVSX01BWF9DTElFTlRfQ09OTlwiKSxcbiAgICBcImRlZmF1bHRfcG9vbF9zaXplXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfREVGQVVMVF9QT09MX1NJWkVcIiksXG4gICAgXCJkZWZhdWx0X3BhcmFtZXRlcl9zdGF0dXNcIiA9PiAle1wic2VydmVyX3ZlcnNpb25cIiA9PiB2ZXJzaW9ufSxcbiAgICBcInVzZXJzXCIgPT4gWyV7XG4gICAgXCJkYl91c2VyXCIgPT4gXCJwZ2JvdW5jZXJcIixcbiAgICBcImRiX3Bhc3N3b3JkXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19QQVNTV09SRFwiKSxcbiAgICBcIm1vZGVfdHlwZVwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9PTEVSX1BPT0xfTU9ERVwiKSxcbiAgICBcInBvb2xfc2l6ZVwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9PTEVSX0RFRkFVTFRfUE9PTF9TSVpFXCIpLFxuICAgIFwiaXNfbWFuYWdlclwiID0+IHRydWVcbiAgICB9XVxufVxuXG50ZW5hbnQgPSBTdXBhdmlzb3IuVGVuYW50cy5nZXRfdGVuYW50X2J5X2V4dGVybmFsX2lkKHBhcmFtc1tcImV4dGVybmFsX2lkXCJdKVxuXG5pZiB0ZW5hbnQgZG9cbiAgezpvaywgX30gPSBTdXBhdmlzb3IuVGVuYW50cy51cGRhdGVfdGVuYW50KHRlbmFudCwgcGFyYW1zKVxuZWxzZVxuICB7Om9rLCBffSA9IFN1cGF2aXNvci5UZW5hbnRzLmNyZWF0ZV90ZW5hbnQocGFyYW1zKVxuZW5kXG4iCg==", + "compose": "c2VydmljZXM6CiAgc3VwYWJhc2Uta29uZzoKICAgIGltYWdlOiAna29uZzoyLjguMScKICAgIGVudHJ5cG9pbnQ6ICdiYXNoIC1jICcnZXZhbCAiZWNobyBcIiQkKGNhdCB+L3RlbXAueW1sKVwiIiA+IH4va29uZy55bWwgJiYgL2RvY2tlci1lbnRyeXBvaW50LnNoIGtvbmcgZG9ja2VyLXN0YXJ0JycnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX1NVUEFCQVNFS09OR184MDAwCiAgICAgIC0gJ0tPTkdfUE9SVF9NQVBTPTQ0Mzo4MDAwJwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIEtPTkdfREFUQUJBU0U9b2ZmCiAgICAgIC0gS09OR19ERUNMQVJBVElWRV9DT05GSUc9L2hvbWUva29uZy9rb25nLnltbAogICAgICAtICdLT05HX0ROU19PUkRFUj1MQVNULEEsQ05BTUUnCiAgICAgIC0gJ0tPTkdfUExVR0lOUz1yZXF1ZXN0LXRyYW5zZm9ybWVyLGNvcnMsa2V5LWF1dGgsYWNsLGJhc2ljLWF1dGgnCiAgICAgIC0gS09OR19OR0lOWF9QUk9YWV9QUk9YWV9CVUZGRVJfU0laRT0xNjBrCiAgICAgIC0gJ0tPTkdfTkdJTlhfUFJPWFlfUFJPWFlfQlVGRkVSUz02NCAxNjBrJwogICAgICAtICdTVVBBQkFTRV9BTk9OX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX1NFUlZJQ0VfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnREFTSEJPQVJEX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX0FETUlOfScKICAgICAgLSAnREFTSEJPQVJEX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0nCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2FwaS9rb25nLnltbAogICAgICAgIHRhcmdldDogL2hvbWUva29uZy90ZW1wLnltbAogICAgICAgIGNvbnRlbnQ6ICJfZm9ybWF0X3ZlcnNpb246ICcyLjEnXG5fdHJhbnNmb3JtOiB0cnVlXG5cbiMjI1xuIyMjIENvbnN1bWVycyAvIFVzZXJzXG4jIyNcbmNvbnN1bWVyczpcbiAgLSB1c2VybmFtZTogREFTSEJPQVJEXG4gIC0gdXNlcm5hbWU6IGFub25cbiAgICBrZXlhdXRoX2NyZWRlbnRpYWxzOlxuICAgICAgLSBrZXk6ICRTVVBBQkFTRV9BTk9OX0tFWVxuICAtIHVzZXJuYW1lOiBzZXJ2aWNlX3JvbGVcbiAgICBrZXlhdXRoX2NyZWRlbnRpYWxzOlxuICAgICAgLSBrZXk6ICRTVVBBQkFTRV9TRVJWSUNFX0tFWVxuXG4jIyNcbiMjIyBBY2Nlc3MgQ29udHJvbCBMaXN0XG4jIyNcbmFjbHM6XG4gIC0gY29uc3VtZXI6IGFub25cbiAgICBncm91cDogYW5vblxuICAtIGNvbnN1bWVyOiBzZXJ2aWNlX3JvbGVcbiAgICBncm91cDogYWRtaW5cblxuIyMjXG4jIyMgRGFzaGJvYXJkIGNyZWRlbnRpYWxzXG4jIyNcbmJhc2ljYXV0aF9jcmVkZW50aWFsczpcbi0gY29uc3VtZXI6IERBU0hCT0FSRFxuICB1c2VybmFtZTogJERBU0hCT0FSRF9VU0VSTkFNRVxuICBwYXNzd29yZDogJERBU0hCT0FSRF9QQVNTV09SRFxuXG5cbiMjI1xuIyMjIEFQSSBSb3V0ZXNcbiMjI1xuc2VydmljZXM6XG5cbiAgIyMgT3BlbiBBdXRoIHJvdXRlc1xuICAtIG5hbWU6IGF1dGgtdjEtb3BlblxuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS92ZXJpZnlcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtb3BlblxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvdmVyaWZ5XG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAtIG5hbWU6IGF1dGgtdjEtb3Blbi1jYWxsYmFja1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS9jYWxsYmFja1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1vcGVuLWNhbGxiYWNrXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYXV0aC92MS9jYWxsYmFja1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgLSBuYW1lOiBhdXRoLXYxLW9wZW4tYXV0aG9yaXplXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5L2F1dGhvcml6ZVxuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1vcGVuLWF1dGhvcml6ZVxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvYXV0aG9yaXplXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIFNlY3VyZSBBdXRoIHJvdXRlc1xuICAtIG5hbWU6IGF1dGgtdjFcbiAgICBfY29tbWVudDogJ0dvVHJ1ZTogL2F1dGgvdjEvKiAtPiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5LyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYXV0aDo5OTk5L1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYXV0aC12MS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9hdXRoL3YxL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IGZhbHNlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgUkVTVCByb3V0ZXNcbiAgLSBuYW1lOiByZXN0LXYxXG4gICAgX2NvbW1lbnQ6ICdQb3N0Z1JFU1Q6IC9yZXN0L3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlc3QtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVzdC92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgR3JhcGhRTCByb3V0ZXNcbiAgLSBuYW1lOiBncmFwaHFsLXYxXG4gICAgX2NvbW1lbnQ6ICdQb3N0Z1JFU1Q6IC9ncmFwaHFsL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXJlc3Q6MzAwMC9ycGMvZ3JhcGhxbCdcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvcnBjL2dyYXBocWxcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGdyYXBocWwtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvZ3JhcGhxbC92MVxuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IHRydWVcbiAgICAgIC0gbmFtZTogcmVxdWVzdC10cmFuc2Zvcm1lclxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgYWRkOlxuICAgICAgICAgICAgaGVhZGVyczpcbiAgICAgICAgICAgICAgLSBDb250ZW50LVByb2ZpbGU6Z3JhcGhxbF9wdWJsaWNcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuXG4gICMjIFNlY3VyZSBSZWFsdGltZSByb3V0ZXNcbiAgLSBuYW1lOiByZWFsdGltZS12MS13c1xuICAgIF9jb21tZW50OiAnUmVhbHRpbWU6IC9yZWFsdGltZS92MS8qIC0+IHdzOi8vcmVhbHRpbWU6NDAwMC9zb2NrZXQvKidcbiAgICB1cmw6IGh0dHA6Ly9yZWFsdGltZS1kZXY6NDAwMC9zb2NrZXRcbiAgICBwcm90b2NvbDogd3NcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlYWx0aW1lLXYxLXdzXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVhbHRpbWUvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuICAtIG5hbWU6IHJlYWx0aW1lLXYxLXJlc3RcbiAgICBfY29tbWVudDogJ1JlYWx0aW1lOiAvcmVhbHRpbWUvdjEvKiAtPiB3czovL3JlYWx0aW1lOjQwMDAvc29ja2V0LyonXG4gICAgdXJsOiBodHRwOi8vcmVhbHRpbWUtZGV2OjQwMDAvYXBpXG4gICAgcHJvdG9jb2w6IGh0dHBcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHJlYWx0aW1lLXYxLXJlc3RcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9yZWFsdGltZS92MS9hcGlcbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiBmYWxzZVxuICAgICAgLSBuYW1lOiBhY2xcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfZ3JvdXBzX2hlYWRlcjogdHJ1ZVxuICAgICAgICAgIGFsbG93OlxuICAgICAgICAgICAgLSBhZG1pblxuICAgICAgICAgICAgLSBhbm9uXG5cbiAgIyMgU3RvcmFnZSByb3V0ZXM6IHRoZSBzdG9yYWdlIHNlcnZlciBtYW5hZ2VzIGl0cyBvd24gYXV0aFxuICAtIG5hbWU6IHN0b3JhZ2UtdjFcbiAgICBfY29tbWVudDogJ1N0b3JhZ2U6IC9zdG9yYWdlL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLXN0b3JhZ2U6NTAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXN0b3JhZ2U6NTAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IHN0b3JhZ2UtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvc3RvcmFnZS92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG5cbiAgIyMgRWRnZSBGdW5jdGlvbnMgcm91dGVzXG4gIC0gbmFtZTogZnVuY3Rpb25zLXYxXG4gICAgX2NvbW1lbnQ6ICdFZGdlIEZ1bmN0aW9uczogL2Z1bmN0aW9ucy92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1lZGdlLWZ1bmN0aW9uczo5MDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtZWRnZS1mdW5jdGlvbnM6OTAwMC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGZ1bmN0aW9ucy12MS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9mdW5jdGlvbnMvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIEFuYWx5dGljcyByb3V0ZXNcbiAgLSBuYW1lOiBhbmFseXRpY3MtdjFcbiAgICBfY29tbWVudDogJ0FuYWx5dGljczogL2FuYWx5dGljcy92MS8qIC0+IGh0dHA6Ly9sb2dmbGFyZTo0MDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBhbmFseXRpY3MtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYW5hbHl0aWNzL3YxL1xuXG4gICMjIFNlY3VyZSBEYXRhYmFzZSByb3V0ZXNcbiAgLSBuYW1lOiBtZXRhXG4gICAgX2NvbW1lbnQ6ICdwZy1tZXRhOiAvcGcvKiAtPiBodHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwL1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogbWV0YS1hbGxcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9wZy9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cblxuICAjIyBQcm90ZWN0ZWQgRGFzaGJvYXJkIC0gY2F0Y2ggYWxsIHJlbWFpbmluZyByb3V0ZXNcbiAgLSBuYW1lOiBkYXNoYm9hcmRcbiAgICBfY29tbWVudDogJ1N0dWRpbzogLyogLT4gaHR0cDovL3N0dWRpbzozMDAwLyonXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2Utc3R1ZGlvOjMwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBkYXNoYm9hcmQtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBiYXNpYy1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4iCiAgc3VwYWJhc2Utc3R1ZGlvOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9zdHVkaW86MjAyNS4xMi4xNy1zaGEtNDNmNGY3ZicKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBub2RlCiAgICAgICAgLSAnLWUnCiAgICAgICAgLSAiZmV0Y2goJ2h0dHA6Ly8xMjcuMC4wLjE6MzAwMC9hcGkvcGxhdGZvcm0vcHJvZmlsZScpLnRoZW4oKHIpID0+IHtpZiAoci5zdGF0dXMgIT09IDIwMCkgdGhyb3cgbmV3IEVycm9yKHIuc3RhdHVzKX0pIgogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBIT1NUTkFNRT0wLjAuMC4wCiAgICAgIC0gJ1NUVURJT19QR19NRVRBX1VSTD1odHRwOi8vc3VwYWJhc2UtbWV0YTo4MDgwJwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdERUZBVUxUX09SR0FOSVpBVElPTl9OQU1FPSR7U1RVRElPX0RFRkFVTFRfT1JHQU5JWkFUSU9OOi1EZWZhdWx0IE9yZ2FuaXphdGlvbn0nCiAgICAgIC0gJ0RFRkFVTFRfUFJPSkVDVF9OQU1FPSR7U1RVRElPX0RFRkFVTFRfUFJPSkVDVDotRGVmYXVsdCBQcm9qZWN0fScKICAgICAgLSAnU1VQQUJBU0VfVVJMPWh0dHA6Ly9zdXBhYmFzZS1rb25nOjgwMDAnCiAgICAgIC0gJ1NVUEFCQVNFX1BVQkxJQ19VUkw9JHtTRVJWSUNFX1VSTF9TVVBBQkFTRUtPTkd9JwogICAgICAtICdTVVBBQkFTRV9BTk9OX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX1NFUlZJQ0VfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnQVVUSF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdMT0dGTEFSRV9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9MT0dGTEFSRX0nCiAgICAgIC0gJ0xPR0ZMQVJFX1VSTD1odHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAnCiAgICAgIC0gJ1NVUEFCQVNFX1BVQkxJQ19BUEk9JHtTRVJWSUNFX1VSTF9TVVBBQkFTRUtPTkd9JwogICAgICAtIE5FWFRfUFVCTElDX0VOQUJMRV9MT0dTPXRydWUKICAgICAgLSBORVhUX0FOQUxZVElDU19CQUNLRU5EX1BST1ZJREVSPXBvc3RncmVzCiAgICAgIC0gJ09QRU5BSV9BUElfS0VZPSR7T1BFTkFJX0FQSV9LRVl9JwogIHN1cGFiYXNlLWRiOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9wb3N0Z3JlczoxNS44LjEuMDQ4JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdwZ19pc3JlYWR5IC1VIHBvc3RncmVzIC1oIDEyNy4wLjAuMScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS12ZWN0b3I6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGNvbW1hbmQ6CiAgICAgIC0gcG9zdGdyZXMKICAgICAgLSAnLWMnCiAgICAgIC0gY29uZmlnX2ZpbGU9L2V0Yy9wb3N0Z3Jlc3FsL3Bvc3RncmVzcWwuY29uZgogICAgICAtICctYycKICAgICAgLSBsb2dfbWluX21lc3NhZ2VzPWZhdGFsCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19IT1NUPS92YXIvcnVuL3Bvc3RncmVzcWwKICAgICAgLSAnUEdQT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ1BPU1RHUkVTX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUEdQQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQR0RBVEFCQVNFPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdKV1RfRVhQPSR7SldUX0VYUElSWTotMzYwMH0nCiAgICB2b2x1bWVzOgogICAgICAtICdzdXBhYmFzZS1kYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9yZWFsdGltZS5zcWwKICAgICAgICB0YXJnZXQ6IC9kb2NrZXItZW50cnlwb2ludC1pbml0ZGIuZC9taWdyYXRpb25zLzk5LXJlYWx0aW1lLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCJzdXBhYmFzZV9hZG1pblwiYFxuXG5jcmVhdGUgc2NoZW1hIGlmIG5vdCBleGlzdHMgX3JlYWx0aW1lO1xuYWx0ZXIgc2NoZW1hIF9yZWFsdGltZSBvd25lciB0byA6cGd1c2VyO1xuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2RiL19zdXBhYmFzZS5zcWwKICAgICAgICB0YXJnZXQ6IC9kb2NrZXItZW50cnlwb2ludC1pbml0ZGIuZC9taWdyYXRpb25zLzk3LV9zdXBhYmFzZS5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwiJFBPU1RHUkVTX1VTRVJcImBcblxuQ1JFQVRFIERBVEFCQVNFIF9zdXBhYmFzZSBXSVRIIE9XTkVSIDpwZ3VzZXI7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcG9vbGVyLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktcG9vbGVyLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCJzdXBhYmFzZV9hZG1pblwiYFxuXFxjIF9zdXBhYmFzZVxuY3JlYXRlIHNjaGVtYSBpZiBub3QgZXhpc3RzIF9zdXBhdmlzb3I7XG5hbHRlciBzY2hlbWEgX3N1cGF2aXNvciBvd25lciB0byA6cGd1c2VyO1xuXFxjIHBvc3RncmVzXG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvd2ViaG9va3Muc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk4LXdlYmhvb2tzLnNxbAogICAgICAgIGNvbnRlbnQ6ICJCRUdJTjtcbi0tIENyZWF0ZSBwZ19uZXQgZXh0ZW5zaW9uXG5DUkVBVEUgRVhURU5TSU9OIElGIE5PVCBFWElTVFMgcGdfbmV0IFNDSEVNQSBleHRlbnNpb25zO1xuLS0gQ3JlYXRlIHN1cGFiYXNlX2Z1bmN0aW9ucyBzY2hlbWFcbkNSRUFURSBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIEFVVEhPUklaQVRJT04gc3VwYWJhc2VfYWRtaW47XG5HUkFOVCBVU0FHRSBPTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gVEFCTEVTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gRlVOQ1RJT05TIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gU0VRVUVOQ0VTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMubWlncmF0aW9ucyBkZWZpbml0aW9uXG5DUkVBVEUgVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLm1pZ3JhdGlvbnMgKFxuICB2ZXJzaW9uIHRleHQgUFJJTUFSWSBLRVksXG4gIGluc2VydGVkX2F0IHRpbWVzdGFtcHR6IE5PVCBOVUxMIERFRkFVTFQgTk9XKClcbik7XG4tLSBJbml0aWFsIHN1cGFiYXNlX2Z1bmN0aW9ucyBtaWdyYXRpb25cbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCdpbml0aWFsJyk7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMuaG9va3MgZGVmaW5pdGlvblxuQ1JFQVRFIFRBQkxFIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyAoXG4gIGlkIGJpZ3NlcmlhbCBQUklNQVJZIEtFWSxcbiAgaG9va190YWJsZV9pZCBpbnRlZ2VyIE5PVCBOVUxMLFxuICBob29rX25hbWUgdGV4dCBOT1QgTlVMTCxcbiAgY3JlYXRlZF9hdCB0aW1lc3RhbXB0eiBOT1QgTlVMTCBERUZBVUxUIE5PVygpLFxuICByZXF1ZXN0X2lkIGJpZ2ludFxuKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfcmVxdWVzdF9pZF9pZHggT04gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIFVTSU5HIGJ0cmVlIChyZXF1ZXN0X2lkKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfaF90YWJsZV9pZF9oX25hbWVfaWR4IE9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyBVU0lORyBidHJlZSAoaG9va190YWJsZV9pZCwgaG9va19uYW1lKTtcbkNPTU1FTlQgT04gVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIElTICdTdXBhYmFzZSBGdW5jdGlvbnMgSG9va3M6IEF1ZGl0IHRyYWlsIGZvciB0cmlnZ2VyZWQgaG9va3MuJztcbkNSRUFURSBGVU5DVElPTiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KClcbiAgUkVUVVJOUyB0cmlnZ2VyXG4gIExBTkdVQUdFIHBscGdzcWxcbiAgQVMgJGZ1bmN0aW9uJFxuICBERUNMQVJFXG4gICAgcmVxdWVzdF9pZCBiaWdpbnQ7XG4gICAgcGF5bG9hZCBqc29uYjtcbiAgICB1cmwgdGV4dCA6PSBUR19BUkdWWzBdOjp0ZXh0O1xuICAgIG1ldGhvZCB0ZXh0IDo9IFRHX0FSR1ZbMV06OnRleHQ7XG4gICAgaGVhZGVycyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHBhcmFtcyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHRpbWVvdXRfbXMgaW50ZWdlciBERUZBVUxUIDEwMDA7XG4gIEJFR0lOXG4gICAgSUYgdXJsIElTIE5VTEwgT1IgdXJsID0gJ251bGwnIFRIRU5cbiAgICAgIFJBSVNFIEVYQ0VQVElPTiAndXJsIGFyZ3VtZW50IGlzIG1pc3NpbmcnO1xuICAgIEVORCBJRjtcblxuICAgIElGIG1ldGhvZCBJUyBOVUxMIE9SIG1ldGhvZCA9ICdudWxsJyBUSEVOXG4gICAgICBSQUlTRSBFWENFUFRJT04gJ21ldGhvZCBhcmd1bWVudCBpcyBtaXNzaW5nJztcbiAgICBFTkQgSUY7XG5cbiAgICBJRiBUR19BUkdWWzJdIElTIE5VTEwgT1IgVEdfQVJHVlsyXSA9ICdudWxsJyBUSEVOXG4gICAgICBoZWFkZXJzID0gJ3tcIkNvbnRlbnQtVHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIn0nOjpqc29uYjtcbiAgICBFTFNFXG4gICAgICBoZWFkZXJzID0gVEdfQVJHVlsyXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVlszXSBJUyBOVUxMIE9SIFRHX0FSR1ZbM10gPSAnbnVsbCcgVEhFTlxuICAgICAgcGFyYW1zID0gJ3t9Jzo6anNvbmI7XG4gICAgRUxTRVxuICAgICAgcGFyYW1zID0gVEdfQVJHVlszXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVls0XSBJUyBOVUxMIE9SIFRHX0FSR1ZbNF0gPSAnbnVsbCcgVEhFTlxuICAgICAgdGltZW91dF9tcyA9IDEwMDA7XG4gICAgRUxTRVxuICAgICAgdGltZW91dF9tcyA9IFRHX0FSR1ZbNF06OmludGVnZXI7XG4gICAgRU5EIElGO1xuXG4gICAgQ0FTRVxuICAgICAgV0hFTiBtZXRob2QgPSAnR0VUJyBUSEVOXG4gICAgICAgIFNFTEVDVCBodHRwX2dldCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9nZXQoXG4gICAgICAgICAgdXJsLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIFdIRU4gbWV0aG9kID0gJ1BPU1QnIFRIRU5cbiAgICAgICAgcGF5bG9hZCA9IGpzb25iX2J1aWxkX29iamVjdChcbiAgICAgICAgICAnb2xkX3JlY29yZCcsIE9MRCxcbiAgICAgICAgICAncmVjb3JkJywgTkVXLFxuICAgICAgICAgICd0eXBlJywgVEdfT1AsXG4gICAgICAgICAgJ3RhYmxlJywgVEdfVEFCTEVfTkFNRSxcbiAgICAgICAgICAnc2NoZW1hJywgVEdfVEFCTEVfU0NIRU1BXG4gICAgICAgICk7XG5cbiAgICAgICAgU0VMRUNUIGh0dHBfcG9zdCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9wb3N0KFxuICAgICAgICAgIHVybCxcbiAgICAgICAgICBwYXlsb2FkLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIEVMU0VcbiAgICAgICAgUkFJU0UgRVhDRVBUSU9OICdtZXRob2QgYXJndW1lbnQgJSBpcyBpbnZhbGlkJywgbWV0aG9kO1xuICAgIEVORCBDQVNFO1xuXG4gICAgSU5TRVJUIElOVE8gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzXG4gICAgICAoaG9va190YWJsZV9pZCwgaG9va19uYW1lLCByZXF1ZXN0X2lkKVxuICAgIFZBTFVFU1xuICAgICAgKFRHX1JFTElELCBUR19OQU1FLCByZXF1ZXN0X2lkKTtcblxuICAgIFJFVFVSTiBORVc7XG4gIEVORFxuJGZ1bmN0aW9uJDtcbi0tIFN1cGFiYXNlIHN1cGVyIGFkbWluXG5ET1xuJCRcbkJFR0lOXG4gIElGIE5PVCBFWElTVFMgKFxuICAgIFNFTEVDVCAxXG4gICAgRlJPTSBwZ19yb2xlc1xuICAgIFdIRVJFIHJvbG5hbWUgPSAnc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluJ1xuICApXG4gIFRIRU5cbiAgICBDUkVBVEUgVVNFUiBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gTk9JTkhFUklUIENSRUFURVJPTEUgTE9HSU4gTk9SRVBMSUNBVElPTjtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIFNDSEVNQSBzdXBhYmFzZV9mdW5jdGlvbnMgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuR1JBTlQgQUxMIFBSSVZJTEVHRVMgT04gQUxMIFRBQkxFUyBJTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIEFMTCBTRVFVRU5DRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5BTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBTRVQgc2VhcmNoX3BhdGggPSBcInN1cGFiYXNlX2Z1bmN0aW9uc1wiO1xuQUxURVIgdGFibGUgXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5taWdyYXRpb25zIE9XTkVSIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkFMVEVSIHRhYmxlIFwic3VwYWJhc2VfZnVuY3Rpb25zXCIuaG9va3MgT1dORVIgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuQUxURVIgZnVuY3Rpb24gXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5odHRwX3JlcXVlc3QoKSBPV05FUiBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5HUkFOVCBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gVE8gcG9zdGdyZXM7XG4tLSBSZW1vdmUgdW51c2VkIHN1cGFiYXNlX3BnX25ldF9hZG1pbiByb2xlXG5ET1xuJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX3JvbGVzXG4gICAgV0hFUkUgcm9sbmFtZSA9ICdzdXBhYmFzZV9wZ19uZXRfYWRtaW4nXG4gIClcbiAgVEhFTlxuICAgIFJFQVNTSUdOIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbiBUTyBzdXBhYmFzZV9hZG1pbjtcbiAgICBEUk9QIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbjtcbiAgICBEUk9QIFJPTEUgc3VwYWJhc2VfcGdfbmV0X2FkbWluO1xuICBFTkQgSUY7XG5FTkRcbiQkO1xuLS0gcGdfbmV0IGdyYW50cyB3aGVuIGV4dGVuc2lvbiBpcyBhbHJlYWR5IGVuYWJsZWRcbkRPXG4kJFxuQkVHSU5cbiAgSUYgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXh0ZW5zaW9uXG4gICAgV0hFUkUgZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbi0tIEV2ZW50IHRyaWdnZXIgZm9yIHBnX25ldFxuQ1JFQVRFIE9SIFJFUExBQ0UgRlVOQ1RJT04gZXh0ZW5zaW9ucy5ncmFudF9wZ19uZXRfYWNjZXNzKClcblJFVFVSTlMgZXZlbnRfdHJpZ2dlclxuTEFOR1VBR0UgcGxwZ3NxbFxuQVMgJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX2V2ZW50X3RyaWdnZXJfZGRsX2NvbW1hbmRzKCkgQVMgZXZcbiAgICBKT0lOIHBnX2V4dGVuc2lvbiBBUyBleHRcbiAgICBPTiBldi5vYmppZCA9IGV4dC5vaWRcbiAgICBXSEVSRSBleHQuZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EO1xuJCQ7XG5DT01NRU5UIE9OIEZVTkNUSU9OIGV4dGVuc2lvbnMuZ3JhbnRfcGdfbmV0X2FjY2VzcyBJUyAnR3JhbnRzIGFjY2VzcyB0byBwZ19uZXQnO1xuRE9cbiQkXG5CRUdJTlxuICBJRiBOT1QgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXZlbnRfdHJpZ2dlclxuICAgIFdIRVJFIGV2dG5hbWUgPSAnaXNzdWVfcGdfbmV0X2FjY2VzcydcbiAgKSBUSEVOXG4gICAgQ1JFQVRFIEVWRU5UIFRSSUdHRVIgaXNzdWVfcGdfbmV0X2FjY2VzcyBPTiBkZGxfY29tbWFuZF9lbmQgV0hFTiBUQUcgSU4gKCdDUkVBVEUgRVhURU5TSU9OJylcbiAgICBFWEVDVVRFIFBST0NFRFVSRSBleHRlbnNpb25zLmdyYW50X3BnX25ldF9hY2Nlc3MoKTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCcyMDIxMDgwOTE4MzQyM191cGRhdGVfZ3JhbnRzJyk7XG5BTFRFUiBmdW5jdGlvbiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KCkgU0VDVVJJVFkgREVGSU5FUjtcbkFMVEVSIGZ1bmN0aW9uIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBTRVQgc2VhcmNoX3BhdGggPSBzdXBhYmFzZV9mdW5jdGlvbnM7XG5SRVZPS0UgQUxMIE9OIEZVTkNUSU9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBGUk9NIFBVQkxJQztcbkdSQU5UIEVYRUNVVEUgT04gRlVOQ1RJT04gc3VwYWJhc2VfZnVuY3Rpb25zLmh0dHBfcmVxdWVzdCgpIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5DT01NSVQ7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcm9sZXMuc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk5LXJvbGVzLnNxbAogICAgICAgIGNvbnRlbnQ6ICItLSBOT1RFOiBjaGFuZ2UgdG8geW91ciBvd24gcGFzc3dvcmRzIGZvciBwcm9kdWN0aW9uIGVudmlyb25tZW50c1xuIFxcc2V0IHBncGFzcyBgZWNobyBcIiRQT1NUR1JFU19QQVNTV09SRFwiYFxuXG4gQUxURVIgVVNFUiBhdXRoZW50aWNhdG9yIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgcGdib3VuY2VyIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgc3VwYWJhc2VfYXV0aF9hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX3N0b3JhZ2VfYWRtaW4gV0lUSCBQQVNTV09SRCA6J3BncGFzcyc7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvand0LnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL2luaXQtc2NyaXB0cy85OS1qd3Quc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IGp3dF9zZWNyZXQgYGVjaG8gXCIkSldUX1NFQ1JFVFwiYFxuXFxzZXQgand0X2V4cCBgZWNobyBcIiRKV1RfRVhQXCJgXG5cXHNldCBkYl9uYW1lIGBlY2hvIFwiJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9XCJgXG5cbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3Rfc2VjcmV0XCIgVE8gOidqd3Rfc2VjcmV0JztcbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3RfZXhwXCIgVE8gOidqd3RfZXhwJztcbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9sb2dzLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktbG9ncy5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwic3VwYWJhc2VfYWRtaW5cImBcblxcYyBfc3VwYWJhc2VcbmNyZWF0ZSBzY2hlbWEgaWYgbm90IGV4aXN0cyBfYW5hbHl0aWNzO1xuYWx0ZXIgc2NoZW1hIF9hbmFseXRpY3Mgb3duZXIgdG8gOnBndXNlcjtcblxcYyBwb3N0Z3Jlc1xuIgogICAgICAtICdzdXBhYmFzZS1kYi1jb25maWc6L2V0Yy9wb3N0Z3Jlc3FsLWN1c3RvbScKICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICBpbWFnZTogJ3N1cGFiYXNlL2xvZ2ZsYXJlOjEuNC4wJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjQwMDAvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBMT0dGTEFSRV9OT0RFX0hPU1Q9MTI3LjAuMC4xCiAgICAgIC0gREJfVVNFUk5BTUU9c3VwYWJhc2VfYWRtaW4KICAgICAgLSBEQl9EQVRBQkFTRT1fc3VwYWJhc2UKICAgICAgLSAnREJfSE9TVE5BTUU9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdEQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gREJfU0NIRU1BPV9hbmFseXRpY3MKICAgICAgLSAnTE9HRkxBUkVfQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTE9HRkxBUkV9JwogICAgICAtIExPR0ZMQVJFX1NJTkdMRV9URU5BTlQ9dHJ1ZQogICAgICAtIExPR0ZMQVJFX1NJTkdMRV9URU5BTlRfTU9ERT10cnVlCiAgICAgIC0gTE9HRkxBUkVfU1VQQUJBU0VfTU9ERT10cnVlCiAgICAgIC0gTE9HRkxBUkVfTUlOX0NMVVNURVJfU0laRT0xCiAgICAgIC0gJ1BPU1RHUkVTX0JBQ0tFTkRfVVJMPXBvc3RncmVzcWw6Ly9zdXBhYmFzZV9hZG1pbjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9L19zdXBhYmFzZScKICAgICAgLSBQT1NUR1JFU19CQUNLRU5EX1NDSEVNQT1fYW5hbHl0aWNzCiAgICAgIC0gTE9HRkxBUkVfRkVBVFVSRV9GTEFHX09WRVJSSURFPW11bHRpYmFja2VuZD10cnVlCiAgc3VwYWJhc2UtdmVjdG9yOgogICAgaW1hZ2U6ICd0aW1iZXJpby92ZWN0b3I6MC4yOC4xLWFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vc3VwYWJhc2UtdmVjdG9yOjkwMDEvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9sb2dzL3ZlY3Rvci55bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvdmVjdG9yL3ZlY3Rvci55bWwKICAgICAgICByZWFkX29ubHk6IHRydWUKICAgICAgICBjb250ZW50OiAiYXBpOlxuICBlbmFibGVkOiB0cnVlXG4gIGFkZHJlc3M6IDAuMC4wLjA6OTAwMVxuXG5zb3VyY2VzOlxuICBkb2NrZXJfaG9zdDpcbiAgICB0eXBlOiBkb2NrZXJfbG9nc1xuICAgIGV4Y2x1ZGVfY29udGFpbmVyczpcbiAgICAgIC0gc3VwYWJhc2UtdmVjdG9yXG5cbnRyYW5zZm9ybXM6XG4gIHByb2plY3RfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gZG9ja2VyX2hvc3RcbiAgICBzb3VyY2U6IHwtXG4gICAgICAucHJvamVjdCA9IFwiZGVmYXVsdFwiXG4gICAgICAuZXZlbnRfbWVzc2FnZSA9IGRlbCgubWVzc2FnZSlcbiAgICAgIC5hcHBuYW1lID0gZGVsKC5jb250YWluZXJfbmFtZSlcbiAgICAgIGRlbCguY29udGFpbmVyX2NyZWF0ZWRfYXQpXG4gICAgICBkZWwoLmNvbnRhaW5lcl9pZClcbiAgICAgIGRlbCguc291cmNlX3R5cGUpXG4gICAgICBkZWwoLnN0cmVhbSlcbiAgICAgIGRlbCgubGFiZWwpXG4gICAgICBkZWwoLmltYWdlKVxuICAgICAgZGVsKC5ob3N0KVxuICAgICAgZGVsKC5zdHJlYW0pXG4gIHJvdXRlcjpcbiAgICB0eXBlOiByb3V0ZVxuICAgIGlucHV0czpcbiAgICAgIC0gcHJvamVjdF9sb2dzXG4gICAgcm91dGU6XG4gICAgICBrb25nOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2Uta29uZ1wiKSdcbiAgICAgIGF1dGg6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1hdXRoXCIpJ1xuICAgICAgcmVzdDogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInN1cGFiYXNlLXJlc3RcIiknXG4gICAgICByZWFsdGltZTogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInJlYWx0aW1lLWRldlwiKSdcbiAgICAgIHN0b3JhZ2U6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1zdG9yYWdlXCIpJ1xuICAgICAgZnVuY3Rpb25zOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2UtZnVuY3Rpb25zXCIpJ1xuICAgICAgZGI6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1kYlwiKSdcbiAgIyBJZ25vcmVzIG5vbiBuZ2lueCBlcnJvcnMgc2luY2UgdGhleSBhcmUgcmVsYXRlZCB3aXRoIGtvbmcgYm9vdGluZyB1cFxuICBrb25nX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5rb25nXG4gICAgc291cmNlOiB8LVxuICAgICAgcmVxLCBlcnIgPSBwYXJzZV9uZ2lueF9sb2coLmV2ZW50X21lc3NhZ2UsIFwiY29tYmluZWRcIilcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAudGltZXN0YW1wID0gcmVxLnRpbWVzdGFtcFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LmhlYWRlcnMucmVmZXJlciA9IHJlcS5yZWZlcmVyXG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy51c2VyX2FnZW50ID0gcmVxLmFnZW50XG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy5jZl9jb25uZWN0aW5nX2lwID0gcmVxLmNsaWVudFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0Lm1ldGhvZCA9IHJlcS5tZXRob2RcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wYXRoID0gcmVxLnBhdGhcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wcm90b2NvbCA9IHJlcS5wcm90b2NvbFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXNwb25zZS5zdGF0dXNfY29kZSA9IHJlcS5zdGF0dXNcbiAgICAgIH1cbiAgICAgIGlmIGVyciAhPSBudWxsIHtcbiAgICAgICAgYWJvcnRcbiAgICAgIH1cbiAgIyBJZ25vcmVzIG5vbiBuZ2lueCBlcnJvcnMgc2luY2UgdGhleSBhcmUgcmVsYXRlZCB3aXRoIGtvbmcgYm9vdGluZyB1cFxuICBrb25nX2VycjpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmtvbmdcbiAgICBzb3VyY2U6IHwtXG4gICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSBcIkdFVFwiXG4gICAgICAubWV0YWRhdGEucmVzcG9uc2Uuc3RhdHVzX2NvZGUgPSAyMDBcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfbmdpbnhfbG9nKC5ldmVudF9tZXNzYWdlLCBcImVycm9yXCIpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lc3RhbXBcbiAgICAgICAgICAuc2V2ZXJpdHkgPSBwYXJzZWQuc2V2ZXJpdHlcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5ob3N0ID0gcGFyc2VkLmhvc3RcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5oZWFkZXJzLmNmX2Nvbm5lY3RpbmdfaXAgPSBwYXJzZWQuY2xpZW50XG4gICAgICAgICAgdXJsLCBlcnIgPSBzcGxpdChwYXJzZWQucmVxdWVzdCwgXCIgXCIpXG4gICAgICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSB1cmxbMF1cbiAgICAgICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucGF0aCA9IHVybFsxXVxuICAgICAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wcm90b2NvbCA9IHVybFsyXVxuICAgICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIGVyciAhPSBudWxsIHtcbiAgICAgICAgYWJvcnRcbiAgICAgIH1cbiAgIyBHb3RydWUgbG9ncyBhcmUgc3RydWN0dXJlZCBqc29uIHN0cmluZ3Mgd2hpY2ggZnJvbnRlbmQgcGFyc2VzIGRpcmVjdGx5LiBCdXQgd2Uga2VlcCBtZXRhZGF0YSBmb3IgY29uc2lzdGVuY3kuXG4gIGF1dGhfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmF1dGhcbiAgICBzb3VyY2U6IHwtXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX2pzb24oLmV2ZW50X21lc3NhZ2UpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLm1ldGFkYXRhLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lXG4gICAgICAgICAgLm1ldGFkYXRhID0gbWVyZ2UhKC5tZXRhZGF0YSwgcGFyc2VkKVxuICAgICAgfVxuICAjIFBvc3RnUkVTVCBsb2dzIGFyZSBzdHJ1Y3R1cmVkIHNvIHdlIHNlcGFyYXRlIHRpbWVzdGFtcCBmcm9tIG1lc3NhZ2UgdXNpbmcgcmVnZXhcbiAgcmVzdF9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIucmVzdFxuICAgIHNvdXJjZTogfC1cbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInXig/UDx0aW1lPi4qKTogKD9QPG1zZz4uKikkJylcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAuZXZlbnRfbWVzc2FnZSA9IHBhcnNlZC5tc2dcbiAgICAgICAgICAudGltZXN0YW1wID0gdG9fdGltZXN0YW1wIShwYXJzZWQudGltZSlcbiAgICAgICAgICAubWV0YWRhdGEuaG9zdCA9IC5wcm9qZWN0XG4gICAgICB9XG4gICMgUmVhbHRpbWUgbG9ncyBhcmUgc3RydWN0dXJlZCBzbyB3ZSBwYXJzZSB0aGUgc2V2ZXJpdHkgbGV2ZWwgdXNpbmcgcmVnZXggKGlnbm9yZSB0aW1lIGJlY2F1c2UgaXQgaGFzIG5vIGRhdGUpXG4gIHJlYWx0aW1lX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5yZWFsdGltZVxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5wcm9qZWN0ID0gZGVsKC5wcm9qZWN0KVxuICAgICAgLm1ldGFkYXRhLmV4dGVybmFsX2lkID0gLm1ldGFkYXRhLnByb2plY3RcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInXig/UDx0aW1lPlxcZCs6XFxkKzpcXGQrXFwuXFxkKykgXFxbKD9QPGxldmVsPlxcdyspXFxdICg/UDxtc2c+LiopJCcpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLmV2ZW50X21lc3NhZ2UgPSBwYXJzZWQubXNnXG4gICAgICAgICAgLm1ldGFkYXRhLmxldmVsID0gcGFyc2VkLmxldmVsXG4gICAgICB9XG4gICMgU3RvcmFnZSBsb2dzIG1heSBjb250YWluIGpzb24gb2JqZWN0cyBzbyB3ZSBwYXJzZSB0aGVtIGZvciBjb21wbGV0ZW5lc3NcbiAgc3RvcmFnZV9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIuc3RvcmFnZVxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5wcm9qZWN0ID0gZGVsKC5wcm9qZWN0KVxuICAgICAgLm1ldGFkYXRhLnRlbmFudElkID0gLm1ldGFkYXRhLnByb2plY3RcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfanNvbiguZXZlbnRfbWVzc2FnZSlcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAuZXZlbnRfbWVzc2FnZSA9IHBhcnNlZC5tc2dcbiAgICAgICAgICAubWV0YWRhdGEubGV2ZWwgPSBwYXJzZWQubGV2ZWxcbiAgICAgICAgICAubWV0YWRhdGEudGltZXN0YW1wID0gcGFyc2VkLnRpbWVcbiAgICAgICAgICAubWV0YWRhdGEuY29udGV4dFswXS5ob3N0ID0gcGFyc2VkLmhvc3RuYW1lXG4gICAgICAgICAgLm1ldGFkYXRhLmNvbnRleHRbMF0ucGlkID0gcGFyc2VkLnBpZFxuICAgICAgfVxuICAjIFBvc3RncmVzIGxvZ3Mgc29tZSBtZXNzYWdlcyB0byBzdGRlcnIgd2hpY2ggd2UgbWFwIHRvIHdhcm5pbmcgc2V2ZXJpdHkgbGV2ZWxcbiAgZGJfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmRiXG4gICAgc291cmNlOiB8LVxuICAgICAgLm1ldGFkYXRhLmhvc3QgPSBcImRiLWRlZmF1bHRcIlxuICAgICAgLm1ldGFkYXRhLnBhcnNlZC50aW1lc3RhbXAgPSAudGltZXN0YW1wXG5cbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInLiooP1A8bGV2ZWw+SU5GT3xOT1RJQ0V8V0FSTklOR3xFUlJPUnxMT0d8RkFUQUx8UEFOSUM/KTouKicsIG51bWVyaWNfZ3JvdXBzOiB0cnVlKVxuXG4gICAgICBpZiBlcnIgIT0gbnVsbCB8fCBwYXJzZWQgPT0gbnVsbCB7XG4gICAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSBcImluZm9cIlxuICAgICAgfVxuICAgICAgaWYgcGFyc2VkICE9IG51bGwge1xuICAgICAgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9IHBhcnNlZC5sZXZlbFxuICAgICAgfVxuICAgICAgaWYgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9PSBcImluZm9cIiB7XG4gICAgICAgICAgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9IFwibG9nXCJcbiAgICAgIH1cbiAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSB1cGNhc2UhKC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkpXG5cbnNpbmtzOlxuICBsb2dmbGFyZV9hdXRoOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gYXV0aF9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWdvdHJ1ZS5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3JlYWx0aW1lOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gcmVhbHRpbWVfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1yZWFsdGltZS5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3Jlc3Q6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSByZXN0X2xvZ3NcbiAgICBlbmNvZGluZzpcbiAgICAgIGNvZGVjOiAnanNvbidcbiAgICBtZXRob2Q6ICdwb3N0J1xuICAgIHJlcXVlc3Q6XG4gICAgICByZXRyeV9tYXhfZHVyYXRpb25fc2VjczogMTBcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvYXBpL2xvZ3M/c291cmNlX25hbWU9cG9zdGdSRVNULmxvZ3MucHJvZCZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfZGI6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSBkYl9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgIyBXZSBtdXN0IHJvdXRlIHRoZSBzaW5rIHRocm91Z2gga29uZyBiZWNhdXNlIGluZ2VzdGluZyBsb2dzIGJlZm9yZSBsb2dmbGFyZSBpcyBmdWxseSBpbml0aWFsaXNlZCB3aWxsXG4gICAgIyBsZWFkIHRvIGJyb2tlbiBxdWVyaWVzIGZyb20gc3R1ZGlvLiBUaGlzIHdvcmtzIGJ5IHRoZSBhc3N1bXB0aW9uIHRoYXQgY29udGFpbmVycyBhcmUgc3RhcnRlZCBpbiB0aGVcbiAgICAjIGZvbGxvd2luZyBvcmRlcjogdmVjdG9yID4gZGIgPiBsb2dmbGFyZSA+IGtvbmdcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2Uta29uZzo4MDAwL2FuYWx5dGljcy92MS9hcGkvbG9ncz9zb3VyY2VfbmFtZT1wb3N0Z3Jlcy5sb2dzJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9mdW5jdGlvbnM6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIuZnVuY3Rpb25zXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWRlbm8tcmVsYXktbG9ncyZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfc3RvcmFnZTpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIHN0b3JhZ2VfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1zdG9yYWdlLmxvZ3MucHJvZC4yJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9rb25nOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0ga29uZ19sb2dzXG4gICAgICAtIGtvbmdfZXJyXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWNsb3VkZmxhcmUubG9ncy5wcm9kJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuIgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jazpybycKICAgIGVudmlyb25tZW50OgogICAgICAtICdMT0dGTEFSRV9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9MT0dGTEFSRX0nCiAgICBjb21tYW5kOgogICAgICAtICctLWNvbmZpZycKICAgICAgLSBldGMvdmVjdG9yL3ZlY3Rvci55bWwKICBzdXBhYmFzZS1yZXN0OgogICAgaW1hZ2U6ICdwb3N0Z3Jlc3QvcG9zdGdyZXN0OnYxMi4yLjEyJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUEdSU1RfREJfVVJJPXBvc3RncmVzOi8vYXV0aGVudGljYXRvcjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUEdSU1RfREJfU0NIRU1BUz0ke1BHUlNUX0RCX1NDSEVNQVM6LXB1YmxpYyxzdG9yYWdlLGdyYXBocWxfcHVibGljfScKICAgICAgLSBQR1JTVF9EQl9BTk9OX1JPTEU9YW5vbgogICAgICAtICdQR1JTVF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIFBHUlNUX0RCX1VTRV9MRUdBQ1lfR1VDUz1mYWxzZQogICAgICAtICdQR1JTVF9BUFBfU0VUVElOR1NfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnUEdSU1RfQVBQX1NFVFRJTkdTX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgIGNvbW1hbmQ6IHBvc3RncmVzdAogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgc3VwYWJhc2UtYXV0aDoKICAgIGltYWdlOiAnc3VwYWJhc2UvZ290cnVlOnYyLjE3NC4wJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo5OTk5L2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPVFJVRV9BUElfSE9TVD0wLjAuMC4wCiAgICAgIC0gR09UUlVFX0FQSV9QT1JUPTk5OTkKICAgICAgLSAnQVBJX0VYVEVSTkFMX1VSTD0ke0FQSV9FWFRFUk5BTF9VUkw6LWh0dHA6Ly9zdXBhYmFzZS1rb25nOjgwMDB9JwogICAgICAtIEdPVFJVRV9EQl9EUklWRVI9cG9zdGdyZXMKICAgICAgLSAnR09UUlVFX0RCX0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovL3N1cGFiYXNlX2F1dGhfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0dPVFJVRV9TSVRFX1VSTD0ke1NFUlZJQ0VfVVJMX1NVUEFCQVNFS09OR30nCiAgICAgIC0gJ0dPVFJVRV9VUklfQUxMT1dfTElTVD0ke0FERElUSU9OQUxfUkVESVJFQ1RfVVJMU30nCiAgICAgIC0gJ0dPVFJVRV9ESVNBQkxFX1NJR05VUD0ke0RJU0FCTEVfU0lHTlVQOi1mYWxzZX0nCiAgICAgIC0gR09UUlVFX0pXVF9BRE1JTl9ST0xFUz1zZXJ2aWNlX3JvbGUKICAgICAgLSBHT1RSVUVfSldUX0FVRD1hdXRoZW50aWNhdGVkCiAgICAgIC0gR09UUlVFX0pXVF9ERUZBVUxUX0dST1VQX05BTUU9YXV0aGVudGljYXRlZAogICAgICAtICdHT1RSVUVfSldUX0VYUD0ke0pXVF9FWFBJUlk6LTM2MDB9JwogICAgICAtICdHT1RSVUVfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnR09UUlVFX0VYVEVSTkFMX0VNQUlMX0VOQUJMRUQ9JHtFTkFCTEVfRU1BSUxfU0lHTlVQOi10cnVlfScKICAgICAgLSAnR09UUlVFX0VYVEVSTkFMX0FOT05ZTU9VU19VU0VSU19FTkFCTEVEPSR7RU5BQkxFX0FOT05ZTU9VU19VU0VSUzotZmFsc2V9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX0FVVE9DT05GSVJNPSR7RU5BQkxFX0VNQUlMX0FVVE9DT05GSVJNOi1mYWxzZX0nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX0FETUlOX0VNQUlMPSR7U01UUF9BRE1JTl9FTUFJTH0nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX0hPU1Q9JHtTTVRQX0hPU1R9JwogICAgICAtICdHT1RSVUVfU01UUF9QT1JUPSR7U01UUF9QT1JUOi01ODd9JwogICAgICAtICdHT1RSVUVfU01UUF9VU0VSPSR7U01UUF9VU0VSfScKICAgICAgLSAnR09UUlVFX1NNVFBfUEFTUz0ke1NNVFBfUEFTU30nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX1NFTkRFUl9OQU1FPSR7U01UUF9TRU5ERVJfTkFNRX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVVJMUEFUSFNfSU5WSVRFPSR7TUFJTEVSX1VSTFBBVEhTX0lOVklURTotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19DT05GSVJNQVRJT049JHtNQUlMRVJfVVJMUEFUSFNfQ09ORklSTUFUSU9OOi0vYXV0aC92MS92ZXJpZnl9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1VSTFBBVEhTX1JFQ09WRVJZPSR7TUFJTEVSX1VSTFBBVEhTX1JFQ09WRVJZOi0vYXV0aC92MS92ZXJpZnl9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1VSTFBBVEhTX0VNQUlMX0NIQU5HRT0ke01BSUxFUl9VUkxQQVRIU19FTUFJTF9DSEFOR0U6LS9hdXRoL3YxL3ZlcmlmeX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0lOVklURT0ke01BSUxFUl9URU1QTEFURVNfSU5WSVRFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfQ09ORklSTUFUSU9OPSR7TUFJTEVSX1RFTVBMQVRFU19DT05GSVJNQVRJT059JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1RFTVBMQVRFU19SRUNPVkVSWT0ke01BSUxFUl9URU1QTEFURVNfUkVDT1ZFUll9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1RFTVBMQVRFU19NQUdJQ19MSU5LPSR7TUFJTEVSX1RFTVBMQVRFU19NQUdJQ19MSU5LfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfRU1BSUxfQ0hBTkdFPSR7TUFJTEVSX1RFTVBMQVRFU19FTUFJTF9DSEFOR0V9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1NVQkpFQ1RTX0NPTkZJUk1BVElPTj0ke01BSUxFUl9TVUJKRUNUU19DT05GSVJNQVRJT059JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1NVQkpFQ1RTX1JFQ09WRVJZPSR7TUFJTEVSX1NVQkpFQ1RTX1JFQ09WRVJZfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19NQUdJQ19MSU5LPSR7TUFJTEVSX1NVQkpFQ1RTX01BR0lDX0xJTkt9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1NVQkpFQ1RTX0VNQUlMX0NIQU5HRT0ke01BSUxFUl9TVUJKRUNUU19FTUFJTF9DSEFOR0V9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1NVQkpFQ1RTX0lOVklURT0ke01BSUxFUl9TVUJKRUNUU19JTlZJVEV9JwogICAgICAtICdHT1RSVUVfRVhURVJOQUxfUEhPTkVfRU5BQkxFRD0ke0VOQUJMRV9QSE9ORV9TSUdOVVA6LXRydWV9JwogICAgICAtICdHT1RSVUVfU01TX0FVVE9DT05GSVJNPSR7RU5BQkxFX1BIT05FX0FVVE9DT05GSVJNOi10cnVlfScKICByZWFsdGltZS1kZXY6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3JlYWx0aW1lOnYyLjM0LjQ3JwogICAgY29udGFpbmVyX25hbWU6IHJlYWx0aW1lLWRldi5zdXBhYmFzZS1yZWFsdGltZQogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1zU2ZMJwogICAgICAgIC0gJy0taGVhZCcKICAgICAgICAtICctbycKICAgICAgICAtIC9kZXYvbnVsbAogICAgICAgIC0gJy1IJwogICAgICAgIC0gJ0F1dGhvcml6YXRpb246IEJlYXJlciAke1NFUlZJQ0VfU1VQQUJBU0VBTk9OX0tFWX0nCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo0MDAwL2FwaS90ZW5hbnRzL3JlYWx0aW1lLWRldi9oZWFsdGgnCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1JUPTQwMDAKICAgICAgLSAnREJfSE9TVD0ke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn0nCiAgICAgIC0gJ0RCX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSBEQl9VU0VSPXN1cGFiYXNlX2FkbWluCiAgICAgIC0gJ0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RCX05BTUU9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdEQl9BRlRFUl9DT05ORUNUX1FVRVJZPVNFVCBzZWFyY2hfcGF0aCBUTyBfcmVhbHRpbWUnCiAgICAgIC0gREJfRU5DX0tFWT1zdXBhYmFzZXJlYWx0aW1lCiAgICAgIC0gJ0FQSV9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIEZMWV9BTExPQ19JRD1mbHkxMjMKICAgICAgLSBGTFlfQVBQX05BTUU9cmVhbHRpbWUKICAgICAgLSAnU0VDUkVUX0tFWV9CQVNFPSR7U0VDUkVUX1BBU1NXT1JEX1JFQUxUSU1FfScKICAgICAgLSAnRVJMX0FGTEFHUz0tcHJvdG9fZGlzdCBpbmV0X3RjcCcKICAgICAgLSBFTkFCTEVfVEFJTFNDQUxFPWZhbHNlCiAgICAgIC0gIkROU19OT0RFUz0nJyIKICAgICAgLSBSTElNSVRfTk9GSUxFPTEwMDAwCiAgICAgIC0gQVBQX05BTUU9cmVhbHRpbWUKICAgICAgLSBTRUVEX1NFTEZfSE9TVD10cnVlCiAgICAgIC0gTE9HX0xFVkVMPWVycm9yCiAgICAgIC0gUlVOX0pBTklUT1I9dHJ1ZQogICAgICAtIEpBTklUT1JfSU5URVJWQUw9NjAwMDAKICAgIGNvbW1hbmQ6ICJzaCAtYyBcIi9hcHAvYmluL21pZ3JhdGUgJiYgL2FwcC9iaW4vcmVhbHRpbWUgZXZhbCAnUmVhbHRpbWUuUmVsZWFzZS5zZWVkcyhSZWFsdGltZS5SZXBvKScgJiYgL2FwcC9iaW4vc2VydmVyXCJcbiIKICBzdXBhYmFzZS1taW5pbzoKICAgIGltYWdlOiAnZ2hjci5pby9jb29sbGFic2lvL21pbmlvOlJFTEVBU0UuMjAyNS0xMC0xNVQxNy0yOS01NVonCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgY29tbWFuZDogJ3NlcnZlciAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiIC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG1jCiAgICAgICAgLSByZWFkeQogICAgICAgIC0gbG9jYWwKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgdm9sdW1lczoKICAgICAgLSAnLi92b2x1bWVzL3N0b3JhZ2U6L2RhdGEnCiAgbWluaW8tY3JlYXRlYnVja2V0OgogICAgaW1hZ2U6IG1pbmlvL21jCiAgICByZXN0YXJ0OiAnbm8nCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtbWluaW86CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudHJ5cG9pbnQ6CiAgICAgIC0gL2VudHJ5cG9pbnQuc2gKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2VudHJ5cG9pbnQuc2gKICAgICAgICB0YXJnZXQ6IC9lbnRyeXBvaW50LnNoCiAgICAgICAgY29udGVudDogIiMhL2Jpbi9zaFxuL3Vzci9iaW4vbWMgYWxpYXMgc2V0IHN1cGFiYXNlLW1pbmlvIGh0dHA6Ly9zdXBhYmFzZS1taW5pbzo5MDAwICR7TUlOSU9fUk9PVF9VU0VSfSAke01JTklPX1JPT1RfUEFTU1dPUkR9O1xuL3Vzci9iaW4vbWMgbWIgLS1pZ25vcmUtZXhpc3Rpbmcgc3VwYWJhc2UtbWluaW8vc3R1YjtcbmV4aXQgMFxuIgogIHN1cGFiYXNlLXN0b3JhZ2U6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3N0b3JhZ2UtYXBpOnYxLjE0LjYnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1kYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzdXBhYmFzZS1yZXN0OgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9zdGFydGVkCiAgICAgIGltZ3Byb3h5OgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9zdGFydGVkCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo1MDAwL3N0YXR1cycKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZFUl9QT1JUPTUwMDAKICAgICAgLSBTRVJWRVJfUkVHSU9OPWxvY2FsCiAgICAgIC0gTVVMVElfVEVOQU5UPWZhbHNlCiAgICAgIC0gJ0FVVEhfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vc3VwYWJhc2Vfc3RvcmFnZV9hZG1pbjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSBEQl9JTlNUQUxMX1JPTEVTPWZhbHNlCiAgICAgIC0gU1RPUkFHRV9CQUNLRU5EPXMzCiAgICAgIC0gU1RPUkFHRV9TM19CVUNLRVQ9c3R1YgogICAgICAtICdTVE9SQUdFX1MzX0VORFBPSU5UPWh0dHA6Ly9zdXBhYmFzZS1taW5pbzo5MDAwJwogICAgICAtIFNUT1JBR0VfUzNfRk9SQ0VfUEFUSF9TVFlMRT10cnVlCiAgICAgIC0gU1RPUkFHRV9TM19SRUdJT049dXMtZWFzdC0xCiAgICAgIC0gJ0FXU19BQ0NFU1NfS0VZX0lEPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnQVdTX1NFQ1JFVF9BQ0NFU1NfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9NSU5JT30nCiAgICAgIC0gVVBMT0FEX0ZJTEVfU0laRV9MSU1JVD01MjQyODgwMDAKICAgICAgLSBVUExPQURfRklMRV9TSVpFX0xJTUlUX1NUQU5EQVJEPTUyNDI4ODAwMAogICAgICAtIFVQTE9BRF9TSUdORURfVVJMX0VYUElSQVRJT05fVElNRT0xMjAKICAgICAgLSBUVVNfVVJMX1BBVEg9dXBsb2FkL3Jlc3VtYWJsZQogICAgICAtIFRVU19NQVhfU0laRT0zNjAwMDAwCiAgICAgIC0gRU5BQkxFX0lNQUdFX1RSQU5TRk9STUFUSU9OPXRydWUKICAgICAgLSAnSU1HUFJPWFlfVVJMPWh0dHA6Ly9pbWdwcm94eTo4MDgwJwogICAgICAtIElNR1BST1hZX1JFUVVFU1RfVElNRU9VVD0xNQogICAgICAtIERBVEFCQVNFX1NFQVJDSF9QQVRIPXN0b3JhZ2UKICAgICAgLSBOT0RFX0VOVj1wcm9kdWN0aW9uCiAgICAgIC0gUkVRVUVTVF9BTExPV19YX0ZPUldBUkRFRF9QQVRIPXRydWUKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vdm9sdW1lcy9zdG9yYWdlOi92YXIvbGliL3N0b3JhZ2UnCiAgaW1ncHJveHk6CiAgICBpbWFnZTogJ2RhcnRoc2ltL2ltZ3Byb3h5OnYzLjguMCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBpbWdwcm94eQogICAgICAgIC0gaGVhbHRoCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBJTUdQUk9YWV9MT0NBTF9GSUxFU1lTVEVNX1JPT1Q9LwogICAgICAtIElNR1BST1hZX1VTRV9FVEFHPXRydWUKICAgICAgLSAnSU1HUFJPWFlfRU5BQkxFX1dFQlBfREVURUNUSU9OPSR7SU1HUFJPWFlfRU5BQkxFX1dFQlBfREVURUNUSU9OOi10cnVlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vdm9sdW1lcy9zdG9yYWdlOi92YXIvbGliL3N0b3JhZ2UnCiAgc3VwYWJhc2UtbWV0YToKICAgIGltYWdlOiAnc3VwYWJhc2UvcG9zdGdyZXMtbWV0YTp2MC44OS4zJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQR19NRVRBX1BPUlQ9ODA4MAogICAgICAtICdQR19NRVRBX0RCX0hPU1Q9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdQR19NRVRBX0RCX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUEdfTUVUQV9EQl9OQU1FPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSBQR19NRVRBX0RCX1VTRVI9c3VwYWJhc2VfYWRtaW4KICAgICAgLSAnUEdfTUVUQV9EQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogIHN1cGFiYXNlLWVkZ2UtZnVuY3Rpb25zOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9lZGdlLXJ1bnRpbWU6djEuNjcuNCcKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGVjaG8KICAgICAgICAtICdFZGdlIEZ1bmN0aW9ucyBpcyBoZWFsdGh5JwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ1NVUEFCQVNFX1VSTD0ke1NFUlZJQ0VfVVJMX1NVUEFCQVNFS09OR30nCiAgICAgIC0gJ1NVUEFCQVNFX0FOT05fS0VZPSR7U0VSVklDRV9TVVBBQkFTRUFOT05fS0VZfScKICAgICAgLSAnU1VQQUJBU0VfU0VSVklDRV9ST0xFX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VTRVJWSUNFX0tFWX0nCiAgICAgIC0gJ1NVUEFCQVNFX0RCX1VSTD1wb3N0Z3Jlc3FsOi8vcG9zdGdyZXM6JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ1ZFUklGWV9KV1Q9JHtGVU5DVElPTlNfVkVSSUZZX0pXVDotZmFsc2V9JwogICAgdm9sdW1lczoKICAgICAgLSAnLi92b2x1bWVzL2Z1bmN0aW9uczovaG9tZS9kZW5vL2Z1bmN0aW9ucycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9mdW5jdGlvbnMvbWFpbi9pbmRleC50cwogICAgICAgIHRhcmdldDogL2hvbWUvZGVuby9mdW5jdGlvbnMvbWFpbi9pbmRleC50cwogICAgICAgIGNvbnRlbnQ6ICJpbXBvcnQgeyBzZXJ2ZSB9IGZyb20gJ2h0dHBzOi8vZGVuby5sYW5kL3N0ZEAwLjEzMS4wL2h0dHAvc2VydmVyLnRzJ1xuaW1wb3J0ICogYXMgam9zZSBmcm9tICdodHRwczovL2Rlbm8ubGFuZC94L2pvc2VAdjQuMTQuNC9pbmRleC50cydcblxuY29uc29sZS5sb2coJ21haW4gZnVuY3Rpb24gc3RhcnRlZCcpXG5cbmNvbnN0IEpXVF9TRUNSRVQgPSBEZW5vLmVudi5nZXQoJ0pXVF9TRUNSRVQnKVxuY29uc3QgVkVSSUZZX0pXVCA9IERlbm8uZW52LmdldCgnVkVSSUZZX0pXVCcpID09PSAndHJ1ZSdcblxuZnVuY3Rpb24gZ2V0QXV0aFRva2VuKHJlcTogUmVxdWVzdCkge1xuICBjb25zdCBhdXRoSGVhZGVyID0gcmVxLmhlYWRlcnMuZ2V0KCdhdXRob3JpemF0aW9uJylcbiAgaWYgKCFhdXRoSGVhZGVyKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdNaXNzaW5nIGF1dGhvcml6YXRpb24gaGVhZGVyJylcbiAgfVxuICBjb25zdCBbYmVhcmVyLCB0b2tlbl0gPSBhdXRoSGVhZGVyLnNwbGl0KCcgJylcbiAgaWYgKGJlYXJlciAhPT0gJ0JlYXJlcicpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEF1dGggaGVhZGVyIGlzIG5vdCAnQmVhcmVyIHt0b2tlbn0nYClcbiAgfVxuICByZXR1cm4gdG9rZW5cbn1cblxuYXN5bmMgZnVuY3Rpb24gdmVyaWZ5SldUKGp3dDogc3RyaW5nKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGNvbnN0IGVuY29kZXIgPSBuZXcgVGV4dEVuY29kZXIoKVxuICBjb25zdCBzZWNyZXRLZXkgPSBlbmNvZGVyLmVuY29kZShKV1RfU0VDUkVUKVxuICB0cnkge1xuICAgIGF3YWl0IGpvc2Uuand0VmVyaWZ5KGp3dCwgc2VjcmV0S2V5KVxuICB9IGNhdGNoIChlcnIpIHtcbiAgICBjb25zb2xlLmVycm9yKGVycilcbiAgICByZXR1cm4gZmFsc2VcbiAgfVxuICByZXR1cm4gdHJ1ZVxufVxuXG5zZXJ2ZShhc3luYyAocmVxOiBSZXF1ZXN0KSA9PiB7XG4gIGlmIChyZXEubWV0aG9kICE9PSAnT1BUSU9OUycgJiYgVkVSSUZZX0pXVCkge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCB0b2tlbiA9IGdldEF1dGhUb2tlbihyZXEpXG4gICAgICBjb25zdCBpc1ZhbGlkSldUID0gYXdhaXQgdmVyaWZ5SldUKHRva2VuKVxuXG4gICAgICBpZiAoIWlzVmFsaWRKV1QpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBSZXNwb25zZShKU09OLnN0cmluZ2lmeSh7IG1zZzogJ0ludmFsaWQgSldUJyB9KSwge1xuICAgICAgICAgIHN0YXR1czogNDAxLFxuICAgICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LFxuICAgICAgICB9KVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoZSlcbiAgICAgIHJldHVybiBuZXcgUmVzcG9uc2UoSlNPTi5zdHJpbmdpZnkoeyBtc2c6IGUudG9TdHJpbmcoKSB9KSwge1xuICAgICAgICBzdGF0dXM6IDQwMSxcbiAgICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG4gICAgICB9KVxuICAgIH1cbiAgfVxuXG4gIGNvbnN0IHVybCA9IG5ldyBVUkwocmVxLnVybClcbiAgY29uc3QgeyBwYXRobmFtZSB9ID0gdXJsXG4gIGNvbnN0IHBhdGhfcGFydHMgPSBwYXRobmFtZS5zcGxpdCgnLycpXG4gIGNvbnN0IHNlcnZpY2VfbmFtZSA9IHBhdGhfcGFydHNbMV1cblxuICBpZiAoIXNlcnZpY2VfbmFtZSB8fCBzZXJ2aWNlX25hbWUgPT09ICcnKSB7XG4gICAgY29uc3QgZXJyb3IgPSB7IG1zZzogJ21pc3NpbmcgZnVuY3Rpb24gbmFtZSBpbiByZXF1ZXN0JyB9XG4gICAgcmV0dXJuIG5ldyBSZXNwb25zZShKU09OLnN0cmluZ2lmeShlcnJvciksIHtcbiAgICAgIHN0YXR1czogNDAwLFxuICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG4gICAgfSlcbiAgfVxuXG4gIGNvbnN0IHNlcnZpY2VQYXRoID0gYC9ob21lL2Rlbm8vZnVuY3Rpb25zLyR7c2VydmljZV9uYW1lfWBcbiAgY29uc29sZS5lcnJvcihgc2VydmluZyB0aGUgcmVxdWVzdCB3aXRoICR7c2VydmljZVBhdGh9YClcblxuICBjb25zdCBtZW1vcnlMaW1pdE1iID0gMTUwXG4gIGNvbnN0IHdvcmtlclRpbWVvdXRNcyA9IDEgKiA2MCAqIDEwMDBcbiAgY29uc3Qgbm9Nb2R1bGVDYWNoZSA9IGZhbHNlXG4gIGNvbnN0IGltcG9ydE1hcFBhdGggPSBudWxsXG4gIGNvbnN0IGVudlZhcnNPYmogPSBEZW5vLmVudi50b09iamVjdCgpXG4gIGNvbnN0IGVudlZhcnMgPSBPYmplY3Qua2V5cyhlbnZWYXJzT2JqKS5tYXAoKGspID0+IFtrLCBlbnZWYXJzT2JqW2tdXSlcblxuICB0cnkge1xuICAgIGNvbnN0IHdvcmtlciA9IGF3YWl0IEVkZ2VSdW50aW1lLnVzZXJXb3JrZXJzLmNyZWF0ZSh7XG4gICAgICBzZXJ2aWNlUGF0aCxcbiAgICAgIG1lbW9yeUxpbWl0TWIsXG4gICAgICB3b3JrZXJUaW1lb3V0TXMsXG4gICAgICBub01vZHVsZUNhY2hlLFxuICAgICAgaW1wb3J0TWFwUGF0aCxcbiAgICAgIGVudlZhcnMsXG4gICAgfSlcbiAgICByZXR1cm4gYXdhaXQgd29ya2VyLmZldGNoKHJlcSlcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnN0IGVycm9yID0geyBtc2c6IGUudG9TdHJpbmcoKSB9XG4gICAgcmV0dXJuIG5ldyBSZXNwb25zZShKU09OLnN0cmluZ2lmeShlcnJvciksIHtcbiAgICAgIHN0YXR1czogNTAwLFxuICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG4gICAgfSlcbiAgfVxufSkiCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZnVuY3Rpb25zL2hlbGxvL2luZGV4LnRzCiAgICAgICAgdGFyZ2V0OiAvaG9tZS9kZW5vL2Z1bmN0aW9ucy9oZWxsby9pbmRleC50cwogICAgICAgIGNvbnRlbnQ6ICIvLyBGb2xsb3cgdGhpcyBzZXR1cCBndWlkZSB0byBpbnRlZ3JhdGUgdGhlIERlbm8gbGFuZ3VhZ2Ugc2VydmVyIHdpdGggeW91ciBlZGl0b3I6XG4vLyBodHRwczovL2Rlbm8ubGFuZC9tYW51YWwvZ2V0dGluZ19zdGFydGVkL3NldHVwX3lvdXJfZW52aXJvbm1lbnRcbi8vIFRoaXMgZW5hYmxlcyBhdXRvY29tcGxldGUsIGdvIHRvIGRlZmluaXRpb24sIGV0Yy5cblxuaW1wb3J0IHsgc2VydmUgfSBmcm9tIFwiaHR0cHM6Ly9kZW5vLmxhbmQvc3RkQDAuMTc3LjEvaHR0cC9zZXJ2ZXIudHNcIlxuXG5zZXJ2ZShhc3luYyAoKSA9PiB7XG4gIHJldHVybiBuZXcgUmVzcG9uc2UoXG4gICAgYFwiSGVsbG8gZnJvbSBFZGdlIEZ1bmN0aW9ucyFcImAsXG4gICAgeyBoZWFkZXJzOiB7IFwiQ29udGVudC1UeXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiIH0gfSxcbiAgKVxufSlcblxuLy8gVG8gaW52b2tlOlxuLy8gY3VybCAnaHR0cDovL2xvY2FsaG9zdDo8S09OR19IVFRQX1BPUlQ+L2Z1bmN0aW9ucy92MS9oZWxsbycgXFxcbi8vICAgLS1oZWFkZXIgJ0F1dGhvcml6YXRpb246IEJlYXJlciA8YW5vbi9zZXJ2aWNlX3JvbGUgQVBJIGtleT4nXG4iCiAgICBjb21tYW5kOgogICAgICAtIHN0YXJ0CiAgICAgIC0gJy0tbWFpbi1zZXJ2aWNlJwogICAgICAtIC9ob21lL2Rlbm8vZnVuY3Rpb25zL21haW4KICBzdXBhYmFzZS1zdXBhdmlzb3I6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3N1cGF2aXNvcjoyLjUuMScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLXNTZkwnCiAgICAgICAgLSAnLW8nCiAgICAgICAgLSAvZGV2L251bGwKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjQwMDAvYXBpL2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1kYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPT0xFUl9URU5BTlRfSUQ9ZGV2X3RlbmFudAogICAgICAtIFBPT0xFUl9QT09MX01PREU9dHJhbnNhY3Rpb24KICAgICAgLSAnUE9PTEVSX0RFRkFVTFRfUE9PTF9TSVpFPSR7UE9PTEVSX0RFRkFVTFRfUE9PTF9TSVpFOi0yMH0nCiAgICAgIC0gJ1BPT0xFUl9NQVhfQ0xJRU5UX0NPTk49JHtQT09MRVJfTUFYX0NMSUVOVF9DT05OOi0xMDB9JwogICAgICAtIFBPUlQ9NDAwMAogICAgICAtICdQT1NUR1JFU19QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ1BPU1RHUkVTX0hPU1ROQU1FPSR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdEQVRBQkFTRV9VUkw9ZWN0bzovL3N1cGFiYXNlX2FkbWluOiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AJHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9OiR7UE9TVEdSRVNfUE9SVDotNTQzMn0vX3N1cGFiYXNlJwogICAgICAtIENMVVNURVJfUE9TVEdSRVM9dHJ1ZQogICAgICAtICdTRUNSRVRfS0VZX0JBU0U9JHtTRVJWSUNFX1BBU1NXT1JEX1NVUEFWSVNPUlNFQ1JFVH0nCiAgICAgIC0gJ1ZBVUxUX0VOQ19LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX1ZBVUxURU5DfScKICAgICAgLSAnQVBJX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ01FVFJJQ1NfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSBSRUdJT049bG9jYWwKICAgICAgLSAnRVJMX0FGTEFHUz0tcHJvdG9fZGlzdCBpbmV0X3RjcCcKICAgIGNvbW1hbmQ6CiAgICAgIC0gL2Jpbi9zaAogICAgICAtICctYycKICAgICAgLSAnL2FwcC9iaW4vbWlncmF0ZSAmJiAvYXBwL2Jpbi9zdXBhdmlzb3IgZXZhbCAiJCQoY2F0IC9ldGMvcG9vbGVyL3Bvb2xlci5leHMpIiAmJiAvYXBwL2Jpbi9zZXJ2ZXInCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL3Bvb2xlci9wb29sZXIuZXhzCiAgICAgICAgdGFyZ2V0OiAvZXRjL3Bvb2xlci9wb29sZXIuZXhzCiAgICAgICAgY29udGVudDogIns6b2ssIF99ID0gQXBwbGljYXRpb24uZW5zdXJlX2FsbF9zdGFydGVkKDpzdXBhdmlzb3IpXG57Om9rLCB2ZXJzaW9ufSA9XG4gICAgY2FzZSBTdXBhdmlzb3IuUmVwby5xdWVyeSEoXCJzZWxlY3QgdmVyc2lvbigpXCIpIGRvXG4gICAgJXtyb3dzOiBbW3Zlcl1dfSAtPiBTdXBhdmlzb3IuSGVscGVycy5wYXJzZV9wZ192ZXJzaW9uKHZlcilcbiAgICBfIC0+IG5pbFxuICAgIGVuZFxucGFyYW1zID0gJXtcbiAgICBcImV4dGVybmFsX2lkXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfVEVOQU5UX0lEXCIpLFxuICAgIFwiZGJfaG9zdFwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9TVEdSRVNfSE9TVE5BTUVcIiksXG4gICAgXCJkYl9wb3J0XCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19QT1JUXCIpIHw+IFN0cmluZy50b19pbnRlZ2VyKCksXG4gICAgXCJkYl9kYXRhYmFzZVwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9TVEdSRVNfREJcIiksXG4gICAgXCJyZXF1aXJlX3VzZXJcIiA9PiBmYWxzZSxcbiAgICBcImF1dGhfcXVlcnlcIiA9PiBcIlNFTEVDVCAqIEZST00gcGdib3VuY2VyLmdldF9hdXRoKCQxKVwiLFxuICAgIFwiZGVmYXVsdF9tYXhfY2xpZW50c1wiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9PTEVSX01BWF9DTElFTlRfQ09OTlwiKSxcbiAgICBcImRlZmF1bHRfcG9vbF9zaXplXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfREVGQVVMVF9QT09MX1NJWkVcIiksXG4gICAgXCJkZWZhdWx0X3BhcmFtZXRlcl9zdGF0dXNcIiA9PiAle1wic2VydmVyX3ZlcnNpb25cIiA9PiB2ZXJzaW9ufSxcbiAgICBcInVzZXJzXCIgPT4gWyV7XG4gICAgXCJkYl91c2VyXCIgPT4gXCJwZ2JvdW5jZXJcIixcbiAgICBcImRiX3Bhc3N3b3JkXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19QQVNTV09SRFwiKSxcbiAgICBcIm1vZGVfdHlwZVwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9PTEVSX1BPT0xfTU9ERVwiKSxcbiAgICBcInBvb2xfc2l6ZVwiID0+IFN5c3RlbS5nZXRfZW52KFwiUE9PTEVSX0RFRkFVTFRfUE9PTF9TSVpFXCIpLFxuICAgIFwiaXNfbWFuYWdlclwiID0+IHRydWVcbiAgICB9XVxufVxuXG50ZW5hbnQgPSBTdXBhdmlzb3IuVGVuYW50cy5nZXRfdGVuYW50X2J5X2V4dGVybmFsX2lkKHBhcmFtc1tcImV4dGVybmFsX2lkXCJdKVxuXG5pZiB0ZW5hbnQgZG9cbiAgezpvaywgX30gPSBTdXBhdmlzb3IuVGVuYW50cy51cGRhdGVfdGVuYW50KHRlbmFudCwgcGFyYW1zKVxuZWxzZVxuICB7Om9rLCBffSA9IFN1cGF2aXNvci5UZW5hbnRzLmNyZWF0ZV90ZW5hbnQocGFyYW1zKVxuZW5kXG4iCg==", "tags": [ "firebase", "alternative", @@ -4102,7 +4102,7 @@ "superset-with-postgresql": { "documentation": "https://github.com/amancevice/docker-superset?utm_source=coolify.io", "slogan": "Modern data exploration and visualization platform (unofficial community docker image)", - "compose": "c2VydmljZXM6CiAgc3VwZXJzZXQ6CiAgICBpbWFnZTogJ2FtYW5jZXZpY2Uvc3VwZXJzZXQ6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfU1VQRVJTRVRfODA4OAogICAgICAtICdTRUNSRVRfS0VZPSR7U0VSVklDRV9CQVNFNjRfNjRfU1VQRVJTRVRTRUNSRVRLRVl9JwogICAgICAtICdNQVBCT1hfQVBJX0tFWT0ke01BUEJPWF9BUElfS0VZfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXN1cGVyc2V0LWRifScKICAgICAgLSAnUkVESVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3N1cGVyc2V0L3N1cGVyc2V0X2NvbmZpZy5weQogICAgICAgIHRhcmdldDogL2V0Yy9zdXBlcnNldC9zdXBlcnNldF9jb25maWcucHkKICAgICAgICBjb250ZW50OiAiXCJcIlwiXG5Gb3IgbW9yZSBjb25maWd1cmF0aW9uIG9wdGlvbnMsIHNlZTpcbi0gaHR0cHM6Ly9zdXBlcnNldC5hcGFjaGUub3JnL2RvY3MvY29uZmlndXJhdGlvbi9jb25maWd1cmluZy1zdXBlcnNldFxuXCJcIlwiXG5cbmltcG9ydCBvc1xuXG5TRUNSRVRfS0VZID0gb3MuZ2V0ZW52KFwiU0VDUkVUX0tFWVwiKVxuTUFQQk9YX0FQSV9LRVkgPSBvcy5nZXRlbnYoXCJNQVBCT1hfQVBJX0tFWVwiLCBcIlwiKVxuXG5DQUNIRV9DT05GSUcgPSB7XG4gIFwiQ0FDSEVfVFlQRVwiOiBcIlJlZGlzQ2FjaGVcIixcbiAgXCJDQUNIRV9ERUZBVUxUX1RJTUVPVVRcIjogMzAwLFxuICBcIkNBQ0hFX0tFWV9QUkVGSVhcIjogXCJzdXBlcnNldF9cIixcbiAgXCJDQUNIRV9SRURJU19IT1NUXCI6IFwicmVkaXNcIixcbiAgXCJDQUNIRV9SRURJU19QT1JUXCI6IDYzNzksXG4gIFwiQ0FDSEVfUkVESVNfREJcIjogMSxcbiAgXCJDQUNIRV9SRURJU19VUkxcIjogZlwicmVkaXM6Ly86e29zLmdldGVudignUkVESVNfUEFTU1dPUkQnKX1AcmVkaXM6NjM3OS8xXCIsXG59XG5cbkZJTFRFUl9TVEFURV9DQUNIRV9DT05GSUcgPSB7KipDQUNIRV9DT05GSUcsIFwiQ0FDSEVfS0VZX1BSRUZJWFwiOiBcInN1cGVyc2V0X2ZpbHRlcl9cIn1cbkVYUExPUkVfRk9STV9EQVRBX0NBQ0hFX0NPTkZJRyA9IHsqKkNBQ0hFX0NPTkZJRywgXCJDQUNIRV9LRVlfUFJFRklYXCI6IFwic3VwZXJzZXRfZXhwbG9yZV9mb3JtX1wifVxuXG5TUUxBTENIRU1ZX1RSQUNLX01PRElGSUNBVElPTlMgPSBUcnVlXG5TUUxBTENIRU1ZX0RBVEFCQVNFX1VSSSA9IGZcInBvc3RncmVzcWwrcHN5Y29wZzI6Ly97b3MuZ2V0ZW52KCdQT1NUR1JFU19VU0VSJyl9Ontvcy5nZXRlbnYoJ1BPU1RHUkVTX1BBU1NXT1JEJyl9QHBvc3RncmVzOjU0MzIve29zLmdldGVudignUE9TVEdSRVNfREInKX1cIlxuXG4jIFVuY29tbWVudCBpZiB5b3Ugd2FudCB0byBsb2FkIGV4YW1wbGUgZGF0YSAodXNpbmcgXCJzdXBlcnNldCBsb2FkX2V4YW1wbGVzXCIpIGF0IHRoZVxuIyBzYW1lIGxvY2F0aW9uIGFzIHlvdXIgbWV0YWRhdGEgcG9zdGdyZXNxbCBpbnN0YW5jZS4gT3RoZXJ3aXNlLCB0aGUgZGVmYXVsdCBzcWxpdGVcbiMgd2lsbCBiZSB1c2VkLCB3aGljaCB3aWxsIG5vdCBwZXJzaXN0IGluIHZvbHVtZSB3aGVuIHJlc3RhcnRpbmcgc3VwZXJzZXQgYnkgZGVmYXVsdC5cbiNTUUxBTENIRU1ZX0VYQU1QTEVTX1VSSSA9IFNRTEFMQ0hFTVlfREFUQUJBU0VfVVJJIgogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MDg4L2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNy1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXN1cGVyc2V0LWRifScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3N1cGVyc2V0X3Bvc3RncmVzX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo4LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3N1cGVyc2V0X3JlZGlzX2RhdGE6L2RhdGEnCiAgICBjb21tYW5kOiAncmVkaXMtc2VydmVyIC0tcmVxdWlyZXBhc3MgJHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiAncmVkaXMtY2xpIHBpbmcnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "compose": "c2VydmljZXM6CiAgc3VwZXJzZXQ6CiAgICBpbWFnZTogJ2FtYW5jZXZpY2Uvc3VwZXJzZXQ6Ni4wLjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9TVVBFUlNFVF84MDg4CiAgICAgIC0gJ1NFQ1JFVF9LRVk9JHtTRVJWSUNFX0JBU0U2NF82NF9TVVBFUlNFVFNFQ1JFVEtFWX0nCiAgICAgIC0gJ01BUEJPWF9BUElfS0VZPSR7TUFQQk9YX0FQSV9LRVl9JwogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotc3VwZXJzZXQtZGJ9JwogICAgICAtICdSRURJU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vc3VwZXJzZXQvc3VwZXJzZXRfY29uZmlnLnB5CiAgICAgICAgdGFyZ2V0OiAvZXRjL3N1cGVyc2V0L3N1cGVyc2V0X2NvbmZpZy5weQogICAgICAgIGNvbnRlbnQ6ICJcIlwiXCJcbkZvciBtb3JlIGNvbmZpZ3VyYXRpb24gb3B0aW9ucywgc2VlOlxuLSBodHRwczovL3N1cGVyc2V0LmFwYWNoZS5vcmcvZG9jcy9jb25maWd1cmF0aW9uL2NvbmZpZ3VyaW5nLXN1cGVyc2V0XG5cIlwiXCJcblxuaW1wb3J0IG9zXG5cblNFQ1JFVF9LRVkgPSBvcy5nZXRlbnYoXCJTRUNSRVRfS0VZXCIpXG5NQVBCT1hfQVBJX0tFWSA9IG9zLmdldGVudihcIk1BUEJPWF9BUElfS0VZXCIsIFwiXCIpXG5cbkNBQ0hFX0NPTkZJRyA9IHtcbiAgXCJDQUNIRV9UWVBFXCI6IFwiUmVkaXNDYWNoZVwiLFxuICBcIkNBQ0hFX0RFRkFVTFRfVElNRU9VVFwiOiAzMDAsXG4gIFwiQ0FDSEVfS0VZX1BSRUZJWFwiOiBcInN1cGVyc2V0X1wiLFxuICBcIkNBQ0hFX1JFRElTX0hPU1RcIjogXCJyZWRpc1wiLFxuICBcIkNBQ0hFX1JFRElTX1BPUlRcIjogNjM3OSxcbiAgXCJDQUNIRV9SRURJU19EQlwiOiAxLFxuICBcIkNBQ0hFX1JFRElTX1VSTFwiOiBmXCJyZWRpczovLzp7b3MuZ2V0ZW52KCdSRURJU19QQVNTV09SRCcpfUByZWRpczo2Mzc5LzFcIixcbn1cblxuRklMVEVSX1NUQVRFX0NBQ0hFX0NPTkZJRyA9IHsqKkNBQ0hFX0NPTkZJRywgXCJDQUNIRV9LRVlfUFJFRklYXCI6IFwic3VwZXJzZXRfZmlsdGVyX1wifVxuRVhQTE9SRV9GT1JNX0RBVEFfQ0FDSEVfQ09ORklHID0geyoqQ0FDSEVfQ09ORklHLCBcIkNBQ0hFX0tFWV9QUkVGSVhcIjogXCJzdXBlcnNldF9leHBsb3JlX2Zvcm1fXCJ9XG5cblNRTEFMQ0hFTVlfVFJBQ0tfTU9ESUZJQ0FUSU9OUyA9IFRydWVcblNRTEFMQ0hFTVlfREFUQUJBU0VfVVJJID0gZlwicG9zdGdyZXNxbCtwc3ljb3BnMjovL3tvcy5nZXRlbnYoJ1BPU1RHUkVTX1VTRVInKX06e29zLmdldGVudignUE9TVEdSRVNfUEFTU1dPUkQnKX1AcG9zdGdyZXM6NTQzMi97b3MuZ2V0ZW52KCdQT1NUR1JFU19EQicpfVwiXG5cbiMgVW5jb21tZW50IGlmIHlvdSB3YW50IHRvIGxvYWQgZXhhbXBsZSBkYXRhICh1c2luZyBcInN1cGVyc2V0IGxvYWRfZXhhbXBsZXNcIikgYXQgdGhlXG4jIHNhbWUgbG9jYXRpb24gYXMgeW91ciBtZXRhZGF0YSBwb3N0Z3Jlc3FsIGluc3RhbmNlLiBPdGhlcndpc2UsIHRoZSBkZWZhdWx0IHNxbGl0ZVxuIyB3aWxsIGJlIHVzZWQsIHdoaWNoIHdpbGwgbm90IHBlcnNpc3QgaW4gdm9sdW1lIHdoZW4gcmVzdGFydGluZyBzdXBlcnNldCBieSBkZWZhdWx0LlxuI1NRTEFMQ0hFTVlfRVhBTVBMRVNfVVJJID0gU1FMQUxDSEVNWV9EQVRBQkFTRV9VUkkiCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByZWRpczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwODgvaGVhbHRoJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE4JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1zdXBlcnNldC1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdzdXBlcnNldF9wb3N0Z3Jlc19kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjgnCiAgICB2b2x1bWVzOgogICAgICAtICdzdXBlcnNldF9yZWRpc19kYXRhOi9kYXRhJwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLXJlcXVpcmVwYXNzICR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogJ3JlZGlzLWNsaSBwaW5nJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", "tags": [ "analytics", "bi", diff --git a/templates/service-templates.json b/templates/service-templates.json index aae653dac..ccd00c04c 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -851,7 +851,7 @@ "dolibarr": { "documentation": "https://www.dolibarr.org/documentation-home.php?utm_source=coolify.io", "slogan": "Dolibarr is a modern software package to manage your organization's activity (contacts, quotes, invoices, orders, stocks, agenda, hr, expense reports, accountancy, ecm, manufacturing, ...).", - "compose": "c2VydmljZXM6CiAgZG9saWJhcnI6CiAgICBpbWFnZTogJ2RvbGliYXJyL2RvbGliYXJyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9ET0xJQkFSUl84MAogICAgICAtICdXV1dfVVNFUl9JRD0ke1dXV19VU0VSX0lEOi0xMDAwfScKICAgICAgLSAnV1dXX0dST1VQX0lEPSR7V1dXX0dST1VQX0lEOi0xMDAwfScKICAgICAgLSBET0xJX0RCX0hPU1Q9bWFyaWFkYgogICAgICAtICdET0xJX0RCX05BTUU9JHtNWVNRTF9EQVRBQkFTRTotZG9saWJhcnItZGJ9JwogICAgICAtICdET0xJX0RCX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdET0xJX0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTH0nCiAgICAgIC0gJ0RPTElfVVJMX1JPT1Q9JHtTRVJWSUNFX0ZRRE5fRE9MSUJBUlJ9JwogICAgICAtICdET0xJX0FETUlOX0xPR0lOPSR7U0VSVklDRV9VU0VSX0RPTElCQVJSfScKICAgICAgLSAnRE9MSV9BRE1JTl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfRE9MSUJBUlJ9JwogICAgICAtICdET0xJX0NST049JHtET0xJX0NST046LTB9JwogICAgICAtICdET0xJX0lOSVRfREVNTz0ke0RPTElfSU5JVF9ERU1POi0wfScKICAgICAgLSAnRE9MSV9DT01QQU5ZX05BTUU9JHtET0xJX0NPTVBBTllfTkFNRTotTXlCaWdDb21wYW55fScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIG1hcmlhZGI6CiAgICBpbWFnZTogJ21hcmlhZGI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01ZU1FMX0RBVEFCQVNFPSR7TVlTUUxfREFUQUJBU0U6LWRvbGliYXJyLWRifScKICAgICAgLSAnTVlTUUxfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NWVNRTH0nCiAgICAgIC0gJ01ZU1FMX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTH0nCiAgICAgIC0gJ01ZU1FMX1JPT1RfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1JPT1R9JwogICAgdm9sdW1lczoKICAgICAgLSAnZG9saWJhcnJfbWFyaWFkYl9kYXRhOi92YXIvbGliL215c3FsJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGhlYWx0aGNoZWNrLnNoCiAgICAgICAgLSAnLS1jb25uZWN0JwogICAgICAgIC0gJy0taW5ub2RiX2luaXRpYWxpemVkJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "c2VydmljZXM6CiAgZG9saWJhcnI6CiAgICBpbWFnZTogJ2RvbGliYXJyL2RvbGliYXJyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9ET0xJQkFSUl84MAogICAgICAtICdXV1dfVVNFUl9JRD0ke1dXV19VU0VSX0lEOi0xMDAwfScKICAgICAgLSAnV1dXX0dST1VQX0lEPSR7V1dXX0dST1VQX0lEOi0xMDAwfScKICAgICAgLSBET0xJX0RCX0hPU1Q9bWFyaWFkYgogICAgICAtICdET0xJX0RCX05BTUU9JHtNWVNRTF9EQVRBQkFTRTotZG9saWJhcnItZGJ9JwogICAgICAtICdET0xJX0RCX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdET0xJX0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTH0nCiAgICAgIC0gJ0RPTElfVVJMX1JPT1Q9JHtTRVJWSUNFX0ZRRE5fRE9MSUJBUlJ9JwogICAgICAtICdET0xJX0FETUlOX0xPR0lOPSR7U0VSVklDRV9VU0VSX0RPTElCQVJSfScKICAgICAgLSAnRE9MSV9BRE1JTl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfRE9MSUJBUlJ9JwogICAgICAtICdET0xJX0NST049JHtET0xJX0NST046LTB9JwogICAgICAtICdET0xJX0lOSVRfREVNTz0ke0RPTElfSU5JVF9ERU1POi0wfScKICAgICAgLSAnRE9MSV9DT01QQU5ZX05BTUU9JHtET0xJX0NPTVBBTllfTkFNRTotTXlCaWdDb21wYW55fScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RvbGliYXJyX2RvY3M6L3Zhci93d3cvZG9jdW1lbnRzJwogICAgICAtICdkb2xpYmFycl9jdXN0b206L3Zhci93d3cvaHRtbC9jdXN0b20nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBtYXJpYWRiOgogICAgaW1hZ2U6ICdtYXJpYWRiOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtICdNWVNRTF9EQVRBQkFTRT0ke01ZU1FMX0RBVEFCQVNFOi1kb2xpYmFyci1kYn0nCiAgICAgIC0gJ01ZU1FMX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUx9JwogICAgICAtICdNWVNRTF9ST09UX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9ST09UfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RvbGliYXJyX21hcmlhZGJfZGF0YTovdmFyL2xpYi9teXNxbCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBoZWFsdGhjaGVjay5zaAogICAgICAgIC0gJy0tY29ubmVjdCcKICAgICAgICAtICctLWlubm9kYl9pbml0aWFsaXplZCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=", "tags": [ "crm", "erp" @@ -4088,7 +4088,7 @@ "supabase": { "documentation": "https://supabase.io?utm_source=coolify.io", "slogan": "The open source Firebase alternative.", - "compose": "c2VydmljZXM6CiAgc3VwYWJhc2Uta29uZzoKICAgIGltYWdlOiAna29uZzoyLjguMScKICAgIGVudHJ5cG9pbnQ6ICdiYXNoIC1jICcnZXZhbCAiZWNobyBcIiQkKGNhdCB+L3RlbXAueW1sKVwiIiA+IH4va29uZy55bWwgJiYgL2RvY2tlci1lbnRyeXBvaW50LnNoIGtvbmcgZG9ja2VyLXN0YXJ0JycnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkdfODAwMAogICAgICAtICdLT05HX1BPUlRfTUFQUz00NDM6ODAwMCcKICAgICAgLSAnSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSBLT05HX0RBVEFCQVNFPW9mZgogICAgICAtIEtPTkdfREVDTEFSQVRJVkVfQ09ORklHPS9ob21lL2tvbmcva29uZy55bWwKICAgICAgLSAnS09OR19ETlNfT1JERVI9TEFTVCxBLENOQU1FJwogICAgICAtICdLT05HX1BMVUdJTlM9cmVxdWVzdC10cmFuc2Zvcm1lcixjb3JzLGtleS1hdXRoLGFjbCxiYXNpYy1hdXRoJwogICAgICAtIEtPTkdfTkdJTlhfUFJPWFlfUFJPWFlfQlVGRkVSX1NJWkU9MTYwawogICAgICAtICdLT05HX05HSU5YX1BST1hZX1BST1hZX0JVRkZFUlM9NjQgMTYwaycKICAgICAgLSAnU1VQQUJBU0VfQU5PTl9LRVk9JHtTRVJWSUNFX1NVUEFCQVNFQU5PTl9LRVl9JwogICAgICAtICdTVVBBQkFTRV9TRVJWSUNFX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VTRVJWSUNFX0tFWX0nCiAgICAgIC0gJ0RBU0hCT0FSRF9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9BRE1JTn0nCiAgICAgIC0gJ0RBU0hCT0FSRF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfQURNSU59JwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9hcGkva29uZy55bWwKICAgICAgICB0YXJnZXQ6IC9ob21lL2tvbmcvdGVtcC55bWwKICAgICAgICBjb250ZW50OiAiX2Zvcm1hdF92ZXJzaW9uOiAnMi4xJ1xuX3RyYW5zZm9ybTogdHJ1ZVxuXG4jIyNcbiMjIyBDb25zdW1lcnMgLyBVc2Vyc1xuIyMjXG5jb25zdW1lcnM6XG4gIC0gdXNlcm5hbWU6IERBU0hCT0FSRFxuICAtIHVzZXJuYW1lOiBhbm9uXG4gICAga2V5YXV0aF9jcmVkZW50aWFsczpcbiAgICAgIC0ga2V5OiAkU1VQQUJBU0VfQU5PTl9LRVlcbiAgLSB1c2VybmFtZTogc2VydmljZV9yb2xlXG4gICAga2V5YXV0aF9jcmVkZW50aWFsczpcbiAgICAgIC0ga2V5OiAkU1VQQUJBU0VfU0VSVklDRV9LRVlcblxuIyMjXG4jIyMgQWNjZXNzIENvbnRyb2wgTGlzdFxuIyMjXG5hY2xzOlxuICAtIGNvbnN1bWVyOiBhbm9uXG4gICAgZ3JvdXA6IGFub25cbiAgLSBjb25zdW1lcjogc2VydmljZV9yb2xlXG4gICAgZ3JvdXA6IGFkbWluXG5cbiMjI1xuIyMjIERhc2hib2FyZCBjcmVkZW50aWFsc1xuIyMjXG5iYXNpY2F1dGhfY3JlZGVudGlhbHM6XG4tIGNvbnN1bWVyOiBEQVNIQk9BUkRcbiAgdXNlcm5hbWU6ICREQVNIQk9BUkRfVVNFUk5BTUVcbiAgcGFzc3dvcmQ6ICREQVNIQk9BUkRfUEFTU1dPUkRcblxuXG4jIyNcbiMjIyBBUEkgUm91dGVzXG4jIyNcbnNlcnZpY2VzOlxuXG4gICMjIE9wZW4gQXV0aCByb3V0ZXNcbiAgLSBuYW1lOiBhdXRoLXYxLW9wZW5cbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1hdXRoOjk5OTkvdmVyaWZ5XG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBhdXRoLXYxLW9wZW5cbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9hdXRoL3YxL3ZlcmlmeVxuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgLSBuYW1lOiBhdXRoLXYxLW9wZW4tY2FsbGJhY2tcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1hdXRoOjk5OTkvY2FsbGJhY2tcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtb3Blbi1jYWxsYmFja1xuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvY2FsbGJhY2tcbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gIC0gbmFtZTogYXV0aC12MS1vcGVuLWF1dGhvcml6ZVxuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS9hdXRob3JpemVcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtb3Blbi1hdXRob3JpemVcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9hdXRoL3YxL2F1dGhvcml6ZVxuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcblxuICAjIyBTZWN1cmUgQXV0aCByb3V0ZXNcbiAgLSBuYW1lOiBhdXRoLXYxXG4gICAgX2NvbW1lbnQ6ICdHb1RydWU6IC9hdXRoL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYXV0aC92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiBmYWxzZVxuICAgICAgLSBuYW1lOiBhY2xcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfZ3JvdXBzX2hlYWRlcjogdHJ1ZVxuICAgICAgICAgIGFsbG93OlxuICAgICAgICAgICAgLSBhZG1pblxuICAgICAgICAgICAgLSBhbm9uXG5cbiAgIyMgU2VjdXJlIFJFU1Qgcm91dGVzXG4gIC0gbmFtZTogcmVzdC12MVxuICAgIF9jb21tZW50OiAnUG9zdGdSRVNUOiAvcmVzdC92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvKidcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiByZXN0LXYxLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL3Jlc3QvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogdHJ1ZVxuICAgICAgLSBuYW1lOiBhY2xcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfZ3JvdXBzX2hlYWRlcjogdHJ1ZVxuICAgICAgICAgIGFsbG93OlxuICAgICAgICAgICAgLSBhZG1pblxuICAgICAgICAgICAgLSBhbm9uXG5cbiAgIyMgU2VjdXJlIEdyYXBoUUwgcm91dGVzXG4gIC0gbmFtZTogZ3JhcGhxbC12MVxuICAgIF9jb21tZW50OiAnUG9zdGdSRVNUOiAvZ3JhcGhxbC92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvcnBjL2dyYXBocWwnXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtcmVzdDozMDAwL3JwYy9ncmFwaHFsXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBncmFwaHFsLXYxLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2dyYXBocWwvdjFcbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4gICAgICAtIG5hbWU6IHJlcXVlc3QtdHJhbnNmb3JtZXJcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGFkZDpcbiAgICAgICAgICAgIGhlYWRlcnM6XG4gICAgICAgICAgICAgIC0gQ29udGVudC1Qcm9maWxlOmdyYXBocWxfcHVibGljXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgUmVhbHRpbWUgcm91dGVzXG4gIC0gbmFtZTogcmVhbHRpbWUtdjEtd3NcbiAgICBfY29tbWVudDogJ1JlYWx0aW1lOiAvcmVhbHRpbWUvdjEvKiAtPiB3czovL3JlYWx0aW1lOjQwMDAvc29ja2V0LyonXG4gICAgdXJsOiBodHRwOi8vcmVhbHRpbWUtZGV2OjQwMDAvc29ja2V0XG4gICAgcHJvdG9jb2w6IHdzXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiByZWFsdGltZS12MS13c1xuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL3JlYWx0aW1lL3YxL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IGZhbHNlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cbiAgLSBuYW1lOiByZWFsdGltZS12MS1yZXN0XG4gICAgX2NvbW1lbnQ6ICdSZWFsdGltZTogL3JlYWx0aW1lL3YxLyogLT4gd3M6Ly9yZWFsdGltZTo0MDAwL3NvY2tldC8qJ1xuICAgIHVybDogaHR0cDovL3JlYWx0aW1lLWRldjo0MDAwL2FwaVxuICAgIHByb3RvY29sOiBodHRwXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiByZWFsdGltZS12MS1yZXN0XG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVhbHRpbWUvdjEvYXBpXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuXG4gICMjIFN0b3JhZ2Ugcm91dGVzOiB0aGUgc3RvcmFnZSBzZXJ2ZXIgbWFuYWdlcyBpdHMgb3duIGF1dGhcbiAgLSBuYW1lOiBzdG9yYWdlLXYxXG4gICAgX2NvbW1lbnQ6ICdTdG9yYWdlOiAvc3RvcmFnZS92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1zdG9yYWdlOjUwMDAvKidcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1zdG9yYWdlOjUwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBzdG9yYWdlLXYxLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL3N0b3JhZ2UvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIEVkZ2UgRnVuY3Rpb25zIHJvdXRlc1xuICAtIG5hbWU6IGZ1bmN0aW9ucy12MVxuICAgIF9jb21tZW50OiAnRWRnZSBGdW5jdGlvbnM6IC9mdW5jdGlvbnMvdjEvKiAtPiBodHRwOi8vc3VwYWJhc2UtZWRnZS1mdW5jdGlvbnM6OTAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWVkZ2UtZnVuY3Rpb25zOjkwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBmdW5jdGlvbnMtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvZnVuY3Rpb25zL3YxL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcblxuICAjIyBBbmFseXRpY3Mgcm91dGVzXG4gIC0gbmFtZTogYW5hbHl0aWNzLXYxXG4gICAgX2NvbW1lbnQ6ICdBbmFseXRpY3M6IC9hbmFseXRpY3MvdjEvKiAtPiBodHRwOi8vbG9nZmxhcmU6NDAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYW5hbHl0aWNzLXYxLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2FuYWx5dGljcy92MS9cblxuICAjIyBTZWN1cmUgRGF0YWJhc2Ugcm91dGVzXG4gIC0gbmFtZTogbWV0YVxuICAgIF9jb21tZW50OiAncGctbWV0YTogL3BnLyogLT4gaHR0cDovL3N1cGFiYXNlLW1ldGE6ODA4MC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLW1ldGE6ODA4MC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IG1ldGEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcGcvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IGZhbHNlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG5cbiAgIyMgUHJvdGVjdGVkIERhc2hib2FyZCAtIGNhdGNoIGFsbCByZW1haW5pbmcgcm91dGVzXG4gIC0gbmFtZTogZGFzaGJvYXJkXG4gICAgX2NvbW1lbnQ6ICdTdHVkaW86IC8qIC0+IGh0dHA6Ly9zdHVkaW86MzAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXN0dWRpbzozMDAwL1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogZGFzaGJvYXJkLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZTogYmFzaWMtYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogdHJ1ZVxuIgogIHN1cGFiYXNlLXN0dWRpbzoKICAgIGltYWdlOiAnc3VwYWJhc2Uvc3R1ZGlvOjIwMjUuMDYuMDItc2hhLThmMjk5M2QnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gbm9kZQogICAgICAgIC0gJy1lJwogICAgICAgIC0gImZldGNoKCdodHRwOi8vMTI3LjAuMC4xOjMwMDAvYXBpL3BsYXRmb3JtL3Byb2ZpbGUnKS50aGVuKChyKSA9PiB7aWYgKHIuc3RhdHVzICE9PSAyMDApIHRocm93IG5ldyBFcnJvcihyLnN0YXR1cyl9KSIKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSE9TVE5BTUU9MC4wLjAuMAogICAgICAtICdTVFVESU9fUEdfTUVUQV9VUkw9aHR0cDovL3N1cGFiYXNlLW1ldGE6ODA4MCcKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnREVGQVVMVF9PUkdBTklaQVRJT05fTkFNRT0ke1NUVURJT19ERUZBVUxUX09SR0FOSVpBVElPTjotRGVmYXVsdCBPcmdhbml6YXRpb259JwogICAgICAtICdERUZBVUxUX1BST0pFQ1RfTkFNRT0ke1NUVURJT19ERUZBVUxUX1BST0pFQ1Q6LURlZmF1bHQgUHJvamVjdH0nCiAgICAgIC0gJ1NVUEFCQVNFX1VSTD1odHRwOi8vc3VwYWJhc2Uta29uZzo4MDAwJwogICAgICAtICdTVVBBQkFTRV9QVUJMSUNfVVJMPSR7U0VSVklDRV9GUUROX1NVUEFCQVNFS09OR30nCiAgICAgIC0gJ1NVUEFCQVNFX0FOT05fS0VZPSR7U0VSVklDRV9TVVBBQkFTRUFOT05fS0VZfScKICAgICAgLSAnU1VQQUJBU0VfU0VSVklDRV9LRVk9JHtTRVJWSUNFX1NVUEFCQVNFU0VSVklDRV9LRVl9JwogICAgICAtICdBVVRIX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0xPR0ZMQVJFX0FQSV9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX0xPR0ZMQVJFfScKICAgICAgLSAnTE9HRkxBUkVfVVJMPWh0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMCcKICAgICAgLSAnU1VQQUJBU0VfUFVCTElDX0FQST0ke1NFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkd9JwogICAgICAtIE5FWFRfUFVCTElDX0VOQUJMRV9MT0dTPXRydWUKICAgICAgLSBORVhUX0FOQUxZVElDU19CQUNLRU5EX1BST1ZJREVSPXBvc3RncmVzCiAgICAgIC0gJ09QRU5BSV9BUElfS0VZPSR7T1BFTkFJX0FQSV9LRVl9JwogIHN1cGFiYXNlLWRiOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9wb3N0Z3JlczoxNS44LjEuMDQ4JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdwZ19pc3JlYWR5IC1VIHBvc3RncmVzIC1oIDEyNy4wLjAuMScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS12ZWN0b3I6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGNvbW1hbmQ6CiAgICAgIC0gcG9zdGdyZXMKICAgICAgLSAnLWMnCiAgICAgIC0gY29uZmlnX2ZpbGU9L2V0Yy9wb3N0Z3Jlc3FsL3Bvc3RncmVzcWwuY29uZgogICAgICAtICctYycKICAgICAgLSBsb2dfbWluX21lc3NhZ2VzPWZhdGFsCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19IT1NUPS92YXIvcnVuL3Bvc3RncmVzcWwKICAgICAgLSAnUEdQT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ1BPU1RHUkVTX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUEdQQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQR0RBVEFCQVNFPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdKV1RfRVhQPSR7SldUX0VYUElSWTotMzYwMH0nCiAgICB2b2x1bWVzOgogICAgICAtICdzdXBhYmFzZS1kYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9yZWFsdGltZS5zcWwKICAgICAgICB0YXJnZXQ6IC9kb2NrZXItZW50cnlwb2ludC1pbml0ZGIuZC9taWdyYXRpb25zLzk5LXJlYWx0aW1lLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCJzdXBhYmFzZV9hZG1pblwiYFxuXG5jcmVhdGUgc2NoZW1hIGlmIG5vdCBleGlzdHMgX3JlYWx0aW1lO1xuYWx0ZXIgc2NoZW1hIF9yZWFsdGltZSBvd25lciB0byA6cGd1c2VyO1xuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2RiL19zdXBhYmFzZS5zcWwKICAgICAgICB0YXJnZXQ6IC9kb2NrZXItZW50cnlwb2ludC1pbml0ZGIuZC9taWdyYXRpb25zLzk3LV9zdXBhYmFzZS5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwiJFBPU1RHUkVTX1VTRVJcImBcblxuQ1JFQVRFIERBVEFCQVNFIF9zdXBhYmFzZSBXSVRIIE9XTkVSIDpwZ3VzZXI7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcG9vbGVyLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktcG9vbGVyLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCJzdXBhYmFzZV9hZG1pblwiYFxuXFxjIF9zdXBhYmFzZVxuY3JlYXRlIHNjaGVtYSBpZiBub3QgZXhpc3RzIF9zdXBhdmlzb3I7XG5hbHRlciBzY2hlbWEgX3N1cGF2aXNvciBvd25lciB0byA6cGd1c2VyO1xuXFxjIHBvc3RncmVzXG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvd2ViaG9va3Muc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk4LXdlYmhvb2tzLnNxbAogICAgICAgIGNvbnRlbnQ6ICJCRUdJTjtcbi0tIENyZWF0ZSBwZ19uZXQgZXh0ZW5zaW9uXG5DUkVBVEUgRVhURU5TSU9OIElGIE5PVCBFWElTVFMgcGdfbmV0IFNDSEVNQSBleHRlbnNpb25zO1xuLS0gQ3JlYXRlIHN1cGFiYXNlX2Z1bmN0aW9ucyBzY2hlbWFcbkNSRUFURSBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIEFVVEhPUklaQVRJT04gc3VwYWJhc2VfYWRtaW47XG5HUkFOVCBVU0FHRSBPTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gVEFCTEVTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gRlVOQ1RJT05TIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gU0VRVUVOQ0VTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMubWlncmF0aW9ucyBkZWZpbml0aW9uXG5DUkVBVEUgVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLm1pZ3JhdGlvbnMgKFxuICB2ZXJzaW9uIHRleHQgUFJJTUFSWSBLRVksXG4gIGluc2VydGVkX2F0IHRpbWVzdGFtcHR6IE5PVCBOVUxMIERFRkFVTFQgTk9XKClcbik7XG4tLSBJbml0aWFsIHN1cGFiYXNlX2Z1bmN0aW9ucyBtaWdyYXRpb25cbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCdpbml0aWFsJyk7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMuaG9va3MgZGVmaW5pdGlvblxuQ1JFQVRFIFRBQkxFIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyAoXG4gIGlkIGJpZ3NlcmlhbCBQUklNQVJZIEtFWSxcbiAgaG9va190YWJsZV9pZCBpbnRlZ2VyIE5PVCBOVUxMLFxuICBob29rX25hbWUgdGV4dCBOT1QgTlVMTCxcbiAgY3JlYXRlZF9hdCB0aW1lc3RhbXB0eiBOT1QgTlVMTCBERUZBVUxUIE5PVygpLFxuICByZXF1ZXN0X2lkIGJpZ2ludFxuKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfcmVxdWVzdF9pZF9pZHggT04gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIFVTSU5HIGJ0cmVlIChyZXF1ZXN0X2lkKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfaF90YWJsZV9pZF9oX25hbWVfaWR4IE9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyBVU0lORyBidHJlZSAoaG9va190YWJsZV9pZCwgaG9va19uYW1lKTtcbkNPTU1FTlQgT04gVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIElTICdTdXBhYmFzZSBGdW5jdGlvbnMgSG9va3M6IEF1ZGl0IHRyYWlsIGZvciB0cmlnZ2VyZWQgaG9va3MuJztcbkNSRUFURSBGVU5DVElPTiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KClcbiAgUkVUVVJOUyB0cmlnZ2VyXG4gIExBTkdVQUdFIHBscGdzcWxcbiAgQVMgJGZ1bmN0aW9uJFxuICBERUNMQVJFXG4gICAgcmVxdWVzdF9pZCBiaWdpbnQ7XG4gICAgcGF5bG9hZCBqc29uYjtcbiAgICB1cmwgdGV4dCA6PSBUR19BUkdWWzBdOjp0ZXh0O1xuICAgIG1ldGhvZCB0ZXh0IDo9IFRHX0FSR1ZbMV06OnRleHQ7XG4gICAgaGVhZGVycyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHBhcmFtcyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHRpbWVvdXRfbXMgaW50ZWdlciBERUZBVUxUIDEwMDA7XG4gIEJFR0lOXG4gICAgSUYgdXJsIElTIE5VTEwgT1IgdXJsID0gJ251bGwnIFRIRU5cbiAgICAgIFJBSVNFIEVYQ0VQVElPTiAndXJsIGFyZ3VtZW50IGlzIG1pc3NpbmcnO1xuICAgIEVORCBJRjtcblxuICAgIElGIG1ldGhvZCBJUyBOVUxMIE9SIG1ldGhvZCA9ICdudWxsJyBUSEVOXG4gICAgICBSQUlTRSBFWENFUFRJT04gJ21ldGhvZCBhcmd1bWVudCBpcyBtaXNzaW5nJztcbiAgICBFTkQgSUY7XG5cbiAgICBJRiBUR19BUkdWWzJdIElTIE5VTEwgT1IgVEdfQVJHVlsyXSA9ICdudWxsJyBUSEVOXG4gICAgICBoZWFkZXJzID0gJ3tcIkNvbnRlbnQtVHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIn0nOjpqc29uYjtcbiAgICBFTFNFXG4gICAgICBoZWFkZXJzID0gVEdfQVJHVlsyXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVlszXSBJUyBOVUxMIE9SIFRHX0FSR1ZbM10gPSAnbnVsbCcgVEhFTlxuICAgICAgcGFyYW1zID0gJ3t9Jzo6anNvbmI7XG4gICAgRUxTRVxuICAgICAgcGFyYW1zID0gVEdfQVJHVlszXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVls0XSBJUyBOVUxMIE9SIFRHX0FSR1ZbNF0gPSAnbnVsbCcgVEhFTlxuICAgICAgdGltZW91dF9tcyA9IDEwMDA7XG4gICAgRUxTRVxuICAgICAgdGltZW91dF9tcyA9IFRHX0FSR1ZbNF06OmludGVnZXI7XG4gICAgRU5EIElGO1xuXG4gICAgQ0FTRVxuICAgICAgV0hFTiBtZXRob2QgPSAnR0VUJyBUSEVOXG4gICAgICAgIFNFTEVDVCBodHRwX2dldCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9nZXQoXG4gICAgICAgICAgdXJsLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIFdIRU4gbWV0aG9kID0gJ1BPU1QnIFRIRU5cbiAgICAgICAgcGF5bG9hZCA9IGpzb25iX2J1aWxkX29iamVjdChcbiAgICAgICAgICAnb2xkX3JlY29yZCcsIE9MRCxcbiAgICAgICAgICAncmVjb3JkJywgTkVXLFxuICAgICAgICAgICd0eXBlJywgVEdfT1AsXG4gICAgICAgICAgJ3RhYmxlJywgVEdfVEFCTEVfTkFNRSxcbiAgICAgICAgICAnc2NoZW1hJywgVEdfVEFCTEVfU0NIRU1BXG4gICAgICAgICk7XG5cbiAgICAgICAgU0VMRUNUIGh0dHBfcG9zdCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9wb3N0KFxuICAgICAgICAgIHVybCxcbiAgICAgICAgICBwYXlsb2FkLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIEVMU0VcbiAgICAgICAgUkFJU0UgRVhDRVBUSU9OICdtZXRob2QgYXJndW1lbnQgJSBpcyBpbnZhbGlkJywgbWV0aG9kO1xuICAgIEVORCBDQVNFO1xuXG4gICAgSU5TRVJUIElOVE8gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzXG4gICAgICAoaG9va190YWJsZV9pZCwgaG9va19uYW1lLCByZXF1ZXN0X2lkKVxuICAgIFZBTFVFU1xuICAgICAgKFRHX1JFTElELCBUR19OQU1FLCByZXF1ZXN0X2lkKTtcblxuICAgIFJFVFVSTiBORVc7XG4gIEVORFxuJGZ1bmN0aW9uJDtcbi0tIFN1cGFiYXNlIHN1cGVyIGFkbWluXG5ET1xuJCRcbkJFR0lOXG4gIElGIE5PVCBFWElTVFMgKFxuICAgIFNFTEVDVCAxXG4gICAgRlJPTSBwZ19yb2xlc1xuICAgIFdIRVJFIHJvbG5hbWUgPSAnc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluJ1xuICApXG4gIFRIRU5cbiAgICBDUkVBVEUgVVNFUiBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gTk9JTkhFUklUIENSRUFURVJPTEUgTE9HSU4gTk9SRVBMSUNBVElPTjtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIFNDSEVNQSBzdXBhYmFzZV9mdW5jdGlvbnMgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuR1JBTlQgQUxMIFBSSVZJTEVHRVMgT04gQUxMIFRBQkxFUyBJTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIEFMTCBTRVFVRU5DRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5BTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBTRVQgc2VhcmNoX3BhdGggPSBcInN1cGFiYXNlX2Z1bmN0aW9uc1wiO1xuQUxURVIgdGFibGUgXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5taWdyYXRpb25zIE9XTkVSIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkFMVEVSIHRhYmxlIFwic3VwYWJhc2VfZnVuY3Rpb25zXCIuaG9va3MgT1dORVIgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuQUxURVIgZnVuY3Rpb24gXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5odHRwX3JlcXVlc3QoKSBPV05FUiBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5HUkFOVCBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gVE8gcG9zdGdyZXM7XG4tLSBSZW1vdmUgdW51c2VkIHN1cGFiYXNlX3BnX25ldF9hZG1pbiByb2xlXG5ET1xuJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX3JvbGVzXG4gICAgV0hFUkUgcm9sbmFtZSA9ICdzdXBhYmFzZV9wZ19uZXRfYWRtaW4nXG4gIClcbiAgVEhFTlxuICAgIFJFQVNTSUdOIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbiBUTyBzdXBhYmFzZV9hZG1pbjtcbiAgICBEUk9QIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbjtcbiAgICBEUk9QIFJPTEUgc3VwYWJhc2VfcGdfbmV0X2FkbWluO1xuICBFTkQgSUY7XG5FTkRcbiQkO1xuLS0gcGdfbmV0IGdyYW50cyB3aGVuIGV4dGVuc2lvbiBpcyBhbHJlYWR5IGVuYWJsZWRcbkRPXG4kJFxuQkVHSU5cbiAgSUYgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXh0ZW5zaW9uXG4gICAgV0hFUkUgZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbi0tIEV2ZW50IHRyaWdnZXIgZm9yIHBnX25ldFxuQ1JFQVRFIE9SIFJFUExBQ0UgRlVOQ1RJT04gZXh0ZW5zaW9ucy5ncmFudF9wZ19uZXRfYWNjZXNzKClcblJFVFVSTlMgZXZlbnRfdHJpZ2dlclxuTEFOR1VBR0UgcGxwZ3NxbFxuQVMgJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX2V2ZW50X3RyaWdnZXJfZGRsX2NvbW1hbmRzKCkgQVMgZXZcbiAgICBKT0lOIHBnX2V4dGVuc2lvbiBBUyBleHRcbiAgICBPTiBldi5vYmppZCA9IGV4dC5vaWRcbiAgICBXSEVSRSBleHQuZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EO1xuJCQ7XG5DT01NRU5UIE9OIEZVTkNUSU9OIGV4dGVuc2lvbnMuZ3JhbnRfcGdfbmV0X2FjY2VzcyBJUyAnR3JhbnRzIGFjY2VzcyB0byBwZ19uZXQnO1xuRE9cbiQkXG5CRUdJTlxuICBJRiBOT1QgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXZlbnRfdHJpZ2dlclxuICAgIFdIRVJFIGV2dG5hbWUgPSAnaXNzdWVfcGdfbmV0X2FjY2VzcydcbiAgKSBUSEVOXG4gICAgQ1JFQVRFIEVWRU5UIFRSSUdHRVIgaXNzdWVfcGdfbmV0X2FjY2VzcyBPTiBkZGxfY29tbWFuZF9lbmQgV0hFTiBUQUcgSU4gKCdDUkVBVEUgRVhURU5TSU9OJylcbiAgICBFWEVDVVRFIFBST0NFRFVSRSBleHRlbnNpb25zLmdyYW50X3BnX25ldF9hY2Nlc3MoKTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCcyMDIxMDgwOTE4MzQyM191cGRhdGVfZ3JhbnRzJyk7XG5BTFRFUiBmdW5jdGlvbiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KCkgU0VDVVJJVFkgREVGSU5FUjtcbkFMVEVSIGZ1bmN0aW9uIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBTRVQgc2VhcmNoX3BhdGggPSBzdXBhYmFzZV9mdW5jdGlvbnM7XG5SRVZPS0UgQUxMIE9OIEZVTkNUSU9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBGUk9NIFBVQkxJQztcbkdSQU5UIEVYRUNVVEUgT04gRlVOQ1RJT04gc3VwYWJhc2VfZnVuY3Rpb25zLmh0dHBfcmVxdWVzdCgpIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5DT01NSVQ7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcm9sZXMuc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk5LXJvbGVzLnNxbAogICAgICAgIGNvbnRlbnQ6ICItLSBOT1RFOiBjaGFuZ2UgdG8geW91ciBvd24gcGFzc3dvcmRzIGZvciBwcm9kdWN0aW9uIGVudmlyb25tZW50c1xuIFxcc2V0IHBncGFzcyBgZWNobyBcIiRQT1NUR1JFU19QQVNTV09SRFwiYFxuXG4gQUxURVIgVVNFUiBhdXRoZW50aWNhdG9yIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgcGdib3VuY2VyIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgc3VwYWJhc2VfYXV0aF9hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX3N0b3JhZ2VfYWRtaW4gV0lUSCBQQVNTV09SRCA6J3BncGFzcyc7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvand0LnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL2luaXQtc2NyaXB0cy85OS1qd3Quc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IGp3dF9zZWNyZXQgYGVjaG8gXCIkSldUX1NFQ1JFVFwiYFxuXFxzZXQgand0X2V4cCBgZWNobyBcIiRKV1RfRVhQXCJgXG5cXHNldCBkYl9uYW1lIGBlY2hvIFwiJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9XCJgXG5cbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3Rfc2VjcmV0XCIgVE8gOidqd3Rfc2VjcmV0JztcbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3RfZXhwXCIgVE8gOidqd3RfZXhwJztcbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9sb2dzLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktbG9ncy5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwic3VwYWJhc2VfYWRtaW5cImBcblxcYyBfc3VwYWJhc2VcbmNyZWF0ZSBzY2hlbWEgaWYgbm90IGV4aXN0cyBfYW5hbHl0aWNzO1xuYWx0ZXIgc2NoZW1hIF9hbmFseXRpY3Mgb3duZXIgdG8gOnBndXNlcjtcblxcYyBwb3N0Z3Jlc1xuIgogICAgICAtICdzdXBhYmFzZS1kYi1jb25maWc6L2V0Yy9wb3N0Z3Jlc3FsLWN1c3RvbScKICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICBpbWFnZTogJ3N1cGFiYXNlL2xvZ2ZsYXJlOjEuNC4wJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjQwMDAvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBMT0dGTEFSRV9OT0RFX0hPU1Q9MTI3LjAuMC4xCiAgICAgIC0gREJfVVNFUk5BTUU9c3VwYWJhc2VfYWRtaW4KICAgICAgLSBEQl9EQVRBQkFTRT1fc3VwYWJhc2UKICAgICAgLSAnREJfSE9TVE5BTUU9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdEQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gREJfU0NIRU1BPV9hbmFseXRpY3MKICAgICAgLSAnTE9HRkxBUkVfQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTE9HRkxBUkV9JwogICAgICAtIExPR0ZMQVJFX1NJTkdMRV9URU5BTlQ9dHJ1ZQogICAgICAtIExPR0ZMQVJFX1NJTkdMRV9URU5BTlRfTU9ERT10cnVlCiAgICAgIC0gTE9HRkxBUkVfU1VQQUJBU0VfTU9ERT10cnVlCiAgICAgIC0gTE9HRkxBUkVfTUlOX0NMVVNURVJfU0laRT0xCiAgICAgIC0gJ1BPU1RHUkVTX0JBQ0tFTkRfVVJMPXBvc3RncmVzcWw6Ly9zdXBhYmFzZV9hZG1pbjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9L19zdXBhYmFzZScKICAgICAgLSBQT1NUR1JFU19CQUNLRU5EX1NDSEVNQT1fYW5hbHl0aWNzCiAgICAgIC0gTE9HRkxBUkVfRkVBVFVSRV9GTEFHX09WRVJSSURFPW11bHRpYmFja2VuZD10cnVlCiAgc3VwYWJhc2UtdmVjdG9yOgogICAgaW1hZ2U6ICd0aW1iZXJpby92ZWN0b3I6MC4yOC4xLWFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vc3VwYWJhc2UtdmVjdG9yOjkwMDEvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9sb2dzL3ZlY3Rvci55bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvdmVjdG9yL3ZlY3Rvci55bWwKICAgICAgICByZWFkX29ubHk6IHRydWUKICAgICAgICBjb250ZW50OiAiYXBpOlxuICBlbmFibGVkOiB0cnVlXG4gIGFkZHJlc3M6IDAuMC4wLjA6OTAwMVxuXG5zb3VyY2VzOlxuICBkb2NrZXJfaG9zdDpcbiAgICB0eXBlOiBkb2NrZXJfbG9nc1xuICAgIGV4Y2x1ZGVfY29udGFpbmVyczpcbiAgICAgIC0gc3VwYWJhc2UtdmVjdG9yXG5cbnRyYW5zZm9ybXM6XG4gIHByb2plY3RfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gZG9ja2VyX2hvc3RcbiAgICBzb3VyY2U6IHwtXG4gICAgICAucHJvamVjdCA9IFwiZGVmYXVsdFwiXG4gICAgICAuZXZlbnRfbWVzc2FnZSA9IGRlbCgubWVzc2FnZSlcbiAgICAgIC5hcHBuYW1lID0gZGVsKC5jb250YWluZXJfbmFtZSlcbiAgICAgIGRlbCguY29udGFpbmVyX2NyZWF0ZWRfYXQpXG4gICAgICBkZWwoLmNvbnRhaW5lcl9pZClcbiAgICAgIGRlbCguc291cmNlX3R5cGUpXG4gICAgICBkZWwoLnN0cmVhbSlcbiAgICAgIGRlbCgubGFiZWwpXG4gICAgICBkZWwoLmltYWdlKVxuICAgICAgZGVsKC5ob3N0KVxuICAgICAgZGVsKC5zdHJlYW0pXG4gIHJvdXRlcjpcbiAgICB0eXBlOiByb3V0ZVxuICAgIGlucHV0czpcbiAgICAgIC0gcHJvamVjdF9sb2dzXG4gICAgcm91dGU6XG4gICAgICBrb25nOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2Uta29uZ1wiKSdcbiAgICAgIGF1dGg6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1hdXRoXCIpJ1xuICAgICAgcmVzdDogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInN1cGFiYXNlLXJlc3RcIiknXG4gICAgICByZWFsdGltZTogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInJlYWx0aW1lLWRldlwiKSdcbiAgICAgIHN0b3JhZ2U6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1zdG9yYWdlXCIpJ1xuICAgICAgZnVuY3Rpb25zOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2UtZnVuY3Rpb25zXCIpJ1xuICAgICAgZGI6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1kYlwiKSdcbiAgIyBJZ25vcmVzIG5vbiBuZ2lueCBlcnJvcnMgc2luY2UgdGhleSBhcmUgcmVsYXRlZCB3aXRoIGtvbmcgYm9vdGluZyB1cFxuICBrb25nX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5rb25nXG4gICAgc291cmNlOiB8LVxuICAgICAgcmVxLCBlcnIgPSBwYXJzZV9uZ2lueF9sb2coLmV2ZW50X21lc3NhZ2UsIFwiY29tYmluZWRcIilcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAudGltZXN0YW1wID0gcmVxLnRpbWVzdGFtcFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LmhlYWRlcnMucmVmZXJlciA9IHJlcS5yZWZlcmVyXG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy51c2VyX2FnZW50ID0gcmVxLmFnZW50XG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy5jZl9jb25uZWN0aW5nX2lwID0gcmVxLmNsaWVudFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0Lm1ldGhvZCA9IHJlcS5tZXRob2RcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wYXRoID0gcmVxLnBhdGhcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wcm90b2NvbCA9IHJlcS5wcm90b2NvbFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXNwb25zZS5zdGF0dXNfY29kZSA9IHJlcS5zdGF0dXNcbiAgICAgIH1cbiAgICAgIGlmIGVyciAhPSBudWxsIHtcbiAgICAgICAgYWJvcnRcbiAgICAgIH1cbiAgIyBJZ25vcmVzIG5vbiBuZ2lueCBlcnJvcnMgc2luY2UgdGhleSBhcmUgcmVsYXRlZCB3aXRoIGtvbmcgYm9vdGluZyB1cFxuICBrb25nX2VycjpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmtvbmdcbiAgICBzb3VyY2U6IHwtXG4gICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSBcIkdFVFwiXG4gICAgICAubWV0YWRhdGEucmVzcG9uc2Uuc3RhdHVzX2NvZGUgPSAyMDBcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfbmdpbnhfbG9nKC5ldmVudF9tZXNzYWdlLCBcImVycm9yXCIpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lc3RhbXBcbiAgICAgICAgICAuc2V2ZXJpdHkgPSBwYXJzZWQuc2V2ZXJpdHlcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5ob3N0ID0gcGFyc2VkLmhvc3RcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5oZWFkZXJzLmNmX2Nvbm5lY3RpbmdfaXAgPSBwYXJzZWQuY2xpZW50XG4gICAgICAgICAgdXJsLCBlcnIgPSBzcGxpdChwYXJzZWQucmVxdWVzdCwgXCIgXCIpXG4gICAgICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSB1cmxbMF1cbiAgICAgICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucGF0aCA9IHVybFsxXVxuICAgICAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wcm90b2NvbCA9IHVybFsyXVxuICAgICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIGVyciAhPSBudWxsIHtcbiAgICAgICAgYWJvcnRcbiAgICAgIH1cbiAgIyBHb3RydWUgbG9ncyBhcmUgc3RydWN0dXJlZCBqc29uIHN0cmluZ3Mgd2hpY2ggZnJvbnRlbmQgcGFyc2VzIGRpcmVjdGx5LiBCdXQgd2Uga2VlcCBtZXRhZGF0YSBmb3IgY29uc2lzdGVuY3kuXG4gIGF1dGhfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmF1dGhcbiAgICBzb3VyY2U6IHwtXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX2pzb24oLmV2ZW50X21lc3NhZ2UpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLm1ldGFkYXRhLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lXG4gICAgICAgICAgLm1ldGFkYXRhID0gbWVyZ2UhKC5tZXRhZGF0YSwgcGFyc2VkKVxuICAgICAgfVxuICAjIFBvc3RnUkVTVCBsb2dzIGFyZSBzdHJ1Y3R1cmVkIHNvIHdlIHNlcGFyYXRlIHRpbWVzdGFtcCBmcm9tIG1lc3NhZ2UgdXNpbmcgcmVnZXhcbiAgcmVzdF9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIucmVzdFxuICAgIHNvdXJjZTogfC1cbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInXig/UDx0aW1lPi4qKTogKD9QPG1zZz4uKikkJylcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAuZXZlbnRfbWVzc2FnZSA9IHBhcnNlZC5tc2dcbiAgICAgICAgICAudGltZXN0YW1wID0gdG9fdGltZXN0YW1wIShwYXJzZWQudGltZSlcbiAgICAgICAgICAubWV0YWRhdGEuaG9zdCA9IC5wcm9qZWN0XG4gICAgICB9XG4gICMgUmVhbHRpbWUgbG9ncyBhcmUgc3RydWN0dXJlZCBzbyB3ZSBwYXJzZSB0aGUgc2V2ZXJpdHkgbGV2ZWwgdXNpbmcgcmVnZXggKGlnbm9yZSB0aW1lIGJlY2F1c2UgaXQgaGFzIG5vIGRhdGUpXG4gIHJlYWx0aW1lX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5yZWFsdGltZVxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5wcm9qZWN0ID0gZGVsKC5wcm9qZWN0KVxuICAgICAgLm1ldGFkYXRhLmV4dGVybmFsX2lkID0gLm1ldGFkYXRhLnByb2plY3RcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInXig/UDx0aW1lPlxcZCs6XFxkKzpcXGQrXFwuXFxkKykgXFxbKD9QPGxldmVsPlxcdyspXFxdICg/UDxtc2c+LiopJCcpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLmV2ZW50X21lc3NhZ2UgPSBwYXJzZWQubXNnXG4gICAgICAgICAgLm1ldGFkYXRhLmxldmVsID0gcGFyc2VkLmxldmVsXG4gICAgICB9XG4gICMgU3RvcmFnZSBsb2dzIG1heSBjb250YWluIGpzb24gb2JqZWN0cyBzbyB3ZSBwYXJzZSB0aGVtIGZvciBjb21wbGV0ZW5lc3NcbiAgc3RvcmFnZV9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIuc3RvcmFnZVxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5wcm9qZWN0ID0gZGVsKC5wcm9qZWN0KVxuICAgICAgLm1ldGFkYXRhLnRlbmFudElkID0gLm1ldGFkYXRhLnByb2plY3RcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfanNvbiguZXZlbnRfbWVzc2FnZSlcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAuZXZlbnRfbWVzc2FnZSA9IHBhcnNlZC5tc2dcbiAgICAgICAgICAubWV0YWRhdGEubGV2ZWwgPSBwYXJzZWQubGV2ZWxcbiAgICAgICAgICAubWV0YWRhdGEudGltZXN0YW1wID0gcGFyc2VkLnRpbWVcbiAgICAgICAgICAubWV0YWRhdGEuY29udGV4dFswXS5ob3N0ID0gcGFyc2VkLmhvc3RuYW1lXG4gICAgICAgICAgLm1ldGFkYXRhLmNvbnRleHRbMF0ucGlkID0gcGFyc2VkLnBpZFxuICAgICAgfVxuICAjIFBvc3RncmVzIGxvZ3Mgc29tZSBtZXNzYWdlcyB0byBzdGRlcnIgd2hpY2ggd2UgbWFwIHRvIHdhcm5pbmcgc2V2ZXJpdHkgbGV2ZWxcbiAgZGJfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmRiXG4gICAgc291cmNlOiB8LVxuICAgICAgLm1ldGFkYXRhLmhvc3QgPSBcImRiLWRlZmF1bHRcIlxuICAgICAgLm1ldGFkYXRhLnBhcnNlZC50aW1lc3RhbXAgPSAudGltZXN0YW1wXG5cbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInLiooP1A8bGV2ZWw+SU5GT3xOT1RJQ0V8V0FSTklOR3xFUlJPUnxMT0d8RkFUQUx8UEFOSUM/KTouKicsIG51bWVyaWNfZ3JvdXBzOiB0cnVlKVxuXG4gICAgICBpZiBlcnIgIT0gbnVsbCB8fCBwYXJzZWQgPT0gbnVsbCB7XG4gICAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSBcImluZm9cIlxuICAgICAgfVxuICAgICAgaWYgcGFyc2VkICE9IG51bGwge1xuICAgICAgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9IHBhcnNlZC5sZXZlbFxuICAgICAgfVxuICAgICAgaWYgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9PSBcImluZm9cIiB7XG4gICAgICAgICAgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9IFwibG9nXCJcbiAgICAgIH1cbiAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSB1cGNhc2UhKC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkpXG5cbnNpbmtzOlxuICBsb2dmbGFyZV9hdXRoOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gYXV0aF9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWdvdHJ1ZS5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3JlYWx0aW1lOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gcmVhbHRpbWVfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1yZWFsdGltZS5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3Jlc3Q6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSByZXN0X2xvZ3NcbiAgICBlbmNvZGluZzpcbiAgICAgIGNvZGVjOiAnanNvbidcbiAgICBtZXRob2Q6ICdwb3N0J1xuICAgIHJlcXVlc3Q6XG4gICAgICByZXRyeV9tYXhfZHVyYXRpb25fc2VjczogMTBcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvYXBpL2xvZ3M/c291cmNlX25hbWU9cG9zdGdSRVNULmxvZ3MucHJvZCZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfZGI6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSBkYl9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgIyBXZSBtdXN0IHJvdXRlIHRoZSBzaW5rIHRocm91Z2gga29uZyBiZWNhdXNlIGluZ2VzdGluZyBsb2dzIGJlZm9yZSBsb2dmbGFyZSBpcyBmdWxseSBpbml0aWFsaXNlZCB3aWxsXG4gICAgIyBsZWFkIHRvIGJyb2tlbiBxdWVyaWVzIGZyb20gc3R1ZGlvLiBUaGlzIHdvcmtzIGJ5IHRoZSBhc3N1bXB0aW9uIHRoYXQgY29udGFpbmVycyBhcmUgc3RhcnRlZCBpbiB0aGVcbiAgICAjIGZvbGxvd2luZyBvcmRlcjogdmVjdG9yID4gZGIgPiBsb2dmbGFyZSA+IGtvbmdcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2Uta29uZzo4MDAwL2FuYWx5dGljcy92MS9hcGkvbG9ncz9zb3VyY2VfbmFtZT1wb3N0Z3Jlcy5sb2dzJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9mdW5jdGlvbnM6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIuZnVuY3Rpb25zXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWRlbm8tcmVsYXktbG9ncyZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfc3RvcmFnZTpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIHN0b3JhZ2VfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1zdG9yYWdlLmxvZ3MucHJvZC4yJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9rb25nOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0ga29uZ19sb2dzXG4gICAgICAtIGtvbmdfZXJyXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWNsb3VkZmxhcmUubG9ncy5wcm9kJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuIgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jazpybycKICAgIGVudmlyb25tZW50OgogICAgICAtICdMT0dGTEFSRV9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9MT0dGTEFSRX0nCiAgICBjb21tYW5kOgogICAgICAtICctLWNvbmZpZycKICAgICAgLSBldGMvdmVjdG9yL3ZlY3Rvci55bWwKICBzdXBhYmFzZS1yZXN0OgogICAgaW1hZ2U6ICdwb3N0Z3Jlc3QvcG9zdGdyZXN0OnYxMi4yLjEyJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUEdSU1RfREJfVVJJPXBvc3RncmVzOi8vYXV0aGVudGljYXRvcjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUEdSU1RfREJfU0NIRU1BUz0ke1BHUlNUX0RCX1NDSEVNQVM6LXB1YmxpYyxzdG9yYWdlLGdyYXBocWxfcHVibGljfScKICAgICAgLSBQR1JTVF9EQl9BTk9OX1JPTEU9YW5vbgogICAgICAtICdQR1JTVF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIFBHUlNUX0RCX1VTRV9MRUdBQ1lfR1VDUz1mYWxzZQogICAgICAtICdQR1JTVF9BUFBfU0VUVElOR1NfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnUEdSU1RfQVBQX1NFVFRJTkdTX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgIGNvbW1hbmQ6IHBvc3RncmVzdAogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgc3VwYWJhc2UtYXV0aDoKICAgIGltYWdlOiAnc3VwYWJhc2UvZ290cnVlOnYyLjE3NC4wJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo5OTk5L2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPVFJVRV9BUElfSE9TVD0wLjAuMC4wCiAgICAgIC0gR09UUlVFX0FQSV9QT1JUPTk5OTkKICAgICAgLSAnQVBJX0VYVEVSTkFMX1VSTD0ke0FQSV9FWFRFUk5BTF9VUkw6LWh0dHA6Ly9zdXBhYmFzZS1rb25nOjgwMDB9JwogICAgICAtIEdPVFJVRV9EQl9EUklWRVI9cG9zdGdyZXMKICAgICAgLSAnR09UUlVFX0RCX0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovL3N1cGFiYXNlX2F1dGhfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0dPVFJVRV9TSVRFX1VSTD0ke1NFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkd9JwogICAgICAtICdHT1RSVUVfVVJJX0FMTE9XX0xJU1Q9JHtBRERJVElPTkFMX1JFRElSRUNUX1VSTFN9JwogICAgICAtICdHT1RSVUVfRElTQUJMRV9TSUdOVVA9JHtESVNBQkxFX1NJR05VUDotZmFsc2V9JwogICAgICAtIEdPVFJVRV9KV1RfQURNSU5fUk9MRVM9c2VydmljZV9yb2xlCiAgICAgIC0gR09UUlVFX0pXVF9BVUQ9YXV0aGVudGljYXRlZAogICAgICAtIEdPVFJVRV9KV1RfREVGQVVMVF9HUk9VUF9OQU1FPWF1dGhlbnRpY2F0ZWQKICAgICAgLSAnR09UUlVFX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgICAgLSAnR09UUlVFX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0dPVFJVRV9FWFRFUk5BTF9FTUFJTF9FTkFCTEVEPSR7RU5BQkxFX0VNQUlMX1NJR05VUDotdHJ1ZX0nCiAgICAgIC0gJ0dPVFJVRV9FWFRFUk5BTF9BTk9OWU1PVVNfVVNFUlNfRU5BQkxFRD0ke0VOQUJMRV9BTk9OWU1PVVNfVVNFUlM6LWZhbHNlfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9BVVRPQ09ORklSTT0ke0VOQUJMRV9FTUFJTF9BVVRPQ09ORklSTTotZmFsc2V9JwogICAgICAtICdHT1RSVUVfU01UUF9BRE1JTl9FTUFJTD0ke1NNVFBfQURNSU5fRU1BSUx9JwogICAgICAtICdHT1RSVUVfU01UUF9IT1NUPSR7U01UUF9IT1NUfScKICAgICAgLSAnR09UUlVFX1NNVFBfUE9SVD0ke1NNVFBfUE9SVDotNTg3fScKICAgICAgLSAnR09UUlVFX1NNVFBfVVNFUj0ke1NNVFBfVVNFUn0nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX1BBU1M9JHtTTVRQX1BBU1N9JwogICAgICAtICdHT1RSVUVfU01UUF9TRU5ERVJfTkFNRT0ke1NNVFBfU0VOREVSX05BTUV9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1VSTFBBVEhTX0lOVklURT0ke01BSUxFUl9VUkxQQVRIU19JTlZJVEU6LS9hdXRoL3YxL3ZlcmlmeX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVVJMUEFUSFNfQ09ORklSTUFUSU9OPSR7TUFJTEVSX1VSTFBBVEhTX0NPTkZJUk1BVElPTjotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19SRUNPVkVSWT0ke01BSUxFUl9VUkxQQVRIU19SRUNPVkVSWTotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19FTUFJTF9DSEFOR0U9JHtNQUlMRVJfVVJMUEFUSFNfRU1BSUxfQ0hBTkdFOi0vYXV0aC92MS92ZXJpZnl9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1RFTVBMQVRFU19JTlZJVEU9JHtNQUlMRVJfVEVNUExBVEVTX0lOVklURX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0NPTkZJUk1BVElPTj0ke01BSUxFUl9URU1QTEFURVNfQ09ORklSTUFUSU9OfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfUkVDT1ZFUlk9JHtNQUlMRVJfVEVNUExBVEVTX1JFQ09WRVJZfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfTUFHSUNfTElOSz0ke01BSUxFUl9URU1QTEFURVNfTUFHSUNfTElOS30nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0VNQUlMX0NIQU5HRT0ke01BSUxFUl9URU1QTEFURVNfRU1BSUxfQ0hBTkdFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19DT05GSVJNQVRJT049JHtNQUlMRVJfU1VCSkVDVFNfQ09ORklSTUFUSU9OfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19SRUNPVkVSWT0ke01BSUxFUl9TVUJKRUNUU19SRUNPVkVSWX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfU1VCSkVDVFNfTUFHSUNfTElOSz0ke01BSUxFUl9TVUJKRUNUU19NQUdJQ19MSU5LfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19FTUFJTF9DSEFOR0U9JHtNQUlMRVJfU1VCSkVDVFNfRU1BSUxfQ0hBTkdFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19JTlZJVEU9JHtNQUlMRVJfU1VCSkVDVFNfSU5WSVRFfScKICAgICAgLSAnR09UUlVFX0VYVEVSTkFMX1BIT05FX0VOQUJMRUQ9JHtFTkFCTEVfUEhPTkVfU0lHTlVQOi10cnVlfScKICAgICAgLSAnR09UUlVFX1NNU19BVVRPQ09ORklSTT0ke0VOQUJMRV9QSE9ORV9BVVRPQ09ORklSTTotdHJ1ZX0nCiAgcmVhbHRpbWUtZGV2OgogICAgaW1hZ2U6ICdzdXBhYmFzZS9yZWFsdGltZTp2Mi4zNC40NycKICAgIGNvbnRhaW5lcl9uYW1lOiByZWFsdGltZS1kZXYuc3VwYWJhc2UtcmVhbHRpbWUKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctc1NmTCcKICAgICAgICAtICctLWhlYWQnCiAgICAgICAgLSAnLW8nCiAgICAgICAgLSAvZGV2L251bGwKICAgICAgICAtICctSCcKICAgICAgICAtICdBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtTRVJWSUNFX1NVUEFCQVNFQU5PTl9LRVl9JwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NDAwMC9hcGkvdGVuYW50cy9yZWFsdGltZS1kZXYvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9SVD00MDAwCiAgICAgIC0gJ0RCX0hPU1Q9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdEQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gREJfVVNFUj1zdXBhYmFzZV9hZG1pbgogICAgICAtICdEQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdEQl9OQU1FPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnREJfQUZURVJfQ09OTkVDVF9RVUVSWT1TRVQgc2VhcmNoX3BhdGggVE8gX3JlYWx0aW1lJwogICAgICAtIERCX0VOQ19LRVk9c3VwYWJhc2VyZWFsdGltZQogICAgICAtICdBUElfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSBGTFlfQUxMT0NfSUQ9Zmx5MTIzCiAgICAgIC0gRkxZX0FQUF9OQU1FPXJlYWx0aW1lCiAgICAgIC0gJ1NFQ1JFVF9LRVlfQkFTRT0ke1NFQ1JFVF9QQVNTV09SRF9SRUFMVElNRX0nCiAgICAgIC0gJ0VSTF9BRkxBR1M9LXByb3RvX2Rpc3QgaW5ldF90Y3AnCiAgICAgIC0gRU5BQkxFX1RBSUxTQ0FMRT1mYWxzZQogICAgICAtICJETlNfTk9ERVM9JyciCiAgICAgIC0gUkxJTUlUX05PRklMRT0xMDAwMAogICAgICAtIEFQUF9OQU1FPXJlYWx0aW1lCiAgICAgIC0gU0VFRF9TRUxGX0hPU1Q9dHJ1ZQogICAgICAtIExPR19MRVZFTD1lcnJvcgogICAgICAtIFJVTl9KQU5JVE9SPXRydWUKICAgICAgLSBKQU5JVE9SX0lOVEVSVkFMPTYwMDAwCiAgICBjb21tYW5kOiAic2ggLWMgXCIvYXBwL2Jpbi9taWdyYXRlICYmIC9hcHAvYmluL3JlYWx0aW1lIGV2YWwgJ1JlYWx0aW1lLlJlbGVhc2Uuc2VlZHMoUmVhbHRpbWUuUmVwbyknICYmIC9hcHAvYmluL3NlcnZlclwiXG4iCiAgc3VwYWJhc2UtbWluaW86CiAgICBpbWFnZTogJ2doY3IuaW8vY29vbGxhYnNpby9taW5pbzpSRUxFQVNFLjIwMjUtMTAtMTVUMTctMjktNTVaJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01JTklPX1JPT1RfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NSU5JT30nCiAgICAgIC0gJ01JTklPX1JPT1RfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01JTklPfScKICAgIGNvbW1hbmQ6ICdzZXJ2ZXIgLS1jb25zb2xlLWFkZHJlc3MgIjo5MDAxIiAvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBtYwogICAgICAgIC0gcmVhZHkKICAgICAgICAtIGxvY2FsCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vdm9sdW1lcy9zdG9yYWdlOi9kYXRhJwogIG1pbmlvLWNyZWF0ZWJ1Y2tldDoKICAgIGltYWdlOiBtaW5pby9tYwogICAgcmVzdGFydDogJ25vJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01JTklPX1JPT1RfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NSU5JT30nCiAgICAgIC0gJ01JTklPX1JPT1RfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01JTklPfScKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLW1pbmlvOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnRyeXBvaW50OgogICAgICAtIC9lbnRyeXBvaW50LnNoCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9lbnRyeXBvaW50LnNoCiAgICAgICAgdGFyZ2V0OiAvZW50cnlwb2ludC5zaAogICAgICAgIGNvbnRlbnQ6ICIjIS9iaW4vc2hcbi91c3IvYmluL21jIGFsaWFzIHNldCBzdXBhYmFzZS1taW5pbyBodHRwOi8vc3VwYWJhc2UtbWluaW86OTAwMCAke01JTklPX1JPT1RfVVNFUn0gJHtNSU5JT19ST09UX1BBU1NXT1JEfTtcbi91c3IvYmluL21jIG1iIC0taWdub3JlLWV4aXN0aW5nIHN1cGFiYXNlLW1pbmlvL3N0dWI7XG5leGl0IDBcbiIKICBzdXBhYmFzZS1zdG9yYWdlOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9zdG9yYWdlLWFwaTp2MS4xNC42JwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtcmVzdDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2Vfc3RhcnRlZAogICAgICBpbWdwcm94eToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2Vfc3RhcnRlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHdnZXQKICAgICAgICAtICctLW5vLXZlcmJvc2UnCiAgICAgICAgLSAnLS10cmllcz0xJwogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NTAwMC9zdGF0dXMnCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWRVJfUE9SVD01MDAwCiAgICAgIC0gU0VSVkVSX1JFR0lPTj1sb2NhbAogICAgICAtIE1VTFRJX1RFTkFOVD1mYWxzZQogICAgICAtICdBVVRIX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovL3N1cGFiYXNlX3N0b3JhZ2VfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gREJfSU5TVEFMTF9ST0xFUz1mYWxzZQogICAgICAtIFNUT1JBR0VfQkFDS0VORD1zMwogICAgICAtIFNUT1JBR0VfUzNfQlVDS0VUPXN0dWIKICAgICAgLSAnU1RPUkFHRV9TM19FTkRQT0lOVD1odHRwOi8vc3VwYWJhc2UtbWluaW86OTAwMCcKICAgICAgLSBTVE9SQUdFX1MzX0ZPUkNFX1BBVEhfU1RZTEU9dHJ1ZQogICAgICAtIFNUT1JBR0VfUzNfUkVHSU9OPXVzLWVhc3QtMQogICAgICAtICdBV1NfQUNDRVNTX0tFWV9JRD0ke1NFUlZJQ0VfVVNFUl9NSU5JT30nCiAgICAgIC0gJ0FXU19TRUNSRVRfQUNDRVNTX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgICAtIFVQTE9BRF9GSUxFX1NJWkVfTElNSVQ9NTI0Mjg4MDAwCiAgICAgIC0gVVBMT0FEX0ZJTEVfU0laRV9MSU1JVF9TVEFOREFSRD01MjQyODgwMDAKICAgICAgLSBVUExPQURfU0lHTkVEX1VSTF9FWFBJUkFUSU9OX1RJTUU9MTIwCiAgICAgIC0gVFVTX1VSTF9QQVRIPXVwbG9hZC9yZXN1bWFibGUKICAgICAgLSBUVVNfTUFYX1NJWkU9MzYwMDAwMAogICAgICAtIEVOQUJMRV9JTUFHRV9UUkFOU0ZPUk1BVElPTj10cnVlCiAgICAgIC0gJ0lNR1BST1hZX1VSTD1odHRwOi8vaW1ncHJveHk6ODA4MCcKICAgICAgLSBJTUdQUk9YWV9SRVFVRVNUX1RJTUVPVVQ9MTUKICAgICAgLSBEQVRBQkFTRV9TRUFSQ0hfUEFUSD1zdG9yYWdlCiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvbgogICAgICAtIFJFUVVFU1RfQUxMT1dfWF9GT1JXQVJERURfUEFUSD10cnVlCiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvc3RvcmFnZTovdmFyL2xpYi9zdG9yYWdlJwogIGltZ3Byb3h5OgogICAgaW1hZ2U6ICdkYXJ0aHNpbS9pbWdwcm94eTp2My44LjAnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gaW1ncHJveHkKICAgICAgICAtIGhlYWx0aAogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSU1HUFJPWFlfTE9DQUxfRklMRVNZU1RFTV9ST09UPS8KICAgICAgLSBJTUdQUk9YWV9VU0VfRVRBRz10cnVlCiAgICAgIC0gJ0lNR1BST1hZX0VOQUJMRV9XRUJQX0RFVEVDVElPTj0ke0lNR1BST1hZX0VOQUJMRV9XRUJQX0RFVEVDVElPTjotdHJ1ZX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvc3RvcmFnZTovdmFyL2xpYi9zdG9yYWdlJwogIHN1cGFiYXNlLW1ldGE6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3Bvc3RncmVzLW1ldGE6djAuODkuMycKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEdfTUVUQV9QT1JUPTgwODAKICAgICAgLSAnUEdfTUVUQV9EQl9IT1NUPSR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifScKICAgICAgLSAnUEdfTUVUQV9EQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ1BHX01FVEFfREJfTkFNRT0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gUEdfTUVUQV9EQl9VU0VSPXN1cGFiYXNlX2FkbWluCiAgICAgIC0gJ1BHX01FVEFfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICBzdXBhYmFzZS1lZGdlLWZ1bmN0aW9uczoKICAgIGltYWdlOiAnc3VwYWJhc2UvZWRnZS1ydW50aW1lOnYxLjY3LjQnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSAnRWRnZSBGdW5jdGlvbnMgaXMgaGVhbHRoeScKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdTVVBBQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fU1VQQUJBU0VLT05HfScKICAgICAgLSAnU1VQQUJBU0VfQU5PTl9LRVk9JHtTRVJWSUNFX1NVUEFCQVNFQU5PTl9LRVl9JwogICAgICAtICdTVVBBQkFTRV9TRVJWSUNFX1JPTEVfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnU1VQQUJBU0VfREJfVVJMPXBvc3RncmVzcWw6Ly9wb3N0Z3Jlczoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnVkVSSUZZX0pXVD0ke0ZVTkNUSU9OU19WRVJJRllfSldUOi1mYWxzZX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvZnVuY3Rpb25zOi9ob21lL2Rlbm8vZnVuY3Rpb25zJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2Z1bmN0aW9ucy9tYWluL2luZGV4LnRzCiAgICAgICAgdGFyZ2V0OiAvaG9tZS9kZW5vL2Z1bmN0aW9ucy9tYWluL2luZGV4LnRzCiAgICAgICAgY29udGVudDogImltcG9ydCB7IHNlcnZlIH0gZnJvbSAnaHR0cHM6Ly9kZW5vLmxhbmQvc3RkQDAuMTMxLjAvaHR0cC9zZXJ2ZXIudHMnXG5pbXBvcnQgKiBhcyBqb3NlIGZyb20gJ2h0dHBzOi8vZGVuby5sYW5kL3gvam9zZUB2NC4xNC40L2luZGV4LnRzJ1xuXG5jb25zb2xlLmxvZygnbWFpbiBmdW5jdGlvbiBzdGFydGVkJylcblxuY29uc3QgSldUX1NFQ1JFVCA9IERlbm8uZW52LmdldCgnSldUX1NFQ1JFVCcpXG5jb25zdCBWRVJJRllfSldUID0gRGVuby5lbnYuZ2V0KCdWRVJJRllfSldUJykgPT09ICd0cnVlJ1xuXG5mdW5jdGlvbiBnZXRBdXRoVG9rZW4ocmVxOiBSZXF1ZXN0KSB7XG4gIGNvbnN0IGF1dGhIZWFkZXIgPSByZXEuaGVhZGVycy5nZXQoJ2F1dGhvcml6YXRpb24nKVxuICBpZiAoIWF1dGhIZWFkZXIpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ01pc3NpbmcgYXV0aG9yaXphdGlvbiBoZWFkZXInKVxuICB9XG4gIGNvbnN0IFtiZWFyZXIsIHRva2VuXSA9IGF1dGhIZWFkZXIuc3BsaXQoJyAnKVxuICBpZiAoYmVhcmVyICE9PSAnQmVhcmVyJykge1xuICAgIHRocm93IG5ldyBFcnJvcihgQXV0aCBoZWFkZXIgaXMgbm90ICdCZWFyZXIge3Rva2VufSdgKVxuICB9XG4gIHJldHVybiB0b2tlblxufVxuXG5hc3luYyBmdW5jdGlvbiB2ZXJpZnlKV1Qoand0OiBzdHJpbmcpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgY29uc3QgZW5jb2RlciA9IG5ldyBUZXh0RW5jb2RlcigpXG4gIGNvbnN0IHNlY3JldEtleSA9IGVuY29kZXIuZW5jb2RlKEpXVF9TRUNSRVQpXG4gIHRyeSB7XG4gICAgYXdhaXQgam9zZS5qd3RWZXJpZnkoand0LCBzZWNyZXRLZXkpXG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGNvbnNvbGUuZXJyb3IoZXJyKVxuICAgIHJldHVybiBmYWxzZVxuICB9XG4gIHJldHVybiB0cnVlXG59XG5cbnNlcnZlKGFzeW5jIChyZXE6IFJlcXVlc3QpID0+IHtcbiAgaWYgKHJlcS5tZXRob2QgIT09ICdPUFRJT05TJyAmJiBWRVJJRllfSldUKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHRva2VuID0gZ2V0QXV0aFRva2VuKHJlcSlcbiAgICAgIGNvbnN0IGlzVmFsaWRKV1QgPSBhd2FpdCB2ZXJpZnlKV1QodG9rZW4pXG5cbiAgICAgIGlmICghaXNWYWxpZEpXVCkge1xuICAgICAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KHsgbXNnOiAnSW52YWxpZCBKV1QnIH0pLCB7XG4gICAgICAgICAgc3RhdHVzOiA0MDEsXG4gICAgICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG4gICAgICAgIH0pXG4gICAgICB9XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgY29uc29sZS5lcnJvcihlKVxuICAgICAgcmV0dXJuIG5ldyBSZXNwb25zZShKU09OLnN0cmluZ2lmeSh7IG1zZzogZS50b1N0cmluZygpIH0pLCB7XG4gICAgICAgIHN0YXR1czogNDAxLFxuICAgICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICAgIH0pXG4gICAgfVxuICB9XG5cbiAgY29uc3QgdXJsID0gbmV3IFVSTChyZXEudXJsKVxuICBjb25zdCB7IHBhdGhuYW1lIH0gPSB1cmxcbiAgY29uc3QgcGF0aF9wYXJ0cyA9IHBhdGhuYW1lLnNwbGl0KCcvJylcbiAgY29uc3Qgc2VydmljZV9uYW1lID0gcGF0aF9wYXJ0c1sxXVxuXG4gIGlmICghc2VydmljZV9uYW1lIHx8IHNlcnZpY2VfbmFtZSA9PT0gJycpIHtcbiAgICBjb25zdCBlcnJvciA9IHsgbXNnOiAnbWlzc2luZyBmdW5jdGlvbiBuYW1lIGluIHJlcXVlc3QnIH1cbiAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KGVycm9yKSwge1xuICAgICAgc3RhdHVzOiA0MDAsXG4gICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICB9KVxuICB9XG5cbiAgY29uc3Qgc2VydmljZVBhdGggPSBgL2hvbWUvZGVuby9mdW5jdGlvbnMvJHtzZXJ2aWNlX25hbWV9YFxuICBjb25zb2xlLmVycm9yKGBzZXJ2aW5nIHRoZSByZXF1ZXN0IHdpdGggJHtzZXJ2aWNlUGF0aH1gKVxuXG4gIGNvbnN0IG1lbW9yeUxpbWl0TWIgPSAxNTBcbiAgY29uc3Qgd29ya2VyVGltZW91dE1zID0gMSAqIDYwICogMTAwMFxuICBjb25zdCBub01vZHVsZUNhY2hlID0gZmFsc2VcbiAgY29uc3QgaW1wb3J0TWFwUGF0aCA9IG51bGxcbiAgY29uc3QgZW52VmFyc09iaiA9IERlbm8uZW52LnRvT2JqZWN0KClcbiAgY29uc3QgZW52VmFycyA9IE9iamVjdC5rZXlzKGVudlZhcnNPYmopLm1hcCgoaykgPT4gW2ssIGVudlZhcnNPYmpba11dKVxuXG4gIHRyeSB7XG4gICAgY29uc3Qgd29ya2VyID0gYXdhaXQgRWRnZVJ1bnRpbWUudXNlcldvcmtlcnMuY3JlYXRlKHtcbiAgICAgIHNlcnZpY2VQYXRoLFxuICAgICAgbWVtb3J5TGltaXRNYixcbiAgICAgIHdvcmtlclRpbWVvdXRNcyxcbiAgICAgIG5vTW9kdWxlQ2FjaGUsXG4gICAgICBpbXBvcnRNYXBQYXRoLFxuICAgICAgZW52VmFycyxcbiAgICB9KVxuICAgIHJldHVybiBhd2FpdCB3b3JrZXIuZmV0Y2gocmVxKVxuICB9IGNhdGNoIChlKSB7XG4gICAgY29uc3QgZXJyb3IgPSB7IG1zZzogZS50b1N0cmluZygpIH1cbiAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KGVycm9yKSwge1xuICAgICAgc3RhdHVzOiA1MDAsXG4gICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICB9KVxuICB9XG59KSIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9mdW5jdGlvbnMvaGVsbG8vaW5kZXgudHMKICAgICAgICB0YXJnZXQ6IC9ob21lL2Rlbm8vZnVuY3Rpb25zL2hlbGxvL2luZGV4LnRzCiAgICAgICAgY29udGVudDogIi8vIEZvbGxvdyB0aGlzIHNldHVwIGd1aWRlIHRvIGludGVncmF0ZSB0aGUgRGVubyBsYW5ndWFnZSBzZXJ2ZXIgd2l0aCB5b3VyIGVkaXRvcjpcbi8vIGh0dHBzOi8vZGVuby5sYW5kL21hbnVhbC9nZXR0aW5nX3N0YXJ0ZWQvc2V0dXBfeW91cl9lbnZpcm9ubWVudFxuLy8gVGhpcyBlbmFibGVzIGF1dG9jb21wbGV0ZSwgZ28gdG8gZGVmaW5pdGlvbiwgZXRjLlxuXG5pbXBvcnQgeyBzZXJ2ZSB9IGZyb20gXCJodHRwczovL2Rlbm8ubGFuZC9zdGRAMC4xNzcuMS9odHRwL3NlcnZlci50c1wiXG5cbnNlcnZlKGFzeW5jICgpID0+IHtcbiAgcmV0dXJuIG5ldyBSZXNwb25zZShcbiAgICBgXCJIZWxsbyBmcm9tIEVkZ2UgRnVuY3Rpb25zIVwiYCxcbiAgICB7IGhlYWRlcnM6IHsgXCJDb250ZW50LVR5cGVcIjogXCJhcHBsaWNhdGlvbi9qc29uXCIgfSB9LFxuICApXG59KVxuXG4vLyBUbyBpbnZva2U6XG4vLyBjdXJsICdodHRwOi8vbG9jYWxob3N0OjxLT05HX0hUVFBfUE9SVD4vZnVuY3Rpb25zL3YxL2hlbGxvJyBcXFxuLy8gICAtLWhlYWRlciAnQXV0aG9yaXphdGlvbjogQmVhcmVyIDxhbm9uL3NlcnZpY2Vfcm9sZSBBUEkga2V5PidcbiIKICAgIGNvbW1hbmQ6CiAgICAgIC0gc3RhcnQKICAgICAgLSAnLS1tYWluLXNlcnZpY2UnCiAgICAgIC0gL2hvbWUvZGVuby9mdW5jdGlvbnMvbWFpbgogIHN1cGFiYXNlLXN1cGF2aXNvcjoKICAgIGltYWdlOiAnc3VwYWJhc2Uvc3VwYXZpc29yOjIuNS4xJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctc1NmTCcKICAgICAgICAtICctbycKICAgICAgICAtIC9kZXYvbnVsbAogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NDAwMC9hcGkvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9PTEVSX1RFTkFOVF9JRD1kZXZfdGVuYW50CiAgICAgIC0gUE9PTEVSX1BPT0xfTU9ERT10cmFuc2FjdGlvbgogICAgICAtICdQT09MRVJfREVGQVVMVF9QT09MX1NJWkU9JHtQT09MRVJfREVGQVVMVF9QT09MX1NJWkU6LTIwfScKICAgICAgLSAnUE9PTEVSX01BWF9DTElFTlRfQ09OTj0ke1BPT0xFUl9NQVhfQ0xJRU5UX0NPTk46LTEwMH0nCiAgICAgIC0gUE9SVD00MDAwCiAgICAgIC0gJ1BPU1RHUkVTX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUE9TVEdSRVNfSE9TVE5BTUU9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1lY3RvOi8vc3VwYWJhc2VfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS9fc3VwYWJhc2UnCiAgICAgIC0gQ0xVU1RFUl9QT1NUR1JFUz10cnVlCiAgICAgIC0gJ1NFQ1JFVF9LRVlfQkFTRT0ke1NFUlZJQ0VfUEFTU1dPUkRfU1VQQVZJU09SU0VDUkVUfScKICAgICAgLSAnVkFVTFRfRU5DX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfVkFVTFRFTkN9JwogICAgICAtICdBUElfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnTUVUUklDU19KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIFJFR0lPTj1sb2NhbAogICAgICAtICdFUkxfQUZMQUdTPS1wcm90b19kaXN0IGluZXRfdGNwJwogICAgY29tbWFuZDoKICAgICAgLSAvYmluL3NoCiAgICAgIC0gJy1jJwogICAgICAtICcvYXBwL2Jpbi9taWdyYXRlICYmIC9hcHAvYmluL3N1cGF2aXNvciBldmFsICIkJChjYXQgL2V0Yy9wb29sZXIvcG9vbGVyLmV4cykiICYmIC9hcHAvYmluL3NlcnZlcicKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvcG9vbGVyL3Bvb2xlci5leHMKICAgICAgICB0YXJnZXQ6IC9ldGMvcG9vbGVyL3Bvb2xlci5leHMKICAgICAgICBjb250ZW50OiAiezpvaywgX30gPSBBcHBsaWNhdGlvbi5lbnN1cmVfYWxsX3N0YXJ0ZWQoOnN1cGF2aXNvcilcbns6b2ssIHZlcnNpb259ID1cbiAgICBjYXNlIFN1cGF2aXNvci5SZXBvLnF1ZXJ5IShcInNlbGVjdCB2ZXJzaW9uKClcIikgZG9cbiAgICAle3Jvd3M6IFtbdmVyXV19IC0+IFN1cGF2aXNvci5IZWxwZXJzLnBhcnNlX3BnX3ZlcnNpb24odmVyKVxuICAgIF8gLT4gbmlsXG4gICAgZW5kXG5wYXJhbXMgPSAle1xuICAgIFwiZXh0ZXJuYWxfaWRcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPT0xFUl9URU5BTlRfSURcIiksXG4gICAgXCJkYl9ob3N0XCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19IT1NUTkFNRVwiKSxcbiAgICBcImRiX3BvcnRcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPU1RHUkVTX1BPUlRcIikgfD4gU3RyaW5nLnRvX2ludGVnZXIoKSxcbiAgICBcImRiX2RhdGFiYXNlXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19EQlwiKSxcbiAgICBcInJlcXVpcmVfdXNlclwiID0+IGZhbHNlLFxuICAgIFwiYXV0aF9xdWVyeVwiID0+IFwiU0VMRUNUICogRlJPTSBwZ2JvdW5jZXIuZ2V0X2F1dGgoJDEpXCIsXG4gICAgXCJkZWZhdWx0X21heF9jbGllbnRzXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfTUFYX0NMSUVOVF9DT05OXCIpLFxuICAgIFwiZGVmYXVsdF9wb29sX3NpemVcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPT0xFUl9ERUZBVUxUX1BPT0xfU0laRVwiKSxcbiAgICBcImRlZmF1bHRfcGFyYW1ldGVyX3N0YXR1c1wiID0+ICV7XCJzZXJ2ZXJfdmVyc2lvblwiID0+IHZlcnNpb259LFxuICAgIFwidXNlcnNcIiA9PiBbJXtcbiAgICBcImRiX3VzZXJcIiA9PiBcInBnYm91bmNlclwiLFxuICAgIFwiZGJfcGFzc3dvcmRcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPU1RHUkVTX1BBU1NXT1JEXCIpLFxuICAgIFwibW9kZV90eXBlXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfUE9PTF9NT0RFXCIpLFxuICAgIFwicG9vbF9zaXplXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfREVGQVVMVF9QT09MX1NJWkVcIiksXG4gICAgXCJpc19tYW5hZ2VyXCIgPT4gdHJ1ZVxuICAgIH1dXG59XG5cbnRlbmFudCA9IFN1cGF2aXNvci5UZW5hbnRzLmdldF90ZW5hbnRfYnlfZXh0ZXJuYWxfaWQocGFyYW1zW1wiZXh0ZXJuYWxfaWRcIl0pXG5cbmlmIHRlbmFudCBkb1xuICB7Om9rLCBffSA9IFN1cGF2aXNvci5UZW5hbnRzLnVwZGF0ZV90ZW5hbnQodGVuYW50LCBwYXJhbXMpXG5lbHNlXG4gIHs6b2ssIF99ID0gU3VwYXZpc29yLlRlbmFudHMuY3JlYXRlX3RlbmFudChwYXJhbXMpXG5lbmRcbiIK", + "compose": "c2VydmljZXM6CiAgc3VwYWJhc2Uta29uZzoKICAgIGltYWdlOiAna29uZzoyLjguMScKICAgIGVudHJ5cG9pbnQ6ICdiYXNoIC1jICcnZXZhbCAiZWNobyBcIiQkKGNhdCB+L3RlbXAueW1sKVwiIiA+IH4va29uZy55bWwgJiYgL2RvY2tlci1lbnRyeXBvaW50LnNoIGtvbmcgZG9ja2VyLXN0YXJ0JycnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkdfODAwMAogICAgICAtICdLT05HX1BPUlRfTUFQUz00NDM6ODAwMCcKICAgICAgLSAnSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSBLT05HX0RBVEFCQVNFPW9mZgogICAgICAtIEtPTkdfREVDTEFSQVRJVkVfQ09ORklHPS9ob21lL2tvbmcva29uZy55bWwKICAgICAgLSAnS09OR19ETlNfT1JERVI9TEFTVCxBLENOQU1FJwogICAgICAtICdLT05HX1BMVUdJTlM9cmVxdWVzdC10cmFuc2Zvcm1lcixjb3JzLGtleS1hdXRoLGFjbCxiYXNpYy1hdXRoJwogICAgICAtIEtPTkdfTkdJTlhfUFJPWFlfUFJPWFlfQlVGRkVSX1NJWkU9MTYwawogICAgICAtICdLT05HX05HSU5YX1BST1hZX1BST1hZX0JVRkZFUlM9NjQgMTYwaycKICAgICAgLSAnU1VQQUJBU0VfQU5PTl9LRVk9JHtTRVJWSUNFX1NVUEFCQVNFQU5PTl9LRVl9JwogICAgICAtICdTVVBBQkFTRV9TRVJWSUNFX0tFWT0ke1NFUlZJQ0VfU1VQQUJBU0VTRVJWSUNFX0tFWX0nCiAgICAgIC0gJ0RBU0hCT0FSRF9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9BRE1JTn0nCiAgICAgIC0gJ0RBU0hCT0FSRF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfQURNSU59JwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9hcGkva29uZy55bWwKICAgICAgICB0YXJnZXQ6IC9ob21lL2tvbmcvdGVtcC55bWwKICAgICAgICBjb250ZW50OiAiX2Zvcm1hdF92ZXJzaW9uOiAnMi4xJ1xuX3RyYW5zZm9ybTogdHJ1ZVxuXG4jIyNcbiMjIyBDb25zdW1lcnMgLyBVc2Vyc1xuIyMjXG5jb25zdW1lcnM6XG4gIC0gdXNlcm5hbWU6IERBU0hCT0FSRFxuICAtIHVzZXJuYW1lOiBhbm9uXG4gICAga2V5YXV0aF9jcmVkZW50aWFsczpcbiAgICAgIC0ga2V5OiAkU1VQQUJBU0VfQU5PTl9LRVlcbiAgLSB1c2VybmFtZTogc2VydmljZV9yb2xlXG4gICAga2V5YXV0aF9jcmVkZW50aWFsczpcbiAgICAgIC0ga2V5OiAkU1VQQUJBU0VfU0VSVklDRV9LRVlcblxuIyMjXG4jIyMgQWNjZXNzIENvbnRyb2wgTGlzdFxuIyMjXG5hY2xzOlxuICAtIGNvbnN1bWVyOiBhbm9uXG4gICAgZ3JvdXA6IGFub25cbiAgLSBjb25zdW1lcjogc2VydmljZV9yb2xlXG4gICAgZ3JvdXA6IGFkbWluXG5cbiMjI1xuIyMjIERhc2hib2FyZCBjcmVkZW50aWFsc1xuIyMjXG5iYXNpY2F1dGhfY3JlZGVudGlhbHM6XG4tIGNvbnN1bWVyOiBEQVNIQk9BUkRcbiAgdXNlcm5hbWU6ICREQVNIQk9BUkRfVVNFUk5BTUVcbiAgcGFzc3dvcmQ6ICREQVNIQk9BUkRfUEFTU1dPUkRcblxuXG4jIyNcbiMjIyBBUEkgUm91dGVzXG4jIyNcbnNlcnZpY2VzOlxuXG4gICMjIE9wZW4gQXV0aCByb3V0ZXNcbiAgLSBuYW1lOiBhdXRoLXYxLW9wZW5cbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1hdXRoOjk5OTkvdmVyaWZ5XG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBhdXRoLXYxLW9wZW5cbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9hdXRoL3YxL3ZlcmlmeVxuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgLSBuYW1lOiBhdXRoLXYxLW9wZW4tY2FsbGJhY2tcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1hdXRoOjk5OTkvY2FsbGJhY2tcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtb3Blbi1jYWxsYmFja1xuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2F1dGgvdjEvY2FsbGJhY2tcbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gIC0gbmFtZTogYXV0aC12MS1vcGVuLWF1dGhvcml6ZVxuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS9hdXRob3JpemVcbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtb3Blbi1hdXRob3JpemVcbiAgICAgICAgc3RyaXBfcGF0aDogdHJ1ZVxuICAgICAgICBwYXRoczpcbiAgICAgICAgICAtIC9hdXRoL3YxL2F1dGhvcml6ZVxuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcblxuICAjIyBTZWN1cmUgQXV0aCByb3V0ZXNcbiAgLSBuYW1lOiBhdXRoLXYxXG4gICAgX2NvbW1lbnQ6ICdHb1RydWU6IC9hdXRoL3YxLyogLT4gaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWF1dGg6OTk5OS9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IGF1dGgtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvYXV0aC92MS9cbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiBmYWxzZVxuICAgICAgLSBuYW1lOiBhY2xcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfZ3JvdXBzX2hlYWRlcjogdHJ1ZVxuICAgICAgICAgIGFsbG93OlxuICAgICAgICAgICAgLSBhZG1pblxuICAgICAgICAgICAgLSBhbm9uXG5cbiAgIyMgU2VjdXJlIFJFU1Qgcm91dGVzXG4gIC0gbmFtZTogcmVzdC12MVxuICAgIF9jb21tZW50OiAnUG9zdGdSRVNUOiAvcmVzdC92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvKidcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiByZXN0LXYxLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL3Jlc3QvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogdHJ1ZVxuICAgICAgLSBuYW1lOiBhY2xcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfZ3JvdXBzX2hlYWRlcjogdHJ1ZVxuICAgICAgICAgIGFsbG93OlxuICAgICAgICAgICAgLSBhZG1pblxuICAgICAgICAgICAgLSBhbm9uXG5cbiAgIyMgU2VjdXJlIEdyYXBoUUwgcm91dGVzXG4gIC0gbmFtZTogZ3JhcGhxbC12MVxuICAgIF9jb21tZW50OiAnUG9zdGdSRVNUOiAvZ3JhcGhxbC92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1yZXN0OjMwMDAvcnBjL2dyYXBocWwnXG4gICAgdXJsOiBodHRwOi8vc3VwYWJhc2UtcmVzdDozMDAwL3JwYy9ncmFwaHFsXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBncmFwaHFsLXYxLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2dyYXBocWwvdjFcbiAgICBwbHVnaW5zOlxuICAgICAgLSBuYW1lOiBjb3JzXG4gICAgICAtIG5hbWU6IGtleS1hdXRoXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2NyZWRlbnRpYWxzOiB0cnVlXG4gICAgICAtIG5hbWU6IHJlcXVlc3QtdHJhbnNmb3JtZXJcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGFkZDpcbiAgICAgICAgICAgIGhlYWRlcnM6XG4gICAgICAgICAgICAgIC0gQ29udGVudC1Qcm9maWxlOmdyYXBocWxfcHVibGljXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cblxuICAjIyBTZWN1cmUgUmVhbHRpbWUgcm91dGVzXG4gIC0gbmFtZTogcmVhbHRpbWUtdjEtd3NcbiAgICBfY29tbWVudDogJ1JlYWx0aW1lOiAvcmVhbHRpbWUvdjEvKiAtPiB3czovL3JlYWx0aW1lOjQwMDAvc29ja2V0LyonXG4gICAgdXJsOiBodHRwOi8vcmVhbHRpbWUtZGV2OjQwMDAvc29ja2V0XG4gICAgcHJvdG9jb2w6IHdzXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiByZWFsdGltZS12MS13c1xuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL3JlYWx0aW1lL3YxL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IGZhbHNlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG4gICAgICAgICAgICAtIGFub25cbiAgLSBuYW1lOiByZWFsdGltZS12MS1yZXN0XG4gICAgX2NvbW1lbnQ6ICdSZWFsdGltZTogL3JlYWx0aW1lL3YxLyogLT4gd3M6Ly9yZWFsdGltZTo0MDAwL3NvY2tldC8qJ1xuICAgIHVybDogaHR0cDovL3JlYWx0aW1lLWRldjo0MDAwL2FwaVxuICAgIHByb3RvY29sOiBodHRwXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiByZWFsdGltZS12MS1yZXN0XG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcmVhbHRpbWUvdjEvYXBpXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuICAgICAgLSBuYW1lOiBrZXktYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogZmFsc2VcbiAgICAgIC0gbmFtZTogYWNsXG4gICAgICAgIGNvbmZpZzpcbiAgICAgICAgICBoaWRlX2dyb3Vwc19oZWFkZXI6IHRydWVcbiAgICAgICAgICBhbGxvdzpcbiAgICAgICAgICAgIC0gYWRtaW5cbiAgICAgICAgICAgIC0gYW5vblxuXG4gICMjIFN0b3JhZ2Ugcm91dGVzOiB0aGUgc3RvcmFnZSBzZXJ2ZXIgbWFuYWdlcyBpdHMgb3duIGF1dGhcbiAgLSBuYW1lOiBzdG9yYWdlLXYxXG4gICAgX2NvbW1lbnQ6ICdTdG9yYWdlOiAvc3RvcmFnZS92MS8qIC0+IGh0dHA6Ly9zdXBhYmFzZS1zdG9yYWdlOjUwMDAvKidcbiAgICB1cmw6IGh0dHA6Ly9zdXBhYmFzZS1zdG9yYWdlOjUwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBzdG9yYWdlLXYxLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL3N0b3JhZ2UvdjEvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZTogY29yc1xuXG4gICMjIEVkZ2UgRnVuY3Rpb25zIHJvdXRlc1xuICAtIG5hbWU6IGZ1bmN0aW9ucy12MVxuICAgIF9jb21tZW50OiAnRWRnZSBGdW5jdGlvbnM6IC9mdW5jdGlvbnMvdjEvKiAtPiBodHRwOi8vc3VwYWJhc2UtZWRnZS1mdW5jdGlvbnM6OTAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWVkZ2UtZnVuY3Rpb25zOjkwMDAvXG4gICAgcm91dGVzOlxuICAgICAgLSBuYW1lOiBmdW5jdGlvbnMtdjEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvZnVuY3Rpb25zL3YxL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcblxuICAjIyBBbmFseXRpY3Mgcm91dGVzXG4gIC0gbmFtZTogYW5hbHl0aWNzLXYxXG4gICAgX2NvbW1lbnQ6ICdBbmFseXRpY3M6IC9hbmFseXRpY3MvdjEvKiAtPiBodHRwOi8vbG9nZmxhcmU6NDAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogYW5hbHl0aWNzLXYxLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL2FuYWx5dGljcy92MS9cblxuICAjIyBTZWN1cmUgRGF0YWJhc2Ugcm91dGVzXG4gIC0gbmFtZTogbWV0YVxuICAgIF9jb21tZW50OiAncGctbWV0YTogL3BnLyogLT4gaHR0cDovL3N1cGFiYXNlLW1ldGE6ODA4MC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLW1ldGE6ODA4MC9cbiAgICByb3V0ZXM6XG4gICAgICAtIG5hbWU6IG1ldGEtYWxsXG4gICAgICAgIHN0cmlwX3BhdGg6IHRydWVcbiAgICAgICAgcGF0aHM6XG4gICAgICAgICAgLSAvcGcvXG4gICAgcGx1Z2luczpcbiAgICAgIC0gbmFtZToga2V5LWF1dGhcbiAgICAgICAgY29uZmlnOlxuICAgICAgICAgIGhpZGVfY3JlZGVudGlhbHM6IGZhbHNlXG4gICAgICAtIG5hbWU6IGFjbFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9ncm91cHNfaGVhZGVyOiB0cnVlXG4gICAgICAgICAgYWxsb3c6XG4gICAgICAgICAgICAtIGFkbWluXG5cbiAgIyMgUHJvdGVjdGVkIERhc2hib2FyZCAtIGNhdGNoIGFsbCByZW1haW5pbmcgcm91dGVzXG4gIC0gbmFtZTogZGFzaGJvYXJkXG4gICAgX2NvbW1lbnQ6ICdTdHVkaW86IC8qIC0+IGh0dHA6Ly9zdHVkaW86MzAwMC8qJ1xuICAgIHVybDogaHR0cDovL3N1cGFiYXNlLXN0dWRpbzozMDAwL1xuICAgIHJvdXRlczpcbiAgICAgIC0gbmFtZTogZGFzaGJvYXJkLWFsbFxuICAgICAgICBzdHJpcF9wYXRoOiB0cnVlXG4gICAgICAgIHBhdGhzOlxuICAgICAgICAgIC0gL1xuICAgIHBsdWdpbnM6XG4gICAgICAtIG5hbWU6IGNvcnNcbiAgICAgIC0gbmFtZTogYmFzaWMtYXV0aFxuICAgICAgICBjb25maWc6XG4gICAgICAgICAgaGlkZV9jcmVkZW50aWFsczogdHJ1ZVxuIgogIHN1cGFiYXNlLXN0dWRpbzoKICAgIGltYWdlOiAnc3VwYWJhc2Uvc3R1ZGlvOjIwMjUuMTIuMTctc2hhLTQzZjRmN2YnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gbm9kZQogICAgICAgIC0gJy1lJwogICAgICAgIC0gImZldGNoKCdodHRwOi8vMTI3LjAuMC4xOjMwMDAvYXBpL3BsYXRmb3JtL3Byb2ZpbGUnKS50aGVuKChyKSA9PiB7aWYgKHIuc3RhdHVzICE9PSAyMDApIHRocm93IG5ldyBFcnJvcihyLnN0YXR1cyl9KSIKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSE9TVE5BTUU9MC4wLjAuMAogICAgICAtICdTVFVESU9fUEdfTUVUQV9VUkw9aHR0cDovL3N1cGFiYXNlLW1ldGE6ODA4MCcKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnREVGQVVMVF9PUkdBTklaQVRJT05fTkFNRT0ke1NUVURJT19ERUZBVUxUX09SR0FOSVpBVElPTjotRGVmYXVsdCBPcmdhbml6YXRpb259JwogICAgICAtICdERUZBVUxUX1BST0pFQ1RfTkFNRT0ke1NUVURJT19ERUZBVUxUX1BST0pFQ1Q6LURlZmF1bHQgUHJvamVjdH0nCiAgICAgIC0gJ1NVUEFCQVNFX1VSTD1odHRwOi8vc3VwYWJhc2Uta29uZzo4MDAwJwogICAgICAtICdTVVBBQkFTRV9QVUJMSUNfVVJMPSR7U0VSVklDRV9GUUROX1NVUEFCQVNFS09OR30nCiAgICAgIC0gJ1NVUEFCQVNFX0FOT05fS0VZPSR7U0VSVklDRV9TVVBBQkFTRUFOT05fS0VZfScKICAgICAgLSAnU1VQQUJBU0VfU0VSVklDRV9LRVk9JHtTRVJWSUNFX1NVUEFCQVNFU0VSVklDRV9LRVl9JwogICAgICAtICdBVVRIX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0xPR0ZMQVJFX0FQSV9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX0xPR0ZMQVJFfScKICAgICAgLSAnTE9HRkxBUkVfVVJMPWh0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMCcKICAgICAgLSAnU1VQQUJBU0VfUFVCTElDX0FQST0ke1NFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkd9JwogICAgICAtIE5FWFRfUFVCTElDX0VOQUJMRV9MT0dTPXRydWUKICAgICAgLSBORVhUX0FOQUxZVElDU19CQUNLRU5EX1BST1ZJREVSPXBvc3RncmVzCiAgICAgIC0gJ09QRU5BSV9BUElfS0VZPSR7T1BFTkFJX0FQSV9LRVl9JwogIHN1cGFiYXNlLWRiOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9wb3N0Z3JlczoxNS44LjEuMDQ4JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdwZ19pc3JlYWR5IC1VIHBvc3RncmVzIC1oIDEyNy4wLjAuMScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS12ZWN0b3I6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGNvbW1hbmQ6CiAgICAgIC0gcG9zdGdyZXMKICAgICAgLSAnLWMnCiAgICAgIC0gY29uZmlnX2ZpbGU9L2V0Yy9wb3N0Z3Jlc3FsL3Bvc3RncmVzcWwuY29uZgogICAgICAtICctYycKICAgICAgLSBsb2dfbWluX21lc3NhZ2VzPWZhdGFsCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19IT1NUPS92YXIvcnVuL3Bvc3RncmVzcWwKICAgICAgLSAnUEdQT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ1BPU1RHUkVTX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUEdQQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQR0RBVEFCQVNFPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdKV1RfRVhQPSR7SldUX0VYUElSWTotMzYwMH0nCiAgICB2b2x1bWVzOgogICAgICAtICdzdXBhYmFzZS1kYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9yZWFsdGltZS5zcWwKICAgICAgICB0YXJnZXQ6IC9kb2NrZXItZW50cnlwb2ludC1pbml0ZGIuZC9taWdyYXRpb25zLzk5LXJlYWx0aW1lLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCJzdXBhYmFzZV9hZG1pblwiYFxuXG5jcmVhdGUgc2NoZW1hIGlmIG5vdCBleGlzdHMgX3JlYWx0aW1lO1xuYWx0ZXIgc2NoZW1hIF9yZWFsdGltZSBvd25lciB0byA6cGd1c2VyO1xuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2RiL19zdXBhYmFzZS5zcWwKICAgICAgICB0YXJnZXQ6IC9kb2NrZXItZW50cnlwb2ludC1pbml0ZGIuZC9taWdyYXRpb25zLzk3LV9zdXBhYmFzZS5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwiJFBPU1RHUkVTX1VTRVJcImBcblxuQ1JFQVRFIERBVEFCQVNFIF9zdXBhYmFzZSBXSVRIIE9XTkVSIDpwZ3VzZXI7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcG9vbGVyLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktcG9vbGVyLnNxbAogICAgICAgIGNvbnRlbnQ6ICJcXHNldCBwZ3VzZXIgYGVjaG8gXCJzdXBhYmFzZV9hZG1pblwiYFxuXFxjIF9zdXBhYmFzZVxuY3JlYXRlIHNjaGVtYSBpZiBub3QgZXhpc3RzIF9zdXBhdmlzb3I7XG5hbHRlciBzY2hlbWEgX3N1cGF2aXNvciBvd25lciB0byA6cGd1c2VyO1xuXFxjIHBvc3RncmVzXG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvd2ViaG9va3Muc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk4LXdlYmhvb2tzLnNxbAogICAgICAgIGNvbnRlbnQ6ICJCRUdJTjtcbi0tIENyZWF0ZSBwZ19uZXQgZXh0ZW5zaW9uXG5DUkVBVEUgRVhURU5TSU9OIElGIE5PVCBFWElTVFMgcGdfbmV0IFNDSEVNQSBleHRlbnNpb25zO1xuLS0gQ3JlYXRlIHN1cGFiYXNlX2Z1bmN0aW9ucyBzY2hlbWFcbkNSRUFURSBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIEFVVEhPUklaQVRJT04gc3VwYWJhc2VfYWRtaW47XG5HUkFOVCBVU0FHRSBPTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gVEFCTEVTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gRlVOQ1RJT05TIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5BTFRFUiBERUZBVUxUIFBSSVZJTEVHRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBHUkFOVCBBTEwgT04gU0VRVUVOQ0VTIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMubWlncmF0aW9ucyBkZWZpbml0aW9uXG5DUkVBVEUgVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLm1pZ3JhdGlvbnMgKFxuICB2ZXJzaW9uIHRleHQgUFJJTUFSWSBLRVksXG4gIGluc2VydGVkX2F0IHRpbWVzdGFtcHR6IE5PVCBOVUxMIERFRkFVTFQgTk9XKClcbik7XG4tLSBJbml0aWFsIHN1cGFiYXNlX2Z1bmN0aW9ucyBtaWdyYXRpb25cbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCdpbml0aWFsJyk7XG4tLSBzdXBhYmFzZV9mdW5jdGlvbnMuaG9va3MgZGVmaW5pdGlvblxuQ1JFQVRFIFRBQkxFIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyAoXG4gIGlkIGJpZ3NlcmlhbCBQUklNQVJZIEtFWSxcbiAgaG9va190YWJsZV9pZCBpbnRlZ2VyIE5PVCBOVUxMLFxuICBob29rX25hbWUgdGV4dCBOT1QgTlVMTCxcbiAgY3JlYXRlZF9hdCB0aW1lc3RhbXB0eiBOT1QgTlVMTCBERUZBVUxUIE5PVygpLFxuICByZXF1ZXN0X2lkIGJpZ2ludFxuKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfcmVxdWVzdF9pZF9pZHggT04gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIFVTSU5HIGJ0cmVlIChyZXF1ZXN0X2lkKTtcbkNSRUFURSBJTkRFWCBzdXBhYmFzZV9mdW5jdGlvbnNfaG9va3NfaF90YWJsZV9pZF9oX25hbWVfaWR4IE9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5ob29rcyBVU0lORyBidHJlZSAoaG9va190YWJsZV9pZCwgaG9va19uYW1lKTtcbkNPTU1FTlQgT04gVEFCTEUgc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzIElTICdTdXBhYmFzZSBGdW5jdGlvbnMgSG9va3M6IEF1ZGl0IHRyYWlsIGZvciB0cmlnZ2VyZWQgaG9va3MuJztcbkNSRUFURSBGVU5DVElPTiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KClcbiAgUkVUVVJOUyB0cmlnZ2VyXG4gIExBTkdVQUdFIHBscGdzcWxcbiAgQVMgJGZ1bmN0aW9uJFxuICBERUNMQVJFXG4gICAgcmVxdWVzdF9pZCBiaWdpbnQ7XG4gICAgcGF5bG9hZCBqc29uYjtcbiAgICB1cmwgdGV4dCA6PSBUR19BUkdWWzBdOjp0ZXh0O1xuICAgIG1ldGhvZCB0ZXh0IDo9IFRHX0FSR1ZbMV06OnRleHQ7XG4gICAgaGVhZGVycyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHBhcmFtcyBqc29uYiBERUZBVUxUICd7fSc6Ompzb25iO1xuICAgIHRpbWVvdXRfbXMgaW50ZWdlciBERUZBVUxUIDEwMDA7XG4gIEJFR0lOXG4gICAgSUYgdXJsIElTIE5VTEwgT1IgdXJsID0gJ251bGwnIFRIRU5cbiAgICAgIFJBSVNFIEVYQ0VQVElPTiAndXJsIGFyZ3VtZW50IGlzIG1pc3NpbmcnO1xuICAgIEVORCBJRjtcblxuICAgIElGIG1ldGhvZCBJUyBOVUxMIE9SIG1ldGhvZCA9ICdudWxsJyBUSEVOXG4gICAgICBSQUlTRSBFWENFUFRJT04gJ21ldGhvZCBhcmd1bWVudCBpcyBtaXNzaW5nJztcbiAgICBFTkQgSUY7XG5cbiAgICBJRiBUR19BUkdWWzJdIElTIE5VTEwgT1IgVEdfQVJHVlsyXSA9ICdudWxsJyBUSEVOXG4gICAgICBoZWFkZXJzID0gJ3tcIkNvbnRlbnQtVHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIn0nOjpqc29uYjtcbiAgICBFTFNFXG4gICAgICBoZWFkZXJzID0gVEdfQVJHVlsyXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVlszXSBJUyBOVUxMIE9SIFRHX0FSR1ZbM10gPSAnbnVsbCcgVEhFTlxuICAgICAgcGFyYW1zID0gJ3t9Jzo6anNvbmI7XG4gICAgRUxTRVxuICAgICAgcGFyYW1zID0gVEdfQVJHVlszXTo6anNvbmI7XG4gICAgRU5EIElGO1xuXG4gICAgSUYgVEdfQVJHVls0XSBJUyBOVUxMIE9SIFRHX0FSR1ZbNF0gPSAnbnVsbCcgVEhFTlxuICAgICAgdGltZW91dF9tcyA9IDEwMDA7XG4gICAgRUxTRVxuICAgICAgdGltZW91dF9tcyA9IFRHX0FSR1ZbNF06OmludGVnZXI7XG4gICAgRU5EIElGO1xuXG4gICAgQ0FTRVxuICAgICAgV0hFTiBtZXRob2QgPSAnR0VUJyBUSEVOXG4gICAgICAgIFNFTEVDVCBodHRwX2dldCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9nZXQoXG4gICAgICAgICAgdXJsLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIFdIRU4gbWV0aG9kID0gJ1BPU1QnIFRIRU5cbiAgICAgICAgcGF5bG9hZCA9IGpzb25iX2J1aWxkX29iamVjdChcbiAgICAgICAgICAnb2xkX3JlY29yZCcsIE9MRCxcbiAgICAgICAgICAncmVjb3JkJywgTkVXLFxuICAgICAgICAgICd0eXBlJywgVEdfT1AsXG4gICAgICAgICAgJ3RhYmxlJywgVEdfVEFCTEVfTkFNRSxcbiAgICAgICAgICAnc2NoZW1hJywgVEdfVEFCTEVfU0NIRU1BXG4gICAgICAgICk7XG5cbiAgICAgICAgU0VMRUNUIGh0dHBfcG9zdCBJTlRPIHJlcXVlc3RfaWQgRlJPTSBuZXQuaHR0cF9wb3N0KFxuICAgICAgICAgIHVybCxcbiAgICAgICAgICBwYXlsb2FkLFxuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICBoZWFkZXJzLFxuICAgICAgICAgIHRpbWVvdXRfbXNcbiAgICAgICAgKTtcbiAgICAgIEVMU0VcbiAgICAgICAgUkFJU0UgRVhDRVBUSU9OICdtZXRob2QgYXJndW1lbnQgJSBpcyBpbnZhbGlkJywgbWV0aG9kO1xuICAgIEVORCBDQVNFO1xuXG4gICAgSU5TRVJUIElOVE8gc3VwYWJhc2VfZnVuY3Rpb25zLmhvb2tzXG4gICAgICAoaG9va190YWJsZV9pZCwgaG9va19uYW1lLCByZXF1ZXN0X2lkKVxuICAgIFZBTFVFU1xuICAgICAgKFRHX1JFTElELCBUR19OQU1FLCByZXF1ZXN0X2lkKTtcblxuICAgIFJFVFVSTiBORVc7XG4gIEVORFxuJGZ1bmN0aW9uJDtcbi0tIFN1cGFiYXNlIHN1cGVyIGFkbWluXG5ET1xuJCRcbkJFR0lOXG4gIElGIE5PVCBFWElTVFMgKFxuICAgIFNFTEVDVCAxXG4gICAgRlJPTSBwZ19yb2xlc1xuICAgIFdIRVJFIHJvbG5hbWUgPSAnc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluJ1xuICApXG4gIFRIRU5cbiAgICBDUkVBVEUgVVNFUiBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gTk9JTkhFUklUIENSRUFURVJPTEUgTE9HSU4gTk9SRVBMSUNBVElPTjtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIFNDSEVNQSBzdXBhYmFzZV9mdW5jdGlvbnMgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuR1JBTlQgQUxMIFBSSVZJTEVHRVMgT04gQUxMIFRBQkxFUyBJTiBTQ0hFTUEgc3VwYWJhc2VfZnVuY3Rpb25zIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkdSQU5UIEFMTCBQUklWSUxFR0VTIE9OIEFMTCBTRVFVRU5DRVMgSU4gU0NIRU1BIHN1cGFiYXNlX2Z1bmN0aW9ucyBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5BTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBTRVQgc2VhcmNoX3BhdGggPSBcInN1cGFiYXNlX2Z1bmN0aW9uc1wiO1xuQUxURVIgdGFibGUgXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5taWdyYXRpb25zIE9XTkVSIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbjtcbkFMVEVSIHRhYmxlIFwic3VwYWJhc2VfZnVuY3Rpb25zXCIuaG9va3MgT1dORVIgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluO1xuQUxURVIgZnVuY3Rpb24gXCJzdXBhYmFzZV9mdW5jdGlvbnNcIi5odHRwX3JlcXVlc3QoKSBPV05FUiBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW47XG5HUkFOVCBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4gVE8gcG9zdGdyZXM7XG4tLSBSZW1vdmUgdW51c2VkIHN1cGFiYXNlX3BnX25ldF9hZG1pbiByb2xlXG5ET1xuJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX3JvbGVzXG4gICAgV0hFUkUgcm9sbmFtZSA9ICdzdXBhYmFzZV9wZ19uZXRfYWRtaW4nXG4gIClcbiAgVEhFTlxuICAgIFJFQVNTSUdOIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbiBUTyBzdXBhYmFzZV9hZG1pbjtcbiAgICBEUk9QIE9XTkVEIEJZIHN1cGFiYXNlX3BnX25ldF9hZG1pbjtcbiAgICBEUk9QIFJPTEUgc3VwYWJhc2VfcGdfbmV0X2FkbWluO1xuICBFTkQgSUY7XG5FTkRcbiQkO1xuLS0gcGdfbmV0IGdyYW50cyB3aGVuIGV4dGVuc2lvbiBpcyBhbHJlYWR5IGVuYWJsZWRcbkRPXG4kJFxuQkVHSU5cbiAgSUYgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXh0ZW5zaW9uXG4gICAgV0hFUkUgZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbi0tIEV2ZW50IHRyaWdnZXIgZm9yIHBnX25ldFxuQ1JFQVRFIE9SIFJFUExBQ0UgRlVOQ1RJT04gZXh0ZW5zaW9ucy5ncmFudF9wZ19uZXRfYWNjZXNzKClcblJFVFVSTlMgZXZlbnRfdHJpZ2dlclxuTEFOR1VBR0UgcGxwZ3NxbFxuQVMgJCRcbkJFR0lOXG4gIElGIEVYSVNUUyAoXG4gICAgU0VMRUNUIDFcbiAgICBGUk9NIHBnX2V2ZW50X3RyaWdnZXJfZGRsX2NvbW1hbmRzKCkgQVMgZXZcbiAgICBKT0lOIHBnX2V4dGVuc2lvbiBBUyBleHRcbiAgICBPTiBldi5vYmppZCA9IGV4dC5vaWRcbiAgICBXSEVSRSBleHQuZXh0bmFtZSA9ICdwZ19uZXQnXG4gIClcbiAgVEhFTlxuICAgIEdSQU5UIFVTQUdFIE9OIFNDSEVNQSBuZXQgVE8gc3VwYWJhc2VfZnVuY3Rpb25zX2FkbWluLCBwb3N0Z3JlcywgYW5vbiwgYXV0aGVudGljYXRlZCwgc2VydmljZV9yb2xlO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBTRUNVUklUWSBERUZJTkVSO1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VDVVJJVFkgREVGSU5FUjtcbiAgICBBTFRFUiBmdW5jdGlvbiBuZXQuaHR0cF9nZXQodXJsIHRleHQsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIEFMVEVSIGZ1bmN0aW9uIG5ldC5odHRwX3Bvc3QodXJsIHRleHQsIGJvZHkganNvbmIsIHBhcmFtcyBqc29uYiwgaGVhZGVycyBqc29uYiwgdGltZW91dF9taWxsaXNlY29uZHMgaW50ZWdlcikgU0VUIHNlYXJjaF9wYXRoID0gbmV0O1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfZ2V0KHVybCB0ZXh0LCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIEZST00gUFVCTElDO1xuICAgIFJFVk9LRSBBTEwgT04gRlVOQ1RJT04gbmV0Lmh0dHBfcG9zdCh1cmwgdGV4dCwgYm9keSBqc29uYiwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBGUk9NIFBVQkxJQztcbiAgICBHUkFOVCBFWEVDVVRFIE9OIEZVTkNUSU9OIG5ldC5odHRwX2dldCh1cmwgdGV4dCwgcGFyYW1zIGpzb25iLCBoZWFkZXJzIGpzb25iLCB0aW1lb3V0X21pbGxpc2Vjb25kcyBpbnRlZ2VyKSBUTyBzdXBhYmFzZV9mdW5jdGlvbnNfYWRtaW4sIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG4gICAgR1JBTlQgRVhFQ1VURSBPTiBGVU5DVElPTiBuZXQuaHR0cF9wb3N0KHVybCB0ZXh0LCBib2R5IGpzb25iLCBwYXJhbXMganNvbmIsIGhlYWRlcnMganNvbmIsIHRpbWVvdXRfbWlsbGlzZWNvbmRzIGludGVnZXIpIFRPIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiwgcG9zdGdyZXMsIGFub24sIGF1dGhlbnRpY2F0ZWQsIHNlcnZpY2Vfcm9sZTtcbiAgRU5EIElGO1xuRU5EO1xuJCQ7XG5DT01NRU5UIE9OIEZVTkNUSU9OIGV4dGVuc2lvbnMuZ3JhbnRfcGdfbmV0X2FjY2VzcyBJUyAnR3JhbnRzIGFjY2VzcyB0byBwZ19uZXQnO1xuRE9cbiQkXG5CRUdJTlxuICBJRiBOT1QgRVhJU1RTIChcbiAgICBTRUxFQ1QgMVxuICAgIEZST00gcGdfZXZlbnRfdHJpZ2dlclxuICAgIFdIRVJFIGV2dG5hbWUgPSAnaXNzdWVfcGdfbmV0X2FjY2VzcydcbiAgKSBUSEVOXG4gICAgQ1JFQVRFIEVWRU5UIFRSSUdHRVIgaXNzdWVfcGdfbmV0X2FjY2VzcyBPTiBkZGxfY29tbWFuZF9lbmQgV0hFTiBUQUcgSU4gKCdDUkVBVEUgRVhURU5TSU9OJylcbiAgICBFWEVDVVRFIFBST0NFRFVSRSBleHRlbnNpb25zLmdyYW50X3BnX25ldF9hY2Nlc3MoKTtcbiAgRU5EIElGO1xuRU5EXG4kJDtcbklOU0VSVCBJTlRPIHN1cGFiYXNlX2Z1bmN0aW9ucy5taWdyYXRpb25zICh2ZXJzaW9uKSBWQUxVRVMgKCcyMDIxMDgwOTE4MzQyM191cGRhdGVfZ3JhbnRzJyk7XG5BTFRFUiBmdW5jdGlvbiBzdXBhYmFzZV9mdW5jdGlvbnMuaHR0cF9yZXF1ZXN0KCkgU0VDVVJJVFkgREVGSU5FUjtcbkFMVEVSIGZ1bmN0aW9uIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBTRVQgc2VhcmNoX3BhdGggPSBzdXBhYmFzZV9mdW5jdGlvbnM7XG5SRVZPS0UgQUxMIE9OIEZVTkNUSU9OIHN1cGFiYXNlX2Z1bmN0aW9ucy5odHRwX3JlcXVlc3QoKSBGUk9NIFBVQkxJQztcbkdSQU5UIEVYRUNVVEUgT04gRlVOQ1RJT04gc3VwYWJhc2VfZnVuY3Rpb25zLmh0dHBfcmVxdWVzdCgpIFRPIHBvc3RncmVzLCBhbm9uLCBhdXRoZW50aWNhdGVkLCBzZXJ2aWNlX3JvbGU7XG5DT01NSVQ7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvcm9sZXMuc3FsCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1zY3JpcHRzLzk5LXJvbGVzLnNxbAogICAgICAgIGNvbnRlbnQ6ICItLSBOT1RFOiBjaGFuZ2UgdG8geW91ciBvd24gcGFzc3dvcmRzIGZvciBwcm9kdWN0aW9uIGVudmlyb25tZW50c1xuIFxcc2V0IHBncGFzcyBgZWNobyBcIiRQT1NUR1JFU19QQVNTV09SRFwiYFxuXG4gQUxURVIgVVNFUiBhdXRoZW50aWNhdG9yIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgcGdib3VuY2VyIFdJVEggUEFTU1dPUkQgOidwZ3Bhc3MnO1xuIEFMVEVSIFVTRVIgc3VwYWJhc2VfYXV0aF9hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX2Z1bmN0aW9uc19hZG1pbiBXSVRIIFBBU1NXT1JEIDoncGdwYXNzJztcbiBBTFRFUiBVU0VSIHN1cGFiYXNlX3N0b3JhZ2VfYWRtaW4gV0lUSCBQQVNTV09SRCA6J3BncGFzcyc7XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvZGIvand0LnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL2luaXQtc2NyaXB0cy85OS1qd3Quc3FsCiAgICAgICAgY29udGVudDogIlxcc2V0IGp3dF9zZWNyZXQgYGVjaG8gXCIkSldUX1NFQ1JFVFwiYFxuXFxzZXQgand0X2V4cCBgZWNobyBcIiRKV1RfRVhQXCJgXG5cXHNldCBkYl9uYW1lIGBlY2hvIFwiJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9XCJgXG5cbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3Rfc2VjcmV0XCIgVE8gOidqd3Rfc2VjcmV0JztcbkFMVEVSIERBVEFCQVNFIDpkYl9uYW1lIFNFVCBcImFwcC5zZXR0aW5ncy5qd3RfZXhwXCIgVE8gOidqd3RfZXhwJztcbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9kYi9sb2dzLnNxbAogICAgICAgIHRhcmdldDogL2RvY2tlci1lbnRyeXBvaW50LWluaXRkYi5kL21pZ3JhdGlvbnMvOTktbG9ncy5zcWwKICAgICAgICBjb250ZW50OiAiXFxzZXQgcGd1c2VyIGBlY2hvIFwic3VwYWJhc2VfYWRtaW5cImBcblxcYyBfc3VwYWJhc2VcbmNyZWF0ZSBzY2hlbWEgaWYgbm90IGV4aXN0cyBfYW5hbHl0aWNzO1xuYWx0ZXIgc2NoZW1hIF9hbmFseXRpY3Mgb3duZXIgdG8gOnBndXNlcjtcblxcYyBwb3N0Z3Jlc1xuIgogICAgICAtICdzdXBhYmFzZS1kYi1jb25maWc6L2V0Yy9wb3N0Z3Jlc3FsLWN1c3RvbScKICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICBpbWFnZTogJ3N1cGFiYXNlL2xvZ2ZsYXJlOjEuNC4wJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjQwMDAvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBMT0dGTEFSRV9OT0RFX0hPU1Q9MTI3LjAuMC4xCiAgICAgIC0gREJfVVNFUk5BTUU9c3VwYWJhc2VfYWRtaW4KICAgICAgLSBEQl9EQVRBQkFTRT1fc3VwYWJhc2UKICAgICAgLSAnREJfSE9TVE5BTUU9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdEQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gREJfU0NIRU1BPV9hbmFseXRpY3MKICAgICAgLSAnTE9HRkxBUkVfQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTE9HRkxBUkV9JwogICAgICAtIExPR0ZMQVJFX1NJTkdMRV9URU5BTlQ9dHJ1ZQogICAgICAtIExPR0ZMQVJFX1NJTkdMRV9URU5BTlRfTU9ERT10cnVlCiAgICAgIC0gTE9HRkxBUkVfU1VQQUJBU0VfTU9ERT10cnVlCiAgICAgIC0gTE9HRkxBUkVfTUlOX0NMVVNURVJfU0laRT0xCiAgICAgIC0gJ1BPU1RHUkVTX0JBQ0tFTkRfVVJMPXBvc3RncmVzcWw6Ly9zdXBhYmFzZV9hZG1pbjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9L19zdXBhYmFzZScKICAgICAgLSBQT1NUR1JFU19CQUNLRU5EX1NDSEVNQT1fYW5hbHl0aWNzCiAgICAgIC0gTE9HRkxBUkVfRkVBVFVSRV9GTEFHX09WRVJSSURFPW11bHRpYmFja2VuZD10cnVlCiAgc3VwYWJhc2UtdmVjdG9yOgogICAgaW1hZ2U6ICd0aW1iZXJpby92ZWN0b3I6MC4yOC4xLWFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vc3VwYWJhc2UtdmVjdG9yOjkwMDEvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9sb2dzL3ZlY3Rvci55bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvdmVjdG9yL3ZlY3Rvci55bWwKICAgICAgICByZWFkX29ubHk6IHRydWUKICAgICAgICBjb250ZW50OiAiYXBpOlxuICBlbmFibGVkOiB0cnVlXG4gIGFkZHJlc3M6IDAuMC4wLjA6OTAwMVxuXG5zb3VyY2VzOlxuICBkb2NrZXJfaG9zdDpcbiAgICB0eXBlOiBkb2NrZXJfbG9nc1xuICAgIGV4Y2x1ZGVfY29udGFpbmVyczpcbiAgICAgIC0gc3VwYWJhc2UtdmVjdG9yXG5cbnRyYW5zZm9ybXM6XG4gIHByb2plY3RfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gZG9ja2VyX2hvc3RcbiAgICBzb3VyY2U6IHwtXG4gICAgICAucHJvamVjdCA9IFwiZGVmYXVsdFwiXG4gICAgICAuZXZlbnRfbWVzc2FnZSA9IGRlbCgubWVzc2FnZSlcbiAgICAgIC5hcHBuYW1lID0gZGVsKC5jb250YWluZXJfbmFtZSlcbiAgICAgIGRlbCguY29udGFpbmVyX2NyZWF0ZWRfYXQpXG4gICAgICBkZWwoLmNvbnRhaW5lcl9pZClcbiAgICAgIGRlbCguc291cmNlX3R5cGUpXG4gICAgICBkZWwoLnN0cmVhbSlcbiAgICAgIGRlbCgubGFiZWwpXG4gICAgICBkZWwoLmltYWdlKVxuICAgICAgZGVsKC5ob3N0KVxuICAgICAgZGVsKC5zdHJlYW0pXG4gIHJvdXRlcjpcbiAgICB0eXBlOiByb3V0ZVxuICAgIGlucHV0czpcbiAgICAgIC0gcHJvamVjdF9sb2dzXG4gICAgcm91dGU6XG4gICAgICBrb25nOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2Uta29uZ1wiKSdcbiAgICAgIGF1dGg6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1hdXRoXCIpJ1xuICAgICAgcmVzdDogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInN1cGFiYXNlLXJlc3RcIiknXG4gICAgICByZWFsdGltZTogJ3N0YXJ0c193aXRoKHN0cmluZyEoLmFwcG5hbWUpLCBcInJlYWx0aW1lLWRldlwiKSdcbiAgICAgIHN0b3JhZ2U6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1zdG9yYWdlXCIpJ1xuICAgICAgZnVuY3Rpb25zOiAnc3RhcnRzX3dpdGgoc3RyaW5nISguYXBwbmFtZSksIFwic3VwYWJhc2UtZnVuY3Rpb25zXCIpJ1xuICAgICAgZGI6ICdzdGFydHNfd2l0aChzdHJpbmchKC5hcHBuYW1lKSwgXCJzdXBhYmFzZS1kYlwiKSdcbiAgIyBJZ25vcmVzIG5vbiBuZ2lueCBlcnJvcnMgc2luY2UgdGhleSBhcmUgcmVsYXRlZCB3aXRoIGtvbmcgYm9vdGluZyB1cFxuICBrb25nX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5rb25nXG4gICAgc291cmNlOiB8LVxuICAgICAgcmVxLCBlcnIgPSBwYXJzZV9uZ2lueF9sb2coLmV2ZW50X21lc3NhZ2UsIFwiY29tYmluZWRcIilcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAudGltZXN0YW1wID0gcmVxLnRpbWVzdGFtcFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0LmhlYWRlcnMucmVmZXJlciA9IHJlcS5yZWZlcmVyXG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy51c2VyX2FnZW50ID0gcmVxLmFnZW50XG4gICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QuaGVhZGVycy5jZl9jb25uZWN0aW5nX2lwID0gcmVxLmNsaWVudFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXF1ZXN0Lm1ldGhvZCA9IHJlcS5tZXRob2RcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wYXRoID0gcmVxLnBhdGhcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wcm90b2NvbCA9IHJlcS5wcm90b2NvbFxuICAgICAgICAgIC5tZXRhZGF0YS5yZXNwb25zZS5zdGF0dXNfY29kZSA9IHJlcS5zdGF0dXNcbiAgICAgIH1cbiAgICAgIGlmIGVyciAhPSBudWxsIHtcbiAgICAgICAgYWJvcnRcbiAgICAgIH1cbiAgIyBJZ25vcmVzIG5vbiBuZ2lueCBlcnJvcnMgc2luY2UgdGhleSBhcmUgcmVsYXRlZCB3aXRoIGtvbmcgYm9vdGluZyB1cFxuICBrb25nX2VycjpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmtvbmdcbiAgICBzb3VyY2U6IHwtXG4gICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSBcIkdFVFwiXG4gICAgICAubWV0YWRhdGEucmVzcG9uc2Uuc3RhdHVzX2NvZGUgPSAyMDBcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfbmdpbnhfbG9nKC5ldmVudF9tZXNzYWdlLCBcImVycm9yXCIpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lc3RhbXBcbiAgICAgICAgICAuc2V2ZXJpdHkgPSBwYXJzZWQuc2V2ZXJpdHlcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5ob3N0ID0gcGFyc2VkLmhvc3RcbiAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5oZWFkZXJzLmNmX2Nvbm5lY3RpbmdfaXAgPSBwYXJzZWQuY2xpZW50XG4gICAgICAgICAgdXJsLCBlcnIgPSBzcGxpdChwYXJzZWQucmVxdWVzdCwgXCIgXCIpXG4gICAgICAgICAgaWYgZXJyID09IG51bGwge1xuICAgICAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5tZXRob2QgPSB1cmxbMF1cbiAgICAgICAgICAgICAgLm1ldGFkYXRhLnJlcXVlc3QucGF0aCA9IHVybFsxXVxuICAgICAgICAgICAgICAubWV0YWRhdGEucmVxdWVzdC5wcm90b2NvbCA9IHVybFsyXVxuICAgICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIGVyciAhPSBudWxsIHtcbiAgICAgICAgYWJvcnRcbiAgICAgIH1cbiAgIyBHb3RydWUgbG9ncyBhcmUgc3RydWN0dXJlZCBqc29uIHN0cmluZ3Mgd2hpY2ggZnJvbnRlbmQgcGFyc2VzIGRpcmVjdGx5LiBCdXQgd2Uga2VlcCBtZXRhZGF0YSBmb3IgY29uc2lzdGVuY3kuXG4gIGF1dGhfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmF1dGhcbiAgICBzb3VyY2U6IHwtXG4gICAgICBwYXJzZWQsIGVyciA9IHBhcnNlX2pzb24oLmV2ZW50X21lc3NhZ2UpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLm1ldGFkYXRhLnRpbWVzdGFtcCA9IHBhcnNlZC50aW1lXG4gICAgICAgICAgLm1ldGFkYXRhID0gbWVyZ2UhKC5tZXRhZGF0YSwgcGFyc2VkKVxuICAgICAgfVxuICAjIFBvc3RnUkVTVCBsb2dzIGFyZSBzdHJ1Y3R1cmVkIHNvIHdlIHNlcGFyYXRlIHRpbWVzdGFtcCBmcm9tIG1lc3NhZ2UgdXNpbmcgcmVnZXhcbiAgcmVzdF9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIucmVzdFxuICAgIHNvdXJjZTogfC1cbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInXig/UDx0aW1lPi4qKTogKD9QPG1zZz4uKikkJylcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAuZXZlbnRfbWVzc2FnZSA9IHBhcnNlZC5tc2dcbiAgICAgICAgICAudGltZXN0YW1wID0gdG9fdGltZXN0YW1wIShwYXJzZWQudGltZSlcbiAgICAgICAgICAubWV0YWRhdGEuaG9zdCA9IC5wcm9qZWN0XG4gICAgICB9XG4gICMgUmVhbHRpbWUgbG9ncyBhcmUgc3RydWN0dXJlZCBzbyB3ZSBwYXJzZSB0aGUgc2V2ZXJpdHkgbGV2ZWwgdXNpbmcgcmVnZXggKGlnbm9yZSB0aW1lIGJlY2F1c2UgaXQgaGFzIG5vIGRhdGUpXG4gIHJlYWx0aW1lX2xvZ3M6XG4gICAgdHlwZTogcmVtYXBcbiAgICBpbnB1dHM6XG4gICAgICAtIHJvdXRlci5yZWFsdGltZVxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5wcm9qZWN0ID0gZGVsKC5wcm9qZWN0KVxuICAgICAgLm1ldGFkYXRhLmV4dGVybmFsX2lkID0gLm1ldGFkYXRhLnByb2plY3RcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInXig/UDx0aW1lPlxcZCs6XFxkKzpcXGQrXFwuXFxkKykgXFxbKD9QPGxldmVsPlxcdyspXFxdICg/UDxtc2c+LiopJCcpXG4gICAgICBpZiBlcnIgPT0gbnVsbCB7XG4gICAgICAgICAgLmV2ZW50X21lc3NhZ2UgPSBwYXJzZWQubXNnXG4gICAgICAgICAgLm1ldGFkYXRhLmxldmVsID0gcGFyc2VkLmxldmVsXG4gICAgICB9XG4gICMgU3RvcmFnZSBsb2dzIG1heSBjb250YWluIGpzb24gb2JqZWN0cyBzbyB3ZSBwYXJzZSB0aGVtIGZvciBjb21wbGV0ZW5lc3NcbiAgc3RvcmFnZV9sb2dzOlxuICAgIHR5cGU6IHJlbWFwXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIuc3RvcmFnZVxuICAgIHNvdXJjZTogfC1cbiAgICAgIC5tZXRhZGF0YS5wcm9qZWN0ID0gZGVsKC5wcm9qZWN0KVxuICAgICAgLm1ldGFkYXRhLnRlbmFudElkID0gLm1ldGFkYXRhLnByb2plY3RcbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfanNvbiguZXZlbnRfbWVzc2FnZSlcbiAgICAgIGlmIGVyciA9PSBudWxsIHtcbiAgICAgICAgICAuZXZlbnRfbWVzc2FnZSA9IHBhcnNlZC5tc2dcbiAgICAgICAgICAubWV0YWRhdGEubGV2ZWwgPSBwYXJzZWQubGV2ZWxcbiAgICAgICAgICAubWV0YWRhdGEudGltZXN0YW1wID0gcGFyc2VkLnRpbWVcbiAgICAgICAgICAubWV0YWRhdGEuY29udGV4dFswXS5ob3N0ID0gcGFyc2VkLmhvc3RuYW1lXG4gICAgICAgICAgLm1ldGFkYXRhLmNvbnRleHRbMF0ucGlkID0gcGFyc2VkLnBpZFxuICAgICAgfVxuICAjIFBvc3RncmVzIGxvZ3Mgc29tZSBtZXNzYWdlcyB0byBzdGRlcnIgd2hpY2ggd2UgbWFwIHRvIHdhcm5pbmcgc2V2ZXJpdHkgbGV2ZWxcbiAgZGJfbG9nczpcbiAgICB0eXBlOiByZW1hcFxuICAgIGlucHV0czpcbiAgICAgIC0gcm91dGVyLmRiXG4gICAgc291cmNlOiB8LVxuICAgICAgLm1ldGFkYXRhLmhvc3QgPSBcImRiLWRlZmF1bHRcIlxuICAgICAgLm1ldGFkYXRhLnBhcnNlZC50aW1lc3RhbXAgPSAudGltZXN0YW1wXG5cbiAgICAgIHBhcnNlZCwgZXJyID0gcGFyc2VfcmVnZXgoLmV2ZW50X21lc3NhZ2UsIHInLiooP1A8bGV2ZWw+SU5GT3xOT1RJQ0V8V0FSTklOR3xFUlJPUnxMT0d8RkFUQUx8UEFOSUM/KTouKicsIG51bWVyaWNfZ3JvdXBzOiB0cnVlKVxuXG4gICAgICBpZiBlcnIgIT0gbnVsbCB8fCBwYXJzZWQgPT0gbnVsbCB7XG4gICAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSBcImluZm9cIlxuICAgICAgfVxuICAgICAgaWYgcGFyc2VkICE9IG51bGwge1xuICAgICAgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9IHBhcnNlZC5sZXZlbFxuICAgICAgfVxuICAgICAgaWYgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9PSBcImluZm9cIiB7XG4gICAgICAgICAgLm1ldGFkYXRhLnBhcnNlZC5lcnJvcl9zZXZlcml0eSA9IFwibG9nXCJcbiAgICAgIH1cbiAgICAgIC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkgPSB1cGNhc2UhKC5tZXRhZGF0YS5wYXJzZWQuZXJyb3Jfc2V2ZXJpdHkpXG5cbnNpbmtzOlxuICBsb2dmbGFyZV9hdXRoOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gYXV0aF9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWdvdHJ1ZS5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3JlYWx0aW1lOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0gcmVhbHRpbWVfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1yZWFsdGltZS5sb2dzLnByb2QmYXBpX2tleT0ke0xPR0ZMQVJFX0FQSV9LRVk/TE9HRkxBUkVfQVBJX0tFWSBpcyByZXF1aXJlZH0nXG4gIGxvZ2ZsYXJlX3Jlc3Q6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSByZXN0X2xvZ3NcbiAgICBlbmNvZGluZzpcbiAgICAgIGNvZGVjOiAnanNvbidcbiAgICBtZXRob2Q6ICdwb3N0J1xuICAgIHJlcXVlc3Q6XG4gICAgICByZXRyeV9tYXhfZHVyYXRpb25fc2VjczogMTBcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2UtYW5hbHl0aWNzOjQwMDAvYXBpL2xvZ3M/c291cmNlX25hbWU9cG9zdGdSRVNULmxvZ3MucHJvZCZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfZGI6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSBkYl9sb2dzXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgIyBXZSBtdXN0IHJvdXRlIHRoZSBzaW5rIHRocm91Z2gga29uZyBiZWNhdXNlIGluZ2VzdGluZyBsb2dzIGJlZm9yZSBsb2dmbGFyZSBpcyBmdWxseSBpbml0aWFsaXNlZCB3aWxsXG4gICAgIyBsZWFkIHRvIGJyb2tlbiBxdWVyaWVzIGZyb20gc3R1ZGlvLiBUaGlzIHdvcmtzIGJ5IHRoZSBhc3N1bXB0aW9uIHRoYXQgY29udGFpbmVycyBhcmUgc3RhcnRlZCBpbiB0aGVcbiAgICAjIGZvbGxvd2luZyBvcmRlcjogdmVjdG9yID4gZGIgPiBsb2dmbGFyZSA+IGtvbmdcbiAgICB1cmk6ICdodHRwOi8vc3VwYWJhc2Uta29uZzo4MDAwL2FuYWx5dGljcy92MS9hcGkvbG9ncz9zb3VyY2VfbmFtZT1wb3N0Z3Jlcy5sb2dzJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9mdW5jdGlvbnM6XG4gICAgdHlwZTogJ2h0dHAnXG4gICAgaW5wdXRzOlxuICAgICAgLSByb3V0ZXIuZnVuY3Rpb25zXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWRlbm8tcmVsYXktbG9ncyZhcGlfa2V5PSR7TE9HRkxBUkVfQVBJX0tFWT9MT0dGTEFSRV9BUElfS0VZIGlzIHJlcXVpcmVkfSdcbiAgbG9nZmxhcmVfc3RvcmFnZTpcbiAgICB0eXBlOiAnaHR0cCdcbiAgICBpbnB1dHM6XG4gICAgICAtIHN0b3JhZ2VfbG9nc1xuICAgIGVuY29kaW5nOlxuICAgICAgY29kZWM6ICdqc29uJ1xuICAgIG1ldGhvZDogJ3Bvc3QnXG4gICAgcmVxdWVzdDpcbiAgICAgIHJldHJ5X21heF9kdXJhdGlvbl9zZWNzOiAxMFxuICAgIHVyaTogJ2h0dHA6Ly9zdXBhYmFzZS1hbmFseXRpY3M6NDAwMC9hcGkvbG9ncz9zb3VyY2VfbmFtZT1zdG9yYWdlLmxvZ3MucHJvZC4yJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuICBsb2dmbGFyZV9rb25nOlxuICAgIHR5cGU6ICdodHRwJ1xuICAgIGlucHV0czpcbiAgICAgIC0ga29uZ19sb2dzXG4gICAgICAtIGtvbmdfZXJyXG4gICAgZW5jb2Rpbmc6XG4gICAgICBjb2RlYzogJ2pzb24nXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgICByZXF1ZXN0OlxuICAgICAgcmV0cnlfbWF4X2R1cmF0aW9uX3NlY3M6IDEwXG4gICAgdXJpOiAnaHR0cDovL3N1cGFiYXNlLWFuYWx5dGljczo0MDAwL2FwaS9sb2dzP3NvdXJjZV9uYW1lPWNsb3VkZmxhcmUubG9ncy5wcm9kJmFwaV9rZXk9JHtMT0dGTEFSRV9BUElfS0VZP0xPR0ZMQVJFX0FQSV9LRVkgaXMgcmVxdWlyZWR9J1xuIgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jazpybycKICAgIGVudmlyb25tZW50OgogICAgICAtICdMT0dGTEFSRV9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9MT0dGTEFSRX0nCiAgICBjb21tYW5kOgogICAgICAtICctLWNvbmZpZycKICAgICAgLSBldGMvdmVjdG9yL3ZlY3Rvci55bWwKICBzdXBhYmFzZS1yZXN0OgogICAgaW1hZ2U6ICdwb3N0Z3Jlc3QvcG9zdGdyZXN0OnYxMi4yLjEyJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUEdSU1RfREJfVVJJPXBvc3RncmVzOi8vYXV0aGVudGljYXRvcjoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnUEdSU1RfREJfU0NIRU1BUz0ke1BHUlNUX0RCX1NDSEVNQVM6LXB1YmxpYyxzdG9yYWdlLGdyYXBocWxfcHVibGljfScKICAgICAgLSBQR1JTVF9EQl9BTk9OX1JPTEU9YW5vbgogICAgICAtICdQR1JTVF9KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIFBHUlNUX0RCX1VTRV9MRUdBQ1lfR1VDUz1mYWxzZQogICAgICAtICdQR1JTVF9BUFBfU0VUVElOR1NfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnUEdSU1RfQVBQX1NFVFRJTkdTX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgIGNvbW1hbmQ6IHBvc3RncmVzdAogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgc3VwYWJhc2UtYXV0aDoKICAgIGltYWdlOiAnc3VwYWJhc2UvZ290cnVlOnYyLjE3NC4wJwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtYW5hbHl0aWNzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo5OTk5L2hlYWx0aCcKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPVFJVRV9BUElfSE9TVD0wLjAuMC4wCiAgICAgIC0gR09UUlVFX0FQSV9QT1JUPTk5OTkKICAgICAgLSAnQVBJX0VYVEVSTkFMX1VSTD0ke0FQSV9FWFRFUk5BTF9VUkw6LWh0dHA6Ly9zdXBhYmFzZS1rb25nOjgwMDB9JwogICAgICAtIEdPVFJVRV9EQl9EUklWRVI9cG9zdGdyZXMKICAgICAgLSAnR09UUlVFX0RCX0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovL3N1cGFiYXNlX2F1dGhfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0dPVFJVRV9TSVRFX1VSTD0ke1NFUlZJQ0VfRlFETl9TVVBBQkFTRUtPTkd9JwogICAgICAtICdHT1RSVUVfVVJJX0FMTE9XX0xJU1Q9JHtBRERJVElPTkFMX1JFRElSRUNUX1VSTFN9JwogICAgICAtICdHT1RSVUVfRElTQUJMRV9TSUdOVVA9JHtESVNBQkxFX1NJR05VUDotZmFsc2V9JwogICAgICAtIEdPVFJVRV9KV1RfQURNSU5fUk9MRVM9c2VydmljZV9yb2xlCiAgICAgIC0gR09UUlVFX0pXVF9BVUQ9YXV0aGVudGljYXRlZAogICAgICAtIEdPVFJVRV9KV1RfREVGQVVMVF9HUk9VUF9OQU1FPWF1dGhlbnRpY2F0ZWQKICAgICAgLSAnR09UUlVFX0pXVF9FWFA9JHtKV1RfRVhQSVJZOi0zNjAwfScKICAgICAgLSAnR09UUlVFX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0dPVFJVRV9FWFRFUk5BTF9FTUFJTF9FTkFCTEVEPSR7RU5BQkxFX0VNQUlMX1NJR05VUDotdHJ1ZX0nCiAgICAgIC0gJ0dPVFJVRV9FWFRFUk5BTF9BTk9OWU1PVVNfVVNFUlNfRU5BQkxFRD0ke0VOQUJMRV9BTk9OWU1PVVNfVVNFUlM6LWZhbHNlfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9BVVRPQ09ORklSTT0ke0VOQUJMRV9FTUFJTF9BVVRPQ09ORklSTTotZmFsc2V9JwogICAgICAtICdHT1RSVUVfU01UUF9BRE1JTl9FTUFJTD0ke1NNVFBfQURNSU5fRU1BSUx9JwogICAgICAtICdHT1RSVUVfU01UUF9IT1NUPSR7U01UUF9IT1NUfScKICAgICAgLSAnR09UUlVFX1NNVFBfUE9SVD0ke1NNVFBfUE9SVDotNTg3fScKICAgICAgLSAnR09UUlVFX1NNVFBfVVNFUj0ke1NNVFBfVVNFUn0nCiAgICAgIC0gJ0dPVFJVRV9TTVRQX1BBU1M9JHtTTVRQX1BBU1N9JwogICAgICAtICdHT1RSVUVfU01UUF9TRU5ERVJfTkFNRT0ke1NNVFBfU0VOREVSX05BTUV9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1VSTFBBVEhTX0lOVklURT0ke01BSUxFUl9VUkxQQVRIU19JTlZJVEU6LS9hdXRoL3YxL3ZlcmlmeX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVVJMUEFUSFNfQ09ORklSTUFUSU9OPSR7TUFJTEVSX1VSTFBBVEhTX0NPTkZJUk1BVElPTjotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19SRUNPVkVSWT0ke01BSUxFUl9VUkxQQVRIU19SRUNPVkVSWTotL2F1dGgvdjEvdmVyaWZ5fScKICAgICAgLSAnR09UUlVFX01BSUxFUl9VUkxQQVRIU19FTUFJTF9DSEFOR0U9JHtNQUlMRVJfVVJMUEFUSFNfRU1BSUxfQ0hBTkdFOi0vYXV0aC92MS92ZXJpZnl9JwogICAgICAtICdHT1RSVUVfTUFJTEVSX1RFTVBMQVRFU19JTlZJVEU9JHtNQUlMRVJfVEVNUExBVEVTX0lOVklURX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0NPTkZJUk1BVElPTj0ke01BSUxFUl9URU1QTEFURVNfQ09ORklSTUFUSU9OfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfUkVDT1ZFUlk9JHtNQUlMRVJfVEVNUExBVEVTX1JFQ09WRVJZfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9URU1QTEFURVNfTUFHSUNfTElOSz0ke01BSUxFUl9URU1QTEFURVNfTUFHSUNfTElOS30nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfVEVNUExBVEVTX0VNQUlMX0NIQU5HRT0ke01BSUxFUl9URU1QTEFURVNfRU1BSUxfQ0hBTkdFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19DT05GSVJNQVRJT049JHtNQUlMRVJfU1VCSkVDVFNfQ09ORklSTUFUSU9OfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19SRUNPVkVSWT0ke01BSUxFUl9TVUJKRUNUU19SRUNPVkVSWX0nCiAgICAgIC0gJ0dPVFJVRV9NQUlMRVJfU1VCSkVDVFNfTUFHSUNfTElOSz0ke01BSUxFUl9TVUJKRUNUU19NQUdJQ19MSU5LfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19FTUFJTF9DSEFOR0U9JHtNQUlMRVJfU1VCSkVDVFNfRU1BSUxfQ0hBTkdFfScKICAgICAgLSAnR09UUlVFX01BSUxFUl9TVUJKRUNUU19JTlZJVEU9JHtNQUlMRVJfU1VCSkVDVFNfSU5WSVRFfScKICAgICAgLSAnR09UUlVFX0VYVEVSTkFMX1BIT05FX0VOQUJMRUQ9JHtFTkFCTEVfUEhPTkVfU0lHTlVQOi10cnVlfScKICAgICAgLSAnR09UUlVFX1NNU19BVVRPQ09ORklSTT0ke0VOQUJMRV9QSE9ORV9BVVRPQ09ORklSTTotdHJ1ZX0nCiAgcmVhbHRpbWUtZGV2OgogICAgaW1hZ2U6ICdzdXBhYmFzZS9yZWFsdGltZTp2Mi4zNC40NycKICAgIGNvbnRhaW5lcl9uYW1lOiByZWFsdGltZS1kZXYuc3VwYWJhc2UtcmVhbHRpbWUKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctc1NmTCcKICAgICAgICAtICctLWhlYWQnCiAgICAgICAgLSAnLW8nCiAgICAgICAgLSAvZGV2L251bGwKICAgICAgICAtICctSCcKICAgICAgICAtICdBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtTRVJWSUNFX1NVUEFCQVNFQU5PTl9LRVl9JwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NDAwMC9hcGkvdGVuYW50cy9yZWFsdGltZS1kZXYvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9SVD00MDAwCiAgICAgIC0gJ0RCX0hPU1Q9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdEQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gREJfVVNFUj1zdXBhYmFzZV9hZG1pbgogICAgICAtICdEQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdEQl9OQU1FPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnREJfQUZURVJfQ09OTkVDVF9RVUVSWT1TRVQgc2VhcmNoX3BhdGggVE8gX3JlYWx0aW1lJwogICAgICAtIERCX0VOQ19LRVk9c3VwYWJhc2VyZWFsdGltZQogICAgICAtICdBUElfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSBGTFlfQUxMT0NfSUQ9Zmx5MTIzCiAgICAgIC0gRkxZX0FQUF9OQU1FPXJlYWx0aW1lCiAgICAgIC0gJ1NFQ1JFVF9LRVlfQkFTRT0ke1NFQ1JFVF9QQVNTV09SRF9SRUFMVElNRX0nCiAgICAgIC0gJ0VSTF9BRkxBR1M9LXByb3RvX2Rpc3QgaW5ldF90Y3AnCiAgICAgIC0gRU5BQkxFX1RBSUxTQ0FMRT1mYWxzZQogICAgICAtICJETlNfTk9ERVM9JyciCiAgICAgIC0gUkxJTUlUX05PRklMRT0xMDAwMAogICAgICAtIEFQUF9OQU1FPXJlYWx0aW1lCiAgICAgIC0gU0VFRF9TRUxGX0hPU1Q9dHJ1ZQogICAgICAtIExPR19MRVZFTD1lcnJvcgogICAgICAtIFJVTl9KQU5JVE9SPXRydWUKICAgICAgLSBKQU5JVE9SX0lOVEVSVkFMPTYwMDAwCiAgICBjb21tYW5kOiAic2ggLWMgXCIvYXBwL2Jpbi9taWdyYXRlICYmIC9hcHAvYmluL3JlYWx0aW1lIGV2YWwgJ1JlYWx0aW1lLlJlbGVhc2Uuc2VlZHMoUmVhbHRpbWUuUmVwbyknICYmIC9hcHAvYmluL3NlcnZlclwiXG4iCiAgc3VwYWJhc2UtbWluaW86CiAgICBpbWFnZTogJ2doY3IuaW8vY29vbGxhYnNpby9taW5pbzpSRUxFQVNFLjIwMjUtMTAtMTVUMTctMjktNTVaJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01JTklPX1JPT1RfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NSU5JT30nCiAgICAgIC0gJ01JTklPX1JPT1RfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01JTklPfScKICAgIGNvbW1hbmQ6ICdzZXJ2ZXIgLS1jb25zb2xlLWFkZHJlc3MgIjo5MDAxIiAvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBtYwogICAgICAgIC0gcmVhZHkKICAgICAgICAtIGxvY2FsCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vdm9sdW1lcy9zdG9yYWdlOi9kYXRhJwogIG1pbmlvLWNyZWF0ZWJ1Y2tldDoKICAgIGltYWdlOiBtaW5pby9tYwogICAgcmVzdGFydDogJ25vJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01JTklPX1JPT1RfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NSU5JT30nCiAgICAgIC0gJ01JTklPX1JPT1RfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01JTklPfScKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLW1pbmlvOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnRyeXBvaW50OgogICAgICAtIC9lbnRyeXBvaW50LnNoCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9lbnRyeXBvaW50LnNoCiAgICAgICAgdGFyZ2V0OiAvZW50cnlwb2ludC5zaAogICAgICAgIGNvbnRlbnQ6ICIjIS9iaW4vc2hcbi91c3IvYmluL21jIGFsaWFzIHNldCBzdXBhYmFzZS1taW5pbyBodHRwOi8vc3VwYWJhc2UtbWluaW86OTAwMCAke01JTklPX1JPT1RfVVNFUn0gJHtNSU5JT19ST09UX1BBU1NXT1JEfTtcbi91c3IvYmluL21jIG1iIC0taWdub3JlLWV4aXN0aW5nIHN1cGFiYXNlLW1pbmlvL3N0dWI7XG5leGl0IDBcbiIKICBzdXBhYmFzZS1zdG9yYWdlOgogICAgaW1hZ2U6ICdzdXBhYmFzZS9zdG9yYWdlLWFwaTp2MS4xNC42JwogICAgZGVwZW5kc19vbjoKICAgICAgc3VwYWJhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc3VwYWJhc2UtcmVzdDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2Vfc3RhcnRlZAogICAgICBpbWdwcm94eToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2Vfc3RhcnRlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHdnZXQKICAgICAgICAtICctLW5vLXZlcmJvc2UnCiAgICAgICAgLSAnLS10cmllcz0xJwogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NTAwMC9zdGF0dXMnCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWRVJfUE9SVD01MDAwCiAgICAgIC0gU0VSVkVSX1JFR0lPTj1sb2NhbAogICAgICAtIE1VTFRJX1RFTkFOVD1mYWxzZQogICAgICAtICdBVVRIX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVH0nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovL3N1cGFiYXNlX3N0b3JhZ2VfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gREJfSU5TVEFMTF9ST0xFUz1mYWxzZQogICAgICAtIFNUT1JBR0VfQkFDS0VORD1zMwogICAgICAtIFNUT1JBR0VfUzNfQlVDS0VUPXN0dWIKICAgICAgLSAnU1RPUkFHRV9TM19FTkRQT0lOVD1odHRwOi8vc3VwYWJhc2UtbWluaW86OTAwMCcKICAgICAgLSBTVE9SQUdFX1MzX0ZPUkNFX1BBVEhfU1RZTEU9dHJ1ZQogICAgICAtIFNUT1JBR0VfUzNfUkVHSU9OPXVzLWVhc3QtMQogICAgICAtICdBV1NfQUNDRVNTX0tFWV9JRD0ke1NFUlZJQ0VfVVNFUl9NSU5JT30nCiAgICAgIC0gJ0FXU19TRUNSRVRfQUNDRVNTX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgICAtIFVQTE9BRF9GSUxFX1NJWkVfTElNSVQ9NTI0Mjg4MDAwCiAgICAgIC0gVVBMT0FEX0ZJTEVfU0laRV9MSU1JVF9TVEFOREFSRD01MjQyODgwMDAKICAgICAgLSBVUExPQURfU0lHTkVEX1VSTF9FWFBJUkFUSU9OX1RJTUU9MTIwCiAgICAgIC0gVFVTX1VSTF9QQVRIPXVwbG9hZC9yZXN1bWFibGUKICAgICAgLSBUVVNfTUFYX1NJWkU9MzYwMDAwMAogICAgICAtIEVOQUJMRV9JTUFHRV9UUkFOU0ZPUk1BVElPTj10cnVlCiAgICAgIC0gJ0lNR1BST1hZX1VSTD1odHRwOi8vaW1ncHJveHk6ODA4MCcKICAgICAgLSBJTUdQUk9YWV9SRVFVRVNUX1RJTUVPVVQ9MTUKICAgICAgLSBEQVRBQkFTRV9TRUFSQ0hfUEFUSD1zdG9yYWdlCiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvbgogICAgICAtIFJFUVVFU1RfQUxMT1dfWF9GT1JXQVJERURfUEFUSD10cnVlCiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvc3RvcmFnZTovdmFyL2xpYi9zdG9yYWdlJwogIGltZ3Byb3h5OgogICAgaW1hZ2U6ICdkYXJ0aHNpbS9pbWdwcm94eTp2My44LjAnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gaW1ncHJveHkKICAgICAgICAtIGhlYWx0aAogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSU1HUFJPWFlfTE9DQUxfRklMRVNZU1RFTV9ST09UPS8KICAgICAgLSBJTUdQUk9YWV9VU0VfRVRBRz10cnVlCiAgICAgIC0gJ0lNR1BST1hZX0VOQUJMRV9XRUJQX0RFVEVDVElPTj0ke0lNR1BST1hZX0VOQUJMRV9XRUJQX0RFVEVDVElPTjotdHJ1ZX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvc3RvcmFnZTovdmFyL2xpYi9zdG9yYWdlJwogIHN1cGFiYXNlLW1ldGE6CiAgICBpbWFnZTogJ3N1cGFiYXNlL3Bvc3RncmVzLW1ldGE6djAuODkuMycKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEdfTUVUQV9QT1JUPTgwODAKICAgICAgLSAnUEdfTUVUQV9EQl9IT1NUPSR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifScKICAgICAgLSAnUEdfTUVUQV9EQl9QT1JUPSR7UE9TVEdSRVNfUE9SVDotNTQzMn0nCiAgICAgIC0gJ1BHX01FVEFfREJfTkFNRT0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gUEdfTUVUQV9EQl9VU0VSPXN1cGFiYXNlX2FkbWluCiAgICAgIC0gJ1BHX01FVEFfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICBzdXBhYmFzZS1lZGdlLWZ1bmN0aW9uczoKICAgIGltYWdlOiAnc3VwYWJhc2UvZWRnZS1ydW50aW1lOnYxLjY3LjQnCiAgICBkZXBlbmRzX29uOgogICAgICBzdXBhYmFzZS1hbmFseXRpY3M6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSAnRWRnZSBGdW5jdGlvbnMgaXMgaGVhbHRoeScKICAgICAgdGltZW91dDogNXMKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdTVVBBQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fU1VQQUJBU0VLT05HfScKICAgICAgLSAnU1VQQUJBU0VfQU5PTl9LRVk9JHtTRVJWSUNFX1NVUEFCQVNFQU5PTl9LRVl9JwogICAgICAtICdTVVBBQkFTRV9TRVJWSUNFX1JPTEVfS0VZPSR7U0VSVklDRV9TVVBBQkFTRVNFUlZJQ0VfS0VZfScKICAgICAgLSAnU1VQQUJBU0VfREJfVVJMPXBvc3RncmVzcWw6Ly9wb3N0Z3Jlczoke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QCR7UE9TVEdSRVNfSE9TVE5BTUU6LXN1cGFiYXNlLWRifToke1BPU1RHUkVTX1BPUlQ6LTU0MzJ9LyR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnVkVSSUZZX0pXVD0ke0ZVTkNUSU9OU19WRVJJRllfSldUOi1mYWxzZX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL3ZvbHVtZXMvZnVuY3Rpb25zOi9ob21lL2Rlbm8vZnVuY3Rpb25zJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi92b2x1bWVzL2Z1bmN0aW9ucy9tYWluL2luZGV4LnRzCiAgICAgICAgdGFyZ2V0OiAvaG9tZS9kZW5vL2Z1bmN0aW9ucy9tYWluL2luZGV4LnRzCiAgICAgICAgY29udGVudDogImltcG9ydCB7IHNlcnZlIH0gZnJvbSAnaHR0cHM6Ly9kZW5vLmxhbmQvc3RkQDAuMTMxLjAvaHR0cC9zZXJ2ZXIudHMnXG5pbXBvcnQgKiBhcyBqb3NlIGZyb20gJ2h0dHBzOi8vZGVuby5sYW5kL3gvam9zZUB2NC4xNC40L2luZGV4LnRzJ1xuXG5jb25zb2xlLmxvZygnbWFpbiBmdW5jdGlvbiBzdGFydGVkJylcblxuY29uc3QgSldUX1NFQ1JFVCA9IERlbm8uZW52LmdldCgnSldUX1NFQ1JFVCcpXG5jb25zdCBWRVJJRllfSldUID0gRGVuby5lbnYuZ2V0KCdWRVJJRllfSldUJykgPT09ICd0cnVlJ1xuXG5mdW5jdGlvbiBnZXRBdXRoVG9rZW4ocmVxOiBSZXF1ZXN0KSB7XG4gIGNvbnN0IGF1dGhIZWFkZXIgPSByZXEuaGVhZGVycy5nZXQoJ2F1dGhvcml6YXRpb24nKVxuICBpZiAoIWF1dGhIZWFkZXIpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ01pc3NpbmcgYXV0aG9yaXphdGlvbiBoZWFkZXInKVxuICB9XG4gIGNvbnN0IFtiZWFyZXIsIHRva2VuXSA9IGF1dGhIZWFkZXIuc3BsaXQoJyAnKVxuICBpZiAoYmVhcmVyICE9PSAnQmVhcmVyJykge1xuICAgIHRocm93IG5ldyBFcnJvcihgQXV0aCBoZWFkZXIgaXMgbm90ICdCZWFyZXIge3Rva2VufSdgKVxuICB9XG4gIHJldHVybiB0b2tlblxufVxuXG5hc3luYyBmdW5jdGlvbiB2ZXJpZnlKV1Qoand0OiBzdHJpbmcpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgY29uc3QgZW5jb2RlciA9IG5ldyBUZXh0RW5jb2RlcigpXG4gIGNvbnN0IHNlY3JldEtleSA9IGVuY29kZXIuZW5jb2RlKEpXVF9TRUNSRVQpXG4gIHRyeSB7XG4gICAgYXdhaXQgam9zZS5qd3RWZXJpZnkoand0LCBzZWNyZXRLZXkpXG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGNvbnNvbGUuZXJyb3IoZXJyKVxuICAgIHJldHVybiBmYWxzZVxuICB9XG4gIHJldHVybiB0cnVlXG59XG5cbnNlcnZlKGFzeW5jIChyZXE6IFJlcXVlc3QpID0+IHtcbiAgaWYgKHJlcS5tZXRob2QgIT09ICdPUFRJT05TJyAmJiBWRVJJRllfSldUKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHRva2VuID0gZ2V0QXV0aFRva2VuKHJlcSlcbiAgICAgIGNvbnN0IGlzVmFsaWRKV1QgPSBhd2FpdCB2ZXJpZnlKV1QodG9rZW4pXG5cbiAgICAgIGlmICghaXNWYWxpZEpXVCkge1xuICAgICAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KHsgbXNnOiAnSW52YWxpZCBKV1QnIH0pLCB7XG4gICAgICAgICAgc3RhdHVzOiA0MDEsXG4gICAgICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG4gICAgICAgIH0pXG4gICAgICB9XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgY29uc29sZS5lcnJvcihlKVxuICAgICAgcmV0dXJuIG5ldyBSZXNwb25zZShKU09OLnN0cmluZ2lmeSh7IG1zZzogZS50b1N0cmluZygpIH0pLCB7XG4gICAgICAgIHN0YXR1czogNDAxLFxuICAgICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICAgIH0pXG4gICAgfVxuICB9XG5cbiAgY29uc3QgdXJsID0gbmV3IFVSTChyZXEudXJsKVxuICBjb25zdCB7IHBhdGhuYW1lIH0gPSB1cmxcbiAgY29uc3QgcGF0aF9wYXJ0cyA9IHBhdGhuYW1lLnNwbGl0KCcvJylcbiAgY29uc3Qgc2VydmljZV9uYW1lID0gcGF0aF9wYXJ0c1sxXVxuXG4gIGlmICghc2VydmljZV9uYW1lIHx8IHNlcnZpY2VfbmFtZSA9PT0gJycpIHtcbiAgICBjb25zdCBlcnJvciA9IHsgbXNnOiAnbWlzc2luZyBmdW5jdGlvbiBuYW1lIGluIHJlcXVlc3QnIH1cbiAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KGVycm9yKSwge1xuICAgICAgc3RhdHVzOiA0MDAsXG4gICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICB9KVxuICB9XG5cbiAgY29uc3Qgc2VydmljZVBhdGggPSBgL2hvbWUvZGVuby9mdW5jdGlvbnMvJHtzZXJ2aWNlX25hbWV9YFxuICBjb25zb2xlLmVycm9yKGBzZXJ2aW5nIHRoZSByZXF1ZXN0IHdpdGggJHtzZXJ2aWNlUGF0aH1gKVxuXG4gIGNvbnN0IG1lbW9yeUxpbWl0TWIgPSAxNTBcbiAgY29uc3Qgd29ya2VyVGltZW91dE1zID0gMSAqIDYwICogMTAwMFxuICBjb25zdCBub01vZHVsZUNhY2hlID0gZmFsc2VcbiAgY29uc3QgaW1wb3J0TWFwUGF0aCA9IG51bGxcbiAgY29uc3QgZW52VmFyc09iaiA9IERlbm8uZW52LnRvT2JqZWN0KClcbiAgY29uc3QgZW52VmFycyA9IE9iamVjdC5rZXlzKGVudlZhcnNPYmopLm1hcCgoaykgPT4gW2ssIGVudlZhcnNPYmpba11dKVxuXG4gIHRyeSB7XG4gICAgY29uc3Qgd29ya2VyID0gYXdhaXQgRWRnZVJ1bnRpbWUudXNlcldvcmtlcnMuY3JlYXRlKHtcbiAgICAgIHNlcnZpY2VQYXRoLFxuICAgICAgbWVtb3J5TGltaXRNYixcbiAgICAgIHdvcmtlclRpbWVvdXRNcyxcbiAgICAgIG5vTW9kdWxlQ2FjaGUsXG4gICAgICBpbXBvcnRNYXBQYXRoLFxuICAgICAgZW52VmFycyxcbiAgICB9KVxuICAgIHJldHVybiBhd2FpdCB3b3JrZXIuZmV0Y2gocmVxKVxuICB9IGNhdGNoIChlKSB7XG4gICAgY29uc3QgZXJyb3IgPSB7IG1zZzogZS50b1N0cmluZygpIH1cbiAgICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KGVycm9yKSwge1xuICAgICAgc3RhdHVzOiA1MDAsXG4gICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICB9KVxuICB9XG59KSIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vdm9sdW1lcy9mdW5jdGlvbnMvaGVsbG8vaW5kZXgudHMKICAgICAgICB0YXJnZXQ6IC9ob21lL2Rlbm8vZnVuY3Rpb25zL2hlbGxvL2luZGV4LnRzCiAgICAgICAgY29udGVudDogIi8vIEZvbGxvdyB0aGlzIHNldHVwIGd1aWRlIHRvIGludGVncmF0ZSB0aGUgRGVubyBsYW5ndWFnZSBzZXJ2ZXIgd2l0aCB5b3VyIGVkaXRvcjpcbi8vIGh0dHBzOi8vZGVuby5sYW5kL21hbnVhbC9nZXR0aW5nX3N0YXJ0ZWQvc2V0dXBfeW91cl9lbnZpcm9ubWVudFxuLy8gVGhpcyBlbmFibGVzIGF1dG9jb21wbGV0ZSwgZ28gdG8gZGVmaW5pdGlvbiwgZXRjLlxuXG5pbXBvcnQgeyBzZXJ2ZSB9IGZyb20gXCJodHRwczovL2Rlbm8ubGFuZC9zdGRAMC4xNzcuMS9odHRwL3NlcnZlci50c1wiXG5cbnNlcnZlKGFzeW5jICgpID0+IHtcbiAgcmV0dXJuIG5ldyBSZXNwb25zZShcbiAgICBgXCJIZWxsbyBmcm9tIEVkZ2UgRnVuY3Rpb25zIVwiYCxcbiAgICB7IGhlYWRlcnM6IHsgXCJDb250ZW50LVR5cGVcIjogXCJhcHBsaWNhdGlvbi9qc29uXCIgfSB9LFxuICApXG59KVxuXG4vLyBUbyBpbnZva2U6XG4vLyBjdXJsICdodHRwOi8vbG9jYWxob3N0OjxLT05HX0hUVFBfUE9SVD4vZnVuY3Rpb25zL3YxL2hlbGxvJyBcXFxuLy8gICAtLWhlYWRlciAnQXV0aG9yaXphdGlvbjogQmVhcmVyIDxhbm9uL3NlcnZpY2Vfcm9sZSBBUEkga2V5PidcbiIKICAgIGNvbW1hbmQ6CiAgICAgIC0gc3RhcnQKICAgICAgLSAnLS1tYWluLXNlcnZpY2UnCiAgICAgIC0gL2hvbWUvZGVuby9mdW5jdGlvbnMvbWFpbgogIHN1cGFiYXNlLXN1cGF2aXNvcjoKICAgIGltYWdlOiAnc3VwYWJhc2Uvc3VwYXZpc29yOjIuNS4xJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctc1NmTCcKICAgICAgICAtICctbycKICAgICAgICAtIC9kZXYvbnVsbAogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NDAwMC9hcGkvaGVhbHRoJwogICAgICB0aW1lb3V0OiA1cwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHN1cGFiYXNlLWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHN1cGFiYXNlLWFuYWx5dGljczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9PTEVSX1RFTkFOVF9JRD1kZXZfdGVuYW50CiAgICAgIC0gUE9PTEVSX1BPT0xfTU9ERT10cmFuc2FjdGlvbgogICAgICAtICdQT09MRVJfREVGQVVMVF9QT09MX1NJWkU9JHtQT09MRVJfREVGQVVMVF9QT09MX1NJWkU6LTIwfScKICAgICAgLSAnUE9PTEVSX01BWF9DTElFTlRfQ09OTj0ke1BPT0xFUl9NQVhfQ0xJRU5UX0NPTk46LTEwMH0nCiAgICAgIC0gUE9SVD00MDAwCiAgICAgIC0gJ1BPU1RHUkVTX1BPUlQ9JHtQT1NUR1JFU19QT1JUOi01NDMyfScKICAgICAgLSAnUE9TVEdSRVNfSE9TVE5BTUU9JHtQT1NUR1JFU19IT1NUTkFNRTotc3VwYWJhc2UtZGJ9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1lY3RvOi8vc3VwYWJhc2VfYWRtaW46JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUAke1BPU1RHUkVTX0hPU1ROQU1FOi1zdXBhYmFzZS1kYn06JHtQT1NUR1JFU19QT1JUOi01NDMyfS9fc3VwYWJhc2UnCiAgICAgIC0gQ0xVU1RFUl9QT1NUR1JFUz10cnVlCiAgICAgIC0gJ1NFQ1JFVF9LRVlfQkFTRT0ke1NFUlZJQ0VfUEFTU1dPUkRfU1VQQVZJU09SU0VDUkVUfScKICAgICAgLSAnVkFVTFRfRU5DX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfVkFVTFRFTkN9JwogICAgICAtICdBUElfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfSldUfScKICAgICAgLSAnTUVUUklDU19KV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtIFJFR0lPTj1sb2NhbAogICAgICAtICdFUkxfQUZMQUdTPS1wcm90b19kaXN0IGluZXRfdGNwJwogICAgY29tbWFuZDoKICAgICAgLSAvYmluL3NoCiAgICAgIC0gJy1jJwogICAgICAtICcvYXBwL2Jpbi9taWdyYXRlICYmIC9hcHAvYmluL3N1cGF2aXNvciBldmFsICIkJChjYXQgL2V0Yy9wb29sZXIvcG9vbGVyLmV4cykiICYmIC9hcHAvYmluL3NlcnZlcicKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3ZvbHVtZXMvcG9vbGVyL3Bvb2xlci5leHMKICAgICAgICB0YXJnZXQ6IC9ldGMvcG9vbGVyL3Bvb2xlci5leHMKICAgICAgICBjb250ZW50OiAiezpvaywgX30gPSBBcHBsaWNhdGlvbi5lbnN1cmVfYWxsX3N0YXJ0ZWQoOnN1cGF2aXNvcilcbns6b2ssIHZlcnNpb259ID1cbiAgICBjYXNlIFN1cGF2aXNvci5SZXBvLnF1ZXJ5IShcInNlbGVjdCB2ZXJzaW9uKClcIikgZG9cbiAgICAle3Jvd3M6IFtbdmVyXV19IC0+IFN1cGF2aXNvci5IZWxwZXJzLnBhcnNlX3BnX3ZlcnNpb24odmVyKVxuICAgIF8gLT4gbmlsXG4gICAgZW5kXG5wYXJhbXMgPSAle1xuICAgIFwiZXh0ZXJuYWxfaWRcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPT0xFUl9URU5BTlRfSURcIiksXG4gICAgXCJkYl9ob3N0XCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19IT1NUTkFNRVwiKSxcbiAgICBcImRiX3BvcnRcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPU1RHUkVTX1BPUlRcIikgfD4gU3RyaW5nLnRvX2ludGVnZXIoKSxcbiAgICBcImRiX2RhdGFiYXNlXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT1NUR1JFU19EQlwiKSxcbiAgICBcInJlcXVpcmVfdXNlclwiID0+IGZhbHNlLFxuICAgIFwiYXV0aF9xdWVyeVwiID0+IFwiU0VMRUNUICogRlJPTSBwZ2JvdW5jZXIuZ2V0X2F1dGgoJDEpXCIsXG4gICAgXCJkZWZhdWx0X21heF9jbGllbnRzXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfTUFYX0NMSUVOVF9DT05OXCIpLFxuICAgIFwiZGVmYXVsdF9wb29sX3NpemVcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPT0xFUl9ERUZBVUxUX1BPT0xfU0laRVwiKSxcbiAgICBcImRlZmF1bHRfcGFyYW1ldGVyX3N0YXR1c1wiID0+ICV7XCJzZXJ2ZXJfdmVyc2lvblwiID0+IHZlcnNpb259LFxuICAgIFwidXNlcnNcIiA9PiBbJXtcbiAgICBcImRiX3VzZXJcIiA9PiBcInBnYm91bmNlclwiLFxuICAgIFwiZGJfcGFzc3dvcmRcIiA9PiBTeXN0ZW0uZ2V0X2VudihcIlBPU1RHUkVTX1BBU1NXT1JEXCIpLFxuICAgIFwibW9kZV90eXBlXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfUE9PTF9NT0RFXCIpLFxuICAgIFwicG9vbF9zaXplXCIgPT4gU3lzdGVtLmdldF9lbnYoXCJQT09MRVJfREVGQVVMVF9QT09MX1NJWkVcIiksXG4gICAgXCJpc19tYW5hZ2VyXCIgPT4gdHJ1ZVxuICAgIH1dXG59XG5cbnRlbmFudCA9IFN1cGF2aXNvci5UZW5hbnRzLmdldF90ZW5hbnRfYnlfZXh0ZXJuYWxfaWQocGFyYW1zW1wiZXh0ZXJuYWxfaWRcIl0pXG5cbmlmIHRlbmFudCBkb1xuICB7Om9rLCBffSA9IFN1cGF2aXNvci5UZW5hbnRzLnVwZGF0ZV90ZW5hbnQodGVuYW50LCBwYXJhbXMpXG5lbHNlXG4gIHs6b2ssIF99ID0gU3VwYXZpc29yLlRlbmFudHMuY3JlYXRlX3RlbmFudChwYXJhbXMpXG5lbmRcbiIK", "tags": [ "firebase", "alternative", @@ -4102,7 +4102,7 @@ "superset-with-postgresql": { "documentation": "https://github.com/amancevice/docker-superset?utm_source=coolify.io", "slogan": "Modern data exploration and visualization platform (unofficial community docker image)", - "compose": "c2VydmljZXM6CiAgc3VwZXJzZXQ6CiAgICBpbWFnZTogJ2FtYW5jZXZpY2Uvc3VwZXJzZXQ6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1NVUEVSU0VUXzgwODgKICAgICAgLSAnU0VDUkVUX0tFWT0ke1NFUlZJQ0VfQkFTRTY0XzY0X1NVUEVSU0VUU0VDUkVUS0VZfScKICAgICAgLSAnTUFQQk9YX0FQSV9LRVk9JHtNQVBCT1hfQVBJX0tFWX0nCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1zdXBlcnNldC1kYn0nCiAgICAgIC0gJ1JFRElTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9zdXBlcnNldC9zdXBlcnNldF9jb25maWcucHkKICAgICAgICB0YXJnZXQ6IC9ldGMvc3VwZXJzZXQvc3VwZXJzZXRfY29uZmlnLnB5CiAgICAgICAgY29udGVudDogIlwiXCJcIlxuRm9yIG1vcmUgY29uZmlndXJhdGlvbiBvcHRpb25zLCBzZWU6XG4tIGh0dHBzOi8vc3VwZXJzZXQuYXBhY2hlLm9yZy9kb2NzL2NvbmZpZ3VyYXRpb24vY29uZmlndXJpbmctc3VwZXJzZXRcblwiXCJcIlxuXG5pbXBvcnQgb3NcblxuU0VDUkVUX0tFWSA9IG9zLmdldGVudihcIlNFQ1JFVF9LRVlcIilcbk1BUEJPWF9BUElfS0VZID0gb3MuZ2V0ZW52KFwiTUFQQk9YX0FQSV9LRVlcIiwgXCJcIilcblxuQ0FDSEVfQ09ORklHID0ge1xuICBcIkNBQ0hFX1RZUEVcIjogXCJSZWRpc0NhY2hlXCIsXG4gIFwiQ0FDSEVfREVGQVVMVF9USU1FT1VUXCI6IDMwMCxcbiAgXCJDQUNIRV9LRVlfUFJFRklYXCI6IFwic3VwZXJzZXRfXCIsXG4gIFwiQ0FDSEVfUkVESVNfSE9TVFwiOiBcInJlZGlzXCIsXG4gIFwiQ0FDSEVfUkVESVNfUE9SVFwiOiA2Mzc5LFxuICBcIkNBQ0hFX1JFRElTX0RCXCI6IDEsXG4gIFwiQ0FDSEVfUkVESVNfVVJMXCI6IGZcInJlZGlzOi8vOntvcy5nZXRlbnYoJ1JFRElTX1BBU1NXT1JEJyl9QHJlZGlzOjYzNzkvMVwiLFxufVxuXG5GSUxURVJfU1RBVEVfQ0FDSEVfQ09ORklHID0geyoqQ0FDSEVfQ09ORklHLCBcIkNBQ0hFX0tFWV9QUkVGSVhcIjogXCJzdXBlcnNldF9maWx0ZXJfXCJ9XG5FWFBMT1JFX0ZPUk1fREFUQV9DQUNIRV9DT05GSUcgPSB7KipDQUNIRV9DT05GSUcsIFwiQ0FDSEVfS0VZX1BSRUZJWFwiOiBcInN1cGVyc2V0X2V4cGxvcmVfZm9ybV9cIn1cblxuU1FMQUxDSEVNWV9UUkFDS19NT0RJRklDQVRJT05TID0gVHJ1ZVxuU1FMQUxDSEVNWV9EQVRBQkFTRV9VUkkgPSBmXCJwb3N0Z3Jlc3FsK3BzeWNvcGcyOi8ve29zLmdldGVudignUE9TVEdSRVNfVVNFUicpfTp7b3MuZ2V0ZW52KCdQT1NUR1JFU19QQVNTV09SRCcpfUBwb3N0Z3Jlczo1NDMyL3tvcy5nZXRlbnYoJ1BPU1RHUkVTX0RCJyl9XCJcblxuIyBVbmNvbW1lbnQgaWYgeW91IHdhbnQgdG8gbG9hZCBleGFtcGxlIGRhdGEgKHVzaW5nIFwic3VwZXJzZXQgbG9hZF9leGFtcGxlc1wiKSBhdCB0aGVcbiMgc2FtZSBsb2NhdGlvbiBhcyB5b3VyIG1ldGFkYXRhIHBvc3RncmVzcWwgaW5zdGFuY2UuIE90aGVyd2lzZSwgdGhlIGRlZmF1bHQgc3FsaXRlXG4jIHdpbGwgYmUgdXNlZCwgd2hpY2ggd2lsbCBub3QgcGVyc2lzdCBpbiB2b2x1bWUgd2hlbiByZXN0YXJ0aW5nIHN1cGVyc2V0IGJ5IGRlZmF1bHQuXG4jU1FMQUxDSEVNWV9FWEFNUExFU19VUkkgPSBTUUxBTENIRU1ZX0RBVEFCQVNFX1VSSSIKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6ODA4OC9oZWFsdGgnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTctYWxwaW5lJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1zdXBlcnNldC1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdzdXBlcnNldF9wb3N0Z3Jlc19kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6OC1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdzdXBlcnNldF9yZWRpc19kYXRhOi9kYXRhJwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLXJlcXVpcmVwYXNzICR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogJ3JlZGlzLWNsaSBwaW5nJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "c2VydmljZXM6CiAgc3VwZXJzZXQ6CiAgICBpbWFnZTogJ2FtYW5jZXZpY2Uvc3VwZXJzZXQ6Ni4wLjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fU1VQRVJTRVRfODA4OAogICAgICAtICdTRUNSRVRfS0VZPSR7U0VSVklDRV9CQVNFNjRfNjRfU1VQRVJTRVRTRUNSRVRLRVl9JwogICAgICAtICdNQVBCT1hfQVBJX0tFWT0ke01BUEJPWF9BUElfS0VZfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXN1cGVyc2V0LWRifScKICAgICAgLSAnUkVESVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3N1cGVyc2V0L3N1cGVyc2V0X2NvbmZpZy5weQogICAgICAgIHRhcmdldDogL2V0Yy9zdXBlcnNldC9zdXBlcnNldF9jb25maWcucHkKICAgICAgICBjb250ZW50OiAiXCJcIlwiXG5Gb3IgbW9yZSBjb25maWd1cmF0aW9uIG9wdGlvbnMsIHNlZTpcbi0gaHR0cHM6Ly9zdXBlcnNldC5hcGFjaGUub3JnL2RvY3MvY29uZmlndXJhdGlvbi9jb25maWd1cmluZy1zdXBlcnNldFxuXCJcIlwiXG5cbmltcG9ydCBvc1xuXG5TRUNSRVRfS0VZID0gb3MuZ2V0ZW52KFwiU0VDUkVUX0tFWVwiKVxuTUFQQk9YX0FQSV9LRVkgPSBvcy5nZXRlbnYoXCJNQVBCT1hfQVBJX0tFWVwiLCBcIlwiKVxuXG5DQUNIRV9DT05GSUcgPSB7XG4gIFwiQ0FDSEVfVFlQRVwiOiBcIlJlZGlzQ2FjaGVcIixcbiAgXCJDQUNIRV9ERUZBVUxUX1RJTUVPVVRcIjogMzAwLFxuICBcIkNBQ0hFX0tFWV9QUkVGSVhcIjogXCJzdXBlcnNldF9cIixcbiAgXCJDQUNIRV9SRURJU19IT1NUXCI6IFwicmVkaXNcIixcbiAgXCJDQUNIRV9SRURJU19QT1JUXCI6IDYzNzksXG4gIFwiQ0FDSEVfUkVESVNfREJcIjogMSxcbiAgXCJDQUNIRV9SRURJU19VUkxcIjogZlwicmVkaXM6Ly86e29zLmdldGVudignUkVESVNfUEFTU1dPUkQnKX1AcmVkaXM6NjM3OS8xXCIsXG59XG5cbkZJTFRFUl9TVEFURV9DQUNIRV9DT05GSUcgPSB7KipDQUNIRV9DT05GSUcsIFwiQ0FDSEVfS0VZX1BSRUZJWFwiOiBcInN1cGVyc2V0X2ZpbHRlcl9cIn1cbkVYUExPUkVfRk9STV9EQVRBX0NBQ0hFX0NPTkZJRyA9IHsqKkNBQ0hFX0NPTkZJRywgXCJDQUNIRV9LRVlfUFJFRklYXCI6IFwic3VwZXJzZXRfZXhwbG9yZV9mb3JtX1wifVxuXG5TUUxBTENIRU1ZX1RSQUNLX01PRElGSUNBVElPTlMgPSBUcnVlXG5TUUxBTENIRU1ZX0RBVEFCQVNFX1VSSSA9IGZcInBvc3RncmVzcWwrcHN5Y29wZzI6Ly97b3MuZ2V0ZW52KCdQT1NUR1JFU19VU0VSJyl9Ontvcy5nZXRlbnYoJ1BPU1RHUkVTX1BBU1NXT1JEJyl9QHBvc3RncmVzOjU0MzIve29zLmdldGVudignUE9TVEdSRVNfREInKX1cIlxuXG4jIFVuY29tbWVudCBpZiB5b3Ugd2FudCB0byBsb2FkIGV4YW1wbGUgZGF0YSAodXNpbmcgXCJzdXBlcnNldCBsb2FkX2V4YW1wbGVzXCIpIGF0IHRoZVxuIyBzYW1lIGxvY2F0aW9uIGFzIHlvdXIgbWV0YWRhdGEgcG9zdGdyZXNxbCBpbnN0YW5jZS4gT3RoZXJ3aXNlLCB0aGUgZGVmYXVsdCBzcWxpdGVcbiMgd2lsbCBiZSB1c2VkLCB3aGljaCB3aWxsIG5vdCBwZXJzaXN0IGluIHZvbHVtZSB3aGVuIHJlc3RhcnRpbmcgc3VwZXJzZXQgYnkgZGVmYXVsdC5cbiNTUUxBTENIRU1ZX0VYQU1QTEVTX1VSSSA9IFNRTEFMQ0hFTVlfREFUQUJBU0VfVVJJIgogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MDg4L2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxOCcKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotc3VwZXJzZXQtZGJ9JwogICAgdm9sdW1lczoKICAgICAgLSAnc3VwZXJzZXRfcG9zdGdyZXNfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo4JwogICAgdm9sdW1lczoKICAgICAgLSAnc3VwZXJzZXRfcmVkaXNfZGF0YTovZGF0YScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1yZXF1aXJlcGFzcyAke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdyZWRpcy1jbGkgcGluZycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=", "tags": [ "analytics", "bi", From 1ef6351701924db1af55a44f4f98c326c50f5590 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Thu, 25 Dec 2025 21:03:49 +0000 Subject: [PATCH 002/434] feat: require health check command for 'cmd' type with backend validation and frontend update --- app/Livewire/Project/Shared/HealthChecks.php | 3 ++- .../views/livewire/project/shared/health-checks.blade.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Livewire/Project/Shared/HealthChecks.php b/app/Livewire/Project/Shared/HealthChecks.php index ec4f494f8..ac25f6a77 100644 --- a/app/Livewire/Project/Shared/HealthChecks.php +++ b/app/Livewire/Project/Shared/HealthChecks.php @@ -19,7 +19,7 @@ class HealthChecks extends Component #[Validate(['string', 'in:http,cmd'])] public string $healthCheckType = 'http'; - #[Validate(['nullable', 'string'])] + #[Validate(['nullable', 'required_if:healthCheckType,cmd', 'string'])] public ?string $healthCheckCommand = null; #[Validate(['string'])] @@ -128,6 +128,7 @@ public function syncData(bool $toModel = false): void public function instantSave() { $this->authorize('update', $this->resource); + $this->validate(); // Sync component properties to model $this->resource->health_check_enabled = $this->healthCheckEnabled; diff --git a/resources/views/livewire/project/shared/health-checks.blade.php b/resources/views/livewire/project/shared/health-checks.blade.php index a0027c62c..c181f3f1a 100644 --- a/resources/views/livewire/project/shared/health-checks.blade.php +++ b/resources/views/livewire/project/shared/health-checks.blade.php @@ -57,7 +57,7 @@ label="Command" placeholder="Example: pg_isready -U postgres Example: redis-cli ping Example: curl -f http://localhost:8080/health" helper="The command to run inside the container. It should exit with code 0 on success and non-zero on failure." - required /> + :required="$healthCheckType === 'cmd'" /> @endif From e33558488eea678d1f879f4e765d917c5b46b9fd Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:10:29 +0100 Subject: [PATCH 003/434] feat: add comment field to environment variables - Add comment field to EnvironmentVariable model and database - Update parseEnvFormatToArray to extract inline comments from env files - Update Livewire components to handle comment field - Add UI for displaying and editing comments - Add tests for comment parsing functionality --- app/Livewire/Project/New/DockerCompose.php | 9 +- .../Shared/EnvironmentVariable/All.php | 23 +- .../Shared/EnvironmentVariable/Show.php | 6 + app/Models/EnvironmentVariable.php | 2 + bootstrap/helpers/shared.php | 70 ++++- ...comment_to_environment_variables_table.php | 36 +++ openapi.json | 4 + openapi.yaml | 3 + .../shared/environment-variable/all.blade.php | 7 + .../environment-variable/show.blade.php | 198 +++++++------- .../EnvironmentVariableCommentTest.php | 120 +++++++++ tests/Unit/ParseEnvFormatToArrayTest.php | 248 ++++++++++++++++++ 12 files changed, 623 insertions(+), 103 deletions(-) create mode 100644 database/migrations/2025_11_17_145255_add_comment_to_environment_variables_table.php create mode 100644 tests/Feature/EnvironmentVariableCommentTest.php create mode 100644 tests/Unit/ParseEnvFormatToArrayTest.php diff --git a/app/Livewire/Project/New/DockerCompose.php b/app/Livewire/Project/New/DockerCompose.php index 18bb237af..8619e7ef8 100644 --- a/app/Livewire/Project/New/DockerCompose.php +++ b/app/Livewire/Project/New/DockerCompose.php @@ -63,10 +63,15 @@ public function submit() ]); $variables = parseEnvFormatToArray($this->envFile); - foreach ($variables as $key => $variable) { + foreach ($variables as $key => $data) { + // Extract value and comment from parsed data + $value = $data['value'] ?? $data; + $comment = $data['comment'] ?? null; + EnvironmentVariable::create([ 'key' => $key, - 'value' => $variable, + 'value' => $value, + 'comment' => $comment, 'is_preview' => false, 'resourceable_id' => $service->id, 'resourceable_type' => $service->getMorphClass(), diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 07938d9d0..d8f9e6302 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -270,18 +270,36 @@ private function deleteRemovedVariables($isPreview, $variables) private function updateOrCreateVariables($isPreview, $variables) { $count = 0; - foreach ($variables as $key => $value) { + foreach ($variables as $key => $data) { if (str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL') || str($key)->startsWith('SERVICE_NAME')) { continue; } + + // Extract value and comment from parsed data + $value = $data['value'] ?? $data; + $comment = $data['comment'] ?? null; + $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; $found = $this->resource->$method()->where('key', $key)->first(); if ($found) { if (! $found->is_shown_once && ! $found->is_multiline) { - // Only count as a change if the value actually changed + $changed = false; + + // Update value if it changed if ($found->value !== $value) { $found->value = $value; + $changed = true; + } + + // Always update comment from inline comment (overwrites existing) + // Set to comment if provided, otherwise set to null if no comment + if ($found->comment !== $comment) { + $found->comment = $comment; + $changed = true; + } + + if ($changed) { $found->save(); $count++; } @@ -290,6 +308,7 @@ private function updateOrCreateVariables($isPreview, $variables) $environment = new EnvironmentVariable; $environment->key = $key; $environment->value = $value; + $environment->comment = $comment; // Set comment from inline comment $environment->is_multiline = false; $environment->is_preview = $isPreview; $environment->resourceable_id = $this->resource->id; diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 2030f631e..33a2c83b9 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -34,6 +34,8 @@ class Show extends Component public ?string $real_value = null; + public ?string $comment = null; + public bool $is_shared = false; public bool $is_multiline = false; @@ -63,6 +65,7 @@ class Show extends Component protected $rules = [ 'key' => 'required|string', 'value' => 'nullable', + 'comment' => 'nullable|string|max:1000', 'is_multiline' => 'required|boolean', 'is_literal' => 'required|boolean', 'is_shown_once' => 'required|boolean', @@ -104,6 +107,7 @@ public function syncData(bool $toModel = false) $this->validate([ 'key' => 'required|string', 'value' => 'nullable', + 'comment' => 'nullable|string|max:1000', 'is_multiline' => 'required|boolean', 'is_literal' => 'required|boolean', 'is_shown_once' => 'required|boolean', @@ -118,6 +122,7 @@ public function syncData(bool $toModel = false) } $this->env->key = $this->key; $this->env->value = $this->value; + $this->env->comment = $this->comment; $this->env->is_multiline = $this->is_multiline; $this->env->is_literal = $this->is_literal; $this->env->is_shown_once = $this->is_shown_once; @@ -125,6 +130,7 @@ public function syncData(bool $toModel = false) } else { $this->key = $this->env->key; $this->value = $this->env->value; + $this->comment = $this->env->comment; $this->is_multiline = $this->env->is_multiline; $this->is_literal = $this->env->is_literal; $this->is_shown_once = $this->env->is_shown_once; diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 895dc1c43..c98b0e82a 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -24,6 +24,7 @@ 'key' => ['type' => 'string'], 'value' => ['type' => 'string'], 'real_value' => ['type' => 'string'], + 'comment' => ['type' => 'string', 'nullable' => true], 'version' => ['type' => 'string'], 'created_at' => ['type' => 'string'], 'updated_at' => ['type' => 'string'], @@ -67,6 +68,7 @@ protected static function booted() 'is_literal' => $environment_variable->is_literal ?? false, 'is_runtime' => $environment_variable->is_runtime ?? false, 'is_buildtime' => $environment_variable->is_buildtime ?? false, + 'comment' => $environment_variable->comment, 'resourceable_type' => Application::class, 'resourceable_id' => $environment_variable->resourceable_id, 'is_preview' => true, diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 9fc1e6f1c..d5b693837 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -441,13 +441,71 @@ function parseEnvFormatToArray($env_file_contents) $equals_pos = strpos($line, '='); if ($equals_pos !== false) { $key = substr($line, 0, $equals_pos); - $value = substr($line, $equals_pos + 1); - if (substr($value, 0, 1) === '"' && substr($value, -1) === '"') { - $value = substr($value, 1, -1); - } elseif (substr($value, 0, 1) === "'" && substr($value, -1) === "'") { - $value = substr($value, 1, -1); + $value_and_comment = substr($line, $equals_pos + 1); + $comment = null; + $remainder = ''; + + // Check if value starts with quotes + $firstChar = isset($value_and_comment[0]) ? $value_and_comment[0] : ''; + $isDoubleQuoted = $firstChar === '"'; + $isSingleQuoted = $firstChar === "'"; + + if ($isDoubleQuoted) { + // Find the closing double quote + $closingPos = strpos($value_and_comment, '"', 1); + if ($closingPos !== false) { + // Extract quoted value and remove quotes + $value = substr($value_and_comment, 1, $closingPos - 1); + // Everything after closing quote (including comments) + $remainder = substr($value_and_comment, $closingPos + 1); + } else { + // No closing quote - treat as unquoted + $value = substr($value_and_comment, 1); + } + } elseif ($isSingleQuoted) { + // Find the closing single quote + $closingPos = strpos($value_and_comment, "'", 1); + if ($closingPos !== false) { + // Extract quoted value and remove quotes + $value = substr($value_and_comment, 1, $closingPos - 1); + // Everything after closing quote (including comments) + $remainder = substr($value_and_comment, $closingPos + 1); + } else { + // No closing quote - treat as unquoted + $value = substr($value_and_comment, 1); + } + } else { + // Unquoted value - strip inline comments + // Only treat # as comment if preceded by whitespace + if (preg_match('/\s+#/', $value_and_comment, $matches, PREG_OFFSET_CAPTURE)) { + // Found whitespace followed by #, extract comment + $remainder = substr($value_and_comment, $matches[0][1]); + $value = substr($value_and_comment, 0, $matches[0][1]); + $value = rtrim($value); + } else { + $value = $value_and_comment; + } } - $env_array[$key] = $value; + + // Extract comment from remainder (if any) + if ($remainder !== '') { + // Look for # in remainder + $hashPos = strpos($remainder, '#'); + if ($hashPos !== false) { + // Extract everything after the # and trim + $comment = substr($remainder, $hashPos + 1); + $comment = trim($comment); + // Set to null if empty after trimming + if ($comment === '') { + $comment = null; + } + } + } + + $env_array[$key] = [ + 'value' => $value, + 'comment' => $comment, + ]; } } diff --git a/database/migrations/2025_11_17_145255_add_comment_to_environment_variables_table.php b/database/migrations/2025_11_17_145255_add_comment_to_environment_variables_table.php new file mode 100644 index 000000000..0e17e720f --- /dev/null +++ b/database/migrations/2025_11_17_145255_add_comment_to_environment_variables_table.php @@ -0,0 +1,36 @@ +text('comment')->nullable(); + }); + + Schema::table('shared_environment_variables', function (Blueprint $table) { + $table->text('comment')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('environment_variables', function (Blueprint $table) { + $table->dropColumn('comment'); + }); + + Schema::table('shared_environment_variables', function (Blueprint $table) { + $table->dropColumn('comment'); + }); + } +}; diff --git a/openapi.json b/openapi.json index fe8ca863e..d70db2b8a 100644 --- a/openapi.json +++ b/openapi.json @@ -10540,6 +10540,10 @@ "real_value": { "type": "string" }, + "comment": { + "type": "string", + "nullable": true + }, "version": { "type": "string" }, diff --git a/openapi.yaml b/openapi.yaml index a7faa8c72..7d9c22e44 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -6687,6 +6687,9 @@ components: type: string real_value: type: string + comment: + type: string + nullable: true version: type: string created_at: diff --git a/resources/views/livewire/project/shared/environment-variable/all.blade.php b/resources/views/livewire/project/shared/environment-variable/all.blade.php index cee6b291d..a009d8d89 100644 --- a/resources/views/livewire/project/shared/environment-variable/all.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/all.blade.php @@ -79,6 +79,13 @@ @else
@can('manageEnvironment', $resource) +
+ + + + Note: Inline comments with space before # (e.g., KEY=value #comment) are stripped. Values like PASSWORD=pass#word are preserved. Use the Comment field in Normal view to document variables. +
+ diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 68e1d7e7d..259f90828 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -37,23 +37,29 @@ helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.

Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true." label="Is Literal?" /> @else - @if ($isSharedVariable) - + @if ($is_shared) + @else - @if (!$env->is_nixpacks) - - @endif - - @if (!$env->is_nixpacks) + @if ($isSharedVariable) - @if ($is_multiline === false) - + @else + @if (!$env->is_nixpacks) + + @endif + + @if (!$env->is_nixpacks) + + @if ($is_multiline === false) + + @endif @endif @endif @endif @@ -77,22 +83,26 @@ helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.

Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true." label="Is Literal?" /> @else - @if ($isSharedVariable) - + @if ($is_shared) + @else - @if (!$env->is_nixpacks) + @if ($isSharedVariable) + + @else - @endif - - - @if ($is_multiline === false) - + + + @if ($is_multiline === false) + + @endif @endif @endif @endif @@ -103,51 +113,43 @@ @else @can('update', $this->env) @if ($isDisabled) +
+
+ + + @if ($is_shared) + + @endif +
+
+ @else +
+
+ @if ($is_multiline) + + + @else + + + @endif + @if ($is_shared) + + @endif +
+ +
+ @endif + @else +
- + @if ($is_shared) @endif
- @else -
- @if ($is_multiline) - - - @else - - - @endif - @if ($is_shared) - - @endif -
- @endif - @else -
- - - @if ($is_shared) - + @if (!$isDisabled) + @endif
@endcan @@ -167,23 +169,29 @@ helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.

Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true." label="Is Literal?" /> @else - @if ($isSharedVariable) - + @if ($is_shared) + @else - @if (!$env->is_nixpacks) - - @endif - - @if (!$env->is_nixpacks) + @if ($isSharedVariable) - @if ($is_multiline === false) - + @else + @if (!$env->is_nixpacks) + + @endif + + @if (!$env->is_nixpacks) + + @if ($is_multiline === false) + + @endif @endif @endif @endif @@ -229,22 +237,26 @@ helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.

Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true." label="Is Literal?" /> @else - @if ($isSharedVariable) - + @if ($is_shared) + @else - @if (!$env->is_nixpacks) + @if ($isSharedVariable) + + @else - @endif - - - @if ($is_multiline === false) - + + + @if ($is_multiline === false) + + @endif @endif @endif @endif diff --git a/tests/Feature/EnvironmentVariableCommentTest.php b/tests/Feature/EnvironmentVariableCommentTest.php new file mode 100644 index 000000000..05126c23a --- /dev/null +++ b/tests/Feature/EnvironmentVariableCommentTest.php @@ -0,0 +1,120 @@ +user = User::factory()->create(); + $this->team = Team::factory()->create(); + $this->team->members()->attach($this->user, ['role' => 'owner']); + $this->application = Application::factory()->create([ + 'team_id' => $this->team->id, + ]); + + $this->actingAs($this->user); +}); + +test('environment variable can be created with comment', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'comment' => 'This is a test environment variable', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + expect($env->comment)->toBe('This is a test environment variable'); + expect($env->key)->toBe('TEST_VAR'); + expect($env->value)->toBe('test_value'); +}); + +test('environment variable comment is optional', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + expect($env->comment)->toBeNull(); + expect($env->key)->toBe('TEST_VAR'); +}); + +test('environment variable comment can be updated', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'comment' => 'Initial comment', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + $env->comment = 'Updated comment'; + $env->save(); + + $env->refresh(); + expect($env->comment)->toBe('Updated comment'); +}); + +test('environment variable comment is preserved when updating value', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'initial_value', + 'comment' => 'Important variable for testing', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + $env->value = 'new_value'; + $env->save(); + + $env->refresh(); + expect($env->value)->toBe('new_value'); + expect($env->comment)->toBe('Important variable for testing'); +}); + +test('environment variable comment is copied to preview environment', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'comment' => 'Test comment', + 'is_preview' => false, + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + // The model's booted() method should create a preview version + $previewEnv = EnvironmentVariable::where('key', 'TEST_VAR') + ->where('resourceable_id', $this->application->id) + ->where('is_preview', true) + ->first(); + + expect($previewEnv)->not->toBeNull(); + expect($previewEnv->comment)->toBe('Test comment'); +}); + +test('parseEnvFormatToArray preserves values without inline comments', function () { + $input = "KEY1=value1\nKEY2=value2"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value1', 'comment' => null], + 'KEY2' => ['value' => 'value2', 'comment' => null], + ]); +}); + +test('developer view format does not break with comment-like values', function () { + // Values that contain # but shouldn't be treated as comments when quoted + $env1 = EnvironmentVariable::create([ + 'key' => 'HASH_VAR', + 'value' => 'value_with_#_in_it', + 'comment' => 'Contains hash symbol', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + expect($env1->value)->toBe('value_with_#_in_it'); + expect($env1->comment)->toBe('Contains hash symbol'); +}); diff --git a/tests/Unit/ParseEnvFormatToArrayTest.php b/tests/Unit/ParseEnvFormatToArrayTest.php new file mode 100644 index 000000000..303ff007d --- /dev/null +++ b/tests/Unit/ParseEnvFormatToArrayTest.php @@ -0,0 +1,248 @@ +toBe([ + 'KEY1' => ['value' => 'value1', 'comment' => null], + 'KEY2' => ['value' => 'value2', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray strips inline comments from unquoted values', function () { + $input = "NIXPACKS_NODE_VERSION=22 #needed for now\nNODE_VERSION=22"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'NIXPACKS_NODE_VERSION' => ['value' => '22', 'comment' => 'needed for now'], + 'NODE_VERSION' => ['value' => '22', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray strips inline comments only when preceded by whitespace', function () { + $input = "KEY1=value1#nocomment\nKEY2=value2 #comment\nKEY3=value3 # comment with spaces"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value1#nocomment', 'comment' => null], + 'KEY2' => ['value' => 'value2', 'comment' => 'comment'], + 'KEY3' => ['value' => 'value3', 'comment' => 'comment with spaces'], + ]); +}); + +test('parseEnvFormatToArray preserves # in quoted values', function () { + $input = "KEY1=\"value with # hash\"\nKEY2='another # hash'"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value with # hash', 'comment' => null], + 'KEY2' => ['value' => 'another # hash', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray handles quoted values correctly', function () { + $input = "KEY1=\"quoted value\"\nKEY2='single quoted'"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'quoted value', 'comment' => null], + 'KEY2' => ['value' => 'single quoted', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray skips comment lines', function () { + $input = "# This is a comment\nKEY1=value1\n# Another comment\nKEY2=value2"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value1', 'comment' => null], + 'KEY2' => ['value' => 'value2', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray skips empty lines', function () { + $input = "KEY1=value1\n\nKEY2=value2\n\n"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value1', 'comment' => null], + 'KEY2' => ['value' => 'value2', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray handles values with equals signs', function () { + $input = 'KEY1=value=with=equals'; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value=with=equals', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray handles empty values', function () { + $input = "KEY1=\nKEY2=value"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => '', 'comment' => null], + 'KEY2' => ['value' => 'value', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray handles complex real-world example', function () { + $input = <<<'ENV' +# Database Configuration +DB_HOST=localhost +DB_PORT=5432 #default postgres port +DB_NAME="my_database" +DB_PASSWORD='p@ssw0rd#123' + +# API Keys +API_KEY=abc123 # Production key +SECRET_KEY=xyz789 +ENV; + + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'DB_HOST' => ['value' => 'localhost', 'comment' => null], + 'DB_PORT' => ['value' => '5432', 'comment' => 'default postgres port'], + 'DB_NAME' => ['value' => 'my_database', 'comment' => null], + 'DB_PASSWORD' => ['value' => 'p@ssw0rd#123', 'comment' => null], + 'API_KEY' => ['value' => 'abc123', 'comment' => 'Production key'], + 'SECRET_KEY' => ['value' => 'xyz789', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray handles the original bug scenario', function () { + $input = "NIXPACKS_NODE_VERSION=22 #needed for now\nNODE_VERSION=22"; + $result = parseEnvFormatToArray($input); + + // The value should be "22", not "22 #needed for now" + expect($result['NIXPACKS_NODE_VERSION']['value'])->toBe('22'); + expect($result['NIXPACKS_NODE_VERSION']['value'])->not->toContain('#'); + expect($result['NIXPACKS_NODE_VERSION']['value'])->not->toContain('needed'); + // And the comment should be extracted + expect($result['NIXPACKS_NODE_VERSION']['comment'])->toBe('needed for now'); +}); + +test('parseEnvFormatToArray handles quoted strings with spaces before hash', function () { + $input = "KEY1=\"value with spaces\" #comment\nKEY2=\"another value\""; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value with spaces', 'comment' => 'comment'], + 'KEY2' => ['value' => 'another value', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray handles unquoted values with multiple hash symbols', function () { + $input = "KEY1=value1#not#comment\nKEY2=value2 # comment # with # hashes"; + $result = parseEnvFormatToArray($input); + + // KEY1: no space before #, so entire value is kept + // KEY2: space before first #, so everything from first space+# is stripped + expect($result)->toBe([ + 'KEY1' => ['value' => 'value1#not#comment', 'comment' => null], + 'KEY2' => ['value' => 'value2', 'comment' => 'comment # with # hashes'], + ]); +}); + +test('parseEnvFormatToArray handles quoted values containing hash symbols at various positions', function () { + $input = "KEY1=\"#starts with hash\"\nKEY2=\"hash # in middle\"\nKEY3=\"ends with hash#\""; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => '#starts with hash', 'comment' => null], + 'KEY2' => ['value' => 'hash # in middle', 'comment' => null], + 'KEY3' => ['value' => 'ends with hash#', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray trims whitespace before comments', function () { + $input = "KEY1=value1 #comment\nKEY2=value2\t#comment with tab"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value1', 'comment' => 'comment'], + 'KEY2' => ['value' => 'value2', 'comment' => 'comment with tab'], + ]); + // Values should not have trailing spaces + expect($result['KEY1']['value'])->not->toEndWith(' '); + expect($result['KEY2']['value'])->not->toEndWith("\t"); +}); + +test('parseEnvFormatToArray preserves hash in passwords without spaces', function () { + $input = "PASSWORD=pass#word123\nAPI_KEY=abc#def#ghi"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'PASSWORD' => ['value' => 'pass#word123', 'comment' => null], + 'API_KEY' => ['value' => 'abc#def#ghi', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray strips comments with space before hash', function () { + $input = "PASSWORD=passw0rd #this is secure\nNODE_VERSION=22 #needed for now"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'PASSWORD' => ['value' => 'passw0rd', 'comment' => 'this is secure'], + 'NODE_VERSION' => ['value' => '22', 'comment' => 'needed for now'], + ]); +}); + +test('parseEnvFormatToArray extracts comments from quoted values followed by comments', function () { + $input = "KEY1=\"value\" #comment after quote\nKEY2='value' #another comment"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value', 'comment' => 'comment after quote'], + 'KEY2' => ['value' => 'value', 'comment' => 'another comment'], + ]); +}); + +test('parseEnvFormatToArray handles empty comments', function () { + $input = "KEY1=value #\nKEY2=value # "; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'KEY1' => ['value' => 'value', 'comment' => null], + 'KEY2' => ['value' => 'value', 'comment' => null], + ]); +}); + +test('parseEnvFormatToArray extracts multi-word comments', function () { + $input = 'DATABASE_URL=postgres://localhost #this is the database connection string for production'; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'DATABASE_URL' => ['value' => 'postgres://localhost', 'comment' => 'this is the database connection string for production'], + ]); +}); + +test('parseEnvFormatToArray handles mixed quoted and unquoted with comments', function () { + $input = "UNQUOTED=value1 #comment1\nDOUBLE=\"value2\" #comment2\nSINGLE='value3' #comment3"; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'UNQUOTED' => ['value' => 'value1', 'comment' => 'comment1'], + 'DOUBLE' => ['value' => 'value2', 'comment' => 'comment2'], + 'SINGLE' => ['value' => 'value3', 'comment' => 'comment3'], + ]); +}); + +test('parseEnvFormatToArray handles the user reported case ASD=asd #asdfgg', function () { + $input = 'ASD=asd #asdfgg'; + $result = parseEnvFormatToArray($input); + + expect($result)->toBe([ + 'ASD' => ['value' => 'asd', 'comment' => 'asdfgg'], + ]); + + // Specifically verify the comment is extracted + expect($result['ASD']['value'])->toBe('asd'); + expect($result['ASD']['comment'])->toBe('asdfgg'); + expect($result['ASD']['comment'])->not->toBeNull(); +}); From 201c9fada342f5584414164f2f7f0cde3a4425e9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:47:43 +0100 Subject: [PATCH 004/434] feat: limit comment field to 256 characters for environment variables --- .../Shared/EnvironmentVariable/Show.php | 4 +-- ...comment_to_environment_variables_table.php | 4 +-- .../environment-variable/show.blade.php | 7 ++-- .../EnvironmentVariableCommentTest.php | 32 ++++++++++++++++++- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 33a2c83b9..75149a0d4 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -65,7 +65,7 @@ class Show extends Component protected $rules = [ 'key' => 'required|string', 'value' => 'nullable', - 'comment' => 'nullable|string|max:1000', + 'comment' => 'nullable|string|max:256', 'is_multiline' => 'required|boolean', 'is_literal' => 'required|boolean', 'is_shown_once' => 'required|boolean', @@ -107,7 +107,7 @@ public function syncData(bool $toModel = false) $this->validate([ 'key' => 'required|string', 'value' => 'nullable', - 'comment' => 'nullable|string|max:1000', + 'comment' => 'nullable|string|max:256', 'is_multiline' => 'required|boolean', 'is_literal' => 'required|boolean', 'is_shown_once' => 'required|boolean', diff --git a/database/migrations/2025_11_17_145255_add_comment_to_environment_variables_table.php b/database/migrations/2025_11_17_145255_add_comment_to_environment_variables_table.php index 0e17e720f..abbae3573 100644 --- a/database/migrations/2025_11_17_145255_add_comment_to_environment_variables_table.php +++ b/database/migrations/2025_11_17_145255_add_comment_to_environment_variables_table.php @@ -12,11 +12,11 @@ public function up(): void { Schema::table('environment_variables', function (Blueprint $table) { - $table->text('comment')->nullable(); + $table->string('comment', 256)->nullable(); }); Schema::table('shared_environment_variables', function (Blueprint $table) { - $table->text('comment')->nullable(); + $table->string('comment', 256)->nullable(); }); } diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 259f90828..80b607bd7 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -121,6 +121,7 @@ @endif
+ @else
@@ -136,7 +137,7 @@ @endif
- + @endif @else @@ -148,9 +149,7 @@ @endif - @if (!$isDisabled) - - @endif + @endcan @can('update', $this->env) diff --git a/tests/Feature/EnvironmentVariableCommentTest.php b/tests/Feature/EnvironmentVariableCommentTest.php index 05126c23a..5efb2b953 100644 --- a/tests/Feature/EnvironmentVariableCommentTest.php +++ b/tests/Feature/EnvironmentVariableCommentTest.php @@ -85,7 +85,7 @@ 'resourceable_id' => $this->application->id, ]); - // The model's booted() method should create a preview version + // The model's created() event listener automatically creates a preview version $previewEnv = EnvironmentVariable::where('key', 'TEST_VAR') ->where('resourceable_id', $this->application->id) ->where('is_preview', true) @@ -118,3 +118,33 @@ expect($env1->value)->toBe('value_with_#_in_it'); expect($env1->comment)->toBe('Contains hash symbol'); }); + +test('environment variable comment can store up to 256 characters', function () { + $comment = str_repeat('a', 256); + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'comment' => $comment, + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + expect($env->comment)->toBe($comment); + expect(strlen($env->comment))->toBe(256); +}); + +test('environment variable comment cannot exceed 256 characters via Livewire', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + $longComment = str_repeat('a', 257); + + Livewire::test(\App\Livewire\Project\Shared\EnvironmentVariable\Show::class, ['env' => $env, 'type' => 'application']) + ->set('comment', $longComment) + ->call('submit') + ->assertHasErrors(['comment' => 'max']); +}); From ab472bf5edc962c65018c6aa8014e3df13bc5e89 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:48:40 +0100 Subject: [PATCH 005/434] feat: enhance environment variable handling to support mixed formats and add comprehensive tests --- app/Livewire/Project/New/DockerCompose.php | 5 +- .../Shared/EnvironmentVariable/All.php | 5 +- .../DockerComposeEnvVariableHandlingTest.php | 121 ++++++++++++++++++ 3 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 tests/Unit/Livewire/Project/New/DockerComposeEnvVariableHandlingTest.php diff --git a/app/Livewire/Project/New/DockerCompose.php b/app/Livewire/Project/New/DockerCompose.php index 8619e7ef8..634a012c0 100644 --- a/app/Livewire/Project/New/DockerCompose.php +++ b/app/Livewire/Project/New/DockerCompose.php @@ -65,8 +65,9 @@ public function submit() $variables = parseEnvFormatToArray($this->envFile); foreach ($variables as $key => $data) { // Extract value and comment from parsed data - $value = $data['value'] ?? $data; - $comment = $data['comment'] ?? null; + // Handle both array format ['value' => ..., 'comment' => ...] and plain string values + $value = is_array($data) ? ($data['value'] ?? '') : $data; + $comment = is_array($data) ? ($data['comment'] ?? null) : null; EnvironmentVariable::create([ 'key' => $key, diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index d8f9e6302..35879665d 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -276,8 +276,9 @@ private function updateOrCreateVariables($isPreview, $variables) } // Extract value and comment from parsed data - $value = $data['value'] ?? $data; - $comment = $data['comment'] ?? null; + // Handle both array format ['value' => ..., 'comment' => ...] and plain string values + $value = is_array($data) ? ($data['value'] ?? '') : $data; + $comment = is_array($data) ? ($data['comment'] ?? null) : null; $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; $found = $this->resource->$method()->where('key', $key)->first(); diff --git a/tests/Unit/Livewire/Project/New/DockerComposeEnvVariableHandlingTest.php b/tests/Unit/Livewire/Project/New/DockerComposeEnvVariableHandlingTest.php new file mode 100644 index 000000000..546e24a97 --- /dev/null +++ b/tests/Unit/Livewire/Project/New/DockerComposeEnvVariableHandlingTest.php @@ -0,0 +1,121 @@ + ['value' => 'value1', 'comment' => null], + 'KEY2' => ['value' => 'value2', 'comment' => 'This is a comment'], + 'KEY3' => ['value' => 'value3', 'comment' => null], + ]; + + // Test the extraction logic + foreach ($variables as $key => $data) { + // Handle both array format ['value' => ..., 'comment' => ...] and plain string values + $value = is_array($data) ? ($data['value'] ?? '') : $data; + $comment = is_array($data) ? ($data['comment'] ?? null) : null; + + // Verify the extraction + expect($value)->toBeString(); + expect($key)->toBeIn(['KEY1', 'KEY2', 'KEY3']); + + if ($key === 'KEY1') { + expect($value)->toBe('value1'); + expect($comment)->toBeNull(); + } elseif ($key === 'KEY2') { + expect($value)->toBe('value2'); + expect($comment)->toBe('This is a comment'); + } elseif ($key === 'KEY3') { + expect($value)->toBe('value3'); + expect($comment)->toBeNull(); + } + } +}); + +test('DockerCompose handles plain string format gracefully', function () { + // Simulate a scenario where parseEnvFormatToArray might return plain strings + // (for backward compatibility or edge cases) + $variables = [ + 'KEY1' => 'value1', + 'KEY2' => 'value2', + 'KEY3' => 'value3', + ]; + + // Test the extraction logic + foreach ($variables as $key => $data) { + // Handle both array format ['value' => ..., 'comment' => ...] and plain string values + $value = is_array($data) ? ($data['value'] ?? '') : $data; + $comment = is_array($data) ? ($data['comment'] ?? null) : null; + + // Verify the extraction + expect($value)->toBeString(); + expect($comment)->toBeNull(); + expect($key)->toBeIn(['KEY1', 'KEY2', 'KEY3']); + } +}); + +test('DockerCompose handles mixed array and string formats', function () { + // Simulate a mixed scenario (unlikely but possible) + $variables = [ + 'KEY1' => ['value' => 'value1', 'comment' => 'comment1'], + 'KEY2' => 'value2', // Plain string + 'KEY3' => ['value' => 'value3', 'comment' => null], + 'KEY4' => 'value4', // Plain string + ]; + + // Test the extraction logic + foreach ($variables as $key => $data) { + // Handle both array format ['value' => ..., 'comment' => ...] and plain string values + $value = is_array($data) ? ($data['value'] ?? '') : $data; + $comment = is_array($data) ? ($data['comment'] ?? null) : null; + + // Verify the extraction + expect($value)->toBeString(); + expect($key)->toBeIn(['KEY1', 'KEY2', 'KEY3', 'KEY4']); + + if ($key === 'KEY1') { + expect($value)->toBe('value1'); + expect($comment)->toBe('comment1'); + } elseif ($key === 'KEY2') { + expect($value)->toBe('value2'); + expect($comment)->toBeNull(); + } elseif ($key === 'KEY3') { + expect($value)->toBe('value3'); + expect($comment)->toBeNull(); + } elseif ($key === 'KEY4') { + expect($value)->toBe('value4'); + expect($comment)->toBeNull(); + } + } +}); + +test('DockerCompose handles empty array values gracefully', function () { + // Simulate edge case with incomplete array structure + $variables = [ + 'KEY1' => ['value' => 'value1'], // Missing 'comment' key + 'KEY2' => ['comment' => 'comment2'], // Missing 'value' key (edge case) + 'KEY3' => [], // Empty array (edge case) + ]; + + // Test the extraction logic with improved fallback + foreach ($variables as $key => $data) { + // Handle both array format ['value' => ..., 'comment' => ...] and plain string values + $value = is_array($data) ? ($data['value'] ?? '') : $data; + $comment = is_array($data) ? ($data['comment'] ?? null) : null; + + // Verify the extraction doesn't crash + expect($key)->toBeIn(['KEY1', 'KEY2', 'KEY3']); + + if ($key === 'KEY1') { + expect($value)->toBe('value1'); + expect($comment)->toBeNull(); + } elseif ($key === 'KEY2') { + // If 'value' is missing, fallback to empty string (not the whole array) + expect($value)->toBe(''); + expect($comment)->toBe('comment2'); + } elseif ($key === 'KEY3') { + // If both are missing, fallback to empty string (not empty array) + expect($value)->toBe(''); + expect($comment)->toBeNull(); + } + } +}); From 2bba5ddb2eec4feec77e9838d23095ca89d14fe9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:47:11 +0100 Subject: [PATCH 006/434] refactor: add explicit fillable array to EnvironmentVariable model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace permissive $guarded = [] with explicit $fillable array for better security and clarity. The fillable array includes all 13 fields that are legitimately mass-assignable: - Core: key, value, comment - Polymorphic relationship: resourceable_type, resourceable_id - Boolean flags: is_preview, is_multiline, is_literal, is_runtime, is_buildtime, is_shown_once, is_shared - Metadata: version, order Also adds comprehensive test suite (EnvironmentVariableMassAssignmentTest) with 12 test cases covering: - Mass assignment of all fillable fields - Comment field edge cases (null, empty, long text) - Value encryption verification - Key mutation (trim and space replacement) - Protection of auto-managed fields (id, uuid, timestamps) - Update method compatibility All tests passing (12 passed, 33 assertions). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Models/EnvironmentVariable.php | 24 +- .../EnvironmentVariableMassAssignmentTest.php | 217 ++++++++++++++++++ 2 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/EnvironmentVariableMassAssignmentTest.php diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index c98b0e82a..a2ab0cfc2 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -32,7 +32,29 @@ )] class EnvironmentVariable extends BaseModel { - protected $guarded = []; + protected $fillable = [ + // Core identification + 'key', + 'value', + 'comment', + + // Polymorphic relationship + 'resourceable_type', + 'resourceable_id', + + // Boolean flags + 'is_preview', + 'is_multiline', + 'is_literal', + 'is_runtime', + 'is_buildtime', + 'is_shown_once', + 'is_shared', + + // Metadata + 'version', + 'order', + ]; protected $casts = [ 'key' => 'string', diff --git a/tests/Feature/EnvironmentVariableMassAssignmentTest.php b/tests/Feature/EnvironmentVariableMassAssignmentTest.php new file mode 100644 index 000000000..f2650fdc7 --- /dev/null +++ b/tests/Feature/EnvironmentVariableMassAssignmentTest.php @@ -0,0 +1,217 @@ +user = User::factory()->create(); + $this->team = Team::factory()->create(); + $this->team->members()->attach($this->user, ['role' => 'owner']); + $this->application = Application::factory()->create(); + + $this->actingAs($this->user); +}); + +test('all fillable fields can be mass assigned', function () { + $data = [ + 'key' => 'TEST_KEY', + 'value' => 'test_value', + 'comment' => 'Test comment', + 'is_literal' => true, + 'is_multiline' => true, + 'is_preview' => false, + 'is_runtime' => true, + 'is_buildtime' => false, + 'is_shown_once' => false, + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]; + + $env = EnvironmentVariable::create($data); + + expect($env->key)->toBe('TEST_KEY'); + expect($env->value)->toBe('test_value'); + expect($env->comment)->toBe('Test comment'); + expect($env->is_literal)->toBeTrue(); + expect($env->is_multiline)->toBeTrue(); + expect($env->is_preview)->toBeFalse(); + expect($env->is_runtime)->toBeTrue(); + expect($env->is_buildtime)->toBeFalse(); + expect($env->is_shown_once)->toBeFalse(); + expect($env->resourceable_type)->toBe(Application::class); + expect($env->resourceable_id)->toBe($this->application->id); +}); + +test('comment field can be mass assigned with null', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'comment' => null, + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + expect($env->comment)->toBeNull(); +}); + +test('comment field can be mass assigned with empty string', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'comment' => '', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + expect($env->comment)->toBe(''); +}); + +test('comment field can be mass assigned with long text', function () { + $comment = str_repeat('This is a long comment. ', 10); + + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'comment' => $comment, + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + expect($env->comment)->toBe($comment); + expect(strlen($env->comment))->toBe(strlen($comment)); +}); + +test('all boolean fields default correctly when not provided', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + // Boolean fields can be null or false depending on database defaults + expect($env->is_multiline)->toBeIn([false, null]); + expect($env->is_preview)->toBeIn([false, null]); + expect($env->is_runtime)->toBeIn([false, null]); + expect($env->is_buildtime)->toBeIn([false, null]); + expect($env->is_shown_once)->toBeIn([false, null]); +}); + +test('value field is properly encrypted when mass assigned', function () { + $plainValue = 'secret_value_123'; + + $env = EnvironmentVariable::create([ + 'key' => 'SECRET_KEY', + 'value' => $plainValue, + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + // Value should be decrypted when accessed via model + expect($env->value)->toBe($plainValue); + + // Verify it's actually encrypted in the database + $rawValue = \DB::table('environment_variables') + ->where('id', $env->id) + ->value('value'); + + expect($rawValue)->not->toBe($plainValue); + expect($rawValue)->not->toBeNull(); +}); + +test('key field is trimmed and spaces replaced with underscores', function () { + $env = EnvironmentVariable::create([ + 'key' => ' TEST KEY WITH SPACES ', + 'value' => 'test_value', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + expect($env->key)->toBe('TEST_KEY_WITH_SPACES'); +}); + +test('version field can be mass assigned', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'version' => '1.2.3', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + // The booted() method sets version automatically, so it will be the current version + expect($env->version)->not->toBeNull(); +}); + +test('mass assignment works with update method', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'initial_value', + 'comment' => 'Initial comment', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + $env->update([ + 'value' => 'updated_value', + 'comment' => 'Updated comment', + 'is_literal' => true, + ]); + + $env->refresh(); + + expect($env->value)->toBe('updated_value'); + expect($env->comment)->toBe('Updated comment'); + expect($env->is_literal)->toBeTrue(); +}); + +test('protected attributes cannot be mass assigned', function () { + $customDate = '2020-01-01 00:00:00'; + + $env = EnvironmentVariable::create([ + 'id' => 999999, + 'uuid' => 'custom-uuid', + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + 'created_at' => $customDate, + 'updated_at' => $customDate, + ]); + + // id should be auto-generated, not 999999 + expect($env->id)->not->toBe(999999); + + // uuid should be auto-generated, not 'custom-uuid' + expect($env->uuid)->not->toBe('custom-uuid'); + + // Timestamps should be current, not 2020 + expect($env->created_at->year)->toBe(now()->year); +}); + +test('order field can be mass assigned', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'order' => 5, + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + expect($env->order)->toBe(5); +}); + +test('is_shared field can be mass assigned', function () { + $env = EnvironmentVariable::create([ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + 'is_shared' => true, + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + // Note: is_shared is also computed via accessor, but can be mass assigned + expect($env->is_shared)->not->toBeNull(); +}); From 4e329053ddd22ba13f802f2500f0d743e1835300 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:48:08 +0100 Subject: [PATCH 007/434] feat: add comment field to shared environment variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comment field support to the "New Shared Variable" modal, ensuring it's saved properly for both normal and shared environment variables at all levels (Team, Project, Environment). Changes: - Add comment property, validation, and dispatch to Add component (Livewire & view) - Update saveKey methods in Team, Project, and Environment to accept comment - Replace SharedEnvironmentVariable model's $guarded with explicit $fillable array - Include comment field in creation flow for all shared variable types The comment field (max 256 chars, optional) is now available when creating shared variables and is consistently saved across all variable types. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Project/Shared/EnvironmentVariable/Add.php | 6 ++++++ .../SharedVariables/Environment/Show.php | 1 + app/Livewire/SharedVariables/Project/Show.php | 1 + app/Livewire/SharedVariables/Team/Index.php | 1 + app/Models/SharedEnvironmentVariable.php | 18 +++++++++++++++++- .../shared/environment-variable/add.blade.php | 3 +++ 6 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php index fa65e8bd2..73d5393b0 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php @@ -31,6 +31,8 @@ class Add extends Component public bool $is_buildtime = true; + public ?string $comment = null; + public array $problematicVariables = []; protected $listeners = ['clearAddEnv' => 'clear']; @@ -42,6 +44,7 @@ class Add extends Component 'is_literal' => 'required|boolean', 'is_runtime' => 'required|boolean', 'is_buildtime' => 'required|boolean', + 'comment' => 'nullable|string|max:256', ]; protected $validationAttributes = [ @@ -51,6 +54,7 @@ class Add extends Component 'is_literal' => 'literal', 'is_runtime' => 'runtime', 'is_buildtime' => 'buildtime', + 'comment' => 'comment', ]; public function mount() @@ -136,6 +140,7 @@ public function submit() 'is_runtime' => $this->is_runtime, 'is_buildtime' => $this->is_buildtime, 'is_preview' => $this->is_preview, + 'comment' => $this->comment, ]); $this->clear(); } @@ -148,5 +153,6 @@ public function clear() $this->is_literal = false; $this->is_runtime = true; $this->is_buildtime = true; + $this->comment = null; } } diff --git a/app/Livewire/SharedVariables/Environment/Show.php b/app/Livewire/SharedVariables/Environment/Show.php index 0bdc1503f..e1b230218 100644 --- a/app/Livewire/SharedVariables/Environment/Show.php +++ b/app/Livewire/SharedVariables/Environment/Show.php @@ -40,6 +40,7 @@ public function saveKey($data) 'value' => $data['value'], 'is_multiline' => $data['is_multiline'], 'is_literal' => $data['is_literal'], + 'comment' => $data['comment'] ?? null, 'type' => 'environment', 'team_id' => currentTeam()->id, ]); diff --git a/app/Livewire/SharedVariables/Project/Show.php b/app/Livewire/SharedVariables/Project/Show.php index b205ea1ec..1f304b543 100644 --- a/app/Livewire/SharedVariables/Project/Show.php +++ b/app/Livewire/SharedVariables/Project/Show.php @@ -33,6 +33,7 @@ public function saveKey($data) 'value' => $data['value'], 'is_multiline' => $data['is_multiline'], 'is_literal' => $data['is_literal'], + 'comment' => $data['comment'] ?? null, 'type' => 'project', 'team_id' => currentTeam()->id, ]); diff --git a/app/Livewire/SharedVariables/Team/Index.php b/app/Livewire/SharedVariables/Team/Index.php index e420686f0..75fd415e1 100644 --- a/app/Livewire/SharedVariables/Team/Index.php +++ b/app/Livewire/SharedVariables/Team/Index.php @@ -33,6 +33,7 @@ public function saveKey($data) 'value' => $data['value'], 'is_multiline' => $data['is_multiline'], 'is_literal' => $data['is_literal'], + 'comment' => $data['comment'] ?? null, 'type' => 'team', 'team_id' => currentTeam()->id, ]); diff --git a/app/Models/SharedEnvironmentVariable.php b/app/Models/SharedEnvironmentVariable.php index 7956f006a..9bd42c328 100644 --- a/app/Models/SharedEnvironmentVariable.php +++ b/app/Models/SharedEnvironmentVariable.php @@ -6,7 +6,23 @@ class SharedEnvironmentVariable extends Model { - protected $guarded = []; + protected $fillable = [ + // Core identification + 'key', + 'value', + 'comment', + + // Type and relationships + 'type', + 'team_id', + 'project_id', + 'environment_id', + + // Boolean flags + 'is_multiline', + 'is_literal', + 'is_shown_once', + ]; protected $casts = [ 'key' => 'string', diff --git a/resources/views/livewire/project/shared/environment-variable/add.blade.php b/resources/views/livewire/project/shared/environment-variable/add.blade.php index 9bc4f06a3..a17984d21 100644 --- a/resources/views/livewire/project/shared/environment-variable/add.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/add.blade.php @@ -16,6 +16,9 @@ @endif + + @if (!$shared) Date: Tue, 18 Nov 2025 15:05:27 +0100 Subject: [PATCH 008/434] fix: save comment field when creating application environment variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment field was not being saved when creating environment variables from applications, even though it worked for shared environment variables. The issue was in the createEnvironmentVariable method which was missing the comment assignment. Added: $environment->comment = $data['comment'] ?? null; The comment is already dispatched from the Add component and now it's properly saved to the database for application environment variables. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Livewire/Project/Shared/EnvironmentVariable/All.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 35879665d..ded717b26 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -230,6 +230,7 @@ private function createEnvironmentVariable($data) $environment->is_runtime = $data['is_runtime'] ?? true; $environment->is_buildtime = $data['is_buildtime'] ?? true; $environment->is_preview = $data['is_preview'] ?? false; + $environment->comment = $data['comment'] ?? null; $environment->resourceable_id = $this->resource->id; $environment->resourceable_type = $this->resource->getMorphClass(); From a4d546596337da3983cc819dadf70c91d4b97c92 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:09:11 +0100 Subject: [PATCH 009/434] feat: show comment field for locked environment variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an environment variable is locked (is_shown_once=true), the comment field is now displayed as disabled/read-only for future reference. This allows users to see the documentation/note about what the variable is for, even when the value is hidden for security. The comment field appears after the key field and before the configuration checkboxes in the locked view. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../project/shared/environment-variable/show.blade.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 80b607bd7..48fb5f1bf 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -21,6 +21,12 @@ step2ButtonText="Permanently Delete" /> @endcan + @if ($comment) +
+ +
+ @endif @can('update', $this->env)
From 4623853c997b7454868dd62472d04d0d833b69e0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:12:30 +0100 Subject: [PATCH 010/434] fix: allow editing comments on locked environment variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modified the locked environment variable view to keep the comment field editable even when the variable value is locked. Users with update permission can now edit comments on locked variables, while users without permission can still view the comment for reference. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../shared/environment-variable/show.blade.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 48fb5f1bf..43eb88335 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -21,13 +21,11 @@ step2ButtonText="Permanently Delete" /> @endcan
- @if ($comment) -
- -
- @endif @can('update', $this->env) +
+ +
@if (!$is_redis_credential) @@ -115,6 +113,10 @@ @endif
+
+ +
@endcan @else @can('update', $this->env) From c8558b5f7885b91711429b3c16a78089de81af97 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:13:55 +0100 Subject: [PATCH 011/434] fix: add Update button for locked environment variable comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed instantSave from comment field and added a proper Update button with Delete modal for locked environment variables. This ensures users can explicitly save their comment changes on locked variables. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../shared/environment-variable/show.blade.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 43eb88335..b41d223ce 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -23,7 +23,7 @@
@can('update', $this->env)
-
@@ -71,6 +71,17 @@ @endif
+
+ Update + @can('delete', $this->env) + + @endcan +
@else
From c1be02dfb908d12a34d7f592d5ead4e59f48a589 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:22:57 +0100 Subject: [PATCH 012/434] fix: remove duplicate delete button from locked environment variable view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed the duplicate delete button that was appearing at the bottom of locked environment variables. The delete button at the top (next to the lock icon) is sufficient. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../project/shared/environment-variable/show.blade.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index b41d223ce..918df8e50 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -73,14 +73,6 @@
Update - @can('delete', $this->env) - - @endcan
@else
From dfb180601ae7e28682dd93a28660ac41f1f0d7f0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:24:19 +0100 Subject: [PATCH 013/434] fix: position Update button next to comment field for locked variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moved the Update button to appear inline with the comment field for better UX when editing comments on locked environment variables. The button now appears on the same row as the comment input on larger screens. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../shared/environment-variable/show.blade.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 918df8e50..c2865ccb8 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -22,9 +22,12 @@ @endcan
@can('update', $this->env) -
- +
+
+ +
+ Update
@@ -71,9 +74,6 @@ @endif
-
- Update -
@else
From d640911bb94e7ee4353fbf5d116af5204258fa12 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:26:07 +0100 Subject: [PATCH 014/434] fix: preserve existing comments in bulk update and always show save notification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes two UX issues with environment variable bulk updates: 1. Comment Preservation (High Priority Bug): - When bulk updating environment variables via Developer view, existing manually-entered comments are now preserved when no inline comment is provided - Only overwrites existing comments when an inline comment (#comment) is explicitly provided in the pasted content - Previously: pasting "KEY=value" would erase existing comment to null - Now: pasting "KEY=value" preserves existing comment, "KEY=value #new" overwrites it 2. Save Notification (UX Improvement): - "Save all Environment variables" button now always shows success notification - Previously: only showed notification when changes were detected - Now: provides feedback even when no changes were made - Consistent with other save operations in the codebase Changes: - Modified updateOrCreateVariables() to only update comment field when inline comment is provided (null check prevents overwriting existing comments) - Modified handleBulkSubmit() to always dispatch success notification unless error occurred - Added comprehensive test coverage for bulk update comment preservation scenarios Tests: - Added 4 new feature tests covering comment preservation edge cases - All 22 existing unit tests for parseEnvFormatToArray pass - Code formatted with Pint 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Shared/EnvironmentVariable/All.php | 10 +- .../EnvironmentVariableCommentTest.php | 133 ++++++++++++++++++ 2 files changed, 138 insertions(+), 5 deletions(-) diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index ded717b26..f867aaf3d 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -193,8 +193,8 @@ private function handleBulkSubmit() } } - // Only show success message if changes were actually made and no errors occurred - if ($changesMade && ! $errorOccurred) { + // Always show success message unless an error occurred + if (! $errorOccurred) { $this->dispatch('success', 'Environment variables updated.'); } } @@ -294,9 +294,9 @@ private function updateOrCreateVariables($isPreview, $variables) $changed = true; } - // Always update comment from inline comment (overwrites existing) - // Set to comment if provided, otherwise set to null if no comment - if ($found->comment !== $comment) { + // Only update comment from inline comment if one is provided (overwrites existing) + // If $comment is null, don't touch existing comment field to preserve it + if ($comment !== null && $found->comment !== $comment) { $found->comment = $comment; $changed = true; } diff --git a/tests/Feature/EnvironmentVariableCommentTest.php b/tests/Feature/EnvironmentVariableCommentTest.php index 5efb2b953..8b02ad8bf 100644 --- a/tests/Feature/EnvironmentVariableCommentTest.php +++ b/tests/Feature/EnvironmentVariableCommentTest.php @@ -148,3 +148,136 @@ ->call('submit') ->assertHasErrors(['comment' => 'max']); }); + +test('bulk update preserves existing comments when no inline comment provided', function () { + // Create existing variable with a manually-entered comment + $env = EnvironmentVariable::create([ + 'key' => 'DATABASE_URL', + 'value' => 'postgres://old-host', + 'comment' => 'Production database', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + // User switches to Developer view and pastes new value without inline comment + $bulkContent = "DATABASE_URL=postgres://new-host\nOTHER_VAR=value"; + + Livewire::test(\App\Livewire\Project\Shared\EnvironmentVariable\All::class, [ + 'resource' => $this->application, + 'type' => 'application', + ]) + ->set('variablesInput', $bulkContent) + ->call('saveVariables'); + + // Refresh the environment variable + $env->refresh(); + + // The value should be updated + expect($env->value)->toBe('postgres://new-host'); + + // The manually-entered comment should be PRESERVED + expect($env->comment)->toBe('Production database'); +}); + +test('bulk update overwrites existing comments when inline comment provided', function () { + // Create existing variable with a comment + $env = EnvironmentVariable::create([ + 'key' => 'API_KEY', + 'value' => 'old-key', + 'comment' => 'Old comment', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + // User pastes new value WITH inline comment + $bulkContent = 'API_KEY=new-key #Updated production key'; + + Livewire::test(\App\Livewire\Project\Shared\EnvironmentVariable\All::class, [ + 'resource' => $this->application, + 'type' => 'application', + ]) + ->set('variablesInput', $bulkContent) + ->call('saveVariables'); + + // Refresh the environment variable + $env->refresh(); + + // The value should be updated + expect($env->value)->toBe('new-key'); + + // The comment should be OVERWRITTEN with the inline comment + expect($env->comment)->toBe('Updated production key'); +}); + +test('bulk update handles mixed inline and stored comments correctly', function () { + // Create two variables with comments + $env1 = EnvironmentVariable::create([ + 'key' => 'VAR_WITH_COMMENT', + 'value' => 'value1', + 'comment' => 'Existing comment 1', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + $env2 = EnvironmentVariable::create([ + 'key' => 'VAR_WITHOUT_COMMENT', + 'value' => 'value2', + 'comment' => 'Existing comment 2', + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + + // Bulk paste: one with inline comment, one without + $bulkContent = "VAR_WITH_COMMENT=new_value1 #New inline comment\nVAR_WITHOUT_COMMENT=new_value2"; + + Livewire::test(\App\Livewire\Project\Shared\EnvironmentVariable\All::class, [ + 'resource' => $this->application, + 'type' => 'application', + ]) + ->set('variablesInput', $bulkContent) + ->call('saveVariables'); + + // Refresh both variables + $env1->refresh(); + $env2->refresh(); + + // First variable: comment should be overwritten with inline comment + expect($env1->value)->toBe('new_value1'); + expect($env1->comment)->toBe('New inline comment'); + + // Second variable: comment should be preserved + expect($env2->value)->toBe('new_value2'); + expect($env2->comment)->toBe('Existing comment 2'); +}); + +test('bulk update creates new variables with inline comments', function () { + // Bulk paste creates new variables, some with inline comments + $bulkContent = "NEW_VAR1=value1 #Comment for var1\nNEW_VAR2=value2\nNEW_VAR3=value3 #Comment for var3"; + + Livewire::test(\App\Livewire\Project\Shared\EnvironmentVariable\All::class, [ + 'resource' => $this->application, + 'type' => 'application', + ]) + ->set('variablesInput', $bulkContent) + ->call('saveVariables'); + + // Check that variables were created with correct comments + $var1 = EnvironmentVariable::where('key', 'NEW_VAR1') + ->where('resourceable_id', $this->application->id) + ->first(); + $var2 = EnvironmentVariable::where('key', 'NEW_VAR2') + ->where('resourceable_id', $this->application->id) + ->first(); + $var3 = EnvironmentVariable::where('key', 'NEW_VAR3') + ->where('resourceable_id', $this->application->id) + ->first(); + + expect($var1->value)->toBe('value1'); + expect($var1->comment)->toBe('Comment for var1'); + + expect($var2->value)->toBe('value2'); + expect($var2->comment)->toBeNull(); + + expect($var3->value)->toBe('value3'); + expect($var3->comment)->toBe('Comment for var3'); +}); From 61dcf8b4ac1c6d95d9c11797615136731134015a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:32:12 +0100 Subject: [PATCH 015/434] refactor: replace inline note with callout component for consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use x-callout component in developer view for env var note - Simplify label text from "Comment (Optional)" to "Comment" - Minor code formatting improvements via Pint 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- bootstrap/helpers/shared.php | 2 +- .../shared/environment-variable/add.blade.php | 2 +- .../shared/environment-variable/all.blade.php | 29 +++++++---------- .../environment-variable/show.blade.php | 31 +++++++++++-------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index d5b693837..dc7136275 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -446,7 +446,7 @@ function parseEnvFormatToArray($env_file_contents) $remainder = ''; // Check if value starts with quotes - $firstChar = isset($value_and_comment[0]) ? $value_and_comment[0] : ''; + $firstChar = $value_and_comment[0] ?? ''; $isDoubleQuoted = $firstChar === '"'; $isSingleQuoted = $firstChar === "'"; diff --git a/resources/views/livewire/project/shared/environment-variable/add.blade.php b/resources/views/livewire/project/shared/environment-variable/add.blade.php index a17984d21..7d5fabcb7 100644 --- a/resources/views/livewire/project/shared/environment-variable/add.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/add.blade.php @@ -16,7 +16,7 @@
@endif - @if (!$shared) diff --git a/resources/views/livewire/project/shared/environment-variable/all.blade.php b/resources/views/livewire/project/shared/environment-variable/all.blade.php index a009d8d89..f1d108703 100644 --- a/resources/views/livewire/project/shared/environment-variable/all.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/all.blade.php @@ -61,8 +61,8 @@
Environment (secrets) variables for Production.
@forelse ($this->environmentVariables as $env) - + @empty
No environment variables found.
@endforelse @@ -72,27 +72,23 @@
Environment (secrets) variables for Preview Deployments.
@foreach ($this->environmentVariablesPreview as $env) - + @endforeach @endif @else @can('manageEnvironment', $resource) -
- - - - Note: Inline comments with space before # (e.g., KEY=value #comment) are stripped. Values like PASSWORD=pass#word are preserved. Use the Comment field in Normal view to document variables. -
+ + Inline comments with space before # (e.g., KEY=value #comment) are stripped. + @if ($showPreview) - + @endif Save All Environment Variables @@ -101,11 +97,10 @@ label="Production Environment Variables" disabled> @if ($showPreview) - + @endif @endcan @endif -
+ \ No newline at end of file diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index c2865ccb8..cc95939de 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -15,7 +15,8 @@ @can('delete', $this->env) @@ -24,7 +25,7 @@ @can('update', $this->env)
-
Update @@ -117,8 +118,8 @@
- +
@endcan @else @@ -132,7 +133,8 @@ @endif - + @else
@@ -145,10 +147,12 @@ @endif @if ($is_shared) - + @endif
- + @endif @else @@ -160,7 +164,8 @@ @endif - + @endcan @can('update', $this->env) @@ -213,8 +218,8 @@ @if ($isDisabled) Update Lock - Update Lock - - + \ No newline at end of file From e4cc5c117836ac3dc103e80b921738781c8e5ff8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:11:48 +0100 Subject: [PATCH 016/434] fix: update success message logic to only show when changes are made --- .../Project/Shared/EnvironmentVariable/All.php | 4 ++-- tests/Feature/EnvironmentVariableCommentTest.php | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index f867aaf3d..12a4cae79 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -193,8 +193,8 @@ private function handleBulkSubmit() } } - // Always show success message unless an error occurred - if (! $errorOccurred) { + // Only show success message if changes were actually made and no errors occurred + if ($changesMade && ! $errorOccurred) { $this->dispatch('success', 'Environment variables updated.'); } } diff --git a/tests/Feature/EnvironmentVariableCommentTest.php b/tests/Feature/EnvironmentVariableCommentTest.php index 8b02ad8bf..e7f9a07fb 100644 --- a/tests/Feature/EnvironmentVariableCommentTest.php +++ b/tests/Feature/EnvironmentVariableCommentTest.php @@ -166,8 +166,8 @@ 'resource' => $this->application, 'type' => 'application', ]) - ->set('variablesInput', $bulkContent) - ->call('saveVariables'); + ->set('variables', $bulkContent) + ->call('submit'); // Refresh the environment variable $env->refresh(); @@ -196,8 +196,8 @@ 'resource' => $this->application, 'type' => 'application', ]) - ->set('variablesInput', $bulkContent) - ->call('saveVariables'); + ->set('variables', $bulkContent) + ->call('submit'); // Refresh the environment variable $env->refresh(); @@ -234,8 +234,8 @@ 'resource' => $this->application, 'type' => 'application', ]) - ->set('variablesInput', $bulkContent) - ->call('saveVariables'); + ->set('variables', $bulkContent) + ->call('submit'); // Refresh both variables $env1->refresh(); @@ -258,8 +258,8 @@ 'resource' => $this->application, 'type' => 'application', ]) - ->set('variablesInput', $bulkContent) - ->call('saveVariables'); + ->set('variables', $bulkContent) + ->call('submit'); // Check that variables were created with correct comments $var1 = EnvironmentVariable::where('key', 'NEW_VAR1') From 89192c9862e1d0a2d911fa619bd5caceb80e1bdb Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:57:07 +0100 Subject: [PATCH 017/434] feat: add function to extract inline comments from docker-compose YAML environment variables --- bootstrap/helpers/parsers.php | 46 ++- bootstrap/helpers/shared.php | 220 +++++++++++- .../ExtractYamlEnvironmentCommentsTest.php | 334 ++++++++++++++++++ 3 files changed, 586 insertions(+), 14 deletions(-) create mode 100644 tests/Unit/ExtractYamlEnvironmentCommentsTest.php diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index 43ba58e59..3f942547f 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -1411,6 +1411,9 @@ function serviceParser(Service $resource): Collection return collect([]); } + // Extract inline comments from raw YAML before Symfony parser discards them + $envComments = extractYamlEnvironmentComments($compose); + $server = data_get($resource, 'server'); $allServices = get_service_templates(); @@ -1694,51 +1697,60 @@ function serviceParser(Service $resource): Collection } // ALWAYS create BOTH base SERVICE_URL and SERVICE_FQDN pairs (without port) + $fqdnKey = "SERVICE_FQDN_{$serviceName}"; $resource->environment_variables()->updateOrCreate([ - 'key' => "SERVICE_FQDN_{$serviceName}", + 'key' => $fqdnKey, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'value' => $fqdnValueForEnv, 'is_preview' => false, + 'comment' => $envComments[$fqdnKey] ?? null, ]); + $urlKey = "SERVICE_URL_{$serviceName}"; $resource->environment_variables()->updateOrCreate([ - 'key' => "SERVICE_URL_{$serviceName}", + 'key' => $urlKey, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'value' => $url, 'is_preview' => false, + 'comment' => $envComments[$urlKey] ?? null, ]); // For port-specific variables, ALSO create port-specific pairs // If template variable has port, create both URL and FQDN with port suffix if ($parsed['has_port'] && $port) { + $fqdnPortKey = "SERVICE_FQDN_{$serviceName}_{$port}"; $resource->environment_variables()->updateOrCreate([ - 'key' => "SERVICE_FQDN_{$serviceName}_{$port}", + 'key' => $fqdnPortKey, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'value' => $fqdnValueForEnvWithPort, 'is_preview' => false, + 'comment' => $envComments[$fqdnPortKey] ?? null, ]); + $urlPortKey = "SERVICE_URL_{$serviceName}_{$port}"; $resource->environment_variables()->updateOrCreate([ - 'key' => "SERVICE_URL_{$serviceName}_{$port}", + 'key' => $urlPortKey, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'value' => $urlWithPort, 'is_preview' => false, + 'comment' => $envComments[$urlPortKey] ?? null, ]); } } } $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); if ($magicEnvironments->count() > 0) { - foreach ($magicEnvironments as $key => $value) { - $key = str($key); + foreach ($magicEnvironments as $magicKey => $value) { + $originalMagicKey = $magicKey; // Preserve original key for comment lookup + $key = str($magicKey); $value = replaceVariables($value); $command = parseCommandFromMagicEnvVariable($key); if ($command->value() === 'FQDN') { @@ -1762,13 +1774,14 @@ function serviceParser(Service $resource): Collection $serviceExists->fqdn = $url; $serviceExists->save(); } - $resource->environment_variables()->firstOrCreate([ + $resource->environment_variables()->updateOrCreate([ 'key' => $key->value(), 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'value' => $fqdn, 'is_preview' => false, + 'comment' => $envComments[$originalMagicKey] ?? null, ]); } elseif ($command->value() === 'URL') { @@ -1790,24 +1803,26 @@ function serviceParser(Service $resource): Collection $serviceExists->fqdn = $url; $serviceExists->save(); } - $resource->environment_variables()->firstOrCreate([ + $resource->environment_variables()->updateOrCreate([ 'key' => $key->value(), 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'value' => $url, 'is_preview' => false, + 'comment' => $envComments[$originalMagicKey] ?? null, ]); } else { $value = generateEnvValue($command, $resource); - $resource->environment_variables()->firstOrCreate([ + $resource->environment_variables()->updateOrCreate([ 'key' => $key->value(), 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'value' => $value, 'is_preview' => false, + 'comment' => $envComments[$originalMagicKey] ?? null, ]); } } @@ -2163,18 +2178,20 @@ function serviceParser(Service $resource): Collection return ! str($value)->startsWith('SERVICE_'); }); foreach ($normalEnvironments as $key => $value) { + $originalKey = $key; // Preserve original key for comment lookup $key = str($key); $value = str($value); $originalValue = $value; $parsedValue = replaceVariables($value); if ($parsedValue->startsWith('SERVICE_')) { - $resource->environment_variables()->firstOrCreate([ + $resource->environment_variables()->updateOrCreate([ 'key' => $key, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'value' => $value, 'is_preview' => false, + 'comment' => $envComments[$originalKey] ?? null, ]); continue; @@ -2184,13 +2201,14 @@ function serviceParser(Service $resource): Collection } if ($key->value() === $parsedValue->value()) { $value = null; - $resource->environment_variables()->firstOrCreate([ + $resource->environment_variables()->updateOrCreate([ 'key' => $key, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'value' => $value, 'is_preview' => false, + 'comment' => $envComments[$originalKey] ?? null, ]); } else { if ($value->startsWith('$')) { @@ -2220,20 +2238,21 @@ function serviceParser(Service $resource): Collection if ($originalValue->value() === $value->value()) { // This means the variable does not have a default value, so it needs to be created in Coolify $parsedKeyValue = replaceVariables($value); - $resource->environment_variables()->firstOrCreate([ + $resource->environment_variables()->updateOrCreate([ 'key' => $parsedKeyValue, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ 'is_preview' => false, 'is_required' => $isRequired, + 'comment' => $envComments[$originalKey] ?? null, ]); // Add the variable to the environment so it will be shown in the deployable compose file $environment[$parsedKeyValue->value()] = $value; continue; } - $resource->environment_variables()->firstOrCreate([ + $resource->environment_variables()->updateOrCreate([ 'key' => $key, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, @@ -2241,6 +2260,7 @@ function serviceParser(Service $resource): Collection 'value' => $value, 'is_preview' => false, 'is_required' => $isRequired, + 'comment' => $envComments[$originalKey] ?? null, ]); } } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index dc7136275..f2f29bdc6 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -512,6 +512,215 @@ function parseEnvFormatToArray($env_file_contents) return $env_array; } +/** + * Extract inline comments from environment variables in raw docker-compose YAML. + * + * Parses raw docker-compose YAML to extract inline comments from environment sections. + * Standard YAML parsers discard comments, so this pre-processes the raw text. + * + * Handles both formats: + * - Map format: `KEY: "value" # comment` or `KEY: value # comment` + * - Array format: `- KEY=value # comment` + * + * @param string $rawYaml The raw docker-compose.yml content + * @return array Map of environment variable keys to their inline comments + */ +function extractYamlEnvironmentComments(string $rawYaml): array +{ + $comments = []; + $lines = explode("\n", $rawYaml); + $inEnvironmentBlock = false; + $environmentIndent = 0; + + foreach ($lines as $line) { + // Skip empty lines + if (trim($line) === '') { + continue; + } + + // Calculate current line's indentation (number of leading spaces) + $currentIndent = strlen($line) - strlen(ltrim($line)); + + // Check if this line starts an environment block + if (preg_match('/^(\s*)environment\s*:\s*$/', $line, $matches)) { + $inEnvironmentBlock = true; + $environmentIndent = strlen($matches[1]); + + continue; + } + + // Check if this line starts an environment block with inline content (rare but possible) + if (preg_match('/^(\s*)environment\s*:\s*\{/', $line)) { + // Inline object format - not supported for comment extraction + continue; + } + + // If we're in an environment block, check if we've exited it + if ($inEnvironmentBlock) { + // If we hit a line with same or less indentation that's not empty, we've left the block + // Unless it's a continuation of the environment block + $trimmedLine = ltrim($line); + + // Check if this is a new top-level key (same indent as 'environment:' or less) + if ($currentIndent <= $environmentIndent && ! str_starts_with($trimmedLine, '-') && ! str_starts_with($trimmedLine, '#')) { + // Check if it looks like a YAML key (contains : not inside quotes) + if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*\s*:/', $trimmedLine)) { + $inEnvironmentBlock = false; + + continue; + } + } + + // Skip comment-only lines + if (str_starts_with($trimmedLine, '#')) { + continue; + } + + // Try to extract environment variable and comment from this line + $extracted = extractEnvVarCommentFromYamlLine($trimmedLine); + if ($extracted !== null && $extracted['comment'] !== null) { + $comments[$extracted['key']] = $extracted['comment']; + } + } + } + + return $comments; +} + +/** + * Extract environment variable key and inline comment from a single YAML line. + * + * @param string $line A trimmed line from the environment section + * @return array|null Array with 'key' and 'comment', or null if not an env var line + */ +function extractEnvVarCommentFromYamlLine(string $line): ?array +{ + $key = null; + $comment = null; + + // Handle array format: `- KEY=value # comment` or `- KEY # comment` + if (str_starts_with($line, '-')) { + $content = ltrim(substr($line, 1)); + + // Check for KEY=value format + if (preg_match('/^([A-Za-z_][A-Za-z0-9_]*)/', $content, $keyMatch)) { + $key = $keyMatch[1]; + // Find comment - need to handle quoted values + $comment = extractCommentAfterValue($content); + } + } + // Handle map format: `KEY: "value" # comment` or `KEY: value # comment` + elseif (preg_match('/^([A-Za-z_][A-Za-z0-9_]*)\s*:/', $line, $keyMatch)) { + $key = $keyMatch[1]; + // Get everything after the key and colon + $afterKey = substr($line, strlen($keyMatch[0])); + $comment = extractCommentAfterValue($afterKey); + } + + if ($key === null) { + return null; + } + + return [ + 'key' => $key, + 'comment' => $comment, + ]; +} + +/** + * Extract inline comment from a value portion of a YAML line. + * + * Handles quoted values (where # inside quotes is not a comment). + * + * @param string $valueAndComment The value portion (may include comment) + * @return string|null The comment text, or null if no comment + */ +function extractCommentAfterValue(string $valueAndComment): ?string +{ + $valueAndComment = ltrim($valueAndComment); + + if ($valueAndComment === '') { + return null; + } + + $firstChar = $valueAndComment[0] ?? ''; + + // Handle case where value is empty and line starts directly with comment + // e.g., `KEY: # comment` becomes `# comment` after ltrim + if ($firstChar === '#') { + $comment = trim(substr($valueAndComment, 1)); + + return $comment !== '' ? $comment : null; + } + + // Handle double-quoted value + if ($firstChar === '"') { + // Find closing quote (handle escaped quotes) + $pos = 1; + $len = strlen($valueAndComment); + while ($pos < $len) { + if ($valueAndComment[$pos] === '\\' && $pos + 1 < $len) { + $pos += 2; // Skip escaped character + + continue; + } + if ($valueAndComment[$pos] === '"') { + // Found closing quote + $remainder = substr($valueAndComment, $pos + 1); + + return extractCommentFromRemainder($remainder); + } + $pos++; + } + + // No closing quote found + return null; + } + + // Handle single-quoted value + if ($firstChar === "'") { + // Find closing quote (single quotes don't have escapes in YAML) + $closingPos = strpos($valueAndComment, "'", 1); + if ($closingPos !== false) { + $remainder = substr($valueAndComment, $closingPos + 1); + + return extractCommentFromRemainder($remainder); + } + + // No closing quote found + return null; + } + + // Unquoted value - find # that's preceded by whitespace + // Be careful not to match # at the start of a value like color codes + if (preg_match('/\s+#\s*(.*)$/', $valueAndComment, $matches)) { + $comment = trim($matches[1]); + + return $comment !== '' ? $comment : null; + } + + return null; +} + +/** + * Extract comment from the remainder of a line after a quoted value. + * + * @param string $remainder Text after the closing quote + * @return string|null The comment text, or null if no comment + */ +function extractCommentFromRemainder(string $remainder): ?string +{ + // Look for # in remainder + $hashPos = strpos($remainder, '#'); + if ($hashPos !== false) { + $comment = trim(substr($remainder, $hashPos + 1)); + + return $comment !== '' ? $comment : null; + } + + return null; +} + function data_get_str($data, $key, $default = null): Stringable { $str = data_get($data, $key, $default) ?? $default; @@ -1345,6 +1554,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal { if ($resource->getMorphClass() === \App\Models\Service::class) { if ($resource->docker_compose_raw) { + // Extract inline comments from raw YAML before Symfony parser discards them + $envComments = extractYamlEnvironmentComments($resource->docker_compose_raw); + try { $yaml = Yaml::parse($resource->docker_compose_raw); } catch (\Exception $e) { @@ -1376,7 +1588,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } $topLevelVolumes = collect($tempTopLevelVolumes); } - $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices) { + $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices, $envComments) { // Workarounds for beta users. if ($serviceName === 'registry') { $tempServiceName = 'docker-registry'; @@ -1722,6 +1934,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $key = str($variableName); $value = str($variable); } + // Preserve original key for comment lookup before $key might be reassigned + $originalKey = $key->value(); if ($key->startsWith('SERVICE_FQDN')) { if ($isNew || $savedService->fqdn === null) { $name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower(); @@ -1775,6 +1989,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, 'is_preview' => false, + 'comment' => $envComments[$originalKey] ?? null, ]); } // Caddy needs exact port in some cases. @@ -1854,6 +2069,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, 'is_preview' => false, + 'comment' => $envComments[$originalKey] ?? null, ]); } if (! $isDatabase) { @@ -1892,6 +2108,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, 'is_preview' => false, + 'comment' => $envComments[$originalKey] ?? null, ]); } } @@ -1930,6 +2147,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, 'is_preview' => false, + 'comment' => $envComments[$originalKey] ?? null, ]); } } diff --git a/tests/Unit/ExtractYamlEnvironmentCommentsTest.php b/tests/Unit/ExtractYamlEnvironmentCommentsTest.php new file mode 100644 index 000000000..4300b3abf --- /dev/null +++ b/tests/Unit/ExtractYamlEnvironmentCommentsTest.php @@ -0,0 +1,334 @@ +toBe([]); +}); + +test('extractYamlEnvironmentComments extracts inline comments from map format', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + FOO: bar # This is a comment + BAZ: qux +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'FOO' => 'This is a comment', + ]); +}); + +test('extractYamlEnvironmentComments extracts inline comments from array format', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + - FOO=bar # This is a comment + - BAZ=qux +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'FOO' => 'This is a comment', + ]); +}); + +test('extractYamlEnvironmentComments handles quoted values containing hash symbols', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + COLOR: "#FF0000" # hex color code + DB_URL: "postgres://user:pass#123@localhost" # database URL + PLAIN: value # no quotes +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'COLOR' => 'hex color code', + 'DB_URL' => 'database URL', + 'PLAIN' => 'no quotes', + ]); +}); + +test('extractYamlEnvironmentComments handles single quoted values containing hash symbols', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + PASSWORD: 'secret#123' # my password +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'PASSWORD' => 'my password', + ]); +}); + +test('extractYamlEnvironmentComments skips full-line comments', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + # This is a full line comment + FOO: bar # This is an inline comment + # Another full line comment + BAZ: qux +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'FOO' => 'This is an inline comment', + ]); +}); + +test('extractYamlEnvironmentComments handles multiple services', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + WEB_PORT: 8080 # web server port + db: + image: postgres:15 + environment: + POSTGRES_USER: admin # database admin user + POSTGRES_PASSWORD: secret +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'WEB_PORT' => 'web server port', + 'POSTGRES_USER' => 'database admin user', + ]); +}); + +test('extractYamlEnvironmentComments handles variables without values', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + - DEBUG # enable debug mode + - VERBOSE +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'DEBUG' => 'enable debug mode', + ]); +}); + +test('extractYamlEnvironmentComments handles array format with colons', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + - DATABASE_URL: postgres://localhost # connection string +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'DATABASE_URL' => 'connection string', + ]); +}); + +test('extractYamlEnvironmentComments does not treat hash inside unquoted values as comment start', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + API_KEY: abc#def + OTHER: xyz # this is a comment +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + // abc#def has no space before #, so it's not treated as a comment + expect($result)->toBe([ + 'OTHER' => 'this is a comment', + ]); +}); + +test('extractYamlEnvironmentComments handles empty environment section', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + ports: + - "80:80" +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([]); +}); + +test('extractYamlEnvironmentComments handles environment inline format (not supported)', function () { + // Inline format like environment: { FOO: bar } is not supported for comment extraction + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: { FOO: bar } +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + // No comments extracted from inline format + expect($result)->toBe([]); +}); + +test('extractYamlEnvironmentComments handles complex real-world docker-compose', function () { + $yaml = <<<'YAML' +version: "3.8" + +services: + app: + image: myapp:latest + environment: + NODE_ENV: production # Set to development for local + DATABASE_URL: "postgres://user:pass@db:5432/mydb" # Main database + REDIS_URL: "redis://cache:6379" + API_SECRET: "${API_SECRET}" # From .env file + LOG_LEVEL: debug # Options: debug, info, warn, error + ports: + - "3000:3000" + + db: + image: postgres:15 + environment: + POSTGRES_USER: user # Database admin username + POSTGRES_PASSWORD: "${DB_PASSWORD}" + POSTGRES_DB: mydb + + cache: + image: redis:7 + environment: + - REDIS_MAXMEMORY=256mb # Memory limit for cache +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'NODE_ENV' => 'Set to development for local', + 'DATABASE_URL' => 'Main database', + 'API_SECRET' => 'From .env file', + 'LOG_LEVEL' => 'Options: debug, info, warn, error', + 'POSTGRES_USER' => 'Database admin username', + 'REDIS_MAXMEMORY' => 'Memory limit for cache', + ]); +}); + +test('extractYamlEnvironmentComments handles comment with multiple hash symbols', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + environment: + FOO: bar # comment # with # hashes +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'FOO' => 'comment # with # hashes', + ]); +}); + +test('extractYamlEnvironmentComments handles variables with empty comments', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + environment: + FOO: bar # + BAZ: qux # +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + // Empty comments should not be included + expect($result)->toBe([]); +}); + +test('extractYamlEnvironmentComments properly exits environment block on new section', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + image: nginx:latest + environment: + FOO: bar # env comment + ports: + - "80:80" # port comment should not be captured + volumes: + - ./data:/data # volume comment should not be captured +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + // Only environment variables should have comments extracted + expect($result)->toBe([ + 'FOO' => 'env comment', + ]); +}); + +test('extractYamlEnvironmentComments handles SERVICE_ variables', function () { + $yaml = <<<'YAML' +version: "3.8" +services: + web: + environment: + SERVICE_FQDN_WEB: /api # Path for the web service + SERVICE_URL_WEB: # URL will be generated + NORMAL_VAR: value # Regular variable +YAML; + + $result = extractYamlEnvironmentComments($yaml); + + expect($result)->toBe([ + 'SERVICE_FQDN_WEB' => 'Path for the web service', + 'SERVICE_URL_WEB' => 'URL will be generated', + 'NORMAL_VAR' => 'Regular variable', + ]); +}); From d67fcd1dff7fee7bee21bf54e49f7d03f574d431 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:23:18 +0100 Subject: [PATCH 018/434] feat: add magic variable detection and update UI behavior accordingly --- .../Shared/EnvironmentVariable/Show.php | 6 + .../environment-variable/show.blade.php | 130 +++++++++------- .../EnvironmentVariableMagicVariableTest.php | 141 ++++++++++++++++++ 3 files changed, 227 insertions(+), 50 deletions(-) create mode 100644 tests/Unit/EnvironmentVariableMagicVariableTest.php diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 75149a0d4..2a18be13c 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -24,6 +24,8 @@ class Show extends Component public bool $isLocked = false; + public bool $isMagicVariable = false; + public bool $isSharedVariable = false; public string $type; @@ -146,9 +148,13 @@ public function syncData(bool $toModel = false) public function checkEnvs() { $this->isDisabled = false; + $this->isMagicVariable = false; + if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL') || str($this->env->key)->startsWith('SERVICE_NAME')) { $this->isDisabled = true; + $this->isMagicVariable = true; } + if ($this->env->is_shown_once) { $this->isLocked = true; } diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index cc95939de..86faeeeb4 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -40,10 +40,12 @@ - - + @if (!$isMagicVariable) + + + @endif @else @if ($is_shared) @else @if ($isSharedVariable) - + @if (!$isMagicVariable) + + @endif @else @if (!$env->is_nixpacks) - @if (!$env->is_nixpacks) - - @if ($is_multiline === false) - + @if (!$isMagicVariable) + @if (!$env->is_nixpacks) + + @if ($is_multiline === false) + + @endif @endif @endif @endif @@ -86,10 +92,12 @@ - - + @if (!$isMagicVariable) + + + @endif @else @if ($is_shared) @else @if ($isSharedVariable) - + @if (!$isMagicVariable) + + @endif @else - - @if ($is_multiline === false) - + @if (!$isMagicVariable) + + @if ($is_multiline === false) + + @endif @endif @endif @endif @@ -133,8 +145,10 @@ @endif - + @if (!$isMagicVariable) + + @endif @else
@@ -164,8 +178,10 @@ @endif
- + @if (!$isMagicVariable) + + @endif @endcan @can('update', $this->env) @@ -179,10 +195,12 @@ - - + @if (!$isMagicVariable) + + + @endif @else @if ($is_shared) @else @if ($isSharedVariable) - + @if (!$isMagicVariable) + + @endif @else @if (!$env->is_nixpacks) - @if (!$env->is_nixpacks) - - @if ($is_multiline === false) - + @if (!$isMagicVariable) + @if (!$env->is_nixpacks) + + @if ($is_multiline === false) + + @endif @endif @endif @endif @@ -214,8 +236,9 @@ @endif -
- @if ($isDisabled) + @if (!$isMagicVariable) +
+ @if ($isDisabled) Update Lock - @endif -
+ @endif +
+ @endif @else
@@ -247,10 +271,12 @@ - - + @if (!$isMagicVariable) + + + @endif @else @if ($is_shared) @else @if ($isSharedVariable) - + @if (!$isMagicVariable) + + @endif @else - - @if ($is_multiline === false) - + @if (!$isMagicVariable) + + @if ($is_multiline === false) + + @endif @endif @endif @endif diff --git a/tests/Unit/EnvironmentVariableMagicVariableTest.php b/tests/Unit/EnvironmentVariableMagicVariableTest.php new file mode 100644 index 000000000..ae85ba45f --- /dev/null +++ b/tests/Unit/EnvironmentVariableMagicVariableTest.php @@ -0,0 +1,141 @@ +shouldReceive('getAttribute') + ->with('key') + ->andReturn('SERVICE_FQDN_DB'); + $mock->shouldReceive('getAttribute') + ->with('is_shown_once') + ->andReturn(false); + $mock->shouldReceive('getMorphClass') + ->andReturn(EnvironmentVariable::class); + + $component = new Show; + $component->env = $mock; + $component->checkEnvs(); + + expect($component->isMagicVariable)->toBeTrue(); + expect($component->isDisabled)->toBeTrue(); +}); + +test('SERVICE_URL variables are identified as magic variables', function () { + $mock = Mockery::mock(EnvironmentVariable::class); + $mock->shouldReceive('getAttribute') + ->with('key') + ->andReturn('SERVICE_URL_API'); + $mock->shouldReceive('getAttribute') + ->with('is_shown_once') + ->andReturn(false); + $mock->shouldReceive('getMorphClass') + ->andReturn(EnvironmentVariable::class); + + $component = new Show; + $component->env = $mock; + $component->checkEnvs(); + + expect($component->isMagicVariable)->toBeTrue(); + expect($component->isDisabled)->toBeTrue(); +}); + +test('SERVICE_NAME variables are identified as magic variables', function () { + $mock = Mockery::mock(EnvironmentVariable::class); + $mock->shouldReceive('getAttribute') + ->with('key') + ->andReturn('SERVICE_NAME'); + $mock->shouldReceive('getAttribute') + ->with('is_shown_once') + ->andReturn(false); + $mock->shouldReceive('getMorphClass') + ->andReturn(EnvironmentVariable::class); + + $component = new Show; + $component->env = $mock; + $component->checkEnvs(); + + expect($component->isMagicVariable)->toBeTrue(); + expect($component->isDisabled)->toBeTrue(); +}); + +test('regular variables are not magic variables', function () { + $mock = Mockery::mock(EnvironmentVariable::class); + $mock->shouldReceive('getAttribute') + ->with('key') + ->andReturn('DATABASE_URL'); + $mock->shouldReceive('getAttribute') + ->with('is_shown_once') + ->andReturn(false); + $mock->shouldReceive('getMorphClass') + ->andReturn(EnvironmentVariable::class); + + $component = new Show; + $component->env = $mock; + $component->checkEnvs(); + + expect($component->isMagicVariable)->toBeFalse(); + expect($component->isDisabled)->toBeFalse(); +}); + +test('locked variables are not magic variables unless they start with SERVICE_', function () { + $mock = Mockery::mock(EnvironmentVariable::class); + $mock->shouldReceive('getAttribute') + ->with('key') + ->andReturn('SECRET_KEY'); + $mock->shouldReceive('getAttribute') + ->with('is_shown_once') + ->andReturn(true); + $mock->shouldReceive('getMorphClass') + ->andReturn(EnvironmentVariable::class); + + $component = new Show; + $component->env = $mock; + $component->checkEnvs(); + + expect($component->isMagicVariable)->toBeFalse(); + expect($component->isLocked)->toBeTrue(); +}); + +test('SERVICE_FQDN with port suffix is identified as magic variable', function () { + $mock = Mockery::mock(EnvironmentVariable::class); + $mock->shouldReceive('getAttribute') + ->with('key') + ->andReturn('SERVICE_FQDN_DB_5432'); + $mock->shouldReceive('getAttribute') + ->with('is_shown_once') + ->andReturn(false); + $mock->shouldReceive('getMorphClass') + ->andReturn(EnvironmentVariable::class); + + $component = new Show; + $component->env = $mock; + $component->checkEnvs(); + + expect($component->isMagicVariable)->toBeTrue(); + expect($component->isDisabled)->toBeTrue(); +}); + +test('SERVICE_URL with port suffix is identified as magic variable', function () { + $mock = Mockery::mock(EnvironmentVariable::class); + $mock->shouldReceive('getAttribute') + ->with('key') + ->andReturn('SERVICE_URL_API_8080'); + $mock->shouldReceive('getAttribute') + ->with('is_shown_once') + ->andReturn(false); + $mock->shouldReceive('getMorphClass') + ->andReturn(EnvironmentVariable::class); + + $component = new Show; + $component->env = $mock; + $component->checkEnvs(); + + expect($component->isMagicVariable)->toBeTrue(); + expect($component->isDisabled)->toBeTrue(); +}); From 208f0eac997a516398d20509dc25aac226241234 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:22:38 +0100 Subject: [PATCH 019/434] feat: add comprehensive environment variable parsing with nested resolution and hardcoded variable detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces advanced environment variable handling capabilities including: - Nested environment variable resolution with circular dependency detection - Extraction of hardcoded environment variables from docker-compose.yml - New ShowHardcoded Livewire component for displaying detected variables - Enhanced UI for better environment variable management The changes improve the user experience by automatically detecting and displaying environment variables that are hardcoded in docker-compose files, allowing users to override them if needed. The nested variable resolution ensures complex variable dependencies are properly handled. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Shared/EnvironmentVariable/All.php | 56 +++ .../EnvironmentVariable/ShowHardcoded.php | 31 ++ bootstrap/helpers/parsers.php | 353 ++++++++++++++---- bootstrap/helpers/services.php | 108 +++++- bootstrap/helpers/shared.php | 52 +++ .../shared/environment-variable/all.blade.php | 27 +- .../show-hardcoded.blade.php | 31 ++ ...tractHardcodedEnvironmentVariablesTest.php | 147 ++++++++ .../NestedEnvironmentVariableParsingTest.php | 220 +++++++++++ tests/Unit/NestedEnvironmentVariableTest.php | 207 ++++++++++ 10 files changed, 1145 insertions(+), 87 deletions(-) create mode 100644 app/Livewire/Project/Shared/EnvironmentVariable/ShowHardcoded.php create mode 100644 resources/views/livewire/project/shared/environment-variable/show-hardcoded.blade.php create mode 100644 tests/Unit/ExtractHardcodedEnvironmentVariablesTest.php create mode 100644 tests/Unit/NestedEnvironmentVariableParsingTest.php create mode 100644 tests/Unit/NestedEnvironmentVariableTest.php diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 12a4cae79..b360798ff 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -79,6 +79,62 @@ public function getEnvironmentVariablesPreviewProperty() return $this->resource->environment_variables_preview; } + public function getHardcodedEnvironmentVariablesProperty() + { + return $this->getHardcodedVariables(false); + } + + public function getHardcodedEnvironmentVariablesPreviewProperty() + { + return $this->getHardcodedVariables(true); + } + + protected function getHardcodedVariables(bool $isPreview) + { + // Only for services and docker-compose applications + if ($this->resource->type() !== 'service' && + ($this->resourceClass !== 'App\Models\Application' || + ($this->resourceClass === 'App\Models\Application' && $this->resource->build_pack !== 'dockercompose'))) { + return collect([]); + } + + $dockerComposeRaw = $this->resource->docker_compose_raw ?? $this->resource->docker_compose; + + if (blank($dockerComposeRaw)) { + return collect([]); + } + + // Extract all hard-coded variables + $hardcodedVars = extractHardcodedEnvironmentVariables($dockerComposeRaw); + + // Filter out magic variables (SERVICE_FQDN_*, SERVICE_URL_*, SERVICE_NAME_*) + $hardcodedVars = $hardcodedVars->filter(function ($var) { + $key = $var['key']; + + return ! str($key)->startsWith(['SERVICE_FQDN_', 'SERVICE_URL_', 'SERVICE_NAME_']); + }); + + // Filter out variables that exist in database (user has overridden/managed them) + // For preview, check against preview variables; for production, check against production variables + if ($isPreview) { + $managedKeys = $this->resource->environment_variables_preview()->pluck('key')->toArray(); + } else { + $managedKeys = $this->resource->environment_variables()->where('is_preview', false)->pluck('key')->toArray(); + } + + $hardcodedVars = $hardcodedVars->filter(function ($var) use ($managedKeys) { + return ! in_array($var['key'], $managedKeys); + }); + + // Apply sorting based on is_env_sorting_enabled + if ($this->is_env_sorting_enabled) { + $hardcodedVars = $hardcodedVars->sortBy('key')->values(); + } + // Otherwise keep order from docker-compose file + + return $hardcodedVars; + } + public function getDevView() { $this->variables = $this->formatEnvironmentVariables($this->environmentVariables); diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/ShowHardcoded.php b/app/Livewire/Project/Shared/EnvironmentVariable/ShowHardcoded.php new file mode 100644 index 000000000..3a49ce124 --- /dev/null +++ b/app/Livewire/Project/Shared/EnvironmentVariable/ShowHardcoded.php @@ -0,0 +1,31 @@ +key = $this->env['key']; + $this->value = $this->env['value'] ?? null; + $this->comment = $this->env['comment'] ?? null; + $this->serviceName = $this->env['service_name'] ?? null; + } + + public function render() + { + return view('livewire.project.shared.environment-variable.show-hardcoded'); + } +} diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index 3f942547f..5112e3abd 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -998,53 +998,139 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int } else { if ($value->startsWith('$')) { $isRequired = false; - if ($value->contains(':-')) { - $value = replaceVariables($value); - $key = $value->before(':'); - $value = $value->after(':-'); - } elseif ($value->contains('-')) { - $value = replaceVariables($value); - $key = $value->before('-'); - $value = $value->after('-'); - } elseif ($value->contains(':?')) { - $value = replaceVariables($value); + // Extract variable content between ${...} using balanced brace matching + $result = extractBalancedBraceContent($value->value(), 0); - $key = $value->before(':'); - $value = $value->after(':?'); - $isRequired = true; - } elseif ($value->contains('?')) { - $value = replaceVariables($value); + if ($result !== null) { + $content = $result['content']; + $split = splitOnOperatorOutsideNested($content); - $key = $value->before('?'); - $value = $value->after('?'); - $isRequired = true; - } - if ($originalValue->value() === $value->value()) { - // This means the variable does not have a default value, so it needs to be created in Coolify - $parsedKeyValue = replaceVariables($value); + if ($split !== null) { + // Has default value syntax (:-, -, :?, or ?) + $varName = $split['variable']; + $operator = $split['operator']; + $defaultValue = $split['default']; + $isRequired = str_contains($operator, '?'); + + // Create the primary variable with its default (only if it doesn't exist) + $envVar = $resource->environment_variables()->firstOrCreate([ + 'key' => $varName, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $defaultValue, + 'is_preview' => false, + 'is_required' => $isRequired, + ]); + + // Add the variable to the environment so it will be shown in the deployable compose file + $environment[$varName] = $envVar->value; + + // Recursively process nested variables in default value + if (str_contains($defaultValue, '${')) { + $searchPos = 0; + $nestedResult = extractBalancedBraceContent($defaultValue, $searchPos); + while ($nestedResult !== null) { + $nestedContent = $nestedResult['content']; + $nestedSplit = splitOnOperatorOutsideNested($nestedContent); + + // Determine the nested variable name + $nestedVarName = $nestedSplit !== null ? $nestedSplit['variable'] : $nestedContent; + + // Skip SERVICE_URL_* and SERVICE_FQDN_* variables - they are handled by magic variable system + $isMagicVariable = str_starts_with($nestedVarName, 'SERVICE_URL_') || str_starts_with($nestedVarName, 'SERVICE_FQDN_'); + + if (! $isMagicVariable) { + if ($nestedSplit !== null) { + $nestedEnvVar = $resource->environment_variables()->firstOrCreate([ + 'key' => $nestedSplit['variable'], + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $nestedSplit['default'], + 'is_preview' => false, + ]); + $environment[$nestedSplit['variable']] = $nestedEnvVar->value; + } else { + $nestedEnvVar = $resource->environment_variables()->firstOrCreate([ + 'key' => $nestedContent, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'is_preview' => false, + ]); + $environment[$nestedContent] = $nestedEnvVar->value; + } + } + + $searchPos = $nestedResult['end'] + 1; + if ($searchPos >= strlen($defaultValue)) { + break; + } + $nestedResult = extractBalancedBraceContent($defaultValue, $searchPos); + } + } + } else { + // Simple variable reference without default + $parsedKeyValue = replaceVariables($value); + $resource->environment_variables()->firstOrCreate([ + 'key' => $content, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'is_preview' => false, + 'is_required' => $isRequired, + ]); + // Add the variable to the environment + $environment[$content] = $value; + } + } else { + // Fallback to old behavior for malformed input (backward compatibility) + if ($value->contains(':-')) { + $value = replaceVariables($value); + $key = $value->before(':'); + $value = $value->after(':-'); + } elseif ($value->contains('-')) { + $value = replaceVariables($value); + $key = $value->before('-'); + $value = $value->after('-'); + } elseif ($value->contains(':?')) { + $value = replaceVariables($value); + $key = $value->before(':'); + $value = $value->after(':?'); + $isRequired = true; + } elseif ($value->contains('?')) { + $value = replaceVariables($value); + $key = $value->before('?'); + $value = $value->after('?'); + $isRequired = true; + } + if ($originalValue->value() === $value->value()) { + // This means the variable does not have a default value + $parsedKeyValue = replaceVariables($value); + $resource->environment_variables()->firstOrCreate([ + 'key' => $parsedKeyValue, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'is_preview' => false, + 'is_required' => $isRequired, + ]); + $environment[$parsedKeyValue->value()] = $value; + + continue; + } $resource->environment_variables()->firstOrCreate([ - 'key' => $parsedKeyValue, + 'key' => $key, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ + 'value' => $value, 'is_preview' => false, 'is_required' => $isRequired, ]); - // Add the variable to the environment so it will be shown in the deployable compose file - $environment[$parsedKeyValue->value()] = $value; - - continue; } - $resource->environment_variables()->firstOrCreate([ - 'key' => $key, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_preview' => false, - 'is_required' => $isRequired, - ]); } } } @@ -1774,6 +1860,7 @@ function serviceParser(Service $resource): Collection $serviceExists->fqdn = $url; $serviceExists->save(); } + // Create FQDN variable $resource->environment_variables()->updateOrCreate([ 'key' => $key->value(), 'resourceable_type' => get_class($resource), @@ -1784,9 +1871,22 @@ function serviceParser(Service $resource): Collection 'comment' => $envComments[$originalMagicKey] ?? null, ]); + // Also create the paired SERVICE_URL_* variable + $urlKey = 'SERVICE_URL_'.strtoupper($fqdnFor); + $resource->environment_variables()->updateOrCreate([ + 'key' => $urlKey, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $url, + 'is_preview' => false, + 'comment' => $envComments[$urlKey] ?? null, + ]); + } elseif ($command->value() === 'URL') { $urlFor = $key->after('SERVICE_URL_')->lower()->value(); $url = generateUrl(server: $server, random: str($urlFor)->replace('_', '-')->value()."-$uuid"); + $fqdn = generateFqdn(server: $server, random: str($urlFor)->replace('_', '-')->value()."-$uuid", parserVersion: $resource->compose_parsing_version); $envExists = $resource->environment_variables()->where('key', $key->value())->first(); // Also check if a port-suffixed version exists (e.g., SERVICE_URL_DASHBOARD_6791) @@ -1803,6 +1903,7 @@ function serviceParser(Service $resource): Collection $serviceExists->fqdn = $url; $serviceExists->save(); } + // Create URL variable $resource->environment_variables()->updateOrCreate([ 'key' => $key->value(), 'resourceable_type' => get_class($resource), @@ -1813,6 +1914,18 @@ function serviceParser(Service $resource): Collection 'comment' => $envComments[$originalMagicKey] ?? null, ]); + // Also create the paired SERVICE_FQDN_* variable + $fqdnKey = 'SERVICE_FQDN_'.strtoupper($urlFor); + $resource->environment_variables()->updateOrCreate([ + 'key' => $fqdnKey, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_preview' => false, + 'comment' => $envComments[$fqdnKey] ?? null, + ]); + } else { $value = generateEnvValue($command, $resource); $resource->environment_variables()->updateOrCreate([ @@ -2213,55 +2326,149 @@ function serviceParser(Service $resource): Collection } else { if ($value->startsWith('$')) { $isRequired = false; - if ($value->contains(':-')) { - $value = replaceVariables($value); - $key = $value->before(':'); - $value = $value->after(':-'); - } elseif ($value->contains('-')) { - $value = replaceVariables($value); - $key = $value->before('-'); - $value = $value->after('-'); - } elseif ($value->contains(':?')) { - $value = replaceVariables($value); + // Extract variable content between ${...} using balanced brace matching + $result = extractBalancedBraceContent($value->value(), 0); - $key = $value->before(':'); - $value = $value->after(':?'); - $isRequired = true; - } elseif ($value->contains('?')) { - $value = replaceVariables($value); + if ($result !== null) { + $content = $result['content']; + $split = splitOnOperatorOutsideNested($content); - $key = $value->before('?'); - $value = $value->after('?'); - $isRequired = true; - } - if ($originalValue->value() === $value->value()) { - // This means the variable does not have a default value, so it needs to be created in Coolify - $parsedKeyValue = replaceVariables($value); + if ($split !== null) { + // Has default value syntax (:-, -, :?, or ?) + $varName = $split['variable']; + $operator = $split['operator']; + $defaultValue = $split['default']; + $isRequired = str_contains($operator, '?'); + + // Create the primary variable with its default (only if it doesn't exist) + // Use firstOrCreate instead of updateOrCreate to avoid overwriting user edits + $envVar = $resource->environment_variables()->firstOrCreate([ + 'key' => $varName, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $defaultValue, + 'is_preview' => false, + 'is_required' => $isRequired, + 'comment' => $envComments[$originalKey] ?? null, + ]); + + // Add the variable to the environment so it will be shown in the deployable compose file + $environment[$varName] = $envVar->value; + + // Recursively process nested variables in default value + if (str_contains($defaultValue, '${')) { + // Extract and create nested variables + $searchPos = 0; + $nestedResult = extractBalancedBraceContent($defaultValue, $searchPos); + while ($nestedResult !== null) { + $nestedContent = $nestedResult['content']; + $nestedSplit = splitOnOperatorOutsideNested($nestedContent); + + // Determine the nested variable name + $nestedVarName = $nestedSplit !== null ? $nestedSplit['variable'] : $nestedContent; + + // Skip SERVICE_URL_* and SERVICE_FQDN_* variables - they are handled by magic variable system + $isMagicVariable = str_starts_with($nestedVarName, 'SERVICE_URL_') || str_starts_with($nestedVarName, 'SERVICE_FQDN_'); + + if (! $isMagicVariable) { + if ($nestedSplit !== null) { + // Create nested variable with its default (only if it doesn't exist) + $nestedEnvVar = $resource->environment_variables()->firstOrCreate([ + 'key' => $nestedSplit['variable'], + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $nestedSplit['default'], + 'is_preview' => false, + ]); + // Add nested variable to environment + $environment[$nestedSplit['variable']] = $nestedEnvVar->value; + } else { + // Simple nested variable without default (only if it doesn't exist) + $nestedEnvVar = $resource->environment_variables()->firstOrCreate([ + 'key' => $nestedContent, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'is_preview' => false, + ]); + // Add nested variable to environment + $environment[$nestedContent] = $nestedEnvVar->value; + } + } + + // Look for more nested variables + $searchPos = $nestedResult['end'] + 1; + if ($searchPos >= strlen($defaultValue)) { + break; + } + $nestedResult = extractBalancedBraceContent($defaultValue, $searchPos); + } + } + } else { + // Simple variable reference without default + $resource->environment_variables()->updateOrCreate([ + 'key' => $content, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'is_preview' => false, + 'is_required' => $isRequired, + 'comment' => $envComments[$originalKey] ?? null, + ]); + } + } else { + // Fallback to old behavior for malformed input (backward compatibility) + if ($value->contains(':-')) { + $value = replaceVariables($value); + $key = $value->before(':'); + $value = $value->after(':-'); + } elseif ($value->contains('-')) { + $value = replaceVariables($value); + $key = $value->before('-'); + $value = $value->after('-'); + } elseif ($value->contains(':?')) { + $value = replaceVariables($value); + $key = $value->before(':'); + $value = $value->after(':?'); + $isRequired = true; + } elseif ($value->contains('?')) { + $value = replaceVariables($value); + $key = $value->before('?'); + $value = $value->after('?'); + $isRequired = true; + } + + if ($originalValue->value() === $value->value()) { + // This means the variable does not have a default value + $parsedKeyValue = replaceVariables($value); + $resource->environment_variables()->updateOrCreate([ + 'key' => $parsedKeyValue, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'is_preview' => false, + 'is_required' => $isRequired, + 'comment' => $envComments[$originalKey] ?? null, + ]); + // Add the variable to the environment so it will be shown in the deployable compose file + $environment[$parsedKeyValue->value()] = $value; + + continue; + } $resource->environment_variables()->updateOrCreate([ - 'key' => $parsedKeyValue, + 'key' => $key, 'resourceable_type' => get_class($resource), 'resourceable_id' => $resource->id, ], [ + 'value' => $value, 'is_preview' => false, 'is_required' => $isRequired, 'comment' => $envComments[$originalKey] ?? null, ]); - // Add the variable to the environment so it will be shown in the deployable compose file - $environment[$parsedKeyValue->value()] = $value; - - continue; } - $resource->environment_variables()->updateOrCreate([ - 'key' => $key, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_preview' => false, - 'is_required' => $isRequired, - 'comment' => $envComments[$originalKey] ?? null, - ]); } } } diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 3d2b61b86..64ec282f5 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -17,9 +17,115 @@ function collectRegex(string $name) { return "/{$name}\w+/"; } + +/** + * Extract content between balanced braces, handling nested braces properly. + * + * @param string $str The string to search + * @param int $startPos Position to start searching from + * @return array|null Array with 'content', 'start', and 'end' keys, or null if no balanced braces found + */ +function extractBalancedBraceContent(string $str, int $startPos = 0): ?array +{ + // Find opening brace + $openPos = strpos($str, '{', $startPos); + if ($openPos === false) { + return null; + } + + // Track depth to find matching closing brace + $depth = 1; + $pos = $openPos + 1; + $len = strlen($str); + + while ($pos < $len && $depth > 0) { + if ($str[$pos] === '{') { + $depth++; + } elseif ($str[$pos] === '}') { + $depth--; + } + $pos++; + } + + if ($depth !== 0) { + // Unbalanced braces + return null; + } + + return [ + 'content' => substr($str, $openPos + 1, $pos - $openPos - 2), + 'start' => $openPos, + 'end' => $pos - 1, + ]; +} + +/** + * Split variable expression on operators (:-, -, :?, ?) while respecting nested braces. + * + * @param string $content The content to split (without outer ${...}) + * @return array|null Array with 'variable', 'operator', and 'default' keys, or null if no operator found + */ +function splitOnOperatorOutsideNested(string $content): ?array +{ + $operators = [':-', '-', ':?', '?']; + $depth = 0; + $len = strlen($content); + + for ($i = 0; $i < $len; $i++) { + if ($content[$i] === '{') { + $depth++; + } elseif ($content[$i] === '}') { + $depth--; + } elseif ($depth === 0) { + // Check for operators only at depth 0 (outside nested braces) + foreach ($operators as $op) { + if (substr($content, $i, strlen($op)) === $op) { + return [ + 'variable' => substr($content, 0, $i), + 'operator' => $op, + 'default' => substr($content, $i + strlen($op)), + ]; + } + } + } + } + + return null; +} + function replaceVariables(string $variable): Stringable { - return str($variable)->before('}')->replaceFirst('$', '')->replaceFirst('{', ''); + // Handle ${VAR} syntax with proper brace matching + $str = str($variable); + + // Handle ${VAR} format + if ($str->startsWith('${')) { + $result = extractBalancedBraceContent($variable, 0); + if ($result !== null) { + return str($result['content']); + } + + // Fallback to old behavior for malformed input + return $str->before('}')->replaceFirst('$', '')->replaceFirst('{', ''); + } + + // Handle {VAR} format (from regex capture group without $) + if ($str->startsWith('{') && $str->endsWith('}')) { + return str(substr($variable, 1, -1)); + } + + // Handle {VAR format (from regex capture group, may be truncated) + if ($str->startsWith('{')) { + $result = extractBalancedBraceContent('$'.$variable, 0); + if ($result !== null) { + return str($result['content']); + } + + // Fallback: remove { and get content before } + return $str->replaceFirst('{', '')->before('}'); + } + + return $str; } function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index f2f29bdc6..0437aaa70 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -3692,3 +3692,55 @@ function verifyPasswordConfirmation(mixed $password, ?Livewire\Component $compon return true; } + +/** + * Extract hard-coded environment variables from docker-compose YAML. + * + * @param string $dockerComposeRaw Raw YAML content + * @return \Illuminate\Support\Collection Collection of arrays with: key, value, comment, service_name + */ +function extractHardcodedEnvironmentVariables(string $dockerComposeRaw): \Illuminate\Support\Collection +{ + if (blank($dockerComposeRaw)) { + return collect([]); + } + + try { + $yaml = \Symfony\Component\Yaml\Yaml::parse($dockerComposeRaw); + } catch (\Exception $e) { + // Malformed YAML - return empty collection + return collect([]); + } + + $services = data_get($yaml, 'services', []); + if (empty($services)) { + return collect([]); + } + + // Extract inline comments from raw YAML + $envComments = extractYamlEnvironmentComments($dockerComposeRaw); + + $hardcodedVars = collect([]); + + foreach ($services as $serviceName => $service) { + $environment = collect(data_get($service, 'environment', [])); + + if ($environment->isEmpty()) { + continue; + } + + // Convert environment variables to key-value format + $environment = convertToKeyValueCollection($environment); + + foreach ($environment as $key => $value) { + $hardcodedVars->push([ + 'key' => $key, + 'value' => $value, + 'comment' => $envComments[$key] ?? null, + 'service_name' => $serviceName, + ]); + } + } + + return $hardcodedVars; +} diff --git a/resources/views/livewire/project/shared/environment-variable/all.blade.php b/resources/views/livewire/project/shared/environment-variable/all.blade.php index f1d108703..a962b2cec 100644 --- a/resources/views/livewire/project/shared/environment-variable/all.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/all.blade.php @@ -41,19 +41,6 @@
@endif - @if ($resource->type() === 'service' || $resource?->build_pack === 'dockercompose') -
- - Hardcoded variables are not shown here. -
- {{--
If you would like to add a variable, you must add it to - your compose file.
--}} - @endif @if ($view === 'normal')
@@ -66,6 +53,13 @@ @empty
No environment variables found.
@endforelse + @if (($resource->type() === 'service' || $resource?->build_pack === 'dockercompose') && $this->hardcodedEnvironmentVariables->isNotEmpty()) + @foreach ($this->hardcodedEnvironmentVariables as $index => $env) + + @endforeach + @endif @if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0 && $showPreview)

Preview Deployments Environment Variables

@@ -75,6 +69,13 @@ @endforeach + @if (($resource->type() === 'service' || $resource?->build_pack === 'dockercompose') && $this->hardcodedEnvironmentVariablesPreview->isNotEmpty()) + @foreach ($this->hardcodedEnvironmentVariablesPreview as $index => $env) + + @endforeach + @endif @endif @else
diff --git a/resources/views/livewire/project/shared/environment-variable/show-hardcoded.blade.php b/resources/views/livewire/project/shared/environment-variable/show-hardcoded.blade.php new file mode 100644 index 000000000..9158d127e --- /dev/null +++ b/resources/views/livewire/project/shared/environment-variable/show-hardcoded.blade.php @@ -0,0 +1,31 @@ +
+
+
+ + Hardcoded env + + @if($serviceName) + + Service: {{ $serviceName }} + + @endif +
+
+
+ + @if($value !== null && $value !== '') + + @else + + @endif +
+ @if($comment) + + @endif +
+
+
\ No newline at end of file diff --git a/tests/Unit/ExtractHardcodedEnvironmentVariablesTest.php b/tests/Unit/ExtractHardcodedEnvironmentVariablesTest.php new file mode 100644 index 000000000..8d8caacaf --- /dev/null +++ b/tests/Unit/ExtractHardcodedEnvironmentVariablesTest.php @@ -0,0 +1,147 @@ +toHaveCount(2) + ->and($result[0]['key'])->toBe('NODE_ENV') + ->and($result[0]['value'])->toBe('production') + ->and($result[0]['service_name'])->toBe('app') + ->and($result[1]['key'])->toBe('PORT') + ->and($result[1]['value'])->toBe('3000') + ->and($result[1]['service_name'])->toBe('app'); +}); + +test('extracts environment variables with inline comments', function () { + $yaml = <<<'YAML' +services: + app: + environment: + - NODE_ENV=production # Production environment + - DEBUG=false # Disable debug mode +YAML; + + $result = extractHardcodedEnvironmentVariables($yaml); + + expect($result)->toHaveCount(2) + ->and($result[0]['comment'])->toBe('Production environment') + ->and($result[1]['comment'])->toBe('Disable debug mode'); +}); + +test('handles multiple services', function () { + $yaml = <<<'YAML' +services: + app: + environment: + - APP_ENV=prod + db: + environment: + - POSTGRES_DB=mydb +YAML; + + $result = extractHardcodedEnvironmentVariables($yaml); + + expect($result)->toHaveCount(2) + ->and($result[0]['key'])->toBe('APP_ENV') + ->and($result[0]['service_name'])->toBe('app') + ->and($result[1]['key'])->toBe('POSTGRES_DB') + ->and($result[1]['service_name'])->toBe('db'); +}); + +test('handles associative array format', function () { + $yaml = <<<'YAML' +services: + app: + environment: + NODE_ENV: production + PORT: 3000 +YAML; + + $result = extractHardcodedEnvironmentVariables($yaml); + + expect($result)->toHaveCount(2) + ->and($result[0]['key'])->toBe('NODE_ENV') + ->and($result[0]['value'])->toBe('production') + ->and($result[1]['key'])->toBe('PORT') + ->and($result[1]['value'])->toBe(3000); // Integer values stay as integers from YAML +}); + +test('handles environment variables without values', function () { + $yaml = <<<'YAML' +services: + app: + environment: + - API_KEY + - DEBUG=false +YAML; + + $result = extractHardcodedEnvironmentVariables($yaml); + + expect($result)->toHaveCount(2) + ->and($result[0]['key'])->toBe('API_KEY') + ->and($result[0]['value'])->toBe('') // Variables without values get empty string, not null + ->and($result[1]['key'])->toBe('DEBUG') + ->and($result[1]['value'])->toBe('false'); +}); + +test('returns empty collection for malformed YAML', function () { + $yaml = 'invalid: yaml: content::: [[['; + + $result = extractHardcodedEnvironmentVariables($yaml); + + expect($result)->toBeEmpty(); +}); + +test('returns empty collection for empty compose file', function () { + $result = extractHardcodedEnvironmentVariables(''); + + expect($result)->toBeEmpty(); +}); + +test('returns empty collection when no services defined', function () { + $yaml = <<<'YAML' +version: '3.8' +networks: + default: +YAML; + + $result = extractHardcodedEnvironmentVariables($yaml); + + expect($result)->toBeEmpty(); +}); + +test('returns empty collection when service has no environment section', function () { + $yaml = <<<'YAML' +services: + app: + image: nginx +YAML; + + $result = extractHardcodedEnvironmentVariables($yaml); + + expect($result)->toBeEmpty(); +}); + +test('handles mixed associative and array format', function () { + $yaml = <<<'YAML' +services: + app: + environment: + - NODE_ENV=production + PORT: 3000 +YAML; + + $result = extractHardcodedEnvironmentVariables($yaml); + + // Mixed format is invalid YAML and returns empty collection + expect($result)->toBeEmpty(); +}); diff --git a/tests/Unit/NestedEnvironmentVariableParsingTest.php b/tests/Unit/NestedEnvironmentVariableParsingTest.php new file mode 100644 index 000000000..65e8738cc --- /dev/null +++ b/tests/Unit/NestedEnvironmentVariableParsingTest.php @@ -0,0 +1,220 @@ +not->toBeNull() + ->and($result['content'])->toBe('API_URL:-${SERVICE_URL_YOLO}/api'); + + $split = splitOnOperatorOutsideNested($result['content']); + + expect($split)->not->toBeNull() + ->and($split['variable'])->toBe('API_URL') + ->and($split['operator'])->toBe(':-') + ->and($split['default'])->toBe('${SERVICE_URL_YOLO}/api'); +}); + +test('replaceVariables correctly extracts nested variable content', function () { + // Before the fix, this would incorrectly extract only up to the first closing brace + $result = replaceVariables('${API_URL:-${SERVICE_URL_YOLO}/api}'); + + // Should extract the full content, not just "${API_URL:-${SERVICE_URL_YOLO" + expect($result->value())->toBe('API_URL:-${SERVICE_URL_YOLO}/api') + ->and($result->value())->not->toBe('API_URL:-${SERVICE_URL_YOLO'); // Not truncated +}); + +test('nested defaults with path concatenation work', function () { + $input = '${REDIS_URL:-${SERVICE_URL_REDIS}/db/0}'; + + $result = extractBalancedBraceContent($input, 0); + $split = splitOnOperatorOutsideNested($result['content']); + + expect($split['variable'])->toBe('REDIS_URL') + ->and($split['default'])->toBe('${SERVICE_URL_REDIS}/db/0'); +}); + +test('deeply nested variables are handled', function () { + // Three levels of nesting + $input = '${A:-${B:-${C}}}'; + + $result = extractBalancedBraceContent($input, 0); + + expect($result['content'])->toBe('A:-${B:-${C}}'); + + $split = splitOnOperatorOutsideNested($result['content']); + + expect($split['variable'])->toBe('A') + ->and($split['default'])->toBe('${B:-${C}}'); +}); + +test('multiple nested variables in default value', function () { + // Default value contains multiple variable references + $input = '${API:-${SERVICE_URL}:${SERVICE_PORT}/api}'; + + $result = extractBalancedBraceContent($input, 0); + $split = splitOnOperatorOutsideNested($result['content']); + + expect($split['variable'])->toBe('API') + ->and($split['default'])->toBe('${SERVICE_URL}:${SERVICE_PORT}/api'); +}); + +test('nested variables with different operators', function () { + // Nested variable uses different operator + $input = '${API_URL:-${SERVICE_URL?error message}/api}'; + + $result = extractBalancedBraceContent($input, 0); + $split = splitOnOperatorOutsideNested($result['content']); + + expect($split['variable'])->toBe('API_URL') + ->and($split['operator'])->toBe(':-') + ->and($split['default'])->toBe('${SERVICE_URL?error message}/api'); +}); + +test('backward compatibility with simple variables', function () { + // Simple variable without nesting should still work + $input = '${VAR}'; + + $result = replaceVariables($input); + + expect($result->value())->toBe('VAR'); +}); + +test('backward compatibility with single-level defaults', function () { + // Single-level default without nesting + $input = '${VAR:-default_value}'; + + $result = replaceVariables($input); + + expect($result->value())->toBe('VAR:-default_value'); + + $split = splitOnOperatorOutsideNested($result->value()); + + expect($split['variable'])->toBe('VAR') + ->and($split['default'])->toBe('default_value'); +}); + +test('backward compatibility with dash operator', function () { + $input = '${VAR-default}'; + + $result = replaceVariables($input); + $split = splitOnOperatorOutsideNested($result->value()); + + expect($split['operator'])->toBe('-'); +}); + +test('backward compatibility with colon question operator', function () { + $input = '${VAR:?error message}'; + + $result = replaceVariables($input); + $split = splitOnOperatorOutsideNested($result->value()); + + expect($split['operator'])->toBe(':?') + ->and($split['default'])->toBe('error message'); +}); + +test('backward compatibility with question operator', function () { + $input = '${VAR?error}'; + + $result = replaceVariables($input); + $split = splitOnOperatorOutsideNested($result->value()); + + expect($split['operator'])->toBe('?') + ->and($split['default'])->toBe('error'); +}); + +test('SERVICE_URL magic variables in nested defaults', function () { + // Real-world scenario: SERVICE_URL_* magic variable used in nested default + $input = '${DATABASE_URL:-${SERVICE_URL_POSTGRES}/mydb}'; + + $result = extractBalancedBraceContent($input, 0); + $split = splitOnOperatorOutsideNested($result['content']); + + expect($split['variable'])->toBe('DATABASE_URL') + ->and($split['default'])->toBe('${SERVICE_URL_POSTGRES}/mydb'); + + // Extract the nested SERVICE_URL variable + $nestedResult = extractBalancedBraceContent($split['default'], 0); + + expect($nestedResult['content'])->toBe('SERVICE_URL_POSTGRES'); +}); + +test('SERVICE_FQDN magic variables in nested defaults', function () { + $input = '${API_HOST:-${SERVICE_FQDN_API}}'; + + $result = extractBalancedBraceContent($input, 0); + $split = splitOnOperatorOutsideNested($result['content']); + + expect($split['default'])->toBe('${SERVICE_FQDN_API}'); + + $nestedResult = extractBalancedBraceContent($split['default'], 0); + + expect($nestedResult['content'])->toBe('SERVICE_FQDN_API'); +}); + +test('complex real-world example', function () { + // Complex real-world scenario from the bug report + $input = '${API_URL:-${SERVICE_URL_YOLO}/api}'; + + // Step 1: Extract outer variable content + $result = extractBalancedBraceContent($input, 0); + expect($result['content'])->toBe('API_URL:-${SERVICE_URL_YOLO}/api'); + + // Step 2: Split on operator + $split = splitOnOperatorOutsideNested($result['content']); + expect($split['variable'])->toBe('API_URL'); + expect($split['operator'])->toBe(':-'); + expect($split['default'])->toBe('${SERVICE_URL_YOLO}/api'); + + // Step 3: Extract nested variable + $nestedResult = extractBalancedBraceContent($split['default'], 0); + expect($nestedResult['content'])->toBe('SERVICE_URL_YOLO'); + + // This verifies that: + // 1. API_URL should be created with value "${SERVICE_URL_YOLO}/api" + // 2. SERVICE_URL_YOLO should be recognized and created as magic variable +}); + +test('empty nested default values', function () { + $input = '${VAR:-${NESTED:-}}'; + + $result = extractBalancedBraceContent($input, 0); + $split = splitOnOperatorOutsideNested($result['content']); + + expect($split['default'])->toBe('${NESTED:-}'); + + $nestedResult = extractBalancedBraceContent($split['default'], 0); + $nestedSplit = splitOnOperatorOutsideNested($nestedResult['content']); + + expect($nestedSplit['default'])->toBe(''); +}); + +test('nested variables with complex paths', function () { + $input = '${CONFIG_URL:-${SERVICE_URL_CONFIG}/v2/config.json}'; + + $result = extractBalancedBraceContent($input, 0); + $split = splitOnOperatorOutsideNested($result['content']); + + expect($split['default'])->toBe('${SERVICE_URL_CONFIG}/v2/config.json'); +}); + +test('operator precedence with nesting', function () { + // The first :- at depth 0 should be used, not the one inside nested braces + $input = '${A:-${B:-default}}'; + + $result = extractBalancedBraceContent($input, 0); + $split = splitOnOperatorOutsideNested($result['content']); + + // Should split on first :- (at depth 0) + expect($split['variable'])->toBe('A') + ->and($split['operator'])->toBe(':-') + ->and($split['default'])->toBe('${B:-default}'); // Not split here +}); diff --git a/tests/Unit/NestedEnvironmentVariableTest.php b/tests/Unit/NestedEnvironmentVariableTest.php new file mode 100644 index 000000000..81b440927 --- /dev/null +++ b/tests/Unit/NestedEnvironmentVariableTest.php @@ -0,0 +1,207 @@ +toBe('VAR') + ->and($result['start'])->toBe(1) + ->and($result['end'])->toBe(5); +}); + +test('extractBalancedBraceContent handles nested braces', function () { + $result = extractBalancedBraceContent('${API_URL:-${SERVICE_URL_YOLO}/api}', 0); + + assertNotNull($result); + expect($result['content'])->toBe('API_URL:-${SERVICE_URL_YOLO}/api') + ->and($result['start'])->toBe(1) + ->and($result['end'])->toBe(34); // Position of closing } +}); + +test('extractBalancedBraceContent handles triple nesting', function () { + $result = extractBalancedBraceContent('${A:-${B:-${C}}}', 0); + + assertNotNull($result); + expect($result['content'])->toBe('A:-${B:-${C}}'); +}); + +test('extractBalancedBraceContent returns null for unbalanced braces', function () { + $result = extractBalancedBraceContent('${VAR', 0); + + assertNull($result); +}); + +test('extractBalancedBraceContent returns null when no braces', function () { + $result = extractBalancedBraceContent('VAR', 0); + + assertNull($result); +}); + +test('extractBalancedBraceContent handles startPos parameter', function () { + $result = extractBalancedBraceContent('foo ${VAR} bar', 4); + + assertNotNull($result); + expect($result['content'])->toBe('VAR') + ->and($result['start'])->toBe(5) + ->and($result['end'])->toBe(9); +}); + +test('splitOnOperatorOutsideNested splits on :- operator', function () { + $split = splitOnOperatorOutsideNested('API_URL:-default_value'); + + assertNotNull($split); + expect($split['variable'])->toBe('API_URL') + ->and($split['operator'])->toBe(':-') + ->and($split['default'])->toBe('default_value'); +}); + +test('splitOnOperatorOutsideNested handles nested defaults', function () { + $split = splitOnOperatorOutsideNested('API_URL:-${SERVICE_URL_YOLO}/api'); + + assertNotNull($split); + expect($split['variable'])->toBe('API_URL') + ->and($split['operator'])->toBe(':-') + ->and($split['default'])->toBe('${SERVICE_URL_YOLO}/api'); +}); + +test('splitOnOperatorOutsideNested handles dash operator', function () { + $split = splitOnOperatorOutsideNested('VAR-default'); + + assertNotNull($split); + expect($split['variable'])->toBe('VAR') + ->and($split['operator'])->toBe('-') + ->and($split['default'])->toBe('default'); +}); + +test('splitOnOperatorOutsideNested handles colon question operator', function () { + $split = splitOnOperatorOutsideNested('VAR:?error message'); + + assertNotNull($split); + expect($split['variable'])->toBe('VAR') + ->and($split['operator'])->toBe(':?') + ->and($split['default'])->toBe('error message'); +}); + +test('splitOnOperatorOutsideNested handles question operator', function () { + $split = splitOnOperatorOutsideNested('VAR?error'); + + assertNotNull($split); + expect($split['variable'])->toBe('VAR') + ->and($split['operator'])->toBe('?') + ->and($split['default'])->toBe('error'); +}); + +test('splitOnOperatorOutsideNested returns null for simple variable', function () { + $split = splitOnOperatorOutsideNested('SIMPLE_VAR'); + + assertNull($split); +}); + +test('splitOnOperatorOutsideNested ignores operators inside nested braces', function () { + $split = splitOnOperatorOutsideNested('A:-${B:-default}'); + + assertNotNull($split); + // Should split on first :- (outside nested braces), not the one inside ${B:-default} + expect($split['variable'])->toBe('A') + ->and($split['operator'])->toBe(':-') + ->and($split['default'])->toBe('${B:-default}'); +}); + +test('replaceVariables handles simple variable', function () { + $result = replaceVariables('${VAR}'); + + expect($result->value())->toBe('VAR'); +}); + +test('replaceVariables handles nested expressions', function () { + $result = replaceVariables('${API_URL:-${SERVICE_URL_YOLO}/api}'); + + expect($result->value())->toBe('API_URL:-${SERVICE_URL_YOLO}/api'); +}); + +test('replaceVariables handles variable with default', function () { + $result = replaceVariables('${API_URL:-http://localhost}'); + + expect($result->value())->toBe('API_URL:-http://localhost'); +}); + +test('replaceVariables returns unchanged for non-variable string', function () { + $result = replaceVariables('not_a_variable'); + + expect($result->value())->toBe('not_a_variable'); +}); + +test('replaceVariables handles triple nesting', function () { + $result = replaceVariables('${A:-${B:-${C}}}'); + + expect($result->value())->toBe('A:-${B:-${C}}'); +}); + +test('replaceVariables fallback works for malformed input', function () { + // When braces are unbalanced, it falls back to old behavior + $result = replaceVariables('${VAR'); + + // Old behavior would extract everything before first } + // But since there's no }, it will extract 'VAR' (removing ${) + expect($result->value())->toContain('VAR'); +}); + +test('extractBalancedBraceContent handles complex nested expression', function () { + $result = extractBalancedBraceContent('${API:-${SERVICE_URL}/api/v${VERSION:-1}}', 0); + + assertNotNull($result); + expect($result['content'])->toBe('API:-${SERVICE_URL}/api/v${VERSION:-1}'); +}); + +test('splitOnOperatorOutsideNested handles complex nested expression', function () { + $split = splitOnOperatorOutsideNested('API:-${SERVICE_URL}/api/v${VERSION:-1}'); + + assertNotNull($split); + expect($split['variable'])->toBe('API') + ->and($split['operator'])->toBe(':-') + ->and($split['default'])->toBe('${SERVICE_URL}/api/v${VERSION:-1}'); +}); + +test('extractBalancedBraceContent finds second variable in string', function () { + $str = '${VAR1} and ${VAR2}'; + + // First variable + $result1 = extractBalancedBraceContent($str, 0); + assertNotNull($result1); + expect($result1['content'])->toBe('VAR1'); + + // Second variable + $result2 = extractBalancedBraceContent($str, $result1['end'] + 1); + assertNotNull($result2); + expect($result2['content'])->toBe('VAR2'); +}); + +test('replaceVariables handles empty default value', function () { + $result = replaceVariables('${VAR:-}'); + + expect($result->value())->toBe('VAR:-'); +}); + +test('splitOnOperatorOutsideNested handles empty default value', function () { + $split = splitOnOperatorOutsideNested('VAR:-'); + + assertNotNull($split); + expect($split['variable'])->toBe('VAR') + ->and($split['operator'])->toBe(':-') + ->and($split['default'])->toBe(''); +}); + +test('replaceVariables handles brace format without dollar sign', function () { + // This format is used by the regex capture group in magic variable detection + $result = replaceVariables('{SERVICE_URL_YOLO}'); + expect($result->value())->toBe('SERVICE_URL_YOLO'); +}); + +test('replaceVariables handles truncated brace format', function () { + // When regex captures {VAR from a larger expression, no closing brace + $result = replaceVariables('{API_URL'); + expect($result->value())->toBe('API_URL'); +}); From 87f9ce0674c02e4bd1795ce4dc4bf202b175f645 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:43:16 +0100 Subject: [PATCH 020/434] Add comment field support to environment variable API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API consumers can now create and update environment variables with an optional comment field for documentation purposes. Changes include: - Added comment validation (string, nullable, max 256 chars) to all env endpoints - Updated ApplicationsController create_env and update_env_by_uuid - Updated ServicesController create_env and update_env_by_uuid - Updated openapi.json request schemas to document the comment field 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Api/ApplicationsController.php | 14 +++++++++-- .../Controllers/Api/ServicesController.php | 25 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 92c5f04a2..def672a75 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -2529,7 +2529,7 @@ public function envs(Request $request) )] public function update_env_by_uuid(Request $request) { - $allowedFields = ['key', 'value', 'is_preview', 'is_literal', 'is_multiline', 'is_shown_once', 'is_runtime', 'is_buildtime']; + $allowedFields = ['key', 'value', 'is_preview', 'is_literal', 'is_multiline', 'is_shown_once', 'is_runtime', 'is_buildtime', 'comment']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -2559,6 +2559,7 @@ public function update_env_by_uuid(Request $request) 'is_shown_once' => 'boolean', 'is_runtime' => 'boolean', 'is_buildtime' => 'boolean', + 'comment' => 'string|nullable|max:256', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -2600,6 +2601,9 @@ public function update_env_by_uuid(Request $request) if ($request->has('is_buildtime') && $env->is_buildtime != $request->is_buildtime) { $env->is_buildtime = $request->is_buildtime; } + if ($request->has('comment') && $env->comment != $request->comment) { + $env->comment = $request->comment; + } $env->save(); return response()->json($this->removeSensitiveData($env))->setStatusCode(201); @@ -2630,6 +2634,9 @@ public function update_env_by_uuid(Request $request) if ($request->has('is_buildtime') && $env->is_buildtime != $request->is_buildtime) { $env->is_buildtime = $request->is_buildtime; } + if ($request->has('comment') && $env->comment != $request->comment) { + $env->comment = $request->comment; + } $env->save(); return response()->json($this->removeSensitiveData($env))->setStatusCode(201); @@ -2926,7 +2933,7 @@ public function create_bulk_envs(Request $request) )] public function create_env(Request $request) { - $allowedFields = ['key', 'value', 'is_preview', 'is_literal', 'is_multiline', 'is_shown_once', 'is_runtime', 'is_buildtime']; + $allowedFields = ['key', 'value', 'is_preview', 'is_literal', 'is_multiline', 'is_shown_once', 'is_runtime', 'is_buildtime', 'comment']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -2951,6 +2958,7 @@ public function create_env(Request $request) 'is_shown_once' => 'boolean', 'is_runtime' => 'boolean', 'is_buildtime' => 'boolean', + 'comment' => 'string|nullable|max:256', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -2986,6 +2994,7 @@ public function create_env(Request $request) 'is_shown_once' => $request->is_shown_once ?? false, 'is_runtime' => $request->is_runtime ?? true, 'is_buildtime' => $request->is_buildtime ?? true, + 'comment' => $request->comment ?? null, 'resourceable_type' => get_class($application), 'resourceable_id' => $application->id, ]); @@ -3010,6 +3019,7 @@ public function create_env(Request $request) 'is_shown_once' => $request->is_shown_once ?? false, 'is_runtime' => $request->is_runtime ?? true, 'is_buildtime' => $request->is_buildtime ?? true, + 'comment' => $request->comment ?? null, 'resourceable_type' => get_class($application), 'resourceable_id' => $application->id, ]); diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 587f49fa5..802cfa1a3 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -1031,6 +1031,7 @@ public function update_env_by_uuid(Request $request) 'is_literal' => 'boolean', 'is_multiline' => 'boolean', 'is_shown_once' => 'boolean', + 'comment' => 'string|nullable|max:256', ]); if ($validator->fails()) { @@ -1046,7 +1047,19 @@ public function update_env_by_uuid(Request $request) return response()->json(['message' => 'Environment variable not found.'], 404); } - $env->fill($request->all()); + $env->value = $request->value; + if ($request->has('is_literal')) { + $env->is_literal = $request->is_literal; + } + if ($request->has('is_multiline')) { + $env->is_multiline = $request->is_multiline; + } + if ($request->has('is_shown_once')) { + $env->is_shown_once = $request->is_shown_once; + } + if ($request->has('comment')) { + $env->comment = $request->comment; + } $env->save(); return response()->json($this->removeSensitiveData($env))->setStatusCode(201); @@ -1276,6 +1289,7 @@ public function create_env(Request $request) 'is_literal' => 'boolean', 'is_multiline' => 'boolean', 'is_shown_once' => 'boolean', + 'comment' => 'string|nullable|max:256', ]); if ($validator->fails()) { @@ -1293,7 +1307,14 @@ public function create_env(Request $request) ], 409); } - $env = $service->environment_variables()->create($request->all()); + $env = $service->environment_variables()->create([ + 'key' => $key, + 'value' => $request->value, + 'is_literal' => $request->is_literal ?? false, + 'is_multiline' => $request->is_multiline ?? false, + 'is_shown_once' => $request->is_shown_once ?? false, + 'comment' => $request->comment ?? null, + ]); return response()->json($this->removeSensitiveData($env))->setStatusCode(201); } From 21a7f2f581956e268a0b169b1c3be96685d8545c Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Thu, 1 Jan 2026 12:03:13 +0000 Subject: [PATCH 021/434] fix(api): add docker_cleanup parameter to stop endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add optional docker_cleanup query parameter to the stop endpoints for Services, Applications, and Databases. This allows API users to control whether docker cleanup (pruning networks, volumes, etc.) is performed when stopping resources. The parameter defaults to true for backward compatibility. API Usage: - Stop without docker cleanup: GET /api/v1/{resource}/{uuid}/stop?docker_cleanup=false - Stop with docker cleanup (default): GET /api/v1/{resource}/{uuid}/stop Fixes #7758 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Andras Bacsai --- app/Http/Controllers/Api/ApplicationsController.php | 12 +++++++++++- app/Http/Controllers/Api/DatabasesController.php | 13 ++++++++++++- app/Http/Controllers/Api/ServicesController.php | 13 ++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 92c5f04a2..1e39215ac 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -3250,6 +3250,15 @@ public function action_deploy(Request $request) format: 'uuid', ) ), + new OA\Parameter( + name: 'docker_cleanup', + in: 'query', + description: 'Perform docker cleanup (prune networks, volumes, etc.).', + schema: new OA\Schema( + type: 'boolean', + default: true, + ) + ), ], responses: [ new OA\Response( @@ -3298,7 +3307,8 @@ public function action_stop(Request $request) $this->authorize('deploy', $application); - StopApplication::dispatch($application); + $dockerCleanup = $request->boolean('docker_cleanup', true); + StopApplication::dispatch($application, false, $dockerCleanup); return response()->json( [ diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 0d38b7363..d3adb84d5 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -2611,6 +2611,15 @@ public function action_deploy(Request $request) format: 'uuid', ) ), + new OA\Parameter( + name: 'docker_cleanup', + in: 'query', + description: 'Perform docker cleanup (prune networks, volumes, etc.).', + schema: new OA\Schema( + type: 'boolean', + default: true, + ) + ), ], responses: [ new OA\Response( @@ -2662,7 +2671,9 @@ public function action_stop(Request $request) if (str($database->status)->contains('stopped') || str($database->status)->contains('exited')) { return response()->json(['message' => 'Database is already stopped.'], 400); } - StopDatabase::dispatch($database); + + $dockerCleanup = $request->boolean('docker_cleanup', true); + StopDatabase::dispatch($database, $dockerCleanup); return response()->json( [ diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 587f49fa5..d58ee443a 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -1488,6 +1488,15 @@ public function action_deploy(Request $request) format: 'uuid', ) ), + new OA\Parameter( + name: 'docker_cleanup', + in: 'query', + description: 'Perform docker cleanup (prune networks, volumes, etc.).', + schema: new OA\Schema( + type: 'boolean', + default: true, + ) + ), ], responses: [ new OA\Response( @@ -1539,7 +1548,9 @@ public function action_stop(Request $request) if (str($service->status)->contains('stopped') || str($service->status)->contains('exited')) { return response()->json(['message' => 'Service is already stopped.'], 400); } - StopService::dispatch($service); + + $dockerCleanup = $request->boolean('docker_cleanup', true); + StopService::dispatch($service, false, $dockerCleanup); return response()->json( [ From 764d8861f6ef0755d07b5c9c22ada2ae63993954 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Sun, 11 Jan 2026 22:19:09 +0100 Subject: [PATCH 022/434] feat(api): add update urls support to services api - added update urls support to services api - remove old stale domains update code --- .../Controllers/Api/ServicesController.php | 192 ++++++++++++++---- openapi.json | 34 ++++ openapi.yaml | 8 + 3 files changed, 195 insertions(+), 39 deletions(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 4025898b9..56812c94c 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -12,6 +12,7 @@ use App\Models\Server; use App\Models\Service; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; use OpenApi\Attributes as OA; use Symfony\Component\Yaml\Yaml; @@ -37,6 +38,67 @@ private function removeSensitiveData($service) return serializeApiResponse($service); } + private function applyServiceUrls(Service $service, array $urls, string $teamId): ?array + { + $errors = []; + + foreach ($urls as $item) { + $name = data_get($item, 'name'); + $urls = data_get($item, 'url'); + + if (blank($name)) { + $errors[] = 'Service container name is required to apply URLs.'; + + continue; + } + + $application = $service->applications()->where('name', $name)->first(); + if (! $application) { + $errors[] = "Service container with '{$name}' not found."; + + continue; + } + + if (filled($urls)) { + $urls = str($urls)->replaceStart(',', '')->replaceEnd(',', '')->trim(); + $urls = str($urls)->explode(',')->map(function ($url) use (&$errors) { + $url = trim($url); + if (! filter_var($url, FILTER_VALIDATE_URL)) { + $errors[] = 'Invalid URL: '.$url; + + return str($url)->lower(); + } + $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; + if (! in_array(strtolower($scheme), ['http', 'https'])) { + $errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; + } + + return str($url)->lower(); + })->filter(fn ($u) => $u->isNotEmpty())->unique()->implode(','); + + if ($urls && empty($errors)) { + $result = checkIfDomainIsAlreadyUsedViaAPI(collect(explode(',', $urls)), $teamId, $application->uuid); + if ($result['hasConflicts']) { + foreach ($result['conflicts'] as $conflict) { + $errors[] = $conflict['message']; + } + } + } + } else { + $urls = null; + } + + $application->fqdn = $urls; + $application->save(); + } + + if (! empty($errors)) { + return ['errors' => $errors]; + } + + return null; + } + #[OA\Get( summary: 'List', description: 'List all services.', @@ -115,6 +177,17 @@ public function services(Request $request) 'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'], 'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'], 'docker_compose_raw' => ['type' => 'string', 'description' => 'The base64 encoded Docker Compose content.'], + 'urls' => [ + 'type' => 'array', + 'description' => 'Array of URLs to be applied to containers of a service.', + 'items' => new OA\Schema( + type: 'object', + properties: [ + 'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'], + 'url' => ['type' => 'string', 'description' => 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").'], + ], + ), + ], ], ), ), @@ -152,7 +225,7 @@ public function services(Request $request) )] public function create_service(Request $request) { - $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw']; + $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'urls']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -165,7 +238,7 @@ public function create_service(Request $request) if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $validator = customApiValidator($request->all(), [ + $validationRules = [ 'type' => 'string|required_without:docker_compose_raw', 'docker_compose_raw' => 'string|required_without:type', 'project_uuid' => 'string|required', @@ -176,7 +249,15 @@ public function create_service(Request $request) 'name' => 'string|max:255', 'description' => 'string|nullable', 'instant_deploy' => 'boolean', - ]); + 'urls' => 'array|nullable', + 'urls.*' => 'array:name,url', + 'urls.*.name' => 'string|required', + 'urls.*.url' => 'string|nullable', + ]; + $validationMessages = [ + 'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.', + ]; + $validator = Validator::make($request->all(), $validationRules, $validationMessages); $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { @@ -296,29 +377,31 @@ public function create_service(Request $request) // Apply service-specific application prerequisites applyServiceApplicationPrerequisites($service); + if ($request->has('urls') && is_array($request->urls)) { + $urlResult = $this->applyServiceUrls($service, $request->urls, $teamId); + if ($urlResult !== null) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $urlResult['errors'], + ], 422); + } + } + if ($instantDeploy) { StartService::dispatch($service); } - $domains = $service->applications()->get()->pluck('fqdn')->sort(); - $domains = $domains->map(function ($domain) { - if (count(explode(':', $domain)) > 2) { - return str($domain)->beforeLast(':')->value(); - } - - return $domain; - }); return response()->json([ 'uuid' => $service->uuid, - 'domains' => $domains, - ]); + 'domains' => $service->applications()->pluck('fqdn')->filter()->sort()->values(), + ])->setStatusCode(201); } return response()->json(['message' => 'Service not found.', 'valid_service_types' => $serviceKeys], 404); } elseif (filled($request->docker_compose_raw)) { - $allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network']; + $allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls']; - $validator = customApiValidator($request->all(), [ + $validationRules = [ 'project_uuid' => 'string|required', 'environment_name' => 'string|nullable', 'environment_uuid' => 'string|nullable', @@ -329,7 +412,15 @@ public function create_service(Request $request) 'instant_deploy' => 'boolean', 'connect_to_docker_network' => 'boolean', 'docker_compose_raw' => 'string|required', - ]); + 'urls' => 'array|nullable', + 'urls.*' => 'array:name,url', + 'urls.*.name' => 'string|required', + 'urls.*.url' => 'string|nullable', + ]; + $validationMessages = [ + 'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.', + ]; + $validator = Validator::make($request->all(), $validationRules, $validationMessages); $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { @@ -423,22 +514,24 @@ public function create_service(Request $request) $service->save(); $service->parse(isNew: true); + + if ($request->has('urls') && is_array($request->urls)) { + $urlResult = $this->applyServiceUrls($service, $request->urls, $teamId); + if ($urlResult !== null) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $urlResult['errors'], + ], 422); + } + } + if ($instantDeploy) { StartService::dispatch($service); } - $domains = $service->applications()->get()->pluck('fqdn')->sort(); - $domains = $domains->map(function ($domain) { - if (count(explode(':', $domain)) > 2) { - return str($domain)->beforeLast(':')->value(); - } - - return $domain; - })->values(); - return response()->json([ 'uuid' => $service->uuid, - 'domains' => $domains, + 'domains' => $service->applications()->pluck('fqdn')->filter()->sort()->values(), ])->setStatusCode(201); } elseif (filled($request->type)) { return response()->json([ @@ -622,6 +715,17 @@ public function delete_by_uuid(Request $request) 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the service should be deployed instantly.'], 'connect_to_docker_network' => ['type' => 'boolean', 'default' => false, 'description' => 'Connect the service to the predefined docker network.'], 'docker_compose_raw' => ['type' => 'string', 'description' => 'The base64 encoded Docker Compose content.'], + 'urls' => [ + 'type' => 'array', + 'description' => 'Array of URLs to be applied to containers of a service.', + 'items' => new OA\Schema( + type: 'object', + properties: [ + 'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'], + 'url' => ['type' => 'string', 'description' => 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").'], + ], + ), + ], ], ) ), @@ -681,15 +785,23 @@ public function update_by_uuid(Request $request) $this->authorize('update', $service); - $allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network']; + $allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls']; - $validator = customApiValidator($request->all(), [ + $validationRules = [ 'name' => 'string|max:255', 'description' => 'string|nullable', 'instant_deploy' => 'boolean', 'connect_to_docker_network' => 'boolean', 'docker_compose_raw' => 'string|nullable', - ]); + 'urls' => 'array|nullable', + 'urls.*' => 'array:name,url', + 'urls.*.name' => 'string|required', + 'urls.*.url' => 'string|nullable', + ]; + $validationMessages = [ + 'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.', + ]; + $validator = Validator::make($request->all(), $validationRules, $validationMessages); $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { @@ -753,22 +865,24 @@ public function update_by_uuid(Request $request) $service->save(); $service->parse(); + + if ($request->has('urls') && is_array($request->urls)) { + $urlResult = $this->applyServiceUrls($service, $request->urls, $teamId); + if ($urlResult !== null) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $urlResult['errors'], + ], 422); + } + } + if ($request->instant_deploy) { StartService::dispatch($service); } - $domains = $service->applications()->get()->pluck('fqdn')->sort(); - $domains = $domains->map(function ($domain) { - if (count(explode(':', $domain)) > 2) { - return str($domain)->beforeLast(':')->value(); - } - - return $domain; - })->values(); - return response()->json([ 'uuid' => $service->uuid, - 'domains' => $domains, + 'domains' => $service->applications()->pluck('fqdn')->filter()->sort()->values(), ])->setStatusCode(200); } diff --git a/openapi.json b/openapi.json index 9fdec634f..e9207e5b9 100644 --- a/openapi.json +++ b/openapi.json @@ -8887,6 +8887,23 @@ "docker_compose_raw": { "type": "string", "description": "The base64 encoded Docker Compose content." + }, + "urls": { + "type": "array", + "description": "Array of URLs to be applied to containers of a service.", + "items": { + "properties": { + "name": { + "type": "string", + "description": "The service name as defined in docker-compose." + }, + "url": { + "type": "string", + "description": "Comma-separated list of domains (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")." + } + }, + "type": "object" + } } }, "type": "object" @@ -9137,6 +9154,23 @@ "docker_compose_raw": { "type": "string", "description": "The base64 encoded Docker Compose content." + }, + "urls": { + "type": "array", + "description": "Array of URLs to be applied to containers of a service.", + "items": { + "properties": { + "name": { + "type": "string", + "description": "The service name as defined in docker-compose." + }, + "url": { + "type": "string", + "description": "Comma-separated list of domains (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")." + } + }, + "type": "object" + } } }, "type": "object" diff --git a/openapi.yaml b/openapi.yaml index 383481a8d..735ebad21 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -5606,6 +5606,10 @@ paths: docker_compose_raw: type: string description: 'The base64 encoded Docker Compose content.' + urls: + type: array + description: 'Array of URLs to be applied to containers of a service.' + items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object } type: object responses: '201': @@ -5773,6 +5777,10 @@ paths: docker_compose_raw: type: string description: 'The base64 encoded Docker Compose content.' + urls: + type: array + description: 'Array of URLs to be applied to containers of a service.' + items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object } type: object responses: '200': From 0628268875bd463fdb2ec01779dd2b14347291f6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 13 Jan 2026 19:25:58 +0100 Subject: [PATCH 023/434] feat(api): improve service urls update - add force_domain_override functionality and docs - delete service on creation if there is URL conflicts as otherwise we will have stale services (we need to create the service because we need to parse it and more) --- .../Controllers/Api/ServicesController.php | 172 ++++++++++++++---- openapi.json | 118 ++++++++++++ openapi.yaml | 28 +++ 3 files changed, 287 insertions(+), 31 deletions(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 56812c94c..09547eb1e 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -38,13 +38,14 @@ private function removeSensitiveData($service) return serializeApiResponse($service); } - private function applyServiceUrls(Service $service, array $urls, string $teamId): ?array + private function applyServiceUrls(Service $service, array $urls, string $teamId, bool $forceDomainOverride = false): ?array { $errors = []; + $conflicts = []; - foreach ($urls as $item) { - $name = data_get($item, 'name'); - $urls = data_get($item, 'url'); + foreach ($urls as $url) { + $name = data_get($url, 'name'); + $urls = data_get($url, 'url'); if (blank($name)) { $errors[] = 'Service container name is required to apply URLs.'; @@ -66,7 +67,7 @@ private function applyServiceUrls(Service $service, array $urls, string $teamId) if (! filter_var($url, FILTER_VALIDATE_URL)) { $errors[] = 'Invalid URL: '.$url; - return str($url)->lower(); + return $url; } $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; if (! in_array(strtolower($scheme), ['http', 'https'])) { @@ -74,16 +75,26 @@ private function applyServiceUrls(Service $service, array $urls, string $teamId) } return str($url)->lower(); - })->filter(fn ($u) => $u->isNotEmpty())->unique()->implode(','); + }); - if ($urls && empty($errors)) { - $result = checkIfDomainIsAlreadyUsedViaAPI(collect(explode(',', $urls)), $teamId, $application->uuid); - if ($result['hasConflicts']) { - foreach ($result['conflicts'] as $conflict) { - $errors[] = $conflict['message']; - } - } + if (count($errors) > 0) { + continue; } + + $result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $application->uuid); + if (isset($result['error'])) { + $errors[] = $result['error']; + + continue; + } + + if ($result['hasConflicts'] && ! $forceDomainOverride) { + $conflicts = array_merge($conflicts, $result['conflicts']); + + continue; + } + + $urls = $urls->filter(fn ($u) => filled($u))->unique()->implode(','); } else { $urls = null; } @@ -96,6 +107,13 @@ private function applyServiceUrls(Service $service, array $urls, string $teamId) return ['errors' => $errors]; } + if (! empty($conflicts)) { + return [ + 'conflicts' => $conflicts, + 'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.', + ]; + } + return null; } @@ -188,6 +206,7 @@ public function services(Request $request) ], ), ], + 'force_domain_override' => ['type' => 'boolean', 'default' => false, 'description' => 'Force domain override even if conflicts are detected.'], ], ), ), @@ -217,6 +236,35 @@ public function services(Request $request) response: 400, ref: '#/components/responses/400', ), + new OA\Response( + response: 409, + description: 'Domain conflicts detected.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'], + 'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'], + 'conflicts' => [ + 'type' => 'array', + 'items' => new OA\Schema( + type: 'object', + properties: [ + 'domain' => ['type' => 'string', 'example' => 'example.com'], + 'resource_name' => ['type' => 'string', 'example' => 'My Application'], + 'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'], + 'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'], + 'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''], + ] + ), + ], + ] + ) + ), + ] + ), new OA\Response( response: 422, ref: '#/components/responses/422', @@ -225,7 +273,7 @@ public function services(Request $request) )] public function create_service(Request $request) { - $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'urls']; + $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'urls', 'force_domain_override']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -253,6 +301,7 @@ public function create_service(Request $request) 'urls.*' => 'array:name,url', 'urls.*.name' => 'string|required', 'urls.*.url' => 'string|nullable', + 'force_domain_override' => 'boolean', ]; $validationMessages = [ 'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.', @@ -378,12 +427,22 @@ public function create_service(Request $request) applyServiceApplicationPrerequisites($service); if ($request->has('urls') && is_array($request->urls)) { - $urlResult = $this->applyServiceUrls($service, $request->urls, $teamId); + $urlResult = $this->applyServiceUrls($service, $request->urls, $teamId, $request->boolean('force_domain_override')); if ($urlResult !== null) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => $urlResult['errors'], - ], 422); + $service->delete(); + if (isset($urlResult['errors'])) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $urlResult['errors'], + ], 422); + } + if (isset($urlResult['conflicts'])) { + return response()->json([ + 'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.', + 'conflicts' => $urlResult['conflicts'], + 'warning' => $urlResult['warning'], + ], 409); + } } } @@ -399,7 +458,7 @@ public function create_service(Request $request) return response()->json(['message' => 'Service not found.', 'valid_service_types' => $serviceKeys], 404); } elseif (filled($request->docker_compose_raw)) { - $allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls']; + $allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls', 'force_domain_override']; $validationRules = [ 'project_uuid' => 'string|required', @@ -416,6 +475,7 @@ public function create_service(Request $request) 'urls.*' => 'array:name,url', 'urls.*.name' => 'string|required', 'urls.*.url' => 'string|nullable', + 'force_domain_override' => 'boolean', ]; $validationMessages = [ 'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.', @@ -516,12 +576,22 @@ public function create_service(Request $request) $service->parse(isNew: true); if ($request->has('urls') && is_array($request->urls)) { - $urlResult = $this->applyServiceUrls($service, $request->urls, $teamId); + $urlResult = $this->applyServiceUrls($service, $request->urls, $teamId, $request->boolean('force_domain_override')); if ($urlResult !== null) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => $urlResult['errors'], - ], 422); + $service->delete(); + if (isset($urlResult['errors'])) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $urlResult['errors'], + ], 422); + } + if (isset($urlResult['conflicts'])) { + return response()->json([ + 'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.', + 'conflicts' => $urlResult['conflicts'], + 'warning' => $urlResult['warning'], + ], 409); + } } } @@ -726,6 +796,7 @@ public function delete_by_uuid(Request $request) ], ), ], + 'force_domain_override' => ['type' => 'boolean', 'default' => false, 'description' => 'Force domain override even if conflicts are detected.'], ], ) ), @@ -760,6 +831,35 @@ public function delete_by_uuid(Request $request) response: 404, ref: '#/components/responses/404', ), + new OA\Response( + response: 409, + description: 'Domain conflicts detected.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'], + 'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'], + 'conflicts' => [ + 'type' => 'array', + 'items' => new OA\Schema( + type: 'object', + properties: [ + 'domain' => ['type' => 'string', 'example' => 'example.com'], + 'resource_name' => ['type' => 'string', 'example' => 'My Application'], + 'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'], + 'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'], + 'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''], + ] + ), + ], + ] + ) + ), + ] + ), new OA\Response( response: 422, ref: '#/components/responses/422', @@ -785,7 +885,7 @@ public function update_by_uuid(Request $request) $this->authorize('update', $service); - $allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls']; + $allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls', 'force_domain_override']; $validationRules = [ 'name' => 'string|max:255', @@ -797,6 +897,7 @@ public function update_by_uuid(Request $request) 'urls.*' => 'array:name,url', 'urls.*.name' => 'string|required', 'urls.*.url' => 'string|nullable', + 'force_domain_override' => 'boolean', ]; $validationMessages = [ 'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.', @@ -867,12 +968,21 @@ public function update_by_uuid(Request $request) $service->parse(); if ($request->has('urls') && is_array($request->urls)) { - $urlResult = $this->applyServiceUrls($service, $request->urls, $teamId); + $urlResult = $this->applyServiceUrls($service, $request->urls, $teamId, $request->boolean('force_domain_override')); if ($urlResult !== null) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => $urlResult['errors'], - ], 422); + if (isset($urlResult['errors'])) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $urlResult['errors'], + ], 422); + } + if (isset($urlResult['conflicts'])) { + return response()->json([ + 'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.', + 'conflicts' => $urlResult['conflicts'], + 'warning' => $urlResult['warning'], + ], 409); + } } } diff --git a/openapi.json b/openapi.json index e9207e5b9..46a5b4bc3 100644 --- a/openapi.json +++ b/openapi.json @@ -8904,6 +8904,11 @@ }, "type": "object" } + }, + "force_domain_override": { + "type": "boolean", + "default": false, + "description": "Force domain override even if conflicts are detected." } }, "type": "object" @@ -8941,6 +8946,60 @@ "400": { "$ref": "#\/components\/responses\/400" }, + "409": { + "description": "Domain conflicts detected.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Domain conflicts detected. Use force_domain_override=true to proceed." + }, + "warning": { + "type": "string", + "example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior." + }, + "conflicts": { + "type": "array", + "items": { + "properties": { + "domain": { + "type": "string", + "example": "example.com" + }, + "resource_name": { + "type": "string", + "example": "My Application" + }, + "resource_uuid": { + "type": "string", + "nullable": true, + "example": "abc123-def456" + }, + "resource_type": { + "type": "string", + "enum": [ + "application", + "service", + "instance" + ], + "example": "application" + }, + "message": { + "type": "string", + "example": "Domain example.com is already in use by application 'My Application'" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + } + } + }, "422": { "$ref": "#\/components\/responses\/422" } @@ -9171,6 +9230,11 @@ }, "type": "object" } + }, + "force_domain_override": { + "type": "boolean", + "default": false, + "description": "Force domain override even if conflicts are detected." } }, "type": "object" @@ -9211,6 +9275,60 @@ "404": { "$ref": "#\/components\/responses\/404" }, + "409": { + "description": "Domain conflicts detected.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Domain conflicts detected. Use force_domain_override=true to proceed." + }, + "warning": { + "type": "string", + "example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior." + }, + "conflicts": { + "type": "array", + "items": { + "properties": { + "domain": { + "type": "string", + "example": "example.com" + }, + "resource_name": { + "type": "string", + "example": "My Application" + }, + "resource_uuid": { + "type": "string", + "nullable": true, + "example": "abc123-def456" + }, + "resource_type": { + "type": "string", + "enum": [ + "application", + "service", + "instance" + ], + "example": "application" + }, + "message": { + "type": "string", + "example": "Domain example.com is already in use by application 'My Application'" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + } + } + }, "422": { "$ref": "#\/components\/responses\/422" } diff --git a/openapi.yaml b/openapi.yaml index 735ebad21..ae2999610 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -5610,6 +5610,10 @@ paths: type: array description: 'Array of URLs to be applied to containers of a service.' items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object } + force_domain_override: + type: boolean + default: false + description: 'Force domain override even if conflicts are detected.' type: object responses: '201': @@ -5625,6 +5629,16 @@ paths: $ref: '#/components/responses/401' '400': $ref: '#/components/responses/400' + '409': + description: 'Domain conflicts detected.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' } + warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' } + conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } } + type: object '422': $ref: '#/components/responses/422' security: @@ -5781,6 +5795,10 @@ paths: type: array description: 'Array of URLs to be applied to containers of a service.' items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object } + force_domain_override: + type: boolean + default: false + description: 'Force domain override even if conflicts are detected.' type: object responses: '200': @@ -5798,6 +5816,16 @@ paths: $ref: '#/components/responses/400' '404': $ref: '#/components/responses/404' + '409': + description: 'Domain conflicts detected.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' } + warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' } + conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } } + type: object '422': $ref: '#/components/responses/422' security: From c5196e12d209838a742b20ce630b15ee35801107 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 13 Jan 2026 20:04:44 +0100 Subject: [PATCH 024/434] fix(api): show an error if the same 2 urls are provided --- .../Controllers/Api/ServicesController.php | 81 +++++++++++-------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 09547eb1e..ddd63d60c 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -38,70 +38,85 @@ private function removeSensitiveData($service) return serializeApiResponse($service); } - private function applyServiceUrls(Service $service, array $urls, string $teamId, bool $forceDomainOverride = false): ?array + private function applyServiceUrls(Service $service, array $urlsArray, string $teamId, bool $forceDomainOverride = false): ?array { $errors = []; $conflicts = []; - foreach ($urls as $url) { - $name = data_get($url, 'name'); - $urls = data_get($url, 'url'); + $urls = collect($urlsArray)->flatMap(function ($item) { + $urlValue = data_get($item, 'url'); + if (blank($urlValue)) { + return []; + } + + return str($urlValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter(); + }); + + $urls = $urls->map(function ($url) use (&$errors) { + if (! filter_var($url, FILTER_VALIDATE_URL)) { + $errors[] = "Invalid URL: {$url}"; + + return $url; + } + $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; + if (! in_array(strtolower($scheme), ['http', 'https'])) { + $errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; + } + + return $url; + }); + + $duplicates = $urls->duplicates()->unique()->values(); + if ($duplicates->isNotEmpty() && ! $forceDomainOverride) { + $errors[] = 'The current request contains conflicting URLs across containers: '.implode(', ', $duplicates->toArray()); + } + + if (count($errors) > 0) { + return ['errors' => $errors]; + } + + collect($urlsArray)->each(function ($item) use ($service, $teamId, $forceDomainOverride, &$errors, &$conflicts) { + $name = data_get($item, 'name'); + $containerUrls = data_get($item, 'url'); if (blank($name)) { $errors[] = 'Service container name is required to apply URLs.'; - continue; + return; } $application = $service->applications()->where('name', $name)->first(); if (! $application) { $errors[] = "Service container with '{$name}' not found."; - continue; + return; } - if (filled($urls)) { - $urls = str($urls)->replaceStart(',', '')->replaceEnd(',', '')->trim(); - $urls = str($urls)->explode(',')->map(function ($url) use (&$errors) { - $url = trim($url); - if (! filter_var($url, FILTER_VALIDATE_URL)) { - $errors[] = 'Invalid URL: '.$url; + if (filled($containerUrls)) { + $containerUrls = str($containerUrls)->replaceStart(',', '')->replaceEnd(',', '')->trim(); + $containerUrls = str($containerUrls)->explode(',')->map(fn ($url) => str(trim($url))->lower()); - return $url; - } - $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; - if (! in_array(strtolower($scheme), ['http', 'https'])) { - $errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; - } - - return str($url)->lower(); - }); - - if (count($errors) > 0) { - continue; - } - - $result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $application->uuid); + $result = checkIfDomainIsAlreadyUsedViaAPI($containerUrls, $teamId, $application->uuid); if (isset($result['error'])) { $errors[] = $result['error']; - continue; + return; } if ($result['hasConflicts'] && ! $forceDomainOverride) { $conflicts = array_merge($conflicts, $result['conflicts']); - continue; + return; } - $urls = $urls->filter(fn ($u) => filled($u))->unique()->implode(','); + $containerUrls = $containerUrls->filter(fn ($u) => filled($u))->unique()->implode(','); } else { - $urls = null; + $containerUrls = null; } - $application->fqdn = $urls; + $application->fqdn = $containerUrls; $application->save(); - } + }); if (! empty($errors)) { return ['errors' => $errors]; From d69bdabee24a41a59e40c527c864bba6577083ab Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:27:32 +0100 Subject: [PATCH 025/434] chore: prepare for PR --- templates/compose/sessy.yaml | 22 +++++++ templates/service-templates-latest.json | 82 ++++++++++++++++++++++++- templates/service-templates.json | 82 ++++++++++++++++++++++++- 3 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 templates/compose/sessy.yaml diff --git a/templates/compose/sessy.yaml b/templates/compose/sessy.yaml new file mode 100644 index 000000000..0cfe73bb2 --- /dev/null +++ b/templates/compose/sessy.yaml @@ -0,0 +1,22 @@ +# documentation: https://github.com/marckohlbrugge/sessy/blob/main/docs/docker-deployment.md +# slogan: Email observability platform for monitoring and analyzing email systems. +# category: monitoring +# tags: email, observability, monitoring, analytics +# logo: svgs/sessy.svg +# port: 80 + +services: + sessy: + image: ghcr.io/marckohlbrugge/sessy:main + environment: + - SERVICE_URL_SESSY_80 + - SECRET_KEY_BASE=$SERVICE_HEX_64_SESSYSECRET + - HTTP_AUTH_USERNAME=$SERVICE_USER_SESSY + - HTTP_AUTH_PASSWORD=$SERVICE_PASSWORD_SESSY + volumes: + - sessy-data:/rails/storage + healthcheck: + test: ["CMD-SHELL", "curl -sf http://127.0.0.1:80 || [ $? -eq 22 ]"] + interval: 5s + timeout: 10s + retries: 10 diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json index 483ff8928..540a47bbe 100644 --- a/templates/service-templates-latest.json +++ b/templates/service-templates-latest.json @@ -192,7 +192,7 @@ "autobase": { "documentation": "https://autobase.tech/docs/?utm_source=coolify.io", "slogan": "Autobase for PostgreSQL\u00ae is an open-source alternative to cloud-managed databases (self-hosted DBaaS).", - "compose": "c2VydmljZXM6CiAgYXV0b2Jhc2U6CiAgICBpbWFnZTogJ2F1dG9iYXNlL2NvbnNvbGVfdWk6Mi40LjEnCiAgICBwbGF0Zm9ybTogbGludXgvYW1kNjQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9BVVRPQkFTRV84MAogICAgICAtICdQR19DT05TT0xFX0FVVEhPUklaQVRJT05fVE9LRU49JHtTRVJWSUNFX1BBU1NXT1JEX1VJfScKICAgICAgLSBQR19DT05TT0xFX0FQSV9IT1NUPWF1dG9iYXNlLWFwaQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwLycKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgICBkZXBlbmRzX29uOgogICAgICBhdXRvYmFzZS1hcGk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBhdXRvYmFzZS1kYjoKICAgIGltYWdlOiAnYXV0b2Jhc2UvY29uc29sZV9kYjoyLjQuMScKICAgIHBsYXRmb3JtOiBsaW51eC9hbWQ2NAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICB2b2x1bWVzOgogICAgICAtICdhdXRvYmFzZS1kYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSBwb3N0Z3JlcycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgYXV0b2Jhc2UtYXBpOgogICAgaW1hZ2U6ICdhdXRvYmFzZS9jb25zb2xlX2FwaToyLjQuMScKICAgIHBsYXRmb3JtOiBsaW51eC9hbWQ2NAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEdfQ09OU09MRV9EQl9IT1NUPWF1dG9iYXNlLWRiCiAgICAgIC0gJ1BHX0NPTlNPTEVfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUEdfQ09OU09MRV9BVVRIT1JJWkFUSU9OX1RPS0VOPSR7U0VSVklDRV9QQVNTV09SRF9VSX0nCiAgICAgIC0gJ1BHX0NPTlNPTEVfRU5DUllQVElPTktFWT0ke1NFUlZJQ0VfQkFTRTY0X0VOQ1JZUFRJT05LRVl9JwogICAgICAtICdQR19DT05TT0xFX0xPR0dFUl9MRVZFTD0ke1BHX0NPTlNPTEVfTE9HR0VSX0xFVkVMOi1pbmZvfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgICAtICcvdG1wL2Fuc2libGU6L3RtcC9hbnNpYmxlJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZnNTJwogICAgICAgIC0gJy1IJwogICAgICAgIC0gJ2FjY2VwdDogYXBwbGljYXRpb24vanNvbicKICAgICAgICAtICctSCcKICAgICAgICAtICdBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtTRVJWSUNFX1BBU1NXT1JEX1VJfScKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwODAvYXBpL3YxL3ZlcnNpb24nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogICAgZGVwZW5kc19vbjoKICAgICAgYXV0b2Jhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkK", + "compose": "c2VydmljZXM6CiAgYXV0b2Jhc2U6CiAgICBpbWFnZTogJ2F1dG9iYXNlL2NvbnNvbGVfdWk6Mi41LjInCiAgICBwbGF0Zm9ybTogbGludXgvYW1kNjQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9BVVRPQkFTRV84MAogICAgICAtICdQR19DT05TT0xFX0FVVEhPUklaQVRJT05fVE9LRU49JHtTRVJWSUNFX1BBU1NXT1JEX1VJfScKICAgICAgLSBQR19DT05TT0xFX0FQSV9IT1NUPWF1dG9iYXNlLWFwaQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwLycKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgICBkZXBlbmRzX29uOgogICAgICBhdXRvYmFzZS1hcGk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBhdXRvYmFzZS1kYjoKICAgIGltYWdlOiAnYXV0b2Jhc2UvY29uc29sZV9kYjoyLjUuMicKICAgIHBsYXRmb3JtOiBsaW51eC9hbWQ2NAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICB2b2x1bWVzOgogICAgICAtICdhdXRvYmFzZS1kYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSBwb3N0Z3JlcycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgYXV0b2Jhc2UtYXBpOgogICAgaW1hZ2U6ICdhdXRvYmFzZS9jb25zb2xlX2FwaToyLjUuMicKICAgIHBsYXRmb3JtOiBsaW51eC9hbWQ2NAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEdfQ09OU09MRV9EQl9IT1NUPWF1dG9iYXNlLWRiCiAgICAgIC0gJ1BHX0NPTlNPTEVfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUEdfQ09OU09MRV9BVVRIT1JJWkFUSU9OX1RPS0VOPSR7U0VSVklDRV9QQVNTV09SRF9VSX0nCiAgICAgIC0gJ1BHX0NPTlNPTEVfRU5DUllQVElPTktFWT0ke1NFUlZJQ0VfQkFTRTY0X0VOQ1JZUFRJT05LRVl9JwogICAgICAtICdQR19DT05TT0xFX0xPR0dFUl9MRVZFTD0ke1BHX0NPTlNPTEVfTE9HR0VSX0xFVkVMOi1pbmZvfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgICAtICcvdG1wL2Fuc2libGU6L3RtcC9hbnNpYmxlJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZnNTJwogICAgICAgIC0gJy1IJwogICAgICAgIC0gJ2FjY2VwdDogYXBwbGljYXRpb24vanNvbicKICAgICAgICAtICctSCcKICAgICAgICAtICdBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtTRVJWSUNFX1BBU1NXT1JEX1VJfScKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwODAvYXBpL3YxL3ZlcnNpb24nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogICAgZGVwZW5kc19vbjoKICAgICAgYXV0b2Jhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkK", "tags": [ "database", "postgres", @@ -546,6 +546,21 @@ "minversion": "0.0.0", "port": "80" }, + "chibisafe": { + "documentation": "https://chibisafe.app/docs/intro?utm_source=coolify.io", + "slogan": "A beautiful and performant vault to save all your files in the cloud.", + "compose": "c2VydmljZXM6CiAgY2hpYmlzYWZlOgogICAgaW1hZ2U6ICdjaGliaXNhZmUvY2hpYmlzYWZlOnY2LjUuNScKICAgIGVudmlyb25tZW50OgogICAgICAtICdCQVNFX0FQSV9VUkw9aHR0cDovL2NoaWJpc2FmZS1zZXJ2ZXI6ODAwMCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnLS1xdWlldCcKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwMDEnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICBjaGliaXNhZmUtc2VydmVyOgogICAgaW1hZ2U6ICdjaGliaXNhZmUvY2hpYmlzYWZlLXNlcnZlcjp2Ni41LjUnCiAgICB2b2x1bWVzOgogICAgICAtICdjaGliaXNhZmUtZGF0YWJhc2U6L2FwcC9kYXRhYmFzZTpydycKICAgICAgLSAnY2hpYmlzYWZlLXVwbG9hZHM6L2FwcC91cGxvYWRzOnJ3JwogICAgICAtICdjaGliaXNhZmUtbG9nczovYXBwL2xvZ3M6cncnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJy0tcXVpZXQnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MDAwL2FwaS9oZWFsdGgnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICBjYWRkeToKICAgIGltYWdlOiAnY2FkZHk6Mi1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9DSElCSVNBRkVfODAKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnLS1xdWlldCcKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgICAgIHN0YXJ0X3BlcmlvZDogMTBzCiAgICB2b2x1bWVzOgogICAgICAtICd1cGxvYWRzOi9hcHAvdXBsb2FkczpybycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vQ2FkZHlmaWxlCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NhZGR5L0NhZGR5ZmlsZQogICAgICAgIGNvbnRlbnQ6ICIjIGNoaWJpc2FmZS9DYWRkeWZpbGVcbiMgaHR0cHM6Ly9jaGliaXNhZmUubW9lL2d1aWRlcy9ydW5uaW5nLXdpdGgtZG9ja2VyXG46ODAge1xuICByb3V0ZSB7XG4gICAgZmlsZV9zZXJ2ZXIgKiB7XG4gICAgICAgIHJvb3QgL2FwcC91cGxvYWRzXG4gICAgICAgIHBhc3NfdGhydVxuICAgIH1cbiAgICBAYXBpIHBhdGggL2FwaS8qXG4gICAgcmV2ZXJzZV9wcm94eSBAYXBpIGh0dHA6Ly9jaGliaXNhZmUtc2VydmVyOjgwMDAge1xuICAgICAgICBoZWFkZXJfdXAgSG9zdCB7aHR0cC5yZXZlcnNlX3Byb3h5LnVwc3RyZWFtLmhvc3Rwb3J0fVxuICAgICAgICBoZWFkZXJfdXAgWC1SZWFsLUlQIHtodHRwLnJlcXVlc3QuaGVhZGVyLlgtUmVhbC1JUH1cbiAgICB9XG4gICAgQGRvY3MgcGF0aCAvZG9jcypcbiAgICByZXZlcnNlX3Byb3h5IEBkb2NzIGh0dHA6Ly9jaGliaXNhZmUtc2VydmVyOjgwMDAge1xuICAgICAgICBoZWFkZXJfdXAgSG9zdCB7aHR0cC5yZXZlcnNlX3Byb3h5LnVwc3RyZWFtLmhvc3Rwb3J0fVxuICAgICAgICBoZWFkZXJfdXAgWC1SZWFsLUlQIHtodHRwLnJlcXVlc3QuaGVhZGVyLlgtUmVhbC1JUH1cbiAgICB9XG4gICAgcmV2ZXJzZV9wcm94eSBodHRwOi8vY2hpYmlzYWZlOjgwMDEge1xuICAgICAgICBoZWFkZXJfdXAgSG9zdCB7aHR0cC5yZXZlcnNlX3Byb3h5LnVwc3RyZWFtLmhvc3Rwb3J0fVxuICAgICAgICBoZWFkZXJfdXAgWC1SZWFsLUlQIHtodHRwLnJlcXVlc3QuaGVhZGVyLlgtUmVhbC1JUH1cbiAgICB9XG4gIH1cbn1cbiIK", + "tags": [ + "storage", + "file-sharing", + "upload", + "sharing" + ], + "category": null, + "logo": "svgs/chibisafe.svg", + "minversion": "0.0.0", + "port": "80" + }, "chroma": { "documentation": "https://cookbook.chromadb.dev/?utm_source=coolify.io", "slogan": "Chroma is the open-source search and retrieval database for AI applications.", @@ -1731,6 +1746,21 @@ "minversion": "0.0.0", "port": "8080" }, + "glpi": { + "documentation": "https://help.glpi-project.org/documentation?utm_source=coolify.io", + "slogan": "GLPI (Gestionnaire Libre de Parc Informatique) is a free, open-source IT Service Management (ITSM) platform used for IT asset management, helpdesk, and service desk operations.", + "compose": "c2VydmljZXM6CiAgZ2xwaToKICAgIGltYWdlOiAnZ2xwaS9nbHBpOjExJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfR0xQSV84MAogICAgICAtIEdMUElfREJfSE9TVD1nbHBpLWRiCiAgICAgIC0gR0xQSV9EQl9QT1JUPTMzMDYKICAgICAgLSAnR0xQSV9EQl9OQU1FPSR7TVlTUUxfREFUQUJBU0U6LWdscGktZGJ9JwogICAgICAtICdHTFBJX0RCX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdHTFBJX0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTH0nCiAgICB2b2x1bWVzOgogICAgICAtICdnbHBpLWRhdGE6L3Zhci9nbHBpOnJ3JwogICAgZGVwZW5kc19vbjoKICAgICAgZ2xwaS1kYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0LycKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogIGdscGktZGI6CiAgICBpbWFnZTogJ215c3FsOjgnCiAgICB2b2x1bWVzOgogICAgICAtICdteXNxbC1kYXRhOi92YXIvbGliL215c3FsJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01ZU1FMX1JPT1RfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1JPT1R9JwogICAgICAtICdNWVNRTF9EQVRBQkFTRT0ke01ZU1FMX0RBVEFCQVNFOi1nbHBpLWRifScKICAgICAgLSAnTVlTUUxfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NWVNRTH0nCiAgICAgIC0gJ01ZU1FMX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gbXlzcWxhZG1pbgogICAgICAgIC0gcGluZwogICAgICAgIC0gJy1oJwogICAgICAgIC0gMTI3LjAuMC4xCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "tags": [ + "helpdesk", + "ticketing", + "support", + "open-source" + ], + "category": "helpdesk", + "logo": "svgs/glpi.svg", + "minversion": "0.0.0", + "port": "80" + }, "gotenberg": { "documentation": "https://gotenberg.dev/docs/getting-started/introduction?utm_source=coolify.io", "slogan": "Gotenberg is a Docker-powered stateless API for PDF files.", @@ -3296,6 +3326,21 @@ "minversion": "0.0.0", "port": "3000" }, + "open-archiver": { + "documentation": "https://docs.openarchiver.com/?utm_source=coolify.io", + "slogan": "A self-hosted, open-source email archiving solution with full-text search capability.", + "compose": "c2VydmljZXM6CiAgb3Blbi1hcmNoaXZlcjoKICAgIGltYWdlOiAnbG9naWNsYWJzaHEvb3Blbi1hcmNoaXZlcjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9PUEVOQVJDSElWRVJfMzAwMAogICAgICAtICdFTkNSWVBUSU9OX0tFWT0ke1NFUlZJQ0VfSEVYXzMyX0VOQ1JZUFRJT05LRVl9JwogICAgICAtICdTVE9SQUdFX0VOQ1JZUFRJT05fS0VZPSR7U0VSVklDRV9IRVhfMzJfU1RPUkFHRUVOQ1JZUFRJT05LRVl9JwogICAgICAtICdQT1JUX0JBQ0tFTkQ9JHtQT1JUX0JBQ0tFTkQ6LTQwMDB9JwogICAgICAtICdQT1JUX0ZST05URU5EPSR7UE9SVF9GUk9OVEVORDotMzAwMH0nCiAgICAgIC0gJ05PREVfRU5WPSR7Tk9ERV9FTlY6LXByb2R1Y3Rpb259JwogICAgICAtICdTWU5DX0ZSRVFVRU5DWT0ke1NZTkNfRlJFUVVFTkNZOi0qICogKiAqICp9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1vcGVuX2FyY2hpdmV9JwogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNfREI6LW9wZW4tYXJjaGl2ZXItZGJ9JwogICAgICAtICdNRUlMSV9NQVNURVJfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9NRUlMSVNFQVJDSH0nCiAgICAgIC0gJ01FSUxJX0hPU1Q9aHR0cDovL21laWxpc2VhcmNoOjc3MDAnCiAgICAgIC0gUkVESVNfSE9TVD12YWxrZXkKICAgICAgLSBSRURJU19QT1JUPTYzNzkKICAgICAgLSAnUkVESVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1ZBTEtFWX0nCiAgICAgIC0gUkVESVNfVExTX0VOQUJMRUQ9ZmFsc2UKICAgICAgLSAnU1RPUkFHRV9UWVBFPSR7U1RPUkFHRV9UWVBFOi1sb2NhbH0nCiAgICAgIC0gJ1NUT1JBR0VfTE9DQUxfUk9PVF9QQVRIPSR7U1RPUkFHRV9MT0NBTF9ST09UX1BBVEg6LS92YXIvZGF0YS9vcGVuLWFyY2hpdmVyfScKICAgICAgLSAnQk9EWV9TSVpFX0xJTUlUPSR7Qk9EWV9TSVpFX0xJTUlUOi0xMDBNfScKICAgICAgLSAnU1RPUkFHRV9TM19FTkRQT0lOVD0ke1NUT1JBR0VfUzNfRU5EUE9JTlR9JwogICAgICAtICdTVE9SQUdFX1MzX0JVQ0tFVD0ke1NUT1JBR0VfUzNfQlVDS0VUfScKICAgICAgLSAnU1RPUkFHRV9TM19BQ0NFU1NfS0VZX0lEPSR7U1RPUkFHRV9TM19BQ0NFU1NfS0VZX0lEfScKICAgICAgLSAnU1RPUkFHRV9TM19TRUNSRVRfQUNDRVNTX0tFWT0ke1NUT1JBR0VfUzNfU0VDUkVUX0FDQ0VTU19LRVl9JwogICAgICAtICdTVE9SQUdFX1MzX1JFR0lPTj0ke1NUT1JBR0VfUzNfUkVHSU9OfScKICAgICAgLSAnU1RPUkFHRV9TM19GT1JDRV9QQVRIX1NUWUxFPSR7U1RPUkFHRV9TM19GT1JDRV9QQVRIX1NUWUxFOi1mYWxzZX0nCiAgICAgIC0gJ0pXVF9TRUNSRVQ9JHtTRVJWSUNFX0JBU0U2NF8xMjhfSldUfScKICAgICAgLSAnSldUX0VYUElSRVNfSU49JHtKV1RfRVhQSVJFU19JTjotN2R9JwogICAgICAtICdSQVRFX0xJTUlUX1dJTkRPV19NUz0ke1JBVEVfTElNSVRfV0lORE9XX01TOi02MDAwMH0nCiAgICAgIC0gJ1JBVEVfTElNSVRfTUFYX1JFUVVFU1RTPSR7UkFURV9MSU1JVF9NQVhfUkVRVUVTVFM6LTEwMH0nCiAgICB2b2x1bWVzOgogICAgICAtICdhcmNoaXZlci1kYXRhOi92YXIvZGF0YS9vcGVuLWFyY2hpdmVyJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgdmFsa2V5OgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIG1laWxpc2VhcmNoOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE3LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1vcGVuLWFyY2hpdmVyLWRifScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gTENfQUxMPUMKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3RncmVzLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICB2YWxrZXk6CiAgICBpbWFnZTogJ3ZhbGtleS92YWxrZXk6OC1hbHBpbmUnCiAgICBjb21tYW5kOiAndmFsa2V5LXNlcnZlciAtLXJlcXVpcmVwYXNzICR7U0VSVklDRV9QQVNTV09SRF9WQUxLRVl9JwogICAgdm9sdW1lczoKICAgICAgLSAndmFsa2V5LWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogIG1laWxpc2VhcmNoOgogICAgaW1hZ2U6ICdnZXRtZWlsaS9tZWlsaXNlYXJjaDp2MS4xNScKICAgIGVudmlyb25tZW50OgogICAgICAtICdNRUlMSV9NQVNURVJfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9NRUlMSVNFQVJDSH0nCiAgICB2b2x1bWVzOgogICAgICAtICdtZWlsaXNlYXJjaC1kYXRhOi9tZWlsaV9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjc3MDAvaGVhbHRoJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", + "tags": [ + "email archiving", + "email", + "compliance", + "search" + ], + "category": null, + "logo": "svgs/openarchiver.svg", + "minversion": "0.0.0", + "port": "3000" + }, "open-webui": { "documentation": "https://docs.openwebui.com?utm_source=coolify.io", "slogan": "User-friendly AI Interface (Supports Ollama, OpenAI API, ...)", @@ -4066,6 +4111,22 @@ "minversion": "0.0.0", "port": "8080" }, + "seaweedfs": { + "documentation": "https://github.com/seaweedfs/seaweedfs?utm_source=coolify.io", + "slogan": "SeaweedFS is a simple and highly scalable distributed file system. Compatible with S3, with an admin web interface.", + "compose": "c2VydmljZXM6CiAgc2Vhd2VlZGZzLW1hc3RlcjoKICAgIGltYWdlOiAnY2hyaXNsdXNmL3NlYXdlZWRmczo0LjA1JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfUzNfODMzMwogICAgICAtICdBV1NfQUNDRVNTX0tFWV9JRD0ke1NFUlZJQ0VfVVNFUl9TM30nCiAgICAgIC0gJ0FXU19TRUNSRVRfQUNDRVNTX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfUzN9JwogICAgdm9sdW1lczoKICAgICAgLSAnc2Vhd2VlZGZzLXMzLWRhdGE6L2RhdGEnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2Jhc2UtY29uZmlnLmpzb24KICAgICAgICB0YXJnZXQ6IC9iYXNlLWNvbmZpZy5qc29uCiAgICAgICAgY29udGVudDogIntcbiAgXCJpZGVudGl0aWVzXCI6IFtcbiAgICB7XG4gICAgICBcIm5hbWVcIjogXCJhbm9ueW1vdXNcIixcbiAgICAgIFwiYWN0aW9uc1wiOiBbXG4gICAgICAgIFwiUmVhZFwiXG4gICAgICBdXG4gICAgfSxcbiAgICB7XG4gICAgICBcIm5hbWVcIjogXCJhZG1pblwiLFxuICAgICAgXCJjcmVkZW50aWFsc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcImFjY2Vzc0tleVwiOiBcImVudjpBV1NfQUNDRVNTX0tFWV9JRFwiLFxuICAgICAgICAgIFwic2VjcmV0S2V5XCI6IFwiZW52OkFXU19TRUNSRVRfQUNDRVNTX0tFWVwiXG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImFjdGlvbnNcIjogW1xuICAgICAgICBcIkFkbWluXCIsXG4gICAgICAgIFwiUmVhZFwiLFxuICAgICAgICBcIlJlYWRBY3BcIixcbiAgICAgICAgXCJMaXN0XCIsXG4gICAgICAgIFwiVGFnZ2luZ1wiLFxuICAgICAgICBcIldyaXRlXCIsXG4gICAgICAgIFwiV3JpdGVBY3BcIlxuICAgICAgXVxuICAgIH1cbiAgXVxufVxuIgogICAgZW50cnlwb2ludDogInNoIC1jICdcXFxuc2VkIFwicy9lbnY6QVdTX0FDQ0VTU19LRVlfSUQvJEFXU19BQ0NFU1NfS0VZX0lEL2dcIiAvYmFzZS1jb25maWcuanNvbiA+IC9iYXNlMS1jb25maWcuanNvbjsgXFxcbnNlZCBcInMvZW52OkFXU19TRUNSRVRfQUNDRVNTX0tFWS8kQVdTX1NFQ1JFVF9BQ0NFU1NfS0VZL2dcIiAvYmFzZTEtY29uZmlnLmpzb24gPiAvY29uZmlnLmpzb247IFxcXG53ZWVkIHNlcnZlciAtZGlyPS9kYXRhIC1tYXN0ZXIucG9ydD05MzMzIC1zMyAtczMucG9ydD04MzMzIC1zMy5jb25maWc9L2NvbmZpZy5qc29uXFxcbidcbiIKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtLXNwaWRlciAtcSBodHRwOi8vMC4wLjAuMDo4MzMzOyBbICQkPyAtbGUgOCBdJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgc2Vhd2VlZGZzLWFkbWluOgogICAgaW1hZ2U6ICdjaHJpc2x1c2Yvc2Vhd2VlZGZzOjQuMDUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9BRE1JTl8yMzY0NgogICAgICAtICdTRUFXRUVEX1VTRVJfQURNSU49JHtTRVJWSUNFX1VTRVJfQURNSU59JwogICAgICAtICdTRUFXRUVEX1BBU1NXT1JEX0FETUlOPSR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0nCiAgICBjb21tYW5kOgogICAgICAtIGFkbWluCiAgICAgIC0gJy1tYXN0ZXI9c2Vhd2VlZGZzLW1hc3Rlcjo5MzMzJwogICAgICAtICctYWRtaW5Vc2VyPSR7U0VBV0VFRF9VU0VSX0FETUlOfScKICAgICAgLSAnLWFkbWluUGFzc3dvcmQ9JHtTRUFXRUVEX1BBU1NXT1JEX0FETUlOfScKICAgICAgLSAnLXBvcnQ9MjM2NDYnCiAgICAgIC0gJy1kYXRhRGlyPS9kYXRhJwogICAgdm9sdW1lczoKICAgICAgLSAnc2Vhd2VlZGZzLWFkbWluLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLS1zcGlkZXIgLXEgaHR0cDovLzAuMC4wLjA6MjM2NDY7IFsgJCQ/IC1sZSA4IF0nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHNlYXdlZWRmcy1tYXN0ZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkK", + "tags": [ + "object", + "storage", + "server", + "s3", + "api" + ], + "category": "storage", + "logo": "svgs/garage.svg", + "minversion": "0.0.0", + "port": "8333" + }, "sequin": { "documentation": "https://sequinstream.com/docs/?utm_source=coolify.io", "slogan": "The fastest Postgres change data capture", @@ -4080,10 +4141,25 @@ "minversion": "0.0.0", "port": "7376" }, + "sessy": { + "documentation": "https://github.com/marckohlbrugge/sessy/blob/main/docs/docker-deployment.md?utm_source=coolify.io", + "slogan": "Email observability platform for monitoring and analyzing email systems.", + "compose": "c2VydmljZXM6CiAgc2Vzc3k6CiAgICBpbWFnZTogJ2doY3IuaW8vbWFyY2tvaGxicnVnZ2Uvc2Vzc3k6bWFpbicKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX1NFU1NZXzgwCiAgICAgIC0gU0VDUkVUX0tFWV9CQVNFPSRTRVJWSUNFX0hFWF82NF9TRVNTWVNFQ1JFVAogICAgICAtIEhUVFBfQVVUSF9VU0VSTkFNRT0kU0VSVklDRV9VU0VSX1NFU1NZCiAgICAgIC0gSFRUUF9BVVRIX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1NFU1NZCiAgICB2b2x1bWVzOgogICAgICAtICdzZXNzeS1kYXRhOi9yYWlscy9zdG9yYWdlJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdjdXJsIC1zZiBodHRwOi8vMTI3LjAuMC4xOjgwIHx8IFsgJD8gLWVxIDIyIF0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTAK", + "tags": [ + "email", + "observability", + "monitoring", + "analytics" + ], + "category": "monitoring", + "logo": "svgs/sessy.svg", + "minversion": "0.0.0", + "port": "80" + }, "sftpgo": { "documentation": "https://docs.sftpgo.com/2.7/?utm_source=coolify.io", "slogan": "SFTPGo is an event-driven SFTP, FTP/S, HTTP/S and WebDAV server.", - "compose": "c2VydmljZXM6CiAgc2Z0cGdvOgogICAgaW1hZ2U6ICdnaGNyLmlvL2RyYWtrYW4vc2Z0cGdvOnYyLjctYWxwaW5lJwogICAgcG9ydHM6CiAgICAgIC0gJzIyMjI6MjIyMicKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TRlRQR09fODA4MAogICAgICAtIFNGVFBHT19EQVRBX1BST1ZJREVSX19EUklWRVI9cG9zdGdyZXNxbAogICAgICAtICdTRlRQR09fREFUQV9QUk9WSURFUl9fQ09OTkVDVElPTl9TVFJJTkc9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfNjRfUE9TVEdSRVN9QHBvc3RncmVzcWw6NTQzMi8ke1BPU1RHUkVTX0RBVEFCQVNFOi1zZnRwZ28tZGJ9JwogICAgICAtIFNGVFBHT19TRlRQRF9fQklORElOR1NfXzBfX1BPUlQ9MjAyMgogICAgICAtICdTRlRQR09fTE9HX0xFVkVMPSR7TE9HX0xFVkVMOi1pbmZvfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3NmdHBnby1kYXRhOi9zcnYvc2Z0cGdvL2RhdGEnCiAgICAgIC0gJ3NmdHBnby1rZXlzOi92YXIvbGliL3NmdHBnbycKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNy1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF82NF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREFUQUJBU0U6LXNmdHBnby1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAyMAo=", + "compose": "c2VydmljZXM6CiAgc2Z0cGdvOgogICAgaW1hZ2U6ICdnaGNyLmlvL2RyYWtrYW4vc2Z0cGdvOnYyLjctYWxwaW5lJwogICAgcG9ydHM6CiAgICAgIC0gJzIyMjI6MjIyMicKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TRlRQR09fODA4MAogICAgICAtIFNGVFBHT19EQVRBX1BST1ZJREVSX19EUklWRVI9cG9zdGdyZXNxbAogICAgICAtICdTRlRQR09fREFUQV9QUk9WSURFUl9fQ09OTkVDVElPTl9TVFJJTkc9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfNjRfUE9TVEdSRVN9QHBvc3RncmVzcWw6NTQzMi8ke1BPU1RHUkVTX0RBVEFCQVNFOi1zZnRwZ28tZGJ9JwogICAgICAtICdTRlRQR09fU0ZUUERfX0JJTkRJTkdTX18wX19QT1JUPSR7UE9SVF9TRlRQR086LTIyMjJ9JwogICAgICAtICdTRlRQR09fTE9HX0xFVkVMPSR7TE9HX0xFVkVMOi1pbmZvfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3NmdHBnby1kYXRhOi9zcnYvc2Z0cGdvL2RhdGEnCiAgICAgIC0gJ3NmdHBnby1rZXlzOi92YXIvbGliL3NmdHBnbycKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNy1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF82NF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREFUQUJBU0U6LXNmdHBnby1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAyMAo=", "tags": [ "sftpgo", "sftp", @@ -4117,7 +4193,7 @@ "signoz": { "documentation": "https://signoz.io/docs/introduction/?utm_source=coolify.io", "slogan": "An observability platform native to OpenTelemetry with logs, traces and metrics.", - "compose": "c2VydmljZXM6CiAgaW5pdC1jbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI1LjUuNi1hbHBpbmUnCiAgICBjb21tYW5kOgogICAgICAtIGJhc2gKICAgICAgLSAnLWMnCiAgICAgIC0gInZlcnNpb249XCJ2MC4wLjFcIlxubm9kZV9vcz0kJCh1bmFtZSAtcyB8IHRyICdbOnVwcGVyOl0nICdbOmxvd2VyOl0nKVxubm9kZV9hcmNoPSQkKHVuYW1lIC1tIHwgc2VkIHMvYWFyY2g2NC9hcm02NC8gfCBzZWQgcy94ODZfNjQvYW1kNjQvKVxuZWNobyBcIkZldGNoaW5nIGhpc3RvZ3JhbS1iaW5hcnkgZm9yICQke25vZGVfb3N9LyQke25vZGVfYXJjaH1cIlxuY2QgL3RtcFxud2dldCAtTyBoaXN0b2dyYW0tcXVhbnRpbGUudGFyLmd6IFwiaHR0cHM6Ly9naXRodWIuY29tL1NpZ05vei9zaWdub3ovcmVsZWFzZXMvZG93bmxvYWQvaGlzdG9ncmFtLXF1YW50aWxlJTJGJCR7dmVyc2lvbn0vaGlzdG9ncmFtLXF1YW50aWxlXyQke25vZGVfb3N9XyQke25vZGVfYXJjaH0udGFyLmd6XCJcbnRhciAteHZ6ZiBoaXN0b2dyYW0tcXVhbnRpbGUudGFyLmd6XG5ta2RpciAtcCAvdmFyL2xpYi9jbGlja2hvdXNlL3VzZXJfc2NyaXB0cy9oaXN0b2dyYW1RdWFudGlsZVxubXYgaGlzdG9ncmFtLXF1YW50aWxlIC92YXIvbGliL2NsaWNraG91c2UvdXNlcl9zY3JpcHRzL2hpc3RvZ3JhbVF1YW50aWxlXG4iCiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICB6b29rZWVwZXI6CiAgICBpbWFnZTogJ3NpZ25vei96b29rZWVwZXI6My45LjMnCiAgICB1c2VyOiByb290CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ2N1cmwgLXMgLW0gMiBodHRwOi8vbG9jYWxob3N0OjgwODAvY29tbWFuZHMvcnVvayB8IGdyZXAgZXJyb3IgfCBncmVwIG51bGwnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgbG9nZ2luZzoKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtc2l6ZTogNTBtCiAgICAgICAgbWF4LWZpbGU6ICczJwogICAgdm9sdW1lczoKICAgICAgLSAnem9va2VlcGVyOi9iaXRuYW1pL3pvb2tlZXBlcicKICAgIGVudmlyb25tZW50OgogICAgICAtICdBTExPV19BTk9OWU1PVVNfTE9HSU49JHtaT09fQUxMT1dfQU5PTllNT1VTX0xPR0lOOi15ZXN9JwogICAgICAtICdaT09fQVVUT1BVUkdFX0lOVEVSVkFMPSR7Wk9PX0FVVE9QVVJHRV9JTlRFUlZBTDotMX0nCiAgICAgIC0gJ1pPT19FTkFCTEVfUFJPTUVUSEVVU19NRVRSSUNTPSR7Wk9PX0VOQUJMRV9QUk9NRVRIRVVTX01FVFJJQ1M6LXllc30nCiAgICAgIC0gJ1pPT19QUk9NRVRIRVVTX01FVFJJQ1NfUE9SVF9OVU1CRVI9JHtaT09fUFJPTUVUSEVVU19NRVRSSUNTX1BPUlRfTlVNQkVSOi05MTQxfScKICBjbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI1LjUuNi1hbHBpbmUnCiAgICB0dHk6IHRydWUKICAgIGRlcGVuZHNfb246CiAgICAgIGluaXQtY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgICB6b29rZWVwZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnLXEnCiAgICAgICAgLSAnMC4wLjAuMDo4MTIzL3BpbmcnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgdWxpbWl0czoKICAgICAgbnByb2M6IDY1NTM1CiAgICAgIG5vZmlsZToKICAgICAgICBzb2Z0OiAyNjIxNDQKICAgICAgICBoYXJkOiAyNjIxNDQKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGVudmlyb25tZW50OgogICAgICAtIENMSUNLSE9VU0VfU0tJUF9VU0VSX1NFVFVQPTEKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiB2b2x1bWUKICAgICAgICBzb3VyY2U6IGNsaWNraG91c2UKICAgICAgICB0YXJnZXQ6IC92YXIvbGliL2NsaWNraG91c2UvCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2UvY3VzdG9tLWZ1bmN0aW9uLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jdXN0b20tZnVuY3Rpb24ueG1sCiAgICAgICAgY29udGVudDogIjxmdW5jdGlvbnM+XG4gICAgPGZ1bmN0aW9uPlxuICAgICAgICA8dHlwZT5leGVjdXRhYmxlPC90eXBlPlxuICAgICAgICA8bmFtZT5oaXN0b2dyYW1RdWFudGlsZTwvbmFtZT5cbiAgICAgICAgPHJldHVybl90eXBlPkZsb2F0NjQ8L3JldHVybl90eXBlPlxuICAgICAgICA8YXJndW1lbnQ+XG4gICAgICAgICAgICA8dHlwZT5BcnJheShGbG9hdDY0KTwvdHlwZT5cbiAgICAgICAgICAgIDxuYW1lPmJ1Y2tldHM8L25hbWU+XG4gICAgICAgIDwvYXJndW1lbnQ+XG4gICAgICAgIDxhcmd1bWVudD5cbiAgICAgICAgICAgIDx0eXBlPkFycmF5KEZsb2F0NjQpPC90eXBlPlxuICAgICAgICAgICAgPG5hbWU+Y291bnRzPC9uYW1lPlxuICAgICAgICA8L2FyZ3VtZW50PlxuICAgICAgICA8YXJndW1lbnQ+XG4gICAgICAgICAgICA8dHlwZT5GbG9hdDY0PC90eXBlPlxuICAgICAgICAgICAgPG5hbWU+cXVhbnRpbGU8L25hbWU+XG4gICAgICAgIDwvYXJndW1lbnQ+XG4gICAgICAgIDxmb3JtYXQ+Q1NWPC9mb3JtYXQ+XG4gICAgICAgIDxjb21tYW5kPi4vaGlzdG9ncmFtUXVhbnRpbGU8L2NvbW1hbmQ+XG4gICAgPC9mdW5jdGlvbj5cbjwvZnVuY3Rpb25zPlxuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9jbGlja2hvdXNlL2NsdXN0ZXIueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy5kL2NsdXN0ZXIueG1sCiAgICAgICAgY29udGVudDogIjw/eG1sIHZlcnNpb249XCIxLjBcIj8+XG48Y2xpY2tob3VzZT5cbiAgICA8IS0tIFpvb0tlZXBlciBpcyB1c2VkIHRvIHN0b3JlIG1ldGFkYXRhIGFib3V0IHJlcGxpY2FzLCB3aGVuIHVzaW5nIFJlcGxpY2F0ZWQgdGFibGVzLlxuICAgICAgICBPcHRpb25hbC4gSWYgeW91IGRvbid0IHVzZSByZXBsaWNhdGVkIHRhYmxlcywgeW91IGNvdWxkIG9taXQgdGhhdC5cblxuICAgICAgICBTZWUgaHR0cHM6Ly9jbGlja2hvdXNlLmNvbS9kb2NzL2VuL2VuZ2luZXMvdGFibGUtZW5naW5lcy9tZXJnZXRyZWUtZmFtaWx5L3JlcGxpY2F0aW9uL1xuICAgICAgLS0+XG4gICAgPHpvb2tlZXBlcj5cbiAgICAgICAgPG5vZGUgaW5kZXg9XCIxXCI+XG4gICAgICAgICAgICA8aG9zdD56b29rZWVwZXI8L2hvc3Q+XG4gICAgICAgICAgICA8cG9ydD4yMTgxPC9wb3J0PlxuICAgICAgICA8L25vZGU+XG4gICAgPC96b29rZWVwZXI+XG5cbiAgICA8IS0tIENvbmZpZ3VyYXRpb24gb2YgY2x1c3RlcnMgdGhhdCBjb3VsZCBiZSB1c2VkIGluIERpc3RyaWJ1dGVkIHRhYmxlcy5cbiAgICAgICAgaHR0cHM6Ly9jbGlja2hvdXNlLmNvbS9kb2NzL2VuL29wZXJhdGlvbnMvdGFibGVfZW5naW5lcy9kaXN0cmlidXRlZC9cbiAgICAgIC0tPlxuICAgIDxyZW1vdGVfc2VydmVycz5cbiAgICAgICAgPGNsdXN0ZXI+XG4gICAgICAgICAgICA8IS0tIEludGVyLXNlcnZlciBwZXItY2x1c3RlciBzZWNyZXQgZm9yIERpc3RyaWJ1dGVkIHF1ZXJpZXNcbiAgICAgICAgICAgICAgICBkZWZhdWx0OiBubyBzZWNyZXQgKG5vIGF1dGhlbnRpY2F0aW9uIHdpbGwgYmUgcGVyZm9ybWVkKVxuXG4gICAgICAgICAgICAgICAgSWYgc2V0LCB0aGVuIERpc3RyaWJ1dGVkIHF1ZXJpZXMgd2lsbCBiZSB2YWxpZGF0ZWQgb24gc2hhcmRzLCBzbyBhdCBsZWFzdDpcbiAgICAgICAgICAgICAgICAtIHN1Y2ggY2x1c3RlciBzaG91bGQgZXhpc3Qgb24gdGhlIHNoYXJkLFxuICAgICAgICAgICAgICAgIC0gc3VjaCBjbHVzdGVyIHNob3VsZCBoYXZlIHRoZSBzYW1lIHNlY3JldC5cblxuICAgICAgICAgICAgICAgIEFuZCBhbHNvIChhbmQgd2hpY2ggaXMgbW9yZSBpbXBvcnRhbnQpLCB0aGUgaW5pdGlhbF91c2VyIHdpbGxcbiAgICAgICAgICAgICAgICBiZSB1c2VkIGFzIGN1cnJlbnQgdXNlciBmb3IgdGhlIHF1ZXJ5LlxuXG4gICAgICAgICAgICAgICAgUmlnaHQgbm93IHRoZSBwcm90b2NvbCBpcyBwcmV0dHkgc2ltcGxlIGFuZCBpdCBvbmx5IHRha2VzIGludG8gYWNjb3VudDpcbiAgICAgICAgICAgICAgICAtIGNsdXN0ZXIgbmFtZVxuICAgICAgICAgICAgICAgIC0gcXVlcnlcblxuICAgICAgICAgICAgICAgIEFsc28gaXQgd2lsbCBiZSBuaWNlIGlmIHRoZSBmb2xsb3dpbmcgd2lsbCBiZSBpbXBsZW1lbnRlZDpcbiAgICAgICAgICAgICAgICAtIHNvdXJjZSBob3N0bmFtZSAoc2VlIGludGVyc2VydmVyX2h0dHBfaG9zdCksIGJ1dCB0aGVuIGl0IHdpbGwgZGVwZW5kcyBmcm9tIEROUyxcbiAgICAgICAgICAgICAgICAgIGl0IGNhbiB1c2UgSVAgYWRkcmVzcyBpbnN0ZWFkLCBidXQgdGhlbiB0aGUgeW91IG5lZWQgdG8gZ2V0IGNvcnJlY3Qgb24gdGhlIGluaXRpYXRvciBub2RlLlxuICAgICAgICAgICAgICAgIC0gdGFyZ2V0IGhvc3RuYW1lIC8gaXAgYWRkcmVzcyAoc2FtZSBub3RlcyBhcyBmb3Igc291cmNlIGhvc3RuYW1lKVxuICAgICAgICAgICAgICAgIC0gdGltZS1iYXNlZCBzZWN1cml0eSB0b2tlbnNcbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPCEtLSA8c2VjcmV0Pjwvc2VjcmV0PiAtLT5cbiAgICAgICAgICAgIDxzaGFyZD5cbiAgICAgICAgICAgICAgICA8IS0tIE9wdGlvbmFsLiBXaGV0aGVyIHRvIHdyaXRlIGRhdGEgdG8ganVzdCBvbmUgb2YgdGhlIHJlcGxpY2FzLiBEZWZhdWx0OiBmYWxzZSAod3JpdGUgZGF0YSB0byBhbGwgcmVwbGljYXMpLiAtLT5cbiAgICAgICAgICAgICAgICA8IS0tIDxpbnRlcm5hbF9yZXBsaWNhdGlvbj5mYWxzZTwvaW50ZXJuYWxfcmVwbGljYXRpb24+IC0tPlxuICAgICAgICAgICAgICAgIDwhLS0gT3B0aW9uYWwuIFNoYXJkIHdlaWdodCB3aGVuIHdyaXRpbmcgZGF0YS4gRGVmYXVsdDogMS4gLS0+XG4gICAgICAgICAgICAgICAgPCEtLSA8d2VpZ2h0PjE8L3dlaWdodD4gLS0+XG4gICAgICAgICAgICAgICAgPHJlcGxpY2E+XG4gICAgICAgICAgICAgICAgICAgIDxob3N0PmNsaWNraG91c2U8L2hvc3Q+XG4gICAgICAgICAgICAgICAgICAgIDxwb3J0PjkwMDA8L3BvcnQ+XG4gICAgICAgICAgICAgICAgICAgIDwhLS0gT3B0aW9uYWwuIFByaW9yaXR5IG9mIHRoZSByZXBsaWNhIGZvciBsb2FkX2JhbGFuY2luZy4gRGVmYXVsdDogMSAobGVzcyB2YWx1ZSBoYXMgbW9yZSBwcmlvcml0eSkuIC0tPlxuICAgICAgICAgICAgICAgICAgICA8IS0tIDxwcmlvcml0eT4xPC9wcmlvcml0eT4gLS0+XG4gICAgICAgICAgICAgICAgPC9yZXBsaWNhPlxuICAgICAgICAgICAgPC9zaGFyZD5cbiAgICAgICAgICAgIDwhLS0gPHNoYXJkPlxuICAgICAgICAgICAgICAgIDxyZXBsaWNhPlxuICAgICAgICAgICAgICAgICAgICA8aG9zdD5jbGlja2hvdXNlLTI8L2hvc3Q+XG4gICAgICAgICAgICAgICAgICAgIDxwb3J0PjkwMDA8L3BvcnQ+XG4gICAgICAgICAgICAgICAgPC9yZXBsaWNhPlxuICAgICAgICAgICAgPC9zaGFyZD5cbiAgICAgICAgICAgIDxzaGFyZD5cbiAgICAgICAgICAgICAgICA8cmVwbGljYT5cbiAgICAgICAgICAgICAgICAgICAgPGhvc3Q+Y2xpY2tob3VzZS0zPC9ob3N0PlxuICAgICAgICAgICAgICAgICAgICA8cG9ydD45MDAwPC9wb3J0PlxuICAgICAgICAgICAgICAgIDwvcmVwbGljYT5cbiAgICAgICAgICAgIDwvc2hhcmQ+IC0tPlxuICAgICAgICA8L2NsdXN0ZXI+XG4gICAgPC9yZW1vdGVfc2VydmVycz5cbjwvY2xpY2tob3VzZT5cbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS91c2Vycy54bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvY2xpY2tob3VzZS1zZXJ2ZXIvdXNlcnMueG1sCiAgICAgICAgY29udGVudDogIjw/eG1sIHZlcnNpb249XCIxLjBcIj8+XG48Y2xpY2tob3VzZT5cbiAgICA8IS0tIFNlZSBhbHNvIHRoZSBmaWxlcyBpbiB1c2Vycy5kIGRpcmVjdG9yeSB3aGVyZSB0aGUgc2V0dGluZ3MgY2FuIGJlIG92ZXJyaWRkZW4uIC0tPlxuXG4gICAgPCEtLSBQcm9maWxlcyBvZiBzZXR0aW5ncy4gLS0+XG4gICAgPHByb2ZpbGVzPlxuICAgICAgICA8IS0tIERlZmF1bHQgc2V0dGluZ3MuIC0tPlxuICAgICAgICA8ZGVmYXVsdD5cbiAgICAgICAgICAgIDwhLS0gTWF4aW11bSBtZW1vcnkgdXNhZ2UgZm9yIHByb2Nlc3Npbmcgc2luZ2xlIHF1ZXJ5LCBpbiBieXRlcy4gLS0+XG4gICAgICAgICAgICA8bWF4X21lbW9yeV91c2FnZT4xMDAwMDAwMDAwMDwvbWF4X21lbW9yeV91c2FnZT5cblxuICAgICAgICAgICAgPCEtLSBIb3cgdG8gY2hvb3NlIGJldHdlZW4gcmVwbGljYXMgZHVyaW5nIGRpc3RyaWJ1dGVkIHF1ZXJ5IHByb2Nlc3NpbmcuXG4gICAgICAgICAgICAgICAgcmFuZG9tIC0gY2hvb3NlIHJhbmRvbSByZXBsaWNhIGZyb20gc2V0IG9mIHJlcGxpY2FzIHdpdGggbWluaW11bSBudW1iZXIgb2YgZXJyb3JzXG4gICAgICAgICAgICAgICAgbmVhcmVzdF9ob3N0bmFtZSAtIGZyb20gc2V0IG9mIHJlcGxpY2FzIHdpdGggbWluaW11bSBudW1iZXIgb2YgZXJyb3JzLCBjaG9vc2UgcmVwbGljYVxuICAgICAgICAgICAgICAgICAgd2l0aCBtaW5pbXVtIG51bWJlciBvZiBkaWZmZXJlbnQgc3ltYm9scyBiZXR3ZWVuIHJlcGxpY2EncyBob3N0bmFtZSBhbmQgbG9jYWwgaG9zdG5hbWVcbiAgICAgICAgICAgICAgICAgIChIYW1taW5nIGRpc3RhbmNlKS5cbiAgICAgICAgICAgICAgICBpbl9vcmRlciAtIGZpcnN0IGxpdmUgcmVwbGljYSBpcyBjaG9zZW4gaW4gc3BlY2lmaWVkIG9yZGVyLlxuICAgICAgICAgICAgICAgIGZpcnN0X29yX3JhbmRvbSAtIGlmIGZpcnN0IHJlcGxpY2Egb25lIGhhcyBoaWdoZXIgbnVtYmVyIG9mIGVycm9ycywgcGljayBhIHJhbmRvbSBvbmUgZnJvbSByZXBsaWNhcyB3aXRoIG1pbmltdW0gbnVtYmVyIG9mIGVycm9ycy5cbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPGxvYWRfYmFsYW5jaW5nPnJhbmRvbTwvbG9hZF9iYWxhbmNpbmc+XG4gICAgICAgIDwvZGVmYXVsdD5cblxuICAgICAgICA8IS0tIFByb2ZpbGUgdGhhdCBhbGxvd3Mgb25seSByZWFkIHF1ZXJpZXMuIC0tPlxuICAgICAgICA8cmVhZG9ubHk+XG4gICAgICAgICAgICA8cmVhZG9ubHk+MTwvcmVhZG9ubHk+XG4gICAgICAgIDwvcmVhZG9ubHk+XG4gICAgPC9wcm9maWxlcz5cblxuICAgIDwhLS0gVXNlcnMgYW5kIEFDTC4gLS0+XG4gICAgPHVzZXJzPlxuICAgICAgICA8IS0tIElmIHVzZXIgbmFtZSB3YXMgbm90IHNwZWNpZmllZCwgJ2RlZmF1bHQnIHVzZXIgaXMgdXNlZC4gLS0+XG4gICAgICAgIDxkZWZhdWx0PlxuICAgICAgICAgICAgPCEtLSBTZWUgYWxzbyB0aGUgZmlsZXMgaW4gdXNlcnMuZCBkaXJlY3Rvcnkgd2hlcmUgdGhlIHBhc3N3b3JkIGNhbiBiZSBvdmVycmlkZGVuLlxuXG4gICAgICAgICAgICAgICAgUGFzc3dvcmQgY291bGQgYmUgc3BlY2lmaWVkIGluIHBsYWludGV4dCBvciBpbiBTSEEyNTYgKGluIGhleCBmb3JtYXQpLlxuXG4gICAgICAgICAgICAgICAgSWYgeW91IHdhbnQgdG8gc3BlY2lmeSBwYXNzd29yZCBpbiBwbGFpbnRleHQgKG5vdCByZWNvbW1lbmRlZCksIHBsYWNlIGl0IGluICdwYXNzd29yZCcgZWxlbWVudC5cbiAgICAgICAgICAgICAgICBFeGFtcGxlOiA8cGFzc3dvcmQ+cXdlcnR5PC9wYXNzd29yZD4uXG4gICAgICAgICAgICAgICAgUGFzc3dvcmQgY291bGQgYmUgZW1wdHkuXG5cbiAgICAgICAgICAgICAgICBJZiB5b3Ugd2FudCB0byBzcGVjaWZ5IFNIQTI1NiwgcGxhY2UgaXQgaW4gJ3Bhc3N3b3JkX3NoYTI1Nl9oZXgnIGVsZW1lbnQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPHBhc3N3b3JkX3NoYTI1Nl9oZXg+NjVlODRiZTMzNTMyZmI3ODRjNDgxMjk2NzVmOWVmZjNhNjgyYjI3MTY4YzBlYTc0NGIyY2Y1OGVlMDIzMzdjNTwvcGFzc3dvcmRfc2hhMjU2X2hleD5cbiAgICAgICAgICAgICAgICBSZXN0cmljdGlvbnMgb2YgU0hBMjU2OiBpbXBvc3NpYmlsaXR5IHRvIGNvbm5lY3QgdG8gQ2xpY2tIb3VzZSB1c2luZyBNeVNRTCBKUyBjbGllbnQgKGFzIG9mIEp1bHkgMjAxOSkuXG5cbiAgICAgICAgICAgICAgICBJZiB5b3Ugd2FudCB0byBzcGVjaWZ5IGRvdWJsZSBTSEExLCBwbGFjZSBpdCBpbiAncGFzc3dvcmRfZG91YmxlX3NoYTFfaGV4JyBlbGVtZW50LlxuICAgICAgICAgICAgICAgIEV4YW1wbGU6IDxwYXNzd29yZF9kb3VibGVfc2hhMV9oZXg+ZTM5NTc5NmQ2NTQ2YjFiNjVkYjlkNjY1Y2Q0M2YwZTg1OGRkNDMwMzwvcGFzc3dvcmRfZG91YmxlX3NoYTFfaGV4PlxuXG4gICAgICAgICAgICAgICAgSWYgeW91IHdhbnQgdG8gc3BlY2lmeSBhIHByZXZpb3VzbHkgZGVmaW5lZCBMREFQIHNlcnZlciAoc2VlICdsZGFwX3NlcnZlcnMnIGluIHRoZSBtYWluIGNvbmZpZykgZm9yIGF1dGhlbnRpY2F0aW9uLFxuICAgICAgICAgICAgICAgICAgcGxhY2UgaXRzIG5hbWUgaW4gJ3NlcnZlcicgZWxlbWVudCBpbnNpZGUgJ2xkYXAnIGVsZW1lbnQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGxkYXA+PHNlcnZlcj5teV9sZGFwX3NlcnZlcjwvc2VydmVyPjwvbGRhcD5cblxuICAgICAgICAgICAgICAgIElmIHlvdSB3YW50IHRvIGF1dGhlbnRpY2F0ZSB0aGUgdXNlciB2aWEgS2VyYmVyb3MgKGFzc3VtaW5nIEtlcmJlcm9zIGlzIGVuYWJsZWQsIHNlZSAna2VyYmVyb3MnIGluIHRoZSBtYWluIGNvbmZpZyksXG4gICAgICAgICAgICAgICAgICBwbGFjZSAna2VyYmVyb3MnIGVsZW1lbnQgaW5zdGVhZCBvZiAncGFzc3dvcmQnIChhbmQgc2ltaWxhcikgZWxlbWVudHMuXG4gICAgICAgICAgICAgICAgVGhlIG5hbWUgcGFydCBvZiB0aGUgY2Fub25pY2FsIHByaW5jaXBhbCBuYW1lIG9mIHRoZSBpbml0aWF0b3IgbXVzdCBtYXRjaCB0aGUgdXNlciBuYW1lIGZvciBhdXRoZW50aWNhdGlvbiB0byBzdWNjZWVkLlxuICAgICAgICAgICAgICAgIFlvdSBjYW4gYWxzbyBwbGFjZSAncmVhbG0nIGVsZW1lbnQgaW5zaWRlICdrZXJiZXJvcycgZWxlbWVudCB0byBmdXJ0aGVyIHJlc3RyaWN0IGF1dGhlbnRpY2F0aW9uIHRvIG9ubHkgdGhvc2UgcmVxdWVzdHNcbiAgICAgICAgICAgICAgICAgIHdob3NlIGluaXRpYXRvcidzIHJlYWxtIG1hdGNoZXMgaXQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGtlcmJlcm9zIC8+XG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGtlcmJlcm9zPjxyZWFsbT5FWEFNUExFLkNPTTwvcmVhbG0+PC9rZXJiZXJvcz5cblxuICAgICAgICAgICAgICAgIEhvdyB0byBnZW5lcmF0ZSBkZWNlbnQgcGFzc3dvcmQ6XG4gICAgICAgICAgICAgICAgRXhlY3V0ZTogUEFTU1dPUkQ9JChiYXNlNjQgPCAvZGV2L3VyYW5kb20gfCBoZWFkIC1jOCk7IGVjaG8gXCIkUEFTU1dPUkRcIjsgZWNobyAtbiBcIiRQQVNTV09SRFwiIHwgc2hhMjU2c3VtIHwgdHIgLWQgJy0nXG4gICAgICAgICAgICAgICAgSW4gZmlyc3QgbGluZSB3aWxsIGJlIHBhc3N3b3JkIGFuZCBpbiBzZWNvbmQgLSBjb3JyZXNwb25kaW5nIFNIQTI1Ni5cblxuICAgICAgICAgICAgICAgIEhvdyB0byBnZW5lcmF0ZSBkb3VibGUgU0hBMTpcbiAgICAgICAgICAgICAgICBFeGVjdXRlOiBQQVNTV09SRD0kKGJhc2U2NCA8IC9kZXYvdXJhbmRvbSB8IGhlYWQgLWM4KTsgZWNobyBcIiRQQVNTV09SRFwiOyBlY2hvIC1uIFwiJFBBU1NXT1JEXCIgfCBzaGExc3VtIHwgdHIgLWQgJy0nIHwgeHhkIC1yIC1wIHwgc2hhMXN1bSB8IHRyIC1kICctJ1xuICAgICAgICAgICAgICAgIEluIGZpcnN0IGxpbmUgd2lsbCBiZSBwYXNzd29yZCBhbmQgaW4gc2Vjb25kIC0gY29ycmVzcG9uZGluZyBkb3VibGUgU0hBMS5cbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPHBhc3N3b3JkPjwvcGFzc3dvcmQ+XG5cbiAgICAgICAgICAgIDwhLS0gTGlzdCBvZiBuZXR3b3JrcyB3aXRoIG9wZW4gYWNjZXNzLlxuXG4gICAgICAgICAgICAgICAgVG8gb3BlbiBhY2Nlc3MgZnJvbSBldmVyeXdoZXJlLCBzcGVjaWZ5OlxuICAgICAgICAgICAgICAgICAgICA8aXA+OjovMDwvaXA+XG5cbiAgICAgICAgICAgICAgICBUbyBvcGVuIGFjY2VzcyBvbmx5IGZyb20gbG9jYWxob3N0LCBzcGVjaWZ5OlxuICAgICAgICAgICAgICAgICAgICA8aXA+OjoxPC9pcD5cbiAgICAgICAgICAgICAgICAgICAgPGlwPjEyNy4wLjAuMTwvaXA+XG5cbiAgICAgICAgICAgICAgICBFYWNoIGVsZW1lbnQgb2YgbGlzdCBoYXMgb25lIG9mIHRoZSBmb2xsb3dpbmcgZm9ybXM6XG4gICAgICAgICAgICAgICAgPGlwPiBJUC1hZGRyZXNzIG9yIG5ldHdvcmsgbWFzay4gRXhhbXBsZXM6IDIxMy4xODAuMjA0LjMgb3IgMTAuMC4wLjEvOCBvciAxMC4wLjAuMS8yNTUuMjU1LjI1NS4wXG4gICAgICAgICAgICAgICAgICAgIDJhMDI6NmI4OjozIG9yIDJhMDI6NmI4OjozLzY0IG9yIDJhMDI6NmI4OjozL2ZmZmY6ZmZmZjpmZmZmOmZmZmY6Oi5cbiAgICAgICAgICAgICAgICA8aG9zdD4gSG9zdG5hbWUuIEV4YW1wbGU6IHNlcnZlcjAxLmNsaWNraG91c2UuY29tLlxuICAgICAgICAgICAgICAgICAgICBUbyBjaGVjayBhY2Nlc3MsIEROUyBxdWVyeSBpcyBwZXJmb3JtZWQsIGFuZCBhbGwgcmVjZWl2ZWQgYWRkcmVzc2VzIGNvbXBhcmVkIHRvIHBlZXIgYWRkcmVzcy5cbiAgICAgICAgICAgICAgICA8aG9zdF9yZWdleHA+IFJlZ3VsYXIgZXhwcmVzc2lvbiBmb3IgaG9zdCBuYW1lcy4gRXhhbXBsZSwgXnNlcnZlclxcZFxcZC1cXGRcXGQtXFxkXFwuY2xpY2tob3VzZVxcLmNvbSRcbiAgICAgICAgICAgICAgICAgICAgVG8gY2hlY2sgYWNjZXNzLCBETlMgUFRSIHF1ZXJ5IGlzIHBlcmZvcm1lZCBmb3IgcGVlciBhZGRyZXNzIGFuZCB0aGVuIHJlZ2V4cCBpcyBhcHBsaWVkLlxuICAgICAgICAgICAgICAgICAgICBUaGVuLCBmb3IgcmVzdWx0IG9mIFBUUiBxdWVyeSwgYW5vdGhlciBETlMgcXVlcnkgaXMgcGVyZm9ybWVkIGFuZCBhbGwgcmVjZWl2ZWQgYWRkcmVzc2VzIGNvbXBhcmVkIHRvIHBlZXIgYWRkcmVzcy5cbiAgICAgICAgICAgICAgICAgICAgU3Ryb25nbHkgcmVjb21tZW5kZWQgdGhhdCByZWdleHAgaXMgZW5kcyB3aXRoICRcbiAgICAgICAgICAgICAgICBBbGwgcmVzdWx0cyBvZiBETlMgcmVxdWVzdHMgYXJlIGNhY2hlZCB0aWxsIHNlcnZlciByZXN0YXJ0LlxuICAgICAgICAgICAgLS0+XG4gICAgICAgICAgICA8bmV0d29ya3M+XG4gICAgICAgICAgICAgICAgPGlwPjo6LzA8L2lwPlxuICAgICAgICAgICAgPC9uZXR3b3Jrcz5cblxuICAgICAgICAgICAgPCEtLSBTZXR0aW5ncyBwcm9maWxlIGZvciB1c2VyLiAtLT5cbiAgICAgICAgICAgIDxwcm9maWxlPmRlZmF1bHQ8L3Byb2ZpbGU+XG5cbiAgICAgICAgICAgIDwhLS0gUXVvdGEgZm9yIHVzZXIuIC0tPlxuICAgICAgICAgICAgPHF1b3RhPmRlZmF1bHQ8L3F1b3RhPlxuXG4gICAgICAgICAgICA8IS0tIFVzZXIgY2FuIGNyZWF0ZSBvdGhlciB1c2VycyBhbmQgZ3JhbnQgcmlnaHRzIHRvIHRoZW0uIC0tPlxuICAgICAgICAgICAgPCEtLSA8YWNjZXNzX21hbmFnZW1lbnQ+MTwvYWNjZXNzX21hbmFnZW1lbnQ+IC0tPlxuICAgICAgICA8L2RlZmF1bHQ+XG4gICAgPC91c2Vycz5cblxuICAgIDwhLS0gUXVvdGFzLiAtLT5cbiAgICA8cXVvdGFzPlxuICAgICAgICA8IS0tIE5hbWUgb2YgcXVvdGEuIC0tPlxuICAgICAgICA8ZGVmYXVsdD5cbiAgICAgICAgICAgIDwhLS0gTGltaXRzIGZvciB0aW1lIGludGVydmFsLiBZb3UgY291bGQgc3BlY2lmeSBtYW55IGludGVydmFscyB3aXRoIGRpZmZlcmVudCBsaW1pdHMuIC0tPlxuICAgICAgICAgICAgPGludGVydmFsPlxuICAgICAgICAgICAgICAgIDwhLS0gTGVuZ3RoIG9mIGludGVydmFsLiAtLT5cbiAgICAgICAgICAgICAgICA8ZHVyYXRpb24+MzYwMDwvZHVyYXRpb24+XG5cbiAgICAgICAgICAgICAgICA8IS0tIE5vIGxpbWl0cy4gSnVzdCBjYWxjdWxhdGUgcmVzb3VyY2UgdXNhZ2UgZm9yIHRpbWUgaW50ZXJ2YWwuIC0tPlxuICAgICAgICAgICAgICAgIDxxdWVyaWVzPjA8L3F1ZXJpZXM+XG4gICAgICAgICAgICAgICAgPGVycm9ycz4wPC9lcnJvcnM+XG4gICAgICAgICAgICAgICAgPHJlc3VsdF9yb3dzPjA8L3Jlc3VsdF9yb3dzPlxuICAgICAgICAgICAgICAgIDxyZWFkX3Jvd3M+MDwvcmVhZF9yb3dzPlxuICAgICAgICAgICAgICAgIDxleGVjdXRpb25fdGltZT4wPC9leGVjdXRpb25fdGltZT5cbiAgICAgICAgICAgIDwvaW50ZXJ2YWw+XG4gICAgICAgIDwvZGVmYXVsdD5cbiAgICA8L3F1b3Rhcz5cbjwvY2xpY2tob3VzZT5cbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS9jb25maWcueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy54bWwKICAgICAgICBjb250ZW50OiAiPD94bWwgdmVyc2lvbj1cIjEuMFwiPz5cbjxjbGlja2hvdXNlPlxuICA8bWF4X2Nvbm5lY3Rpb25zPjQwOTY8L21heF9jb25uZWN0aW9ucz5cbiAgPGtlZXBfYWxpdmVfdGltZW91dD4zPC9rZWVwX2FsaXZlX3RpbWVvdXQ+XG4gIDxtYXhfY29uY3VycmVudF9xdWVyaWVzPjEwMDwvbWF4X2NvbmN1cnJlbnRfcXVlcmllcz5cbiAgPG1hcmtfY2FjaGVfc2l6ZT41MzY4NzA5MTIwPC9tYXJrX2NhY2hlX3NpemU+XG4gIDxtbWFwX2NhY2hlX3NpemU+MTAwMDwvbW1hcF9jYWNoZV9zaXplPlxuICA8Y29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9zaXplPjEzNDIxNzcyODwvY29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9zaXplPlxuICA8Y29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9lbGVtZW50c19zaXplPjEwMDAwPC9jb21waWxlZF9leHByZXNzaW9uX2NhY2hlX2VsZW1lbnRzX3NpemU+XG4gIDxjdXN0b21fc2V0dGluZ3NfcHJlZml4ZXM+PC9jdXN0b21fc2V0dGluZ3NfcHJlZml4ZXM+XG4gIDxkaWN0aW9uYXJpZXNfY29uZmlnPipfZGljdGlvbmFyeS54bWw8L2RpY3Rpb25hcmllc19jb25maWc+XG4gIDx1c2VyX2RlZmluZWRfZXhlY3V0YWJsZV9mdW5jdGlvbnNfY29uZmlnPipmdW5jdGlvbi54bWw8L3VzZXJfZGVmaW5lZF9leGVjdXRhYmxlX2Z1bmN0aW9uc19jb25maWc+XG4gIDx1c2VyX3NjcmlwdHNfcGF0aD4vdmFyL2xpYi9jbGlja2hvdXNlL3VzZXJfc2NyaXB0cy88L3VzZXJfc2NyaXB0c19wYXRoPlxuICA8aHR0cF9wb3J0PjgxMjM8L2h0dHBfcG9ydD5cbiAgPHRjcF9wb3J0PjkwMDA8L3RjcF9wb3J0PlxuICA8bXlzcWxfcG9ydD45MDA0PC9teXNxbF9wb3J0PlxuICA8cG9zdGdyZXNxbF9wb3J0PjkwMDU8L3Bvc3RncmVzcWxfcG9ydD5cbiAgPGludGVyc2VydmVyX2h0dHBfcG9ydD45MDA5PC9pbnRlcnNlcnZlcl9odHRwX3BvcnQ+XG4gIDxsb2dnZXI+XG4gICAgPGxldmVsPmluZm9ybWF0aW9uPC9sZXZlbD5cbiAgICA8Zm9ybWF0dGluZz5cbiAgICAgIDx0eXBlPmpzb248L3R5cGU+XG4gICAgPC9mb3JtYXR0aW5nPlxuICA8L2xvZ2dlcj5cbiAgPG1hY3Jvcz5cbiAgICA8c2hhcmQ+MDE8L3NoYXJkPlxuICAgIDxyZXBsaWNhPmV4YW1wbGUwMS0wMS0xPC9yZXBsaWNhPlxuICA8L21hY3Jvcz5cbiAgPHByb21ldGhldXM+XG4gICAgPGVuZHBvaW50Pi9tZXRyaWNzPC9lbmRwb2ludD5cbiAgICA8cG9ydD45MzYzPC9wb3J0PlxuICAgIDxtZXRyaWNzPnRydWU8L21ldHJpY3M+XG4gICAgPGV2ZW50cz50cnVlPC9ldmVudHM+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNzPnRydWU8L2FzeW5jaHJvbm91c19tZXRyaWNzPlxuICAgIDxzdGF0dXNfaW5mbz50cnVlPC9zdGF0dXNfaW5mbz5cbiAgPC9wcm9tZXRoZXVzPlxuICA8b3BlbnRlbGVtZXRyeV9zcGFuX2xvZz5cbiAgICA8ZW5naW5lPmVuZ2luZSBNZXJnZVRyZWVcbiAgICAgICAgICAgIHBhcnRpdGlvbiBieSB0b1lZWVlNTShmaW5pc2hfZGF0ZSlcbiAgICAgICAgICAgIG9yZGVyIGJ5IChmaW5pc2hfZGF0ZSwgZmluaXNoX3RpbWVfdXMsIHRyYWNlX2lkKTwvZW5naW5lPlxuICA8L29wZW50ZWxlbWV0cnlfc3Bhbl9sb2c+XG4gIDxxdWVyeV9tYXNraW5nX3J1bGVzPlxuICAgIDxydWxlPlxuICAgICAgPG5hbWU+aGlkZSBlbmNyeXB0L2RlY3J5cHQgYXJndW1lbnRzPC9uYW1lPlxuICAgICAgPHJlZ2V4cD4oKD86YWVzXyk/KD86ZW5jcnlwdHxkZWNyeXB0KSg/Ol9teXNxbCk/KVxccypcXChcXHMqKD86Jyg/OlxcXFwnfC4pKyd8Lio/KVxccypcXCk8L3JlZ2V4cD5cbiAgICAgIDxyZXBsYWNlPlxcMSg/Pz8pPC9yZXBsYWNlPlxuICAgIDwvcnVsZT5cbiAgPC9xdWVyeV9tYXNraW5nX3J1bGVzPlxuICA8c2VuZF9jcmFzaF9yZXBvcnRzPlxuICAgIDxlbmFibGVkPmZhbHNlPC9lbmFibGVkPlxuICAgIDxhbm9ueW1pemU+ZmFsc2U8L2Fub255bWl6ZT5cbiAgICA8ZW5kcG9pbnQ+aHR0cHM6Ly82ZjMzMDM0Y2ZlNjg0ZGQ3YTNhYjk4NzVlNTdiMWM4ZEBvMzg4ODcwLmluZ2VzdC5zZW50cnkuaW8vNTIyNjI3NzwvZW5kcG9pbnQ+XG4gIDwvc2VuZF9jcmFzaF9yZXBvcnRzPlxuICA8bWVyZ2VfdHJlZV9tZXRhZGF0YV9jYWNoZT5cbiAgICA8bHJ1X2NhY2hlX3NpemU+MjY4NDM1NDU2PC9scnVfY2FjaGVfc2l6ZT5cbiAgICA8Y29udGludWVfaWZfY29ycnVwdGVkPnRydWU8L2NvbnRpbnVlX2lmX2NvcnJ1cHRlZD5cbiAgPC9tZXJnZV90cmVlX21ldGFkYXRhX2NhY2hlPlxuICA8dXNlcl9kaXJlY3Rvcmllcz5cbiAgICA8dXNlcnNfeG1sPlxuICAgICAgICA8IS0tIFBhdGggdG8gY29uZmlndXJhdGlvbiBmaWxlIHdpdGggcHJlZGVmaW5lZCB1c2Vycy4gLS0+XG4gICAgICAgIDxwYXRoPnVzZXJzLnhtbDwvcGF0aD5cbiAgICA8L3VzZXJzX3htbD5cbiAgICA8bG9jYWxfZGlyZWN0b3J5PlxuICAgICAgICA8IS0tIFBhdGggdG8gZm9sZGVyIHdoZXJlIHVzZXJzIGNyZWF0ZWQgYnkgU1FMIGNvbW1hbmRzIGFyZSBzdG9yZWQuIC0tPlxuICAgICAgICA8cGF0aD4vdmFyL2xpYi9jbGlja2hvdXNlL2FjY2Vzcy88L3BhdGg+XG4gICAgPC9sb2NhbF9kaXJlY3Rvcnk+XG4gIDwvdXNlcl9kaXJlY3Rvcmllcz5cbiAgPGRlZmF1bHRfcHJvZmlsZT5kZWZhdWx0PC9kZWZhdWx0X3Byb2ZpbGU+XG4gICAgPGRpc3RyaWJ1dGVkX2RkbD5cbiAgICAgICAgPCEtLSBQYXRoIGluIFpvb0tlZXBlciB0byBxdWV1ZSB3aXRoIERETCBxdWVyaWVzIC0tPlxuICAgICAgICA8cGF0aD4vY2xpY2tob3VzZS90YXNrX3F1ZXVlL2RkbDwvcGF0aD5cbiAgICA8L2Rpc3RyaWJ1dGVkX2RkbD5cbjwvY2xpY2tob3VzZT5cbiIKICBzaWdub3o6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3o6djAuOTcuMScKICAgIGRlcGVuZHNfb246CiAgICAgIGNsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc2NoZW1hLW1pZ3JhdG9yLXN5bmM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2NvbXBsZXRlZF9zdWNjZXNzZnVsbHkKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGNvbW1hbmQ6CiAgICAgIC0gJy0tY29uZmlnPS9yb290L2NvbmZpZy9wcm9tZXRoZXVzLnltbCcKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3Byb21ldGhldXMueW1sCiAgICAgICAgdGFyZ2V0OiAvcm9vdC9jb25maWcvcHJvbWV0aGV1cy55bWwKICAgICAgICBjb250ZW50OiAiIyBteSBnbG9iYWwgY29uZmlnXG5nbG9iYWw6XG4gIHNjcmFwZV9pbnRlcnZhbDogICAgIDVzICMgU2V0IHRoZSBzY3JhcGUgaW50ZXJ2YWwgdG8gZXZlcnkgMTUgc2Vjb25kcy4gRGVmYXVsdCBpcyBldmVyeSAxIG1pbnV0ZS5cbiAgZXZhbHVhdGlvbl9pbnRlcnZhbDogMTVzICMgRXZhbHVhdGUgcnVsZXMgZXZlcnkgMTUgc2Vjb25kcy4gVGhlIGRlZmF1bHQgaXMgZXZlcnkgMSBtaW51dGUuXG4gICMgc2NyYXBlX3RpbWVvdXQgaXMgc2V0IHRvIHRoZSBnbG9iYWwgZGVmYXVsdCAoMTBzKS5cblxuIyBBbGVydG1hbmFnZXIgY29uZmlndXJhdGlvblxuYWxlcnRpbmc6XG4gIGFsZXJ0bWFuYWdlcnM6XG4gIC0gc3RhdGljX2NvbmZpZ3M6XG4gICAgLSB0YXJnZXRzOlxuICAgICAgLSBhbGVydG1hbmFnZXI6OTA5M1xuXG4jIExvYWQgcnVsZXMgb25jZSBhbmQgcGVyaW9kaWNhbGx5IGV2YWx1YXRlIHRoZW0gYWNjb3JkaW5nIHRvIHRoZSBnbG9iYWwgJ2V2YWx1YXRpb25faW50ZXJ2YWwnLlxucnVsZV9maWxlczogW11cbiAgIyAtIFwiZmlyc3RfcnVsZXMueW1sXCJcbiAgIyAtIFwic2Vjb25kX3J1bGVzLnltbFwiXG4gICMgLSAnYWxlcnRzLnltbCdcblxuIyBBIHNjcmFwZSBjb25maWd1cmF0aW9uIGNvbnRhaW5pbmcgZXhhY3RseSBvbmUgZW5kcG9pbnQgdG8gc2NyYXBlOlxuIyBIZXJlIGl0J3MgUHJvbWV0aGV1cyBpdHNlbGYuXG5zY3JhcGVfY29uZmlnczogW11cblxucmVtb3RlX3JlYWQ6XG4gIC0gdXJsOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X21ldHJpY3NcbiIKICAgICAgLQogICAgICAgIHR5cGU6IHZvbHVtZQogICAgICAgIHNvdXJjZTogc3FsaXRlCiAgICAgICAgdGFyZ2V0OiAvdmFyL2xpYi9zaWdub3ovCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9TSUdOT1pfODA4MAogICAgICAtICdTSUdOT1pfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUkVBTEJBU0U2NF9KV1RTRUNSRVR9JwogICAgICAtICdTSUdOT1pfVEVMRU1FVFJZU1RPUkVfQ0xJQ0tIT1VTRV9EU049dGNwOi8vY2xpY2tob3VzZTo5MDAwJwogICAgICAtIFNJR05PWl9TUUxTVE9SRV9TUUxJVEVfUEFUSD0vdmFyL2xpYi9zaWdub3ovc2lnbm96LmRiCiAgICAgIC0gREFTSEJPQVJEU19QQVRIPS9yb290L2NvbmZpZy9kYXNoYm9hcmRzCiAgICAgIC0gU1RPUkFHRT1jbGlja2hvdXNlCiAgICAgIC0gR09ERUJVRz1uZXRkbnM9Z28KICAgICAgLSBERVBMT1lNRU5UX1RZUEU9ZG9ja2VyLXN0YW5kYWxvbmUtYW1kCiAgICAgIC0gJ1NJR05PWl9TVEFUU1JFUE9SVEVSX0VOQUJMRUQ9JHtTSUdOT1pfU1RBVFNSRVBPUlRFUl9FTkFCTEVEOi10cnVlfScKICAgICAgLSAnU0lHTk9aX0VNQUlMSU5HX0VOQUJMRUQ9JHtTSUdOT1pfRU1BSUxJTkdfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdTSUdOT1pfRU1BSUxJTkdfU01UUF9BRERSRVNTPSR7U0lHTk9aX0VNQUlMSU5HX1NNVFBfQUREUkVTU30nCiAgICAgIC0gJ1NJR05PWl9FTUFJTElOR19TTVRQX0ZST009JHtTSUdOT1pfRU1BSUxJTkdfU01UUF9GUk9NfScKICAgICAgLSAnU0lHTk9aX0VNQUlMSU5HX1NNVFBfQVVUSF9VU0VSTkFNRT0ke1NJR05PWl9FTUFJTElOR19TTVRQX0FVVEhfVVNFUk5BTUV9JwogICAgICAtICdTSUdOT1pfRU1BSUxJTkdfU01UUF9BVVRIX1BBU1NXT1JEPSR7U0lHTk9aX0VNQUlMSU5HX1NNVFBfQVVUSF9QQVNTV09SRH0nCiAgICAgIC0gU0lHTk9aX0FMRVJUTUFOQUdFUl9QUk9WSURFUj1zaWdub3oKICAgICAgLSAnU0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0FVVEhfX1BBU1NXT1JEPSR7U0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0FVVEhfX1BBU1NXT1JEfScKICAgICAgLSAnU0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0FVVEhfX1VTRVJOQU1FPSR7U0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0FVVEhfX1VTRVJOQU1FfScKICAgICAgLSAnU0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0ZST009JHtTSUdOT1pfQUxFUlRNQU5BR0VSX1NJR05PWl9HTE9CQUxfU01UUF9fRlJPTX0nCiAgICAgIC0gJ1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19TTUFSVEhPU1Q9JHtTSUdOT1pfQUxFUlRNQU5BR0VSX1NJR05PWl9HTE9CQUxfU01UUF9fU01BUlRIT1NUfScKICAgICAgLSBET1RfTUVUUklDU19FTkFCRUxEPXRydWUKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnLXEnCiAgICAgICAgLSAnbG9jYWxob3N0OjgwODAvYXBpL3YxL2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCiAgb3RlbC1jb2xsZWN0b3I6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3otb3RlbC1jb2xsZWN0b3I6djAuMTI5LjcnCiAgICBkZXBlbmRzX29uOgogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHNjaGVtYS1taWdyYXRvci1zeW5jOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9jb21wbGV0ZWRfc3VjY2Vzc2Z1bGx5CiAgICAgIHNpZ25vejoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgbG9nZ2luZzoKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtc2l6ZTogNTBtCiAgICAgICAgbWF4LWZpbGU6ICczJwogICAgY29tbWFuZDoKICAgICAgLSAnLS1jb25maWc9L2V0Yy9vdGVsLWNvbGxlY3Rvci1jb25maWcueWFtbCcKICAgICAgLSAnLS1tYW5hZ2VyLWNvbmZpZz0vZXRjL21hbmFnZXItY29uZmlnLnlhbWwnCiAgICAgIC0gJy0tY29weS1wYXRoPS92YXIvdG1wL2NvbGxlY3Rvci1jb25maWcueWFtbCcKICAgICAgLSAnLS1mZWF0dXJlLWdhdGVzPS1wa2cudHJhbnNsYXRvci5wcm9tZXRoZXVzLk5vcm1hbGl6ZU5hbWUnCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9vdGVsLWNvbGxlY3Rvci1jb25maWcueWFtbAogICAgICAgIHRhcmdldDogL2V0Yy9vdGVsLWNvbGxlY3Rvci1jb25maWcueWFtbAogICAgICAgIGNvbnRlbnQ6ICJyZWNlaXZlcnM6XG4gIG90bHA6XG4gICAgcHJvdG9jb2xzOlxuICAgICAgZ3JwYzpcbiAgICAgICAgZW5kcG9pbnQ6IDAuMC4wLjA6NDMxN1xuICAgICAgaHR0cDpcbiAgICAgICAgZW5kcG9pbnQ6IDAuMC4wLjA6NDMxOFxuICBwcm9tZXRoZXVzOlxuICAgIGNvbmZpZzpcbiAgICAgIGdsb2JhbDpcbiAgICAgICAgc2NyYXBlX2ludGVydmFsOiA2MHNcbiAgICAgIHNjcmFwZV9jb25maWdzOlxuICAgICAgICAtIGpvYl9uYW1lOiBvdGVsLWNvbGxlY3RvclxuICAgICAgICAgIHN0YXRpY19jb25maWdzOlxuICAgICAgICAgIC0gdGFyZ2V0czpcbiAgICAgICAgICAgICAgLSBsb2NhbGhvc3Q6ODg4OFxuICAgICAgICAgICAgbGFiZWxzOlxuICAgICAgICAgICAgICBqb2JfbmFtZTogb3RlbC1jb2xsZWN0b3JcbnByb2Nlc3NvcnM6XG4gIGJhdGNoOlxuICAgIHNlbmRfYmF0Y2hfc2l6ZTogMTAwMDBcbiAgICBzZW5kX2JhdGNoX21heF9zaXplOiAxMTAwMFxuICAgIHRpbWVvdXQ6IDEwc1xuICByZXNvdXJjZWRldGVjdGlvbjpcbiAgICAjIFVzaW5nIE9URUxfUkVTT1VSQ0VfQVRUUklCVVRFUyBlbnZ2YXIsIGVudiBkZXRlY3RvciBhZGRzIGN1c3RvbSBsYWJlbHMuXG4gICAgZGV0ZWN0b3JzOiBbZW52LCBzeXN0ZW1dXG4gICAgdGltZW91dDogMnNcbiAgc2lnbm96c3Bhbm1ldHJpY3MvZGVsdGE6XG4gICAgbWV0cmljc19leHBvcnRlcjogc2lnbm96Y2xpY2tob3VzZW1ldHJpY3NcbiAgICBtZXRyaWNzX2ZsdXNoX2ludGVydmFsOiA2MHNcbiAgICBsYXRlbmN5X2hpc3RvZ3JhbV9idWNrZXRzOiBbMTAwdXMsIDFtcywgMm1zLCA2bXMsIDEwbXMsIDUwbXMsIDEwMG1zLCAyNTBtcywgNTAwbXMsIDEwMDBtcywgMTQwMG1zLCAyMDAwbXMsIDVzLCAxMHMsIDIwcywgNDBzLCA2MHMgXVxuICAgIGRpbWVuc2lvbnNfY2FjaGVfc2l6ZTogMTAwMDAwXG4gICAgYWdncmVnYXRpb25fdGVtcG9yYWxpdHk6IEFHR1JFR0FUSU9OX1RFTVBPUkFMSVRZX0RFTFRBXG4gICAgZW5hYmxlX2V4cF9oaXN0b2dyYW06IHRydWVcbiAgICBkaW1lbnNpb25zOlxuICAgICAgLSBuYW1lOiBzZXJ2aWNlLm5hbWVzcGFjZVxuICAgICAgICBkZWZhdWx0OiBkZWZhdWx0XG4gICAgICAtIG5hbWU6IGRlcGxveW1lbnQuZW52aXJvbm1lbnRcbiAgICAgICAgZGVmYXVsdDogZGVmYXVsdFxuICAgICAgIyBUaGlzIGlzIGFkZGVkIHRvIGVuc3VyZSB0aGUgdW5pcXVlbmVzcyBvZiB0aGUgdGltZXNlcmllc1xuICAgICAgIyBPdGhlcndpc2UsIGlkZW50aWNhbCB0aW1lc2VyaWVzIHByb2R1Y2VkIGJ5IG11bHRpcGxlIHJlcGxpY2FzIG9mXG4gICAgICAjIGNvbGxlY3RvcnMgcmVzdWx0IGluIGluY29ycmVjdCBBUE0gbWV0cmljc1xuICAgICAgLSBuYW1lOiBzaWdub3ouY29sbGVjdG9yLmlkXG4gICAgICAtIG5hbWU6IHNlcnZpY2UudmVyc2lvblxuICAgICAgLSBuYW1lOiBicm93c2VyLnBsYXRmb3JtXG4gICAgICAtIG5hbWU6IGJyb3dzZXIubW9iaWxlXG4gICAgICAtIG5hbWU6IGs4cy5jbHVzdGVyLm5hbWVcbiAgICAgIC0gbmFtZTogazhzLm5vZGUubmFtZVxuICAgICAgLSBuYW1lOiBrOHMubmFtZXNwYWNlLm5hbWVcbiAgICAgIC0gbmFtZTogaG9zdC5uYW1lXG4gICAgICAtIG5hbWU6IGhvc3QudHlwZVxuICAgICAgLSBuYW1lOiBjb250YWluZXIubmFtZVxuZXh0ZW5zaW9uczpcbiAgaGVhbHRoX2NoZWNrOlxuICAgIGVuZHBvaW50OiAwLjAuMC4wOjEzMTMzXG4gIHBwcm9mOlxuICAgIGVuZHBvaW50OiAwLjAuMC4wOjE3NzdcbmV4cG9ydGVyczpcbiAgY2xpY2tob3VzZXRyYWNlczpcbiAgICBkYXRhc291cmNlOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X3RyYWNlc1xuICAgIGxvd19jYXJkaW5hbF9leGNlcHRpb25fZ3JvdXBpbmc6ICR7ZW52OkxPV19DQVJESU5BTF9FWENFUFRJT05fR1JPVVBJTkd9XG4gICAgdXNlX25ld19zY2hlbWE6IHRydWVcbiAgc2lnbm96Y2xpY2tob3VzZW1ldHJpY3M6XG4gICAgZHNuOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X21ldHJpY3NcbiAgY2xpY2tob3VzZWxvZ3NleHBvcnRlcjpcbiAgICBkc246IHRjcDovL2NsaWNraG91c2U6OTAwMC9zaWdub3pfbG9nc1xuICAgIHRpbWVvdXQ6IDEwc1xuICAgIHVzZV9uZXdfc2NoZW1hOiB0cnVlXG5zZXJ2aWNlOlxuICB0ZWxlbWV0cnk6XG4gICAgbG9nczpcbiAgICAgIGVuY29kaW5nOiBqc29uXG4gIGV4dGVuc2lvbnM6XG4gICAgLSBoZWFsdGhfY2hlY2tcbiAgICAtIHBwcm9mXG4gIHBpcGVsaW5lczpcbiAgICB0cmFjZXM6XG4gICAgICByZWNlaXZlcnM6IFtvdGxwXVxuICAgICAgcHJvY2Vzc29yczogW3NpZ25venNwYW5tZXRyaWNzL2RlbHRhLCBiYXRjaF1cbiAgICAgIGV4cG9ydGVyczogW2NsaWNraG91c2V0cmFjZXNdXG4gICAgbWV0cmljczpcbiAgICAgIHJlY2VpdmVyczogW290bHBdXG4gICAgICBwcm9jZXNzb3JzOiBbYmF0Y2hdXG4gICAgICBleHBvcnRlcnM6IFtzaWdub3pjbGlja2hvdXNlbWV0cmljc11cbiAgICBtZXRyaWNzL3Byb21ldGhldXM6XG4gICAgICByZWNlaXZlcnM6IFtwcm9tZXRoZXVzXVxuICAgICAgcHJvY2Vzc29yczogW2JhdGNoXVxuICAgICAgZXhwb3J0ZXJzOiBbc2lnbm96Y2xpY2tob3VzZW1ldHJpY3NdXG4gICAgbG9nczpcbiAgICAgIHJlY2VpdmVyczogW290bHBdXG4gICAgICBwcm9jZXNzb3JzOiBbYmF0Y2hdXG4gICAgICBleHBvcnRlcnM6IFtjbGlja2hvdXNlbG9nc2V4cG9ydGVyXSIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vb3RlbC1jb2xsZWN0b3Itb3BhbXAtY29uZmlnLnlhbWwKICAgICAgICB0YXJnZXQ6IC9ldGMvbWFuYWdlci1jb25maWcueWFtbAogICAgICAgIGNvbnRlbnQ6ICJzZXJ2ZXJfZW5kcG9pbnQ6IHdzOi8vc2lnbm96OjQzMjAvdjEvb3BhbXBcbiIKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX09URUxDT0xMRUNUT1JIVFRQXzQzMTgKICAgICAgLSAnT1RFTF9SRVNPVVJDRV9BVFRSSUJVVEVTPWhvc3QubmFtZT1zaWdub3otaG9zdCxvcy50eXBlPWxpbnV4JwogICAgICAtIExPV19DQVJESU5BTF9FWENFUFRJT05fR1JPVVBJTkc9ZmFsc2UKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiAnYmFzaCAtYyAiZXhlYyA2PD4gL2Rldi90Y3AvbG9jYWxob3N0LzEzMTMzIicKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCiAgc2NoZW1hLW1pZ3JhdG9yLXN5bmM6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3otc2NoZW1hLW1pZ3JhdG9yOnYwLjEyOS43JwogICAgY29tbWFuZDoKICAgICAgLSBzeW5jCiAgICAgIC0gJy0tZHNuPXRjcDovL2NsaWNraG91c2U6OTAwMCcKICAgICAgLSAnLS11cD0nCiAgICBkZXBlbmRzX29uOgogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICBzY2hlbWEtbWlncmF0b3ItYXN5bmM6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3otc2NoZW1hLW1pZ3JhdG9yOnYwLjEyOS43JwogICAgZGVwZW5kc19vbjoKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzY2hlbWEtbWlncmF0b3Itc3luYzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgcmVzdGFydDogb24tZmFpbHVyZQogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgICBsb2dnaW5nOgogICAgICBvcHRpb25zOgogICAgICAgIG1heC1zaXplOiA1MG0KICAgICAgICBtYXgtZmlsZTogJzMnCiAgICBjb21tYW5kOgogICAgICAtIGFzeW5jCiAgICAgIC0gJy0tZHNuPXRjcDovL2NsaWNraG91c2U6OTAwMCcKICAgICAgLSAnLS11cD0nCg==", + "compose": "c2VydmljZXM6CiAgaW5pdC1jbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI1LjUuNi1hbHBpbmUnCiAgICBjb21tYW5kOgogICAgICAtIGJhc2gKICAgICAgLSAnLWMnCiAgICAgIC0gInZlcnNpb249XCJ2MC4wLjFcIlxubm9kZV9vcz0kJCh1bmFtZSAtcyB8IHRyICdbOnVwcGVyOl0nICdbOmxvd2VyOl0nKVxubm9kZV9hcmNoPSQkKHVuYW1lIC1tIHwgc2VkIHMvYWFyY2g2NC9hcm02NC8gfCBzZWQgcy94ODZfNjQvYW1kNjQvKVxuZWNobyBcIkZldGNoaW5nIGhpc3RvZ3JhbS1iaW5hcnkgZm9yICQke25vZGVfb3N9LyQke25vZGVfYXJjaH1cIlxuY2QgL3RtcFxud2dldCAtTyBoaXN0b2dyYW0tcXVhbnRpbGUudGFyLmd6IFwiaHR0cHM6Ly9naXRodWIuY29tL1NpZ05vei9zaWdub3ovcmVsZWFzZXMvZG93bmxvYWQvaGlzdG9ncmFtLXF1YW50aWxlJTJGJCR7dmVyc2lvbn0vaGlzdG9ncmFtLXF1YW50aWxlXyQke25vZGVfb3N9XyQke25vZGVfYXJjaH0udGFyLmd6XCJcbnRhciAteHZ6ZiBoaXN0b2dyYW0tcXVhbnRpbGUudGFyLmd6XG5ta2RpciAtcCAvdmFyL2xpYi9jbGlja2hvdXNlL3VzZXJfc2NyaXB0cy9oaXN0b2dyYW1RdWFudGlsZVxubXYgaGlzdG9ncmFtLXF1YW50aWxlIC92YXIvbGliL2NsaWNraG91c2UvdXNlcl9zY3JpcHRzL2hpc3RvZ3JhbVF1YW50aWxlXG4iCiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICB6b29rZWVwZXI6CiAgICBpbWFnZTogJ3NpZ25vei96b29rZWVwZXI6My45LjMnCiAgICB1c2VyOiByb290CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ2N1cmwgLXMgLW0gMiBodHRwOi8vbG9jYWxob3N0OjgwODAvY29tbWFuZHMvcnVvayB8IGdyZXAgZXJyb3IgfCBncmVwIG51bGwnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgbG9nZ2luZzoKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtc2l6ZTogNTBtCiAgICAgICAgbWF4LWZpbGU6ICczJwogICAgdm9sdW1lczoKICAgICAgLSAnem9va2VlcGVyOi9iaXRuYW1pL3pvb2tlZXBlcicKICAgIGVudmlyb25tZW50OgogICAgICAtICdBTExPV19BTk9OWU1PVVNfTE9HSU49JHtaT09fQUxMT1dfQU5PTllNT1VTX0xPR0lOOi15ZXN9JwogICAgICAtICdaT09fQVVUT1BVUkdFX0lOVEVSVkFMPSR7Wk9PX0FVVE9QVVJHRV9JTlRFUlZBTDotMX0nCiAgICAgIC0gJ1pPT19FTkFCTEVfUFJPTUVUSEVVU19NRVRSSUNTPSR7Wk9PX0VOQUJMRV9QUk9NRVRIRVVTX01FVFJJQ1M6LXllc30nCiAgICAgIC0gJ1pPT19QUk9NRVRIRVVTX01FVFJJQ1NfUE9SVF9OVU1CRVI9JHtaT09fUFJPTUVUSEVVU19NRVRSSUNTX1BPUlRfTlVNQkVSOi05MTQxfScKICBjbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI1LjUuNi1hbHBpbmUnCiAgICB0dHk6IHRydWUKICAgIGRlcGVuZHNfb246CiAgICAgIGluaXQtY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgICB6b29rZWVwZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnLXEnCiAgICAgICAgLSAnMC4wLjAuMDo4MTIzL3BpbmcnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgdWxpbWl0czoKICAgICAgbnByb2M6IDY1NTM1CiAgICAgIG5vZmlsZToKICAgICAgICBzb2Z0OiAyNjIxNDQKICAgICAgICBoYXJkOiAyNjIxNDQKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGVudmlyb25tZW50OgogICAgICAtIENMSUNLSE9VU0VfU0tJUF9VU0VSX1NFVFVQPTEKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiB2b2x1bWUKICAgICAgICBzb3VyY2U6IGNsaWNraG91c2UKICAgICAgICB0YXJnZXQ6IC92YXIvbGliL2NsaWNraG91c2UvCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2UvY3VzdG9tLWZ1bmN0aW9uLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jdXN0b20tZnVuY3Rpb24ueG1sCiAgICAgICAgY29udGVudDogIjxmdW5jdGlvbnM+XG4gICAgPGZ1bmN0aW9uPlxuICAgICAgICA8dHlwZT5leGVjdXRhYmxlPC90eXBlPlxuICAgICAgICA8bmFtZT5oaXN0b2dyYW1RdWFudGlsZTwvbmFtZT5cbiAgICAgICAgPHJldHVybl90eXBlPkZsb2F0NjQ8L3JldHVybl90eXBlPlxuICAgICAgICA8YXJndW1lbnQ+XG4gICAgICAgICAgICA8dHlwZT5BcnJheShGbG9hdDY0KTwvdHlwZT5cbiAgICAgICAgICAgIDxuYW1lPmJ1Y2tldHM8L25hbWU+XG4gICAgICAgIDwvYXJndW1lbnQ+XG4gICAgICAgIDxhcmd1bWVudD5cbiAgICAgICAgICAgIDx0eXBlPkFycmF5KEZsb2F0NjQpPC90eXBlPlxuICAgICAgICAgICAgPG5hbWU+Y291bnRzPC9uYW1lPlxuICAgICAgICA8L2FyZ3VtZW50PlxuICAgICAgICA8YXJndW1lbnQ+XG4gICAgICAgICAgICA8dHlwZT5GbG9hdDY0PC90eXBlPlxuICAgICAgICAgICAgPG5hbWU+cXVhbnRpbGU8L25hbWU+XG4gICAgICAgIDwvYXJndW1lbnQ+XG4gICAgICAgIDxmb3JtYXQ+Q1NWPC9mb3JtYXQ+XG4gICAgICAgIDxjb21tYW5kPi4vaGlzdG9ncmFtUXVhbnRpbGU8L2NvbW1hbmQ+XG4gICAgPC9mdW5jdGlvbj5cbjwvZnVuY3Rpb25zPlxuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9jbGlja2hvdXNlL2NsdXN0ZXIueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy5kL2NsdXN0ZXIueG1sCiAgICAgICAgY29udGVudDogIjw/eG1sIHZlcnNpb249XCIxLjBcIj8+XG48Y2xpY2tob3VzZT5cbiAgICA8IS0tIFpvb0tlZXBlciBpcyB1c2VkIHRvIHN0b3JlIG1ldGFkYXRhIGFib3V0IHJlcGxpY2FzLCB3aGVuIHVzaW5nIFJlcGxpY2F0ZWQgdGFibGVzLlxuICAgICAgICBPcHRpb25hbC4gSWYgeW91IGRvbid0IHVzZSByZXBsaWNhdGVkIHRhYmxlcywgeW91IGNvdWxkIG9taXQgdGhhdC5cblxuICAgICAgICBTZWUgaHR0cHM6Ly9jbGlja2hvdXNlLmNvbS9kb2NzL2VuL2VuZ2luZXMvdGFibGUtZW5naW5lcy9tZXJnZXRyZWUtZmFtaWx5L3JlcGxpY2F0aW9uL1xuICAgICAgLS0+XG4gICAgPHpvb2tlZXBlcj5cbiAgICAgICAgPG5vZGUgaW5kZXg9XCIxXCI+XG4gICAgICAgICAgICA8aG9zdD56b29rZWVwZXI8L2hvc3Q+XG4gICAgICAgICAgICA8cG9ydD4yMTgxPC9wb3J0PlxuICAgICAgICA8L25vZGU+XG4gICAgPC96b29rZWVwZXI+XG5cbiAgICA8IS0tIENvbmZpZ3VyYXRpb24gb2YgY2x1c3RlcnMgdGhhdCBjb3VsZCBiZSB1c2VkIGluIERpc3RyaWJ1dGVkIHRhYmxlcy5cbiAgICAgICAgaHR0cHM6Ly9jbGlja2hvdXNlLmNvbS9kb2NzL2VuL29wZXJhdGlvbnMvdGFibGVfZW5naW5lcy9kaXN0cmlidXRlZC9cbiAgICAgIC0tPlxuICAgIDxyZW1vdGVfc2VydmVycz5cbiAgICAgICAgPGNsdXN0ZXI+XG4gICAgICAgICAgICA8IS0tIEludGVyLXNlcnZlciBwZXItY2x1c3RlciBzZWNyZXQgZm9yIERpc3RyaWJ1dGVkIHF1ZXJpZXNcbiAgICAgICAgICAgICAgICBkZWZhdWx0OiBubyBzZWNyZXQgKG5vIGF1dGhlbnRpY2F0aW9uIHdpbGwgYmUgcGVyZm9ybWVkKVxuXG4gICAgICAgICAgICAgICAgSWYgc2V0LCB0aGVuIERpc3RyaWJ1dGVkIHF1ZXJpZXMgd2lsbCBiZSB2YWxpZGF0ZWQgb24gc2hhcmRzLCBzbyBhdCBsZWFzdDpcbiAgICAgICAgICAgICAgICAtIHN1Y2ggY2x1c3RlciBzaG91bGQgZXhpc3Qgb24gdGhlIHNoYXJkLFxuICAgICAgICAgICAgICAgIC0gc3VjaCBjbHVzdGVyIHNob3VsZCBoYXZlIHRoZSBzYW1lIHNlY3JldC5cblxuICAgICAgICAgICAgICAgIEFuZCBhbHNvIChhbmQgd2hpY2ggaXMgbW9yZSBpbXBvcnRhbnQpLCB0aGUgaW5pdGlhbF91c2VyIHdpbGxcbiAgICAgICAgICAgICAgICBiZSB1c2VkIGFzIGN1cnJlbnQgdXNlciBmb3IgdGhlIHF1ZXJ5LlxuXG4gICAgICAgICAgICAgICAgUmlnaHQgbm93IHRoZSBwcm90b2NvbCBpcyBwcmV0dHkgc2ltcGxlIGFuZCBpdCBvbmx5IHRha2VzIGludG8gYWNjb3VudDpcbiAgICAgICAgICAgICAgICAtIGNsdXN0ZXIgbmFtZVxuICAgICAgICAgICAgICAgIC0gcXVlcnlcblxuICAgICAgICAgICAgICAgIEFsc28gaXQgd2lsbCBiZSBuaWNlIGlmIHRoZSBmb2xsb3dpbmcgd2lsbCBiZSBpbXBsZW1lbnRlZDpcbiAgICAgICAgICAgICAgICAtIHNvdXJjZSBob3N0bmFtZSAoc2VlIGludGVyc2VydmVyX2h0dHBfaG9zdCksIGJ1dCB0aGVuIGl0IHdpbGwgZGVwZW5kcyBmcm9tIEROUyxcbiAgICAgICAgICAgICAgICAgIGl0IGNhbiB1c2UgSVAgYWRkcmVzcyBpbnN0ZWFkLCBidXQgdGhlbiB0aGUgeW91IG5lZWQgdG8gZ2V0IGNvcnJlY3Qgb24gdGhlIGluaXRpYXRvciBub2RlLlxuICAgICAgICAgICAgICAgIC0gdGFyZ2V0IGhvc3RuYW1lIC8gaXAgYWRkcmVzcyAoc2FtZSBub3RlcyBhcyBmb3Igc291cmNlIGhvc3RuYW1lKVxuICAgICAgICAgICAgICAgIC0gdGltZS1iYXNlZCBzZWN1cml0eSB0b2tlbnNcbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPCEtLSA8c2VjcmV0Pjwvc2VjcmV0PiAtLT5cbiAgICAgICAgICAgIDxzaGFyZD5cbiAgICAgICAgICAgICAgICA8IS0tIE9wdGlvbmFsLiBXaGV0aGVyIHRvIHdyaXRlIGRhdGEgdG8ganVzdCBvbmUgb2YgdGhlIHJlcGxpY2FzLiBEZWZhdWx0OiBmYWxzZSAod3JpdGUgZGF0YSB0byBhbGwgcmVwbGljYXMpLiAtLT5cbiAgICAgICAgICAgICAgICA8IS0tIDxpbnRlcm5hbF9yZXBsaWNhdGlvbj5mYWxzZTwvaW50ZXJuYWxfcmVwbGljYXRpb24+IC0tPlxuICAgICAgICAgICAgICAgIDwhLS0gT3B0aW9uYWwuIFNoYXJkIHdlaWdodCB3aGVuIHdyaXRpbmcgZGF0YS4gRGVmYXVsdDogMS4gLS0+XG4gICAgICAgICAgICAgICAgPCEtLSA8d2VpZ2h0PjE8L3dlaWdodD4gLS0+XG4gICAgICAgICAgICAgICAgPHJlcGxpY2E+XG4gICAgICAgICAgICAgICAgICAgIDxob3N0PmNsaWNraG91c2U8L2hvc3Q+XG4gICAgICAgICAgICAgICAgICAgIDxwb3J0PjkwMDA8L3BvcnQ+XG4gICAgICAgICAgICAgICAgICAgIDwhLS0gT3B0aW9uYWwuIFByaW9yaXR5IG9mIHRoZSByZXBsaWNhIGZvciBsb2FkX2JhbGFuY2luZy4gRGVmYXVsdDogMSAobGVzcyB2YWx1ZSBoYXMgbW9yZSBwcmlvcml0eSkuIC0tPlxuICAgICAgICAgICAgICAgICAgICA8IS0tIDxwcmlvcml0eT4xPC9wcmlvcml0eT4gLS0+XG4gICAgICAgICAgICAgICAgPC9yZXBsaWNhPlxuICAgICAgICAgICAgPC9zaGFyZD5cbiAgICAgICAgICAgIDwhLS0gPHNoYXJkPlxuICAgICAgICAgICAgICAgIDxyZXBsaWNhPlxuICAgICAgICAgICAgICAgICAgICA8aG9zdD5jbGlja2hvdXNlLTI8L2hvc3Q+XG4gICAgICAgICAgICAgICAgICAgIDxwb3J0PjkwMDA8L3BvcnQ+XG4gICAgICAgICAgICAgICAgPC9yZXBsaWNhPlxuICAgICAgICAgICAgPC9zaGFyZD5cbiAgICAgICAgICAgIDxzaGFyZD5cbiAgICAgICAgICAgICAgICA8cmVwbGljYT5cbiAgICAgICAgICAgICAgICAgICAgPGhvc3Q+Y2xpY2tob3VzZS0zPC9ob3N0PlxuICAgICAgICAgICAgICAgICAgICA8cG9ydD45MDAwPC9wb3J0PlxuICAgICAgICAgICAgICAgIDwvcmVwbGljYT5cbiAgICAgICAgICAgIDwvc2hhcmQ+IC0tPlxuICAgICAgICA8L2NsdXN0ZXI+XG4gICAgPC9yZW1vdGVfc2VydmVycz5cbjwvY2xpY2tob3VzZT5cbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS91c2Vycy54bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvY2xpY2tob3VzZS1zZXJ2ZXIvdXNlcnMueG1sCiAgICAgICAgY29udGVudDogIjw/eG1sIHZlcnNpb249XCIxLjBcIj8+XG48Y2xpY2tob3VzZT5cbiAgICA8IS0tIFNlZSBhbHNvIHRoZSBmaWxlcyBpbiB1c2Vycy5kIGRpcmVjdG9yeSB3aGVyZSB0aGUgc2V0dGluZ3MgY2FuIGJlIG92ZXJyaWRkZW4uIC0tPlxuXG4gICAgPCEtLSBQcm9maWxlcyBvZiBzZXR0aW5ncy4gLS0+XG4gICAgPHByb2ZpbGVzPlxuICAgICAgICA8IS0tIERlZmF1bHQgc2V0dGluZ3MuIC0tPlxuICAgICAgICA8ZGVmYXVsdD5cbiAgICAgICAgICAgIDwhLS0gTWF4aW11bSBtZW1vcnkgdXNhZ2UgZm9yIHByb2Nlc3Npbmcgc2luZ2xlIHF1ZXJ5LCBpbiBieXRlcy4gLS0+XG4gICAgICAgICAgICA8bWF4X21lbW9yeV91c2FnZT4xMDAwMDAwMDAwMDwvbWF4X21lbW9yeV91c2FnZT5cblxuICAgICAgICAgICAgPCEtLSBIb3cgdG8gY2hvb3NlIGJldHdlZW4gcmVwbGljYXMgZHVyaW5nIGRpc3RyaWJ1dGVkIHF1ZXJ5IHByb2Nlc3NpbmcuXG4gICAgICAgICAgICAgICAgcmFuZG9tIC0gY2hvb3NlIHJhbmRvbSByZXBsaWNhIGZyb20gc2V0IG9mIHJlcGxpY2FzIHdpdGggbWluaW11bSBudW1iZXIgb2YgZXJyb3JzXG4gICAgICAgICAgICAgICAgbmVhcmVzdF9ob3N0bmFtZSAtIGZyb20gc2V0IG9mIHJlcGxpY2FzIHdpdGggbWluaW11bSBudW1iZXIgb2YgZXJyb3JzLCBjaG9vc2UgcmVwbGljYVxuICAgICAgICAgICAgICAgICAgd2l0aCBtaW5pbXVtIG51bWJlciBvZiBkaWZmZXJlbnQgc3ltYm9scyBiZXR3ZWVuIHJlcGxpY2EncyBob3N0bmFtZSBhbmQgbG9jYWwgaG9zdG5hbWVcbiAgICAgICAgICAgICAgICAgIChIYW1taW5nIGRpc3RhbmNlKS5cbiAgICAgICAgICAgICAgICBpbl9vcmRlciAtIGZpcnN0IGxpdmUgcmVwbGljYSBpcyBjaG9zZW4gaW4gc3BlY2lmaWVkIG9yZGVyLlxuICAgICAgICAgICAgICAgIGZpcnN0X29yX3JhbmRvbSAtIGlmIGZpcnN0IHJlcGxpY2Egb25lIGhhcyBoaWdoZXIgbnVtYmVyIG9mIGVycm9ycywgcGljayBhIHJhbmRvbSBvbmUgZnJvbSByZXBsaWNhcyB3aXRoIG1pbmltdW0gbnVtYmVyIG9mIGVycm9ycy5cbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPGxvYWRfYmFsYW5jaW5nPnJhbmRvbTwvbG9hZF9iYWxhbmNpbmc+XG4gICAgICAgIDwvZGVmYXVsdD5cblxuICAgICAgICA8IS0tIFByb2ZpbGUgdGhhdCBhbGxvd3Mgb25seSByZWFkIHF1ZXJpZXMuIC0tPlxuICAgICAgICA8cmVhZG9ubHk+XG4gICAgICAgICAgICA8cmVhZG9ubHk+MTwvcmVhZG9ubHk+XG4gICAgICAgIDwvcmVhZG9ubHk+XG4gICAgPC9wcm9maWxlcz5cblxuICAgIDwhLS0gVXNlcnMgYW5kIEFDTC4gLS0+XG4gICAgPHVzZXJzPlxuICAgICAgICA8IS0tIElmIHVzZXIgbmFtZSB3YXMgbm90IHNwZWNpZmllZCwgJ2RlZmF1bHQnIHVzZXIgaXMgdXNlZC4gLS0+XG4gICAgICAgIDxkZWZhdWx0PlxuICAgICAgICAgICAgPCEtLSBTZWUgYWxzbyB0aGUgZmlsZXMgaW4gdXNlcnMuZCBkaXJlY3Rvcnkgd2hlcmUgdGhlIHBhc3N3b3JkIGNhbiBiZSBvdmVycmlkZGVuLlxuXG4gICAgICAgICAgICAgICAgUGFzc3dvcmQgY291bGQgYmUgc3BlY2lmaWVkIGluIHBsYWludGV4dCBvciBpbiBTSEEyNTYgKGluIGhleCBmb3JtYXQpLlxuXG4gICAgICAgICAgICAgICAgSWYgeW91IHdhbnQgdG8gc3BlY2lmeSBwYXNzd29yZCBpbiBwbGFpbnRleHQgKG5vdCByZWNvbW1lbmRlZCksIHBsYWNlIGl0IGluICdwYXNzd29yZCcgZWxlbWVudC5cbiAgICAgICAgICAgICAgICBFeGFtcGxlOiA8cGFzc3dvcmQ+cXdlcnR5PC9wYXNzd29yZD4uXG4gICAgICAgICAgICAgICAgUGFzc3dvcmQgY291bGQgYmUgZW1wdHkuXG5cbiAgICAgICAgICAgICAgICBJZiB5b3Ugd2FudCB0byBzcGVjaWZ5IFNIQTI1NiwgcGxhY2UgaXQgaW4gJ3Bhc3N3b3JkX3NoYTI1Nl9oZXgnIGVsZW1lbnQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPHBhc3N3b3JkX3NoYTI1Nl9oZXg+NjVlODRiZTMzNTMyZmI3ODRjNDgxMjk2NzVmOWVmZjNhNjgyYjI3MTY4YzBlYTc0NGIyY2Y1OGVlMDIzMzdjNTwvcGFzc3dvcmRfc2hhMjU2X2hleD5cbiAgICAgICAgICAgICAgICBSZXN0cmljdGlvbnMgb2YgU0hBMjU2OiBpbXBvc3NpYmlsaXR5IHRvIGNvbm5lY3QgdG8gQ2xpY2tIb3VzZSB1c2luZyBNeVNRTCBKUyBjbGllbnQgKGFzIG9mIEp1bHkgMjAxOSkuXG5cbiAgICAgICAgICAgICAgICBJZiB5b3Ugd2FudCB0byBzcGVjaWZ5IGRvdWJsZSBTSEExLCBwbGFjZSBpdCBpbiAncGFzc3dvcmRfZG91YmxlX3NoYTFfaGV4JyBlbGVtZW50LlxuICAgICAgICAgICAgICAgIEV4YW1wbGU6IDxwYXNzd29yZF9kb3VibGVfc2hhMV9oZXg+ZTM5NTc5NmQ2NTQ2YjFiNjVkYjlkNjY1Y2Q0M2YwZTg1OGRkNDMwMzwvcGFzc3dvcmRfZG91YmxlX3NoYTFfaGV4PlxuXG4gICAgICAgICAgICAgICAgSWYgeW91IHdhbnQgdG8gc3BlY2lmeSBhIHByZXZpb3VzbHkgZGVmaW5lZCBMREFQIHNlcnZlciAoc2VlICdsZGFwX3NlcnZlcnMnIGluIHRoZSBtYWluIGNvbmZpZykgZm9yIGF1dGhlbnRpY2F0aW9uLFxuICAgICAgICAgICAgICAgICAgcGxhY2UgaXRzIG5hbWUgaW4gJ3NlcnZlcicgZWxlbWVudCBpbnNpZGUgJ2xkYXAnIGVsZW1lbnQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGxkYXA+PHNlcnZlcj5teV9sZGFwX3NlcnZlcjwvc2VydmVyPjwvbGRhcD5cblxuICAgICAgICAgICAgICAgIElmIHlvdSB3YW50IHRvIGF1dGhlbnRpY2F0ZSB0aGUgdXNlciB2aWEgS2VyYmVyb3MgKGFzc3VtaW5nIEtlcmJlcm9zIGlzIGVuYWJsZWQsIHNlZSAna2VyYmVyb3MnIGluIHRoZSBtYWluIGNvbmZpZyksXG4gICAgICAgICAgICAgICAgICBwbGFjZSAna2VyYmVyb3MnIGVsZW1lbnQgaW5zdGVhZCBvZiAncGFzc3dvcmQnIChhbmQgc2ltaWxhcikgZWxlbWVudHMuXG4gICAgICAgICAgICAgICAgVGhlIG5hbWUgcGFydCBvZiB0aGUgY2Fub25pY2FsIHByaW5jaXBhbCBuYW1lIG9mIHRoZSBpbml0aWF0b3IgbXVzdCBtYXRjaCB0aGUgdXNlciBuYW1lIGZvciBhdXRoZW50aWNhdGlvbiB0byBzdWNjZWVkLlxuICAgICAgICAgICAgICAgIFlvdSBjYW4gYWxzbyBwbGFjZSAncmVhbG0nIGVsZW1lbnQgaW5zaWRlICdrZXJiZXJvcycgZWxlbWVudCB0byBmdXJ0aGVyIHJlc3RyaWN0IGF1dGhlbnRpY2F0aW9uIHRvIG9ubHkgdGhvc2UgcmVxdWVzdHNcbiAgICAgICAgICAgICAgICAgIHdob3NlIGluaXRpYXRvcidzIHJlYWxtIG1hdGNoZXMgaXQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGtlcmJlcm9zIC8+XG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGtlcmJlcm9zPjxyZWFsbT5FWEFNUExFLkNPTTwvcmVhbG0+PC9rZXJiZXJvcz5cblxuICAgICAgICAgICAgICAgIEhvdyB0byBnZW5lcmF0ZSBkZWNlbnQgcGFzc3dvcmQ6XG4gICAgICAgICAgICAgICAgRXhlY3V0ZTogUEFTU1dPUkQ9JChiYXNlNjQgPCAvZGV2L3VyYW5kb20gfCBoZWFkIC1jOCk7IGVjaG8gXCIkUEFTU1dPUkRcIjsgZWNobyAtbiBcIiRQQVNTV09SRFwiIHwgc2hhMjU2c3VtIHwgdHIgLWQgJy0nXG4gICAgICAgICAgICAgICAgSW4gZmlyc3QgbGluZSB3aWxsIGJlIHBhc3N3b3JkIGFuZCBpbiBzZWNvbmQgLSBjb3JyZXNwb25kaW5nIFNIQTI1Ni5cblxuICAgICAgICAgICAgICAgIEhvdyB0byBnZW5lcmF0ZSBkb3VibGUgU0hBMTpcbiAgICAgICAgICAgICAgICBFeGVjdXRlOiBQQVNTV09SRD0kKGJhc2U2NCA8IC9kZXYvdXJhbmRvbSB8IGhlYWQgLWM4KTsgZWNobyBcIiRQQVNTV09SRFwiOyBlY2hvIC1uIFwiJFBBU1NXT1JEXCIgfCBzaGExc3VtIHwgdHIgLWQgJy0nIHwgeHhkIC1yIC1wIHwgc2hhMXN1bSB8IHRyIC1kICctJ1xuICAgICAgICAgICAgICAgIEluIGZpcnN0IGxpbmUgd2lsbCBiZSBwYXNzd29yZCBhbmQgaW4gc2Vjb25kIC0gY29ycmVzcG9uZGluZyBkb3VibGUgU0hBMS5cbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPHBhc3N3b3JkPjwvcGFzc3dvcmQ+XG5cbiAgICAgICAgICAgIDwhLS0gTGlzdCBvZiBuZXR3b3JrcyB3aXRoIG9wZW4gYWNjZXNzLlxuXG4gICAgICAgICAgICAgICAgVG8gb3BlbiBhY2Nlc3MgZnJvbSBldmVyeXdoZXJlLCBzcGVjaWZ5OlxuICAgICAgICAgICAgICAgICAgICA8aXA+OjovMDwvaXA+XG5cbiAgICAgICAgICAgICAgICBUbyBvcGVuIGFjY2VzcyBvbmx5IGZyb20gbG9jYWxob3N0LCBzcGVjaWZ5OlxuICAgICAgICAgICAgICAgICAgICA8aXA+OjoxPC9pcD5cbiAgICAgICAgICAgICAgICAgICAgPGlwPjEyNy4wLjAuMTwvaXA+XG5cbiAgICAgICAgICAgICAgICBFYWNoIGVsZW1lbnQgb2YgbGlzdCBoYXMgb25lIG9mIHRoZSBmb2xsb3dpbmcgZm9ybXM6XG4gICAgICAgICAgICAgICAgPGlwPiBJUC1hZGRyZXNzIG9yIG5ldHdvcmsgbWFzay4gRXhhbXBsZXM6IDIxMy4xODAuMjA0LjMgb3IgMTAuMC4wLjEvOCBvciAxMC4wLjAuMS8yNTUuMjU1LjI1NS4wXG4gICAgICAgICAgICAgICAgICAgIDJhMDI6NmI4OjozIG9yIDJhMDI6NmI4OjozLzY0IG9yIDJhMDI6NmI4OjozL2ZmZmY6ZmZmZjpmZmZmOmZmZmY6Oi5cbiAgICAgICAgICAgICAgICA8aG9zdD4gSG9zdG5hbWUuIEV4YW1wbGU6IHNlcnZlcjAxLmNsaWNraG91c2UuY29tLlxuICAgICAgICAgICAgICAgICAgICBUbyBjaGVjayBhY2Nlc3MsIEROUyBxdWVyeSBpcyBwZXJmb3JtZWQsIGFuZCBhbGwgcmVjZWl2ZWQgYWRkcmVzc2VzIGNvbXBhcmVkIHRvIHBlZXIgYWRkcmVzcy5cbiAgICAgICAgICAgICAgICA8aG9zdF9yZWdleHA+IFJlZ3VsYXIgZXhwcmVzc2lvbiBmb3IgaG9zdCBuYW1lcy4gRXhhbXBsZSwgXnNlcnZlclxcZFxcZC1cXGRcXGQtXFxkXFwuY2xpY2tob3VzZVxcLmNvbSRcbiAgICAgICAgICAgICAgICAgICAgVG8gY2hlY2sgYWNjZXNzLCBETlMgUFRSIHF1ZXJ5IGlzIHBlcmZvcm1lZCBmb3IgcGVlciBhZGRyZXNzIGFuZCB0aGVuIHJlZ2V4cCBpcyBhcHBsaWVkLlxuICAgICAgICAgICAgICAgICAgICBUaGVuLCBmb3IgcmVzdWx0IG9mIFBUUiBxdWVyeSwgYW5vdGhlciBETlMgcXVlcnkgaXMgcGVyZm9ybWVkIGFuZCBhbGwgcmVjZWl2ZWQgYWRkcmVzc2VzIGNvbXBhcmVkIHRvIHBlZXIgYWRkcmVzcy5cbiAgICAgICAgICAgICAgICAgICAgU3Ryb25nbHkgcmVjb21tZW5kZWQgdGhhdCByZWdleHAgaXMgZW5kcyB3aXRoICRcbiAgICAgICAgICAgICAgICBBbGwgcmVzdWx0cyBvZiBETlMgcmVxdWVzdHMgYXJlIGNhY2hlZCB0aWxsIHNlcnZlciByZXN0YXJ0LlxuICAgICAgICAgICAgLS0+XG4gICAgICAgICAgICA8bmV0d29ya3M+XG4gICAgICAgICAgICAgICAgPGlwPjo6LzA8L2lwPlxuICAgICAgICAgICAgPC9uZXR3b3Jrcz5cblxuICAgICAgICAgICAgPCEtLSBTZXR0aW5ncyBwcm9maWxlIGZvciB1c2VyLiAtLT5cbiAgICAgICAgICAgIDxwcm9maWxlPmRlZmF1bHQ8L3Byb2ZpbGU+XG5cbiAgICAgICAgICAgIDwhLS0gUXVvdGEgZm9yIHVzZXIuIC0tPlxuICAgICAgICAgICAgPHF1b3RhPmRlZmF1bHQ8L3F1b3RhPlxuXG4gICAgICAgICAgICA8IS0tIFVzZXIgY2FuIGNyZWF0ZSBvdGhlciB1c2VycyBhbmQgZ3JhbnQgcmlnaHRzIHRvIHRoZW0uIC0tPlxuICAgICAgICAgICAgPCEtLSA8YWNjZXNzX21hbmFnZW1lbnQ+MTwvYWNjZXNzX21hbmFnZW1lbnQ+IC0tPlxuICAgICAgICA8L2RlZmF1bHQ+XG4gICAgPC91c2Vycz5cblxuICAgIDwhLS0gUXVvdGFzLiAtLT5cbiAgICA8cXVvdGFzPlxuICAgICAgICA8IS0tIE5hbWUgb2YgcXVvdGEuIC0tPlxuICAgICAgICA8ZGVmYXVsdD5cbiAgICAgICAgICAgIDwhLS0gTGltaXRzIGZvciB0aW1lIGludGVydmFsLiBZb3UgY291bGQgc3BlY2lmeSBtYW55IGludGVydmFscyB3aXRoIGRpZmZlcmVudCBsaW1pdHMuIC0tPlxuICAgICAgICAgICAgPGludGVydmFsPlxuICAgICAgICAgICAgICAgIDwhLS0gTGVuZ3RoIG9mIGludGVydmFsLiAtLT5cbiAgICAgICAgICAgICAgICA8ZHVyYXRpb24+MzYwMDwvZHVyYXRpb24+XG5cbiAgICAgICAgICAgICAgICA8IS0tIE5vIGxpbWl0cy4gSnVzdCBjYWxjdWxhdGUgcmVzb3VyY2UgdXNhZ2UgZm9yIHRpbWUgaW50ZXJ2YWwuIC0tPlxuICAgICAgICAgICAgICAgIDxxdWVyaWVzPjA8L3F1ZXJpZXM+XG4gICAgICAgICAgICAgICAgPGVycm9ycz4wPC9lcnJvcnM+XG4gICAgICAgICAgICAgICAgPHJlc3VsdF9yb3dzPjA8L3Jlc3VsdF9yb3dzPlxuICAgICAgICAgICAgICAgIDxyZWFkX3Jvd3M+MDwvcmVhZF9yb3dzPlxuICAgICAgICAgICAgICAgIDxleGVjdXRpb25fdGltZT4wPC9leGVjdXRpb25fdGltZT5cbiAgICAgICAgICAgIDwvaW50ZXJ2YWw+XG4gICAgICAgIDwvZGVmYXVsdD5cbiAgICA8L3F1b3Rhcz5cbjwvY2xpY2tob3VzZT5cbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS9jb25maWcueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy54bWwKICAgICAgICBjb250ZW50OiAiPD94bWwgdmVyc2lvbj1cIjEuMFwiPz5cbjxjbGlja2hvdXNlPlxuICA8bWF4X2Nvbm5lY3Rpb25zPjQwOTY8L21heF9jb25uZWN0aW9ucz5cbiAgPGtlZXBfYWxpdmVfdGltZW91dD4zPC9rZWVwX2FsaXZlX3RpbWVvdXQ+XG4gIDxtYXhfY29uY3VycmVudF9xdWVyaWVzPjEwMDwvbWF4X2NvbmN1cnJlbnRfcXVlcmllcz5cbiAgPG1hcmtfY2FjaGVfc2l6ZT41MzY4NzA5MTIwPC9tYXJrX2NhY2hlX3NpemU+XG4gIDxtbWFwX2NhY2hlX3NpemU+MTAwMDwvbW1hcF9jYWNoZV9zaXplPlxuICA8Y29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9zaXplPjEzNDIxNzcyODwvY29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9zaXplPlxuICA8Y29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9lbGVtZW50c19zaXplPjEwMDAwPC9jb21waWxlZF9leHByZXNzaW9uX2NhY2hlX2VsZW1lbnRzX3NpemU+XG4gIDxjdXN0b21fc2V0dGluZ3NfcHJlZml4ZXM+PC9jdXN0b21fc2V0dGluZ3NfcHJlZml4ZXM+XG4gIDxkaWN0aW9uYXJpZXNfY29uZmlnPipfZGljdGlvbmFyeS54bWw8L2RpY3Rpb25hcmllc19jb25maWc+XG4gIDx1c2VyX2RlZmluZWRfZXhlY3V0YWJsZV9mdW5jdGlvbnNfY29uZmlnPipmdW5jdGlvbi54bWw8L3VzZXJfZGVmaW5lZF9leGVjdXRhYmxlX2Z1bmN0aW9uc19jb25maWc+XG4gIDx1c2VyX3NjcmlwdHNfcGF0aD4vdmFyL2xpYi9jbGlja2hvdXNlL3VzZXJfc2NyaXB0cy88L3VzZXJfc2NyaXB0c19wYXRoPlxuICA8aHR0cF9wb3J0PjgxMjM8L2h0dHBfcG9ydD5cbiAgPHRjcF9wb3J0PjkwMDA8L3RjcF9wb3J0PlxuICA8bXlzcWxfcG9ydD45MDA0PC9teXNxbF9wb3J0PlxuICA8cG9zdGdyZXNxbF9wb3J0PjkwMDU8L3Bvc3RncmVzcWxfcG9ydD5cbiAgPGludGVyc2VydmVyX2h0dHBfcG9ydD45MDA5PC9pbnRlcnNlcnZlcl9odHRwX3BvcnQ+XG4gIDxsb2dnZXI+XG4gICAgPGxldmVsPmluZm9ybWF0aW9uPC9sZXZlbD5cbiAgICA8Zm9ybWF0dGluZz5cbiAgICAgIDx0eXBlPmpzb248L3R5cGU+XG4gICAgPC9mb3JtYXR0aW5nPlxuICA8L2xvZ2dlcj5cbiAgPG1hY3Jvcz5cbiAgICA8c2hhcmQ+MDE8L3NoYXJkPlxuICAgIDxyZXBsaWNhPmV4YW1wbGUwMS0wMS0xPC9yZXBsaWNhPlxuICA8L21hY3Jvcz5cbiAgPHByb21ldGhldXM+XG4gICAgPGVuZHBvaW50Pi9tZXRyaWNzPC9lbmRwb2ludD5cbiAgICA8cG9ydD45MzYzPC9wb3J0PlxuICAgIDxtZXRyaWNzPnRydWU8L21ldHJpY3M+XG4gICAgPGV2ZW50cz50cnVlPC9ldmVudHM+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNzPnRydWU8L2FzeW5jaHJvbm91c19tZXRyaWNzPlxuICAgIDxzdGF0dXNfaW5mbz50cnVlPC9zdGF0dXNfaW5mbz5cbiAgPC9wcm9tZXRoZXVzPlxuICA8b3BlbnRlbGVtZXRyeV9zcGFuX2xvZz5cbiAgICA8ZW5naW5lPmVuZ2luZSBNZXJnZVRyZWVcbiAgICAgICAgICAgIHBhcnRpdGlvbiBieSB0b1lZWVlNTShmaW5pc2hfZGF0ZSlcbiAgICAgICAgICAgIG9yZGVyIGJ5IChmaW5pc2hfZGF0ZSwgZmluaXNoX3RpbWVfdXMsIHRyYWNlX2lkKTwvZW5naW5lPlxuICA8L29wZW50ZWxlbWV0cnlfc3Bhbl9sb2c+XG4gIDxxdWVyeV9tYXNraW5nX3J1bGVzPlxuICAgIDxydWxlPlxuICAgICAgPG5hbWU+aGlkZSBlbmNyeXB0L2RlY3J5cHQgYXJndW1lbnRzPC9uYW1lPlxuICAgICAgPHJlZ2V4cD4oKD86YWVzXyk/KD86ZW5jcnlwdHxkZWNyeXB0KSg/Ol9teXNxbCk/KVxccypcXChcXHMqKD86Jyg/OlxcXFwnfC4pKyd8Lio/KVxccypcXCk8L3JlZ2V4cD5cbiAgICAgIDxyZXBsYWNlPlxcMSg/Pz8pPC9yZXBsYWNlPlxuICAgIDwvcnVsZT5cbiAgPC9xdWVyeV9tYXNraW5nX3J1bGVzPlxuICA8c2VuZF9jcmFzaF9yZXBvcnRzPlxuICAgIDxlbmFibGVkPmZhbHNlPC9lbmFibGVkPlxuICAgIDxhbm9ueW1pemU+ZmFsc2U8L2Fub255bWl6ZT5cbiAgICA8ZW5kcG9pbnQ+aHR0cHM6Ly82ZjMzMDM0Y2ZlNjg0ZGQ3YTNhYjk4NzVlNTdiMWM4ZEBvMzg4ODcwLmluZ2VzdC5zZW50cnkuaW8vNTIyNjI3NzwvZW5kcG9pbnQ+XG4gIDwvc2VuZF9jcmFzaF9yZXBvcnRzPlxuICA8bWVyZ2VfdHJlZV9tZXRhZGF0YV9jYWNoZT5cbiAgICA8bHJ1X2NhY2hlX3NpemU+MjY4NDM1NDU2PC9scnVfY2FjaGVfc2l6ZT5cbiAgICA8Y29udGludWVfaWZfY29ycnVwdGVkPnRydWU8L2NvbnRpbnVlX2lmX2NvcnJ1cHRlZD5cbiAgPC9tZXJnZV90cmVlX21ldGFkYXRhX2NhY2hlPlxuICA8dXNlcl9kaXJlY3Rvcmllcz5cbiAgICA8dXNlcnNfeG1sPlxuICAgICAgICA8IS0tIFBhdGggdG8gY29uZmlndXJhdGlvbiBmaWxlIHdpdGggcHJlZGVmaW5lZCB1c2Vycy4gLS0+XG4gICAgICAgIDxwYXRoPnVzZXJzLnhtbDwvcGF0aD5cbiAgICA8L3VzZXJzX3htbD5cbiAgICA8bG9jYWxfZGlyZWN0b3J5PlxuICAgICAgICA8IS0tIFBhdGggdG8gZm9sZGVyIHdoZXJlIHVzZXJzIGNyZWF0ZWQgYnkgU1FMIGNvbW1hbmRzIGFyZSBzdG9yZWQuIC0tPlxuICAgICAgICA8cGF0aD4vdmFyL2xpYi9jbGlja2hvdXNlL2FjY2Vzcy88L3BhdGg+XG4gICAgPC9sb2NhbF9kaXJlY3Rvcnk+XG4gIDwvdXNlcl9kaXJlY3Rvcmllcz5cbiAgPGRlZmF1bHRfcHJvZmlsZT5kZWZhdWx0PC9kZWZhdWx0X3Byb2ZpbGU+XG4gICAgPGRpc3RyaWJ1dGVkX2RkbD5cbiAgICAgICAgPCEtLSBQYXRoIGluIFpvb0tlZXBlciB0byBxdWV1ZSB3aXRoIERETCBxdWVyaWVzIC0tPlxuICAgICAgICA8cGF0aD4vY2xpY2tob3VzZS90YXNrX3F1ZXVlL2RkbDwvcGF0aD5cbiAgICA8L2Rpc3RyaWJ1dGVkX2RkbD5cbjwvY2xpY2tob3VzZT5cbiIKICBzaWdub3o6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3o6djAuOTcuMScKICAgIGRlcGVuZHNfb246CiAgICAgIGNsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc2NoZW1hLW1pZ3JhdG9yLXN5bmM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2NvbXBsZXRlZF9zdWNjZXNzZnVsbHkKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGNvbW1hbmQ6CiAgICAgIC0gJy0tY29uZmlnPS9yb290L2NvbmZpZy9wcm9tZXRoZXVzLnltbCcKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3Byb21ldGhldXMueW1sCiAgICAgICAgdGFyZ2V0OiAvcm9vdC9jb25maWcvcHJvbWV0aGV1cy55bWwKICAgICAgICBjb250ZW50OiAiIyBteSBnbG9iYWwgY29uZmlnXG5nbG9iYWw6XG4gIHNjcmFwZV9pbnRlcnZhbDogICAgIDVzICMgU2V0IHRoZSBzY3JhcGUgaW50ZXJ2YWwgdG8gZXZlcnkgMTUgc2Vjb25kcy4gRGVmYXVsdCBpcyBldmVyeSAxIG1pbnV0ZS5cbiAgZXZhbHVhdGlvbl9pbnRlcnZhbDogMTVzICMgRXZhbHVhdGUgcnVsZXMgZXZlcnkgMTUgc2Vjb25kcy4gVGhlIGRlZmF1bHQgaXMgZXZlcnkgMSBtaW51dGUuXG4gICMgc2NyYXBlX3RpbWVvdXQgaXMgc2V0IHRvIHRoZSBnbG9iYWwgZGVmYXVsdCAoMTBzKS5cblxuIyBBbGVydG1hbmFnZXIgY29uZmlndXJhdGlvblxuYWxlcnRpbmc6XG4gIGFsZXJ0bWFuYWdlcnM6XG4gIC0gc3RhdGljX2NvbmZpZ3M6XG4gICAgLSB0YXJnZXRzOlxuICAgICAgLSBhbGVydG1hbmFnZXI6OTA5M1xuXG4jIExvYWQgcnVsZXMgb25jZSBhbmQgcGVyaW9kaWNhbGx5IGV2YWx1YXRlIHRoZW0gYWNjb3JkaW5nIHRvIHRoZSBnbG9iYWwgJ2V2YWx1YXRpb25faW50ZXJ2YWwnLlxucnVsZV9maWxlczogW11cbiAgIyAtIFwiZmlyc3RfcnVsZXMueW1sXCJcbiAgIyAtIFwic2Vjb25kX3J1bGVzLnltbFwiXG4gICMgLSAnYWxlcnRzLnltbCdcblxuIyBBIHNjcmFwZSBjb25maWd1cmF0aW9uIGNvbnRhaW5pbmcgZXhhY3RseSBvbmUgZW5kcG9pbnQgdG8gc2NyYXBlOlxuIyBIZXJlIGl0J3MgUHJvbWV0aGV1cyBpdHNlbGYuXG5zY3JhcGVfY29uZmlnczogW11cblxucmVtb3RlX3JlYWQ6XG4gIC0gdXJsOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X21ldHJpY3NcbiIKICAgICAgLQogICAgICAgIHR5cGU6IHZvbHVtZQogICAgICAgIHNvdXJjZTogc3FsaXRlCiAgICAgICAgdGFyZ2V0OiAvdmFyL2xpYi9zaWdub3ovCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9TSUdOT1pfODA4MAogICAgICAtICdTSUdOT1pfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUkVBTEJBU0U2NF9KV1RTRUNSRVR9JwogICAgICAtICdTSUdOT1pfVEVMRU1FVFJZU1RPUkVfQ0xJQ0tIT1VTRV9EU049dGNwOi8vY2xpY2tob3VzZTo5MDAwJwogICAgICAtIFNJR05PWl9TUUxTVE9SRV9TUUxJVEVfUEFUSD0vdmFyL2xpYi9zaWdub3ovc2lnbm96LmRiCiAgICAgIC0gREFTSEJPQVJEU19QQVRIPS9yb290L2NvbmZpZy9kYXNoYm9hcmRzCiAgICAgIC0gU1RPUkFHRT1jbGlja2hvdXNlCiAgICAgIC0gR09ERUJVRz1uZXRkbnM9Z28KICAgICAgLSBERVBMT1lNRU5UX1RZUEU9ZG9ja2VyLXN0YW5kYWxvbmUtYW1kCiAgICAgIC0gJ1NJR05PWl9TVEFUU1JFUE9SVEVSX0VOQUJMRUQ9JHtTSUdOT1pfU1RBVFNSRVBPUlRFUl9FTkFCTEVEOi10cnVlfScKICAgICAgLSAnU0lHTk9aX0VNQUlMSU5HX0VOQUJMRUQ9JHtTSUdOT1pfRU1BSUxJTkdfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdTSUdOT1pfRU1BSUxJTkdfU01UUF9BRERSRVNTPSR7U0lHTk9aX0VNQUlMSU5HX1NNVFBfQUREUkVTU30nCiAgICAgIC0gJ1NJR05PWl9FTUFJTElOR19TTVRQX0ZST009JHtTSUdOT1pfRU1BSUxJTkdfU01UUF9GUk9NfScKICAgICAgLSAnU0lHTk9aX0VNQUlMSU5HX1NNVFBfQVVUSF9VU0VSTkFNRT0ke1NJR05PWl9FTUFJTElOR19TTVRQX0FVVEhfVVNFUk5BTUV9JwogICAgICAtICdTSUdOT1pfRU1BSUxJTkdfU01UUF9BVVRIX1BBU1NXT1JEPSR7U0lHTk9aX0VNQUlMSU5HX1NNVFBfQVVUSF9QQVNTV09SRH0nCiAgICAgIC0gU0lHTk9aX0FMRVJUTUFOQUdFUl9QUk9WSURFUj1zaWdub3oKICAgICAgLSAnU0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0FVVEhfX1BBU1NXT1JEPSR7U0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0FVVEhfX1BBU1NXT1JEfScKICAgICAgLSAnU0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0FVVEhfX1VTRVJOQU1FPSR7U0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0FVVEhfX1VTRVJOQU1FfScKICAgICAgLSAnU0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0ZST009JHtTSUdOT1pfQUxFUlRNQU5BR0VSX1NJR05PWl9HTE9CQUxfU01UUF9fRlJPTX0nCiAgICAgIC0gJ1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19TTUFSVEhPU1Q9JHtTSUdOT1pfQUxFUlRNQU5BR0VSX1NJR05PWl9HTE9CQUxfU01UUF9fU01BUlRIT1NUfScKICAgICAgLSBET1RfTUVUUklDU19FTkFCTEVEPXRydWUKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnLXEnCiAgICAgICAgLSAnbG9jYWxob3N0OjgwODAvYXBpL3YxL2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCiAgb3RlbC1jb2xsZWN0b3I6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3otb3RlbC1jb2xsZWN0b3I6djAuMTI5LjcnCiAgICBkZXBlbmRzX29uOgogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHNjaGVtYS1taWdyYXRvci1zeW5jOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9jb21wbGV0ZWRfc3VjY2Vzc2Z1bGx5CiAgICAgIHNpZ25vejoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgbG9nZ2luZzoKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtc2l6ZTogNTBtCiAgICAgICAgbWF4LWZpbGU6ICczJwogICAgY29tbWFuZDoKICAgICAgLSAnLS1jb25maWc9L2V0Yy9vdGVsLWNvbGxlY3Rvci1jb25maWcueWFtbCcKICAgICAgLSAnLS1tYW5hZ2VyLWNvbmZpZz0vZXRjL21hbmFnZXItY29uZmlnLnlhbWwnCiAgICAgIC0gJy0tY29weS1wYXRoPS92YXIvdG1wL2NvbGxlY3Rvci1jb25maWcueWFtbCcKICAgICAgLSAnLS1mZWF0dXJlLWdhdGVzPS1wa2cudHJhbnNsYXRvci5wcm9tZXRoZXVzLk5vcm1hbGl6ZU5hbWUnCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9vdGVsLWNvbGxlY3Rvci1jb25maWcueWFtbAogICAgICAgIHRhcmdldDogL2V0Yy9vdGVsLWNvbGxlY3Rvci1jb25maWcueWFtbAogICAgICAgIGNvbnRlbnQ6ICJyZWNlaXZlcnM6XG4gIG90bHA6XG4gICAgcHJvdG9jb2xzOlxuICAgICAgZ3JwYzpcbiAgICAgICAgZW5kcG9pbnQ6IDAuMC4wLjA6NDMxN1xuICAgICAgaHR0cDpcbiAgICAgICAgZW5kcG9pbnQ6IDAuMC4wLjA6NDMxOFxuICBwcm9tZXRoZXVzOlxuICAgIGNvbmZpZzpcbiAgICAgIGdsb2JhbDpcbiAgICAgICAgc2NyYXBlX2ludGVydmFsOiA2MHNcbiAgICAgIHNjcmFwZV9jb25maWdzOlxuICAgICAgICAtIGpvYl9uYW1lOiBvdGVsLWNvbGxlY3RvclxuICAgICAgICAgIHN0YXRpY19jb25maWdzOlxuICAgICAgICAgIC0gdGFyZ2V0czpcbiAgICAgICAgICAgICAgLSBsb2NhbGhvc3Q6ODg4OFxuICAgICAgICAgICAgbGFiZWxzOlxuICAgICAgICAgICAgICBqb2JfbmFtZTogb3RlbC1jb2xsZWN0b3JcbnByb2Nlc3NvcnM6XG4gIGJhdGNoOlxuICAgIHNlbmRfYmF0Y2hfc2l6ZTogMTAwMDBcbiAgICBzZW5kX2JhdGNoX21heF9zaXplOiAxMTAwMFxuICAgIHRpbWVvdXQ6IDEwc1xuICByZXNvdXJjZWRldGVjdGlvbjpcbiAgICAjIFVzaW5nIE9URUxfUkVTT1VSQ0VfQVRUUklCVVRFUyBlbnZ2YXIsIGVudiBkZXRlY3RvciBhZGRzIGN1c3RvbSBsYWJlbHMuXG4gICAgZGV0ZWN0b3JzOiBbZW52LCBzeXN0ZW1dXG4gICAgdGltZW91dDogMnNcbiAgc2lnbm96c3Bhbm1ldHJpY3MvZGVsdGE6XG4gICAgbWV0cmljc19leHBvcnRlcjogc2lnbm96Y2xpY2tob3VzZW1ldHJpY3NcbiAgICBtZXRyaWNzX2ZsdXNoX2ludGVydmFsOiA2MHNcbiAgICBsYXRlbmN5X2hpc3RvZ3JhbV9idWNrZXRzOiBbMTAwdXMsIDFtcywgMm1zLCA2bXMsIDEwbXMsIDUwbXMsIDEwMG1zLCAyNTBtcywgNTAwbXMsIDEwMDBtcywgMTQwMG1zLCAyMDAwbXMsIDVzLCAxMHMsIDIwcywgNDBzLCA2MHMgXVxuICAgIGRpbWVuc2lvbnNfY2FjaGVfc2l6ZTogMTAwMDAwXG4gICAgYWdncmVnYXRpb25fdGVtcG9yYWxpdHk6IEFHR1JFR0FUSU9OX1RFTVBPUkFMSVRZX0RFTFRBXG4gICAgZW5hYmxlX2V4cF9oaXN0b2dyYW06IHRydWVcbiAgICBkaW1lbnNpb25zOlxuICAgICAgLSBuYW1lOiBzZXJ2aWNlLm5hbWVzcGFjZVxuICAgICAgICBkZWZhdWx0OiBkZWZhdWx0XG4gICAgICAtIG5hbWU6IGRlcGxveW1lbnQuZW52aXJvbm1lbnRcbiAgICAgICAgZGVmYXVsdDogZGVmYXVsdFxuICAgICAgIyBUaGlzIGlzIGFkZGVkIHRvIGVuc3VyZSB0aGUgdW5pcXVlbmVzcyBvZiB0aGUgdGltZXNlcmllc1xuICAgICAgIyBPdGhlcndpc2UsIGlkZW50aWNhbCB0aW1lc2VyaWVzIHByb2R1Y2VkIGJ5IG11bHRpcGxlIHJlcGxpY2FzIG9mXG4gICAgICAjIGNvbGxlY3RvcnMgcmVzdWx0IGluIGluY29ycmVjdCBBUE0gbWV0cmljc1xuICAgICAgLSBuYW1lOiBzaWdub3ouY29sbGVjdG9yLmlkXG4gICAgICAtIG5hbWU6IHNlcnZpY2UudmVyc2lvblxuICAgICAgLSBuYW1lOiBicm93c2VyLnBsYXRmb3JtXG4gICAgICAtIG5hbWU6IGJyb3dzZXIubW9iaWxlXG4gICAgICAtIG5hbWU6IGs4cy5jbHVzdGVyLm5hbWVcbiAgICAgIC0gbmFtZTogazhzLm5vZGUubmFtZVxuICAgICAgLSBuYW1lOiBrOHMubmFtZXNwYWNlLm5hbWVcbiAgICAgIC0gbmFtZTogaG9zdC5uYW1lXG4gICAgICAtIG5hbWU6IGhvc3QudHlwZVxuICAgICAgLSBuYW1lOiBjb250YWluZXIubmFtZVxuZXh0ZW5zaW9uczpcbiAgaGVhbHRoX2NoZWNrOlxuICAgIGVuZHBvaW50OiAwLjAuMC4wOjEzMTMzXG4gIHBwcm9mOlxuICAgIGVuZHBvaW50OiAwLjAuMC4wOjE3NzdcbmV4cG9ydGVyczpcbiAgY2xpY2tob3VzZXRyYWNlczpcbiAgICBkYXRhc291cmNlOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X3RyYWNlc1xuICAgIGxvd19jYXJkaW5hbF9leGNlcHRpb25fZ3JvdXBpbmc6ICR7ZW52OkxPV19DQVJESU5BTF9FWENFUFRJT05fR1JPVVBJTkd9XG4gICAgdXNlX25ld19zY2hlbWE6IHRydWVcbiAgc2lnbm96Y2xpY2tob3VzZW1ldHJpY3M6XG4gICAgZHNuOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X21ldHJpY3NcbiAgY2xpY2tob3VzZWxvZ3NleHBvcnRlcjpcbiAgICBkc246IHRjcDovL2NsaWNraG91c2U6OTAwMC9zaWdub3pfbG9nc1xuICAgIHRpbWVvdXQ6IDEwc1xuICAgIHVzZV9uZXdfc2NoZW1hOiB0cnVlXG5zZXJ2aWNlOlxuICB0ZWxlbWV0cnk6XG4gICAgbG9nczpcbiAgICAgIGVuY29kaW5nOiBqc29uXG4gIGV4dGVuc2lvbnM6XG4gICAgLSBoZWFsdGhfY2hlY2tcbiAgICAtIHBwcm9mXG4gIHBpcGVsaW5lczpcbiAgICB0cmFjZXM6XG4gICAgICByZWNlaXZlcnM6IFtvdGxwXVxuICAgICAgcHJvY2Vzc29yczogW3NpZ25venNwYW5tZXRyaWNzL2RlbHRhLCBiYXRjaF1cbiAgICAgIGV4cG9ydGVyczogW2NsaWNraG91c2V0cmFjZXNdXG4gICAgbWV0cmljczpcbiAgICAgIHJlY2VpdmVyczogW290bHBdXG4gICAgICBwcm9jZXNzb3JzOiBbYmF0Y2hdXG4gICAgICBleHBvcnRlcnM6IFtzaWdub3pjbGlja2hvdXNlbWV0cmljc11cbiAgICBtZXRyaWNzL3Byb21ldGhldXM6XG4gICAgICByZWNlaXZlcnM6IFtwcm9tZXRoZXVzXVxuICAgICAgcHJvY2Vzc29yczogW2JhdGNoXVxuICAgICAgZXhwb3J0ZXJzOiBbc2lnbm96Y2xpY2tob3VzZW1ldHJpY3NdXG4gICAgbG9nczpcbiAgICAgIHJlY2VpdmVyczogW290bHBdXG4gICAgICBwcm9jZXNzb3JzOiBbYmF0Y2hdXG4gICAgICBleHBvcnRlcnM6IFtjbGlja2hvdXNlbG9nc2V4cG9ydGVyXSIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vb3RlbC1jb2xsZWN0b3Itb3BhbXAtY29uZmlnLnlhbWwKICAgICAgICB0YXJnZXQ6IC9ldGMvbWFuYWdlci1jb25maWcueWFtbAogICAgICAgIGNvbnRlbnQ6ICJzZXJ2ZXJfZW5kcG9pbnQ6IHdzOi8vc2lnbm96OjQzMjAvdjEvb3BhbXBcbiIKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX09URUxDT0xMRUNUT1JIVFRQXzQzMTgKICAgICAgLSAnT1RFTF9SRVNPVVJDRV9BVFRSSUJVVEVTPWhvc3QubmFtZT1zaWdub3otaG9zdCxvcy50eXBlPWxpbnV4JwogICAgICAtIExPV19DQVJESU5BTF9FWENFUFRJT05fR1JPVVBJTkc9ZmFsc2UKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiAnYmFzaCAtYyAiZXhlYyA2PD4gL2Rldi90Y3AvbG9jYWxob3N0LzEzMTMzIicKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCiAgc2NoZW1hLW1pZ3JhdG9yLXN5bmM6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3otc2NoZW1hLW1pZ3JhdG9yOnYwLjEyOS43JwogICAgY29tbWFuZDoKICAgICAgLSBzeW5jCiAgICAgIC0gJy0tZHNuPXRjcDovL2NsaWNraG91c2U6OTAwMCcKICAgICAgLSAnLS11cD0nCiAgICBkZXBlbmRzX29uOgogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICBzY2hlbWEtbWlncmF0b3ItYXN5bmM6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3otc2NoZW1hLW1pZ3JhdG9yOnYwLjEyOS43JwogICAgZGVwZW5kc19vbjoKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzY2hlbWEtbWlncmF0b3Itc3luYzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgcmVzdGFydDogb24tZmFpbHVyZQogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgICBsb2dnaW5nOgogICAgICBvcHRpb25zOgogICAgICAgIG1heC1zaXplOiA1MG0KICAgICAgICBtYXgtZmlsZTogJzMnCiAgICBjb21tYW5kOgogICAgICAtIGFzeW5jCiAgICAgIC0gJy0tZHNuPXRjcDovL2NsaWNraG91c2U6OTAwMCcKICAgICAgLSAnLS11cD0nCg==", "tags": [ "telemetry", "server", diff --git a/templates/service-templates.json b/templates/service-templates.json index 09a538c7a..9ad5d9be8 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -192,7 +192,7 @@ "autobase": { "documentation": "https://autobase.tech/docs/?utm_source=coolify.io", "slogan": "Autobase for PostgreSQL\u00ae is an open-source alternative to cloud-managed databases (self-hosted DBaaS).", - "compose": "c2VydmljZXM6CiAgYXV0b2Jhc2U6CiAgICBpbWFnZTogJ2F1dG9iYXNlL2NvbnNvbGVfdWk6Mi40LjEnCiAgICBwbGF0Zm9ybTogbGludXgvYW1kNjQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9BVVRPQkFTRV84MAogICAgICAtICdQR19DT05TT0xFX0FVVEhPUklaQVRJT05fVE9LRU49JHtTRVJWSUNFX1BBU1NXT1JEX1VJfScKICAgICAgLSBQR19DT05TT0xFX0FQSV9IT1NUPWF1dG9iYXNlLWFwaQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwLycKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgICBkZXBlbmRzX29uOgogICAgICBhdXRvYmFzZS1hcGk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBhdXRvYmFzZS1kYjoKICAgIGltYWdlOiAnYXV0b2Jhc2UvY29uc29sZV9kYjoyLjQuMScKICAgIHBsYXRmb3JtOiBsaW51eC9hbWQ2NAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICB2b2x1bWVzOgogICAgICAtICdhdXRvYmFzZS1kYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSBwb3N0Z3JlcycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgYXV0b2Jhc2UtYXBpOgogICAgaW1hZ2U6ICdhdXRvYmFzZS9jb25zb2xlX2FwaToyLjQuMScKICAgIHBsYXRmb3JtOiBsaW51eC9hbWQ2NAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEdfQ09OU09MRV9EQl9IT1NUPWF1dG9iYXNlLWRiCiAgICAgIC0gJ1BHX0NPTlNPTEVfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUEdfQ09OU09MRV9BVVRIT1JJWkFUSU9OX1RPS0VOPSR7U0VSVklDRV9QQVNTV09SRF9VSX0nCiAgICAgIC0gJ1BHX0NPTlNPTEVfRU5DUllQVElPTktFWT0ke1NFUlZJQ0VfQkFTRTY0X0VOQ1JZUFRJT05LRVl9JwogICAgICAtICdQR19DT05TT0xFX0xPR0dFUl9MRVZFTD0ke1BHX0NPTlNPTEVfTE9HR0VSX0xFVkVMOi1pbmZvfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgICAtICcvdG1wL2Fuc2libGU6L3RtcC9hbnNpYmxlJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZnNTJwogICAgICAgIC0gJy1IJwogICAgICAgIC0gJ2FjY2VwdDogYXBwbGljYXRpb24vanNvbicKICAgICAgICAtICctSCcKICAgICAgICAtICdBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtTRVJWSUNFX1BBU1NXT1JEX1VJfScKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwODAvYXBpL3YxL3ZlcnNpb24nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogICAgZGVwZW5kc19vbjoKICAgICAgYXV0b2Jhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkK", + "compose": "c2VydmljZXM6CiAgYXV0b2Jhc2U6CiAgICBpbWFnZTogJ2F1dG9iYXNlL2NvbnNvbGVfdWk6Mi41LjInCiAgICBwbGF0Zm9ybTogbGludXgvYW1kNjQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9BVVRPQkFTRV84MAogICAgICAtICdQR19DT05TT0xFX0FVVEhPUklaQVRJT05fVE9LRU49JHtTRVJWSUNFX1BBU1NXT1JEX1VJfScKICAgICAgLSBQR19DT05TT0xFX0FQSV9IT1NUPWF1dG9iYXNlLWFwaQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwLycKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgICBkZXBlbmRzX29uOgogICAgICBhdXRvYmFzZS1hcGk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBhdXRvYmFzZS1kYjoKICAgIGltYWdlOiAnYXV0b2Jhc2UvY29uc29sZV9kYjoyLjUuMicKICAgIHBsYXRmb3JtOiBsaW51eC9hbWQ2NAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICB2b2x1bWVzOgogICAgICAtICdhdXRvYmFzZS1kYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSBwb3N0Z3JlcycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgYXV0b2Jhc2UtYXBpOgogICAgaW1hZ2U6ICdhdXRvYmFzZS9jb25zb2xlX2FwaToyLjUuMicKICAgIHBsYXRmb3JtOiBsaW51eC9hbWQ2NAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEdfQ09OU09MRV9EQl9IT1NUPWF1dG9iYXNlLWRiCiAgICAgIC0gJ1BHX0NPTlNPTEVfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUEdfQ09OU09MRV9BVVRIT1JJWkFUSU9OX1RPS0VOPSR7U0VSVklDRV9QQVNTV09SRF9VSX0nCiAgICAgIC0gJ1BHX0NPTlNPTEVfRU5DUllQVElPTktFWT0ke1NFUlZJQ0VfQkFTRTY0X0VOQ1JZUFRJT05LRVl9JwogICAgICAtICdQR19DT05TT0xFX0xPR0dFUl9MRVZFTD0ke1BHX0NPTlNPTEVfTE9HR0VSX0xFVkVMOi1pbmZvfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgICAtICcvdG1wL2Fuc2libGU6L3RtcC9hbnNpYmxlJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZnNTJwogICAgICAgIC0gJy1IJwogICAgICAgIC0gJ2FjY2VwdDogYXBwbGljYXRpb24vanNvbicKICAgICAgICAtICctSCcKICAgICAgICAtICdBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtTRVJWSUNFX1BBU1NXT1JEX1VJfScKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwODAvYXBpL3YxL3ZlcnNpb24nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogICAgZGVwZW5kc19vbjoKICAgICAgYXV0b2Jhc2UtZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkK", "tags": [ "database", "postgres", @@ -546,6 +546,21 @@ "minversion": "0.0.0", "port": "80" }, + "chibisafe": { + "documentation": "https://chibisafe.app/docs/intro?utm_source=coolify.io", + "slogan": "A beautiful and performant vault to save all your files in the cloud.", + "compose": "c2VydmljZXM6CiAgY2hpYmlzYWZlOgogICAgaW1hZ2U6ICdjaGliaXNhZmUvY2hpYmlzYWZlOnY2LjUuNScKICAgIGVudmlyb25tZW50OgogICAgICAtICdCQVNFX0FQSV9VUkw9aHR0cDovL2NoaWJpc2FmZS1zZXJ2ZXI6ODAwMCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnLS1xdWlldCcKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwMDEnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICBjaGliaXNhZmUtc2VydmVyOgogICAgaW1hZ2U6ICdjaGliaXNhZmUvY2hpYmlzYWZlLXNlcnZlcjp2Ni41LjUnCiAgICB2b2x1bWVzOgogICAgICAtICdjaGliaXNhZmUtZGF0YWJhc2U6L2FwcC9kYXRhYmFzZTpydycKICAgICAgLSAnY2hpYmlzYWZlLXVwbG9hZHM6L2FwcC91cGxvYWRzOnJ3JwogICAgICAtICdjaGliaXNhZmUtbG9nczovYXBwL2xvZ3M6cncnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJy0tcXVpZXQnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MDAwL2FwaS9oZWFsdGgnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICBjYWRkeToKICAgIGltYWdlOiAnY2FkZHk6Mi1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQ0hJQklTQUZFXzgwCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJy0tcXVpZXQnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MCcKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgdm9sdW1lczoKICAgICAgLSAndXBsb2FkczovYXBwL3VwbG9hZHM6cm8nCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL0NhZGR5ZmlsZQogICAgICAgIHRhcmdldDogL2V0Yy9jYWRkeS9DYWRkeWZpbGUKICAgICAgICBjb250ZW50OiAiIyBjaGliaXNhZmUvQ2FkZHlmaWxlXG4jIGh0dHBzOi8vY2hpYmlzYWZlLm1vZS9ndWlkZXMvcnVubmluZy13aXRoLWRvY2tlclxuOjgwIHtcbiAgcm91dGUge1xuICAgIGZpbGVfc2VydmVyICoge1xuICAgICAgICByb290IC9hcHAvdXBsb2Fkc1xuICAgICAgICBwYXNzX3RocnVcbiAgICB9XG4gICAgQGFwaSBwYXRoIC9hcGkvKlxuICAgIHJldmVyc2VfcHJveHkgQGFwaSBodHRwOi8vY2hpYmlzYWZlLXNlcnZlcjo4MDAwIHtcbiAgICAgICAgaGVhZGVyX3VwIEhvc3Qge2h0dHAucmV2ZXJzZV9wcm94eS51cHN0cmVhbS5ob3N0cG9ydH1cbiAgICAgICAgaGVhZGVyX3VwIFgtUmVhbC1JUCB7aHR0cC5yZXF1ZXN0LmhlYWRlci5YLVJlYWwtSVB9XG4gICAgfVxuICAgIEBkb2NzIHBhdGggL2RvY3MqXG4gICAgcmV2ZXJzZV9wcm94eSBAZG9jcyBodHRwOi8vY2hpYmlzYWZlLXNlcnZlcjo4MDAwIHtcbiAgICAgICAgaGVhZGVyX3VwIEhvc3Qge2h0dHAucmV2ZXJzZV9wcm94eS51cHN0cmVhbS5ob3N0cG9ydH1cbiAgICAgICAgaGVhZGVyX3VwIFgtUmVhbC1JUCB7aHR0cC5yZXF1ZXN0LmhlYWRlci5YLVJlYWwtSVB9XG4gICAgfVxuICAgIHJldmVyc2VfcHJveHkgaHR0cDovL2NoaWJpc2FmZTo4MDAxIHtcbiAgICAgICAgaGVhZGVyX3VwIEhvc3Qge2h0dHAucmV2ZXJzZV9wcm94eS51cHN0cmVhbS5ob3N0cG9ydH1cbiAgICAgICAgaGVhZGVyX3VwIFgtUmVhbC1JUCB7aHR0cC5yZXF1ZXN0LmhlYWRlci5YLVJlYWwtSVB9XG4gICAgfVxuICB9XG59XG4iCg==", + "tags": [ + "storage", + "file-sharing", + "upload", + "sharing" + ], + "category": null, + "logo": "svgs/chibisafe.svg", + "minversion": "0.0.0", + "port": "80" + }, "chroma": { "documentation": "https://cookbook.chromadb.dev/?utm_source=coolify.io", "slogan": "Chroma is the open-source search and retrieval database for AI applications.", @@ -1731,6 +1746,21 @@ "minversion": "0.0.0", "port": "8080" }, + "glpi": { + "documentation": "https://help.glpi-project.org/documentation?utm_source=coolify.io", + "slogan": "GLPI (Gestionnaire Libre de Parc Informatique) is a free, open-source IT Service Management (ITSM) platform used for IT asset management, helpdesk, and service desk operations.", + "compose": "c2VydmljZXM6CiAgZ2xwaToKICAgIGltYWdlOiAnZ2xwaS9nbHBpOjExJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0dMUElfODAKICAgICAgLSBHTFBJX0RCX0hPU1Q9Z2xwaS1kYgogICAgICAtIEdMUElfREJfUE9SVD0zMzA2CiAgICAgIC0gJ0dMUElfREJfTkFNRT0ke01ZU1FMX0RBVEFCQVNFOi1nbHBpLWRifScKICAgICAgLSAnR0xQSV9EQl9VU0VSPSR7U0VSVklDRV9VU0VSX01ZU1FMfScKICAgICAgLSAnR0xQSV9EQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUx9JwogICAgdm9sdW1lczoKICAgICAgLSAnZ2xwaS1kYXRhOi92YXIvZ2xwaTpydycKICAgIGRlcGVuZHNfb246CiAgICAgIGdscGktZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdC8nCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICBnbHBpLWRiOgogICAgaW1hZ2U6ICdteXNxbDo4JwogICAgdm9sdW1lczoKICAgICAgLSAnbXlzcWwtZGF0YTovdmFyL2xpYi9teXNxbCcKICAgIGVudmlyb25tZW50OgogICAgICAtICdNWVNRTF9ST09UX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9ST09UfScKICAgICAgLSAnTVlTUUxfREFUQUJBU0U9JHtNWVNRTF9EQVRBQkFTRTotZ2xwaS1kYn0nCiAgICAgIC0gJ01ZU1FMX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUx9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG15c3FsYWRtaW4KICAgICAgICAtIHBpbmcKICAgICAgICAtICctaCcKICAgICAgICAtIDEyNy4wLjAuMQogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "tags": [ + "helpdesk", + "ticketing", + "support", + "open-source" + ], + "category": "helpdesk", + "logo": "svgs/glpi.svg", + "minversion": "0.0.0", + "port": "80" + }, "gotenberg": { "documentation": "https://gotenberg.dev/docs/getting-started/introduction?utm_source=coolify.io", "slogan": "Gotenberg is a Docker-powered stateless API for PDF files.", @@ -3296,6 +3326,21 @@ "minversion": "0.0.0", "port": "3000" }, + "open-archiver": { + "documentation": "https://docs.openarchiver.com/?utm_source=coolify.io", + "slogan": "A self-hosted, open-source email archiving solution with full-text search capability.", + "compose": "c2VydmljZXM6CiAgb3Blbi1hcmNoaXZlcjoKICAgIGltYWdlOiAnbG9naWNsYWJzaHEvb3Blbi1hcmNoaXZlcjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fT1BFTkFSQ0hJVkVSXzMwMDAKICAgICAgLSAnRU5DUllQVElPTl9LRVk9JHtTRVJWSUNFX0hFWF8zMl9FTkNSWVBUSU9OS0VZfScKICAgICAgLSAnU1RPUkFHRV9FTkNSWVBUSU9OX0tFWT0ke1NFUlZJQ0VfSEVYXzMyX1NUT1JBR0VFTkNSWVBUSU9OS0VZfScKICAgICAgLSAnUE9SVF9CQUNLRU5EPSR7UE9SVF9CQUNLRU5EOi00MDAwfScKICAgICAgLSAnUE9SVF9GUk9OVEVORD0ke1BPUlRfRlJPTlRFTkQ6LTMwMDB9JwogICAgICAtICdOT0RFX0VOVj0ke05PREVfRU5WOi1wcm9kdWN0aW9ufScKICAgICAgLSAnU1lOQ19GUkVRVUVOQ1k9JHtTWU5DX0ZSRVFVRU5DWTotKiAqICogKiAqfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotb3Blbl9hcmNoaXZlfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3Jlc3FsOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke1BPU1RHUkVTX0RCOi1vcGVuLWFyY2hpdmVyLWRifScKICAgICAgLSAnTUVJTElfTUFTVEVSX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTUVJTElTRUFSQ0h9JwogICAgICAtICdNRUlMSV9IT1NUPWh0dHA6Ly9tZWlsaXNlYXJjaDo3NzAwJwogICAgICAtIFJFRElTX0hPU1Q9dmFsa2V5CiAgICAgIC0gUkVESVNfUE9SVD02Mzc5CiAgICAgIC0gJ1JFRElTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9WQUxLRVl9JwogICAgICAtIFJFRElTX1RMU19FTkFCTEVEPWZhbHNlCiAgICAgIC0gJ1NUT1JBR0VfVFlQRT0ke1NUT1JBR0VfVFlQRTotbG9jYWx9JwogICAgICAtICdTVE9SQUdFX0xPQ0FMX1JPT1RfUEFUSD0ke1NUT1JBR0VfTE9DQUxfUk9PVF9QQVRIOi0vdmFyL2RhdGEvb3Blbi1hcmNoaXZlcn0nCiAgICAgIC0gJ0JPRFlfU0laRV9MSU1JVD0ke0JPRFlfU0laRV9MSU1JVDotMTAwTX0nCiAgICAgIC0gJ1NUT1JBR0VfUzNfRU5EUE9JTlQ9JHtTVE9SQUdFX1MzX0VORFBPSU5UfScKICAgICAgLSAnU1RPUkFHRV9TM19CVUNLRVQ9JHtTVE9SQUdFX1MzX0JVQ0tFVH0nCiAgICAgIC0gJ1NUT1JBR0VfUzNfQUNDRVNTX0tFWV9JRD0ke1NUT1JBR0VfUzNfQUNDRVNTX0tFWV9JRH0nCiAgICAgIC0gJ1NUT1JBR0VfUzNfU0VDUkVUX0FDQ0VTU19LRVk9JHtTVE9SQUdFX1MzX1NFQ1JFVF9BQ0NFU1NfS0VZfScKICAgICAgLSAnU1RPUkFHRV9TM19SRUdJT049JHtTVE9SQUdFX1MzX1JFR0lPTn0nCiAgICAgIC0gJ1NUT1JBR0VfUzNfRk9SQ0VfUEFUSF9TVFlMRT0ke1NUT1JBR0VfUzNfRk9SQ0VfUEFUSF9TVFlMRTotZmFsc2V9JwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfMTI4X0pXVH0nCiAgICAgIC0gJ0pXVF9FWFBJUkVTX0lOPSR7SldUX0VYUElSRVNfSU46LTdkfScKICAgICAgLSAnUkFURV9MSU1JVF9XSU5ET1dfTVM9JHtSQVRFX0xJTUlUX1dJTkRPV19NUzotNjAwMDB9JwogICAgICAtICdSQVRFX0xJTUlUX01BWF9SRVFVRVNUUz0ke1JBVEVfTElNSVRfTUFYX1JFUVVFU1RTOi0xMDB9JwogICAgdm9sdW1lczoKICAgICAgLSAnYXJjaGl2ZXItZGF0YTovdmFyL2RhdGEvb3Blbi1hcmNoaXZlcicKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHZhbGtleToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBtZWlsaXNlYXJjaDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNy1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotb3Blbi1hcmNoaXZlci1kYn0nCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtIExDX0FMTD1DCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlcy1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgdmFsa2V5OgogICAgaW1hZ2U6ICd2YWxrZXkvdmFsa2V5OjgtYWxwaW5lJwogICAgY29tbWFuZDogJ3ZhbGtleS1zZXJ2ZXIgLS1yZXF1aXJlcGFzcyAke1NFUlZJQ0VfUEFTU1dPUkRfVkFMS0VZfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3ZhbGtleS1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDMKICBtZWlsaXNlYXJjaDoKICAgIGltYWdlOiAnZ2V0bWVpbGkvbWVpbGlzZWFyY2g6djEuMTUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUVJTElfTUFTVEVSX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfTUVJTElTRUFSQ0h9JwogICAgdm9sdW1lczoKICAgICAgLSAnbWVpbGlzZWFyY2gtZGF0YTovbWVpbGlfZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo3NzAwL2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=", + "tags": [ + "email archiving", + "email", + "compliance", + "search" + ], + "category": null, + "logo": "svgs/openarchiver.svg", + "minversion": "0.0.0", + "port": "3000" + }, "open-webui": { "documentation": "https://docs.openwebui.com?utm_source=coolify.io", "slogan": "User-friendly AI Interface (Supports Ollama, OpenAI API, ...)", @@ -4066,6 +4111,22 @@ "minversion": "0.0.0", "port": "8080" }, + "seaweedfs": { + "documentation": "https://github.com/seaweedfs/seaweedfs?utm_source=coolify.io", + "slogan": "SeaweedFS is a simple and highly scalable distributed file system. Compatible with S3, with an admin web interface.", + "compose": "c2VydmljZXM6CiAgc2Vhd2VlZGZzLW1hc3RlcjoKICAgIGltYWdlOiAnY2hyaXNsdXNmL3NlYXdlZWRmczo0LjA1JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1MzXzgzMzMKICAgICAgLSAnQVdTX0FDQ0VTU19LRVlfSUQ9JHtTRVJWSUNFX1VTRVJfUzN9JwogICAgICAtICdBV1NfU0VDUkVUX0FDQ0VTU19LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX1MzfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3NlYXdlZWRmcy1zMy1kYXRhOi9kYXRhJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9iYXNlLWNvbmZpZy5qc29uCiAgICAgICAgdGFyZ2V0OiAvYmFzZS1jb25maWcuanNvbgogICAgICAgIGNvbnRlbnQ6ICJ7XG4gIFwiaWRlbnRpdGllc1wiOiBbXG4gICAge1xuICAgICAgXCJuYW1lXCI6IFwiYW5vbnltb3VzXCIsXG4gICAgICBcImFjdGlvbnNcIjogW1xuICAgICAgICBcIlJlYWRcIlxuICAgICAgXVxuICAgIH0sXG4gICAge1xuICAgICAgXCJuYW1lXCI6IFwiYWRtaW5cIixcbiAgICAgIFwiY3JlZGVudGlhbHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJhY2Nlc3NLZXlcIjogXCJlbnY6QVdTX0FDQ0VTU19LRVlfSURcIixcbiAgICAgICAgICBcInNlY3JldEtleVwiOiBcImVudjpBV1NfU0VDUkVUX0FDQ0VTU19LRVlcIlxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJhY3Rpb25zXCI6IFtcbiAgICAgICAgXCJBZG1pblwiLFxuICAgICAgICBcIlJlYWRcIixcbiAgICAgICAgXCJSZWFkQWNwXCIsXG4gICAgICAgIFwiTGlzdFwiLFxuICAgICAgICBcIlRhZ2dpbmdcIixcbiAgICAgICAgXCJXcml0ZVwiLFxuICAgICAgICBcIldyaXRlQWNwXCJcbiAgICAgIF1cbiAgICB9XG4gIF1cbn1cbiIKICAgIGVudHJ5cG9pbnQ6ICJzaCAtYyAnXFxcbnNlZCBcInMvZW52OkFXU19BQ0NFU1NfS0VZX0lELyRBV1NfQUNDRVNTX0tFWV9JRC9nXCIgL2Jhc2UtY29uZmlnLmpzb24gPiAvYmFzZTEtY29uZmlnLmpzb247IFxcXG5zZWQgXCJzL2VudjpBV1NfU0VDUkVUX0FDQ0VTU19LRVkvJEFXU19TRUNSRVRfQUNDRVNTX0tFWS9nXCIgL2Jhc2UxLWNvbmZpZy5qc29uID4gL2NvbmZpZy5qc29uOyBcXFxud2VlZCBzZXJ2ZXIgLWRpcj0vZGF0YSAtbWFzdGVyLnBvcnQ9OTMzMyAtczMgLXMzLnBvcnQ9ODMzMyAtczMuY29uZmlnPS9jb25maWcuanNvblxcXG4nXG4iCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLS1zcGlkZXIgLXEgaHR0cDovLzAuMC4wLjA6ODMzMzsgWyAkJD8gLWxlIDggXScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAogIHNlYXdlZWRmcy1hZG1pbjoKICAgIGltYWdlOiAnY2hyaXNsdXNmL3NlYXdlZWRmczo0LjA1JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0FETUlOXzIzNjQ2CiAgICAgIC0gJ1NFQVdFRURfVVNFUl9BRE1JTj0ke1NFUlZJQ0VfVVNFUl9BRE1JTn0nCiAgICAgIC0gJ1NFQVdFRURfUEFTU1dPUkRfQURNSU49JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgIGNvbW1hbmQ6CiAgICAgIC0gYWRtaW4KICAgICAgLSAnLW1hc3Rlcj1zZWF3ZWVkZnMtbWFzdGVyOjkzMzMnCiAgICAgIC0gJy1hZG1pblVzZXI9JHtTRUFXRUVEX1VTRVJfQURNSU59JwogICAgICAtICctYWRtaW5QYXNzd29yZD0ke1NFQVdFRURfUEFTU1dPUkRfQURNSU59JwogICAgICAtICctcG9ydD0yMzY0NicKICAgICAgLSAnLWRhdGFEaXI9L2RhdGEnCiAgICB2b2x1bWVzOgogICAgICAtICdzZWF3ZWVkZnMtYWRtaW4tZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtLXNwaWRlciAtcSBodHRwOi8vMC4wLjAuMDoyMzY0NjsgWyAkJD8gLWxlIDggXScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAogICAgZGVwZW5kc19vbjoKICAgICAgc2Vhd2VlZGZzLW1hc3RlcjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQo=", + "tags": [ + "object", + "storage", + "server", + "s3", + "api" + ], + "category": "storage", + "logo": "svgs/garage.svg", + "minversion": "0.0.0", + "port": "8333" + }, "sequin": { "documentation": "https://sequinstream.com/docs/?utm_source=coolify.io", "slogan": "The fastest Postgres change data capture", @@ -4080,10 +4141,25 @@ "minversion": "0.0.0", "port": "7376" }, + "sessy": { + "documentation": "https://github.com/marckohlbrugge/sessy/blob/main/docs/docker-deployment.md?utm_source=coolify.io", + "slogan": "Email observability platform for monitoring and analyzing email systems.", + "compose": "c2VydmljZXM6CiAgc2Vzc3k6CiAgICBpbWFnZTogJ2doY3IuaW8vbWFyY2tvaGxicnVnZ2Uvc2Vzc3k6bWFpbicKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TRVNTWV84MAogICAgICAtIFNFQ1JFVF9LRVlfQkFTRT0kU0VSVklDRV9IRVhfNjRfU0VTU1lTRUNSRVQKICAgICAgLSBIVFRQX0FVVEhfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9TRVNTWQogICAgICAtIEhUVFBfQVVUSF9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9TRVNTWQogICAgdm9sdW1lczoKICAgICAgLSAnc2Vzc3ktZGF0YTovcmFpbHMvc3RvcmFnZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnY3VybCAtc2YgaHR0cDovLzEyNy4wLjAuMTo4MCB8fCBbICQ/IC1lcSAyMiBdJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "tags": [ + "email", + "observability", + "monitoring", + "analytics" + ], + "category": "monitoring", + "logo": "svgs/sessy.svg", + "minversion": "0.0.0", + "port": "80" + }, "sftpgo": { "documentation": "https://docs.sftpgo.com/2.7/?utm_source=coolify.io", "slogan": "SFTPGo is an event-driven SFTP, FTP/S, HTTP/S and WebDAV server.", - "compose": "c2VydmljZXM6CiAgc2Z0cGdvOgogICAgaW1hZ2U6ICdnaGNyLmlvL2RyYWtrYW4vc2Z0cGdvOnYyLjctYWxwaW5lJwogICAgcG9ydHM6CiAgICAgIC0gJzIyMjI6MjIyMicKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TRlRQR09fODA4MAogICAgICAtIFNGVFBHT19EQVRBX1BST1ZJREVSX19EUklWRVI9cG9zdGdyZXNxbAogICAgICAtICdTRlRQR09fREFUQV9QUk9WSURFUl9fQ09OTkVDVElPTl9TVFJJTkc9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfNjRfUE9TVEdSRVN9QHBvc3RncmVzcWw6NTQzMi8ke1BPU1RHUkVTX0RBVEFCQVNFOi1zZnRwZ28tZGJ9JwogICAgICAtIFNGVFBHT19TRlRQRF9fQklORElOR1NfXzBfX1BPUlQ9MjAyMgogICAgICAtICdTRlRQR09fTE9HX0xFVkVMPSR7TE9HX0xFVkVMOi1pbmZvfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3NmdHBnby1kYXRhOi9zcnYvc2Z0cGdvL2RhdGEnCiAgICAgIC0gJ3NmdHBnby1rZXlzOi92YXIvbGliL3NmdHBnbycKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNy1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF82NF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREFUQUJBU0U6LXNmdHBnby1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAyMAo=", + "compose": "c2VydmljZXM6CiAgc2Z0cGdvOgogICAgaW1hZ2U6ICdnaGNyLmlvL2RyYWtrYW4vc2Z0cGdvOnYyLjctYWxwaW5lJwogICAgcG9ydHM6CiAgICAgIC0gJzIyMjI6MjIyMicKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TRlRQR09fODA4MAogICAgICAtIFNGVFBHT19EQVRBX1BST1ZJREVSX19EUklWRVI9cG9zdGdyZXNxbAogICAgICAtICdTRlRQR09fREFUQV9QUk9WSURFUl9fQ09OTkVDVElPTl9TVFJJTkc9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfNjRfUE9TVEdSRVN9QHBvc3RncmVzcWw6NTQzMi8ke1BPU1RHUkVTX0RBVEFCQVNFOi1zZnRwZ28tZGJ9JwogICAgICAtICdTRlRQR09fU0ZUUERfX0JJTkRJTkdTX18wX19QT1JUPSR7UE9SVF9TRlRQR086LTIyMjJ9JwogICAgICAtICdTRlRQR09fTE9HX0xFVkVMPSR7TE9HX0xFVkVMOi1pbmZvfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6ODA4MC8nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3NmdHBnby1kYXRhOi9zcnYvc2Z0cGdvL2RhdGEnCiAgICAgIC0gJ3NmdHBnby1rZXlzOi92YXIvbGliL3NmdHBnbycKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNy1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF82NF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREFUQUJBU0U6LXNmdHBnby1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAyMAo=", "tags": [ "sftpgo", "sftp", @@ -4117,7 +4193,7 @@ "signoz": { "documentation": "https://signoz.io/docs/introduction/?utm_source=coolify.io", "slogan": "An observability platform native to OpenTelemetry with logs, traces and metrics.", - "compose": "c2VydmljZXM6CiAgaW5pdC1jbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI1LjUuNi1hbHBpbmUnCiAgICBjb21tYW5kOgogICAgICAtIGJhc2gKICAgICAgLSAnLWMnCiAgICAgIC0gInZlcnNpb249XCJ2MC4wLjFcIlxubm9kZV9vcz0kJCh1bmFtZSAtcyB8IHRyICdbOnVwcGVyOl0nICdbOmxvd2VyOl0nKVxubm9kZV9hcmNoPSQkKHVuYW1lIC1tIHwgc2VkIHMvYWFyY2g2NC9hcm02NC8gfCBzZWQgcy94ODZfNjQvYW1kNjQvKVxuZWNobyBcIkZldGNoaW5nIGhpc3RvZ3JhbS1iaW5hcnkgZm9yICQke25vZGVfb3N9LyQke25vZGVfYXJjaH1cIlxuY2QgL3RtcFxud2dldCAtTyBoaXN0b2dyYW0tcXVhbnRpbGUudGFyLmd6IFwiaHR0cHM6Ly9naXRodWIuY29tL1NpZ05vei9zaWdub3ovcmVsZWFzZXMvZG93bmxvYWQvaGlzdG9ncmFtLXF1YW50aWxlJTJGJCR7dmVyc2lvbn0vaGlzdG9ncmFtLXF1YW50aWxlXyQke25vZGVfb3N9XyQke25vZGVfYXJjaH0udGFyLmd6XCJcbnRhciAteHZ6ZiBoaXN0b2dyYW0tcXVhbnRpbGUudGFyLmd6XG5ta2RpciAtcCAvdmFyL2xpYi9jbGlja2hvdXNlL3VzZXJfc2NyaXB0cy9oaXN0b2dyYW1RdWFudGlsZVxubXYgaGlzdG9ncmFtLXF1YW50aWxlIC92YXIvbGliL2NsaWNraG91c2UvdXNlcl9zY3JpcHRzL2hpc3RvZ3JhbVF1YW50aWxlXG4iCiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICB6b29rZWVwZXI6CiAgICBpbWFnZTogJ3NpZ25vei96b29rZWVwZXI6My45LjMnCiAgICB1c2VyOiByb290CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ2N1cmwgLXMgLW0gMiBodHRwOi8vbG9jYWxob3N0OjgwODAvY29tbWFuZHMvcnVvayB8IGdyZXAgZXJyb3IgfCBncmVwIG51bGwnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgbG9nZ2luZzoKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtc2l6ZTogNTBtCiAgICAgICAgbWF4LWZpbGU6ICczJwogICAgdm9sdW1lczoKICAgICAgLSAnem9va2VlcGVyOi9iaXRuYW1pL3pvb2tlZXBlcicKICAgIGVudmlyb25tZW50OgogICAgICAtICdBTExPV19BTk9OWU1PVVNfTE9HSU49JHtaT09fQUxMT1dfQU5PTllNT1VTX0xPR0lOOi15ZXN9JwogICAgICAtICdaT09fQVVUT1BVUkdFX0lOVEVSVkFMPSR7Wk9PX0FVVE9QVVJHRV9JTlRFUlZBTDotMX0nCiAgICAgIC0gJ1pPT19FTkFCTEVfUFJPTUVUSEVVU19NRVRSSUNTPSR7Wk9PX0VOQUJMRV9QUk9NRVRIRVVTX01FVFJJQ1M6LXllc30nCiAgICAgIC0gJ1pPT19QUk9NRVRIRVVTX01FVFJJQ1NfUE9SVF9OVU1CRVI9JHtaT09fUFJPTUVUSEVVU19NRVRSSUNTX1BPUlRfTlVNQkVSOi05MTQxfScKICBjbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI1LjUuNi1hbHBpbmUnCiAgICB0dHk6IHRydWUKICAgIGRlcGVuZHNfb246CiAgICAgIGluaXQtY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgICB6b29rZWVwZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnLXEnCiAgICAgICAgLSAnMC4wLjAuMDo4MTIzL3BpbmcnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgdWxpbWl0czoKICAgICAgbnByb2M6IDY1NTM1CiAgICAgIG5vZmlsZToKICAgICAgICBzb2Z0OiAyNjIxNDQKICAgICAgICBoYXJkOiAyNjIxNDQKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGVudmlyb25tZW50OgogICAgICAtIENMSUNLSE9VU0VfU0tJUF9VU0VSX1NFVFVQPTEKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiB2b2x1bWUKICAgICAgICBzb3VyY2U6IGNsaWNraG91c2UKICAgICAgICB0YXJnZXQ6IC92YXIvbGliL2NsaWNraG91c2UvCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2UvY3VzdG9tLWZ1bmN0aW9uLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jdXN0b20tZnVuY3Rpb24ueG1sCiAgICAgICAgY29udGVudDogIjxmdW5jdGlvbnM+XG4gICAgPGZ1bmN0aW9uPlxuICAgICAgICA8dHlwZT5leGVjdXRhYmxlPC90eXBlPlxuICAgICAgICA8bmFtZT5oaXN0b2dyYW1RdWFudGlsZTwvbmFtZT5cbiAgICAgICAgPHJldHVybl90eXBlPkZsb2F0NjQ8L3JldHVybl90eXBlPlxuICAgICAgICA8YXJndW1lbnQ+XG4gICAgICAgICAgICA8dHlwZT5BcnJheShGbG9hdDY0KTwvdHlwZT5cbiAgICAgICAgICAgIDxuYW1lPmJ1Y2tldHM8L25hbWU+XG4gICAgICAgIDwvYXJndW1lbnQ+XG4gICAgICAgIDxhcmd1bWVudD5cbiAgICAgICAgICAgIDx0eXBlPkFycmF5KEZsb2F0NjQpPC90eXBlPlxuICAgICAgICAgICAgPG5hbWU+Y291bnRzPC9uYW1lPlxuICAgICAgICA8L2FyZ3VtZW50PlxuICAgICAgICA8YXJndW1lbnQ+XG4gICAgICAgICAgICA8dHlwZT5GbG9hdDY0PC90eXBlPlxuICAgICAgICAgICAgPG5hbWU+cXVhbnRpbGU8L25hbWU+XG4gICAgICAgIDwvYXJndW1lbnQ+XG4gICAgICAgIDxmb3JtYXQ+Q1NWPC9mb3JtYXQ+XG4gICAgICAgIDxjb21tYW5kPi4vaGlzdG9ncmFtUXVhbnRpbGU8L2NvbW1hbmQ+XG4gICAgPC9mdW5jdGlvbj5cbjwvZnVuY3Rpb25zPlxuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9jbGlja2hvdXNlL2NsdXN0ZXIueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy5kL2NsdXN0ZXIueG1sCiAgICAgICAgY29udGVudDogIjw/eG1sIHZlcnNpb249XCIxLjBcIj8+XG48Y2xpY2tob3VzZT5cbiAgICA8IS0tIFpvb0tlZXBlciBpcyB1c2VkIHRvIHN0b3JlIG1ldGFkYXRhIGFib3V0IHJlcGxpY2FzLCB3aGVuIHVzaW5nIFJlcGxpY2F0ZWQgdGFibGVzLlxuICAgICAgICBPcHRpb25hbC4gSWYgeW91IGRvbid0IHVzZSByZXBsaWNhdGVkIHRhYmxlcywgeW91IGNvdWxkIG9taXQgdGhhdC5cblxuICAgICAgICBTZWUgaHR0cHM6Ly9jbGlja2hvdXNlLmNvbS9kb2NzL2VuL2VuZ2luZXMvdGFibGUtZW5naW5lcy9tZXJnZXRyZWUtZmFtaWx5L3JlcGxpY2F0aW9uL1xuICAgICAgLS0+XG4gICAgPHpvb2tlZXBlcj5cbiAgICAgICAgPG5vZGUgaW5kZXg9XCIxXCI+XG4gICAgICAgICAgICA8aG9zdD56b29rZWVwZXI8L2hvc3Q+XG4gICAgICAgICAgICA8cG9ydD4yMTgxPC9wb3J0PlxuICAgICAgICA8L25vZGU+XG4gICAgPC96b29rZWVwZXI+XG5cbiAgICA8IS0tIENvbmZpZ3VyYXRpb24gb2YgY2x1c3RlcnMgdGhhdCBjb3VsZCBiZSB1c2VkIGluIERpc3RyaWJ1dGVkIHRhYmxlcy5cbiAgICAgICAgaHR0cHM6Ly9jbGlja2hvdXNlLmNvbS9kb2NzL2VuL29wZXJhdGlvbnMvdGFibGVfZW5naW5lcy9kaXN0cmlidXRlZC9cbiAgICAgIC0tPlxuICAgIDxyZW1vdGVfc2VydmVycz5cbiAgICAgICAgPGNsdXN0ZXI+XG4gICAgICAgICAgICA8IS0tIEludGVyLXNlcnZlciBwZXItY2x1c3RlciBzZWNyZXQgZm9yIERpc3RyaWJ1dGVkIHF1ZXJpZXNcbiAgICAgICAgICAgICAgICBkZWZhdWx0OiBubyBzZWNyZXQgKG5vIGF1dGhlbnRpY2F0aW9uIHdpbGwgYmUgcGVyZm9ybWVkKVxuXG4gICAgICAgICAgICAgICAgSWYgc2V0LCB0aGVuIERpc3RyaWJ1dGVkIHF1ZXJpZXMgd2lsbCBiZSB2YWxpZGF0ZWQgb24gc2hhcmRzLCBzbyBhdCBsZWFzdDpcbiAgICAgICAgICAgICAgICAtIHN1Y2ggY2x1c3RlciBzaG91bGQgZXhpc3Qgb24gdGhlIHNoYXJkLFxuICAgICAgICAgICAgICAgIC0gc3VjaCBjbHVzdGVyIHNob3VsZCBoYXZlIHRoZSBzYW1lIHNlY3JldC5cblxuICAgICAgICAgICAgICAgIEFuZCBhbHNvIChhbmQgd2hpY2ggaXMgbW9yZSBpbXBvcnRhbnQpLCB0aGUgaW5pdGlhbF91c2VyIHdpbGxcbiAgICAgICAgICAgICAgICBiZSB1c2VkIGFzIGN1cnJlbnQgdXNlciBmb3IgdGhlIHF1ZXJ5LlxuXG4gICAgICAgICAgICAgICAgUmlnaHQgbm93IHRoZSBwcm90b2NvbCBpcyBwcmV0dHkgc2ltcGxlIGFuZCBpdCBvbmx5IHRha2VzIGludG8gYWNjb3VudDpcbiAgICAgICAgICAgICAgICAtIGNsdXN0ZXIgbmFtZVxuICAgICAgICAgICAgICAgIC0gcXVlcnlcblxuICAgICAgICAgICAgICAgIEFsc28gaXQgd2lsbCBiZSBuaWNlIGlmIHRoZSBmb2xsb3dpbmcgd2lsbCBiZSBpbXBsZW1lbnRlZDpcbiAgICAgICAgICAgICAgICAtIHNvdXJjZSBob3N0bmFtZSAoc2VlIGludGVyc2VydmVyX2h0dHBfaG9zdCksIGJ1dCB0aGVuIGl0IHdpbGwgZGVwZW5kcyBmcm9tIEROUyxcbiAgICAgICAgICAgICAgICAgIGl0IGNhbiB1c2UgSVAgYWRkcmVzcyBpbnN0ZWFkLCBidXQgdGhlbiB0aGUgeW91IG5lZWQgdG8gZ2V0IGNvcnJlY3Qgb24gdGhlIGluaXRpYXRvciBub2RlLlxuICAgICAgICAgICAgICAgIC0gdGFyZ2V0IGhvc3RuYW1lIC8gaXAgYWRkcmVzcyAoc2FtZSBub3RlcyBhcyBmb3Igc291cmNlIGhvc3RuYW1lKVxuICAgICAgICAgICAgICAgIC0gdGltZS1iYXNlZCBzZWN1cml0eSB0b2tlbnNcbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPCEtLSA8c2VjcmV0Pjwvc2VjcmV0PiAtLT5cbiAgICAgICAgICAgIDxzaGFyZD5cbiAgICAgICAgICAgICAgICA8IS0tIE9wdGlvbmFsLiBXaGV0aGVyIHRvIHdyaXRlIGRhdGEgdG8ganVzdCBvbmUgb2YgdGhlIHJlcGxpY2FzLiBEZWZhdWx0OiBmYWxzZSAod3JpdGUgZGF0YSB0byBhbGwgcmVwbGljYXMpLiAtLT5cbiAgICAgICAgICAgICAgICA8IS0tIDxpbnRlcm5hbF9yZXBsaWNhdGlvbj5mYWxzZTwvaW50ZXJuYWxfcmVwbGljYXRpb24+IC0tPlxuICAgICAgICAgICAgICAgIDwhLS0gT3B0aW9uYWwuIFNoYXJkIHdlaWdodCB3aGVuIHdyaXRpbmcgZGF0YS4gRGVmYXVsdDogMS4gLS0+XG4gICAgICAgICAgICAgICAgPCEtLSA8d2VpZ2h0PjE8L3dlaWdodD4gLS0+XG4gICAgICAgICAgICAgICAgPHJlcGxpY2E+XG4gICAgICAgICAgICAgICAgICAgIDxob3N0PmNsaWNraG91c2U8L2hvc3Q+XG4gICAgICAgICAgICAgICAgICAgIDxwb3J0PjkwMDA8L3BvcnQ+XG4gICAgICAgICAgICAgICAgICAgIDwhLS0gT3B0aW9uYWwuIFByaW9yaXR5IG9mIHRoZSByZXBsaWNhIGZvciBsb2FkX2JhbGFuY2luZy4gRGVmYXVsdDogMSAobGVzcyB2YWx1ZSBoYXMgbW9yZSBwcmlvcml0eSkuIC0tPlxuICAgICAgICAgICAgICAgICAgICA8IS0tIDxwcmlvcml0eT4xPC9wcmlvcml0eT4gLS0+XG4gICAgICAgICAgICAgICAgPC9yZXBsaWNhPlxuICAgICAgICAgICAgPC9zaGFyZD5cbiAgICAgICAgICAgIDwhLS0gPHNoYXJkPlxuICAgICAgICAgICAgICAgIDxyZXBsaWNhPlxuICAgICAgICAgICAgICAgICAgICA8aG9zdD5jbGlja2hvdXNlLTI8L2hvc3Q+XG4gICAgICAgICAgICAgICAgICAgIDxwb3J0PjkwMDA8L3BvcnQ+XG4gICAgICAgICAgICAgICAgPC9yZXBsaWNhPlxuICAgICAgICAgICAgPC9zaGFyZD5cbiAgICAgICAgICAgIDxzaGFyZD5cbiAgICAgICAgICAgICAgICA8cmVwbGljYT5cbiAgICAgICAgICAgICAgICAgICAgPGhvc3Q+Y2xpY2tob3VzZS0zPC9ob3N0PlxuICAgICAgICAgICAgICAgICAgICA8cG9ydD45MDAwPC9wb3J0PlxuICAgICAgICAgICAgICAgIDwvcmVwbGljYT5cbiAgICAgICAgICAgIDwvc2hhcmQ+IC0tPlxuICAgICAgICA8L2NsdXN0ZXI+XG4gICAgPC9yZW1vdGVfc2VydmVycz5cbjwvY2xpY2tob3VzZT5cbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS91c2Vycy54bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvY2xpY2tob3VzZS1zZXJ2ZXIvdXNlcnMueG1sCiAgICAgICAgY29udGVudDogIjw/eG1sIHZlcnNpb249XCIxLjBcIj8+XG48Y2xpY2tob3VzZT5cbiAgICA8IS0tIFNlZSBhbHNvIHRoZSBmaWxlcyBpbiB1c2Vycy5kIGRpcmVjdG9yeSB3aGVyZSB0aGUgc2V0dGluZ3MgY2FuIGJlIG92ZXJyaWRkZW4uIC0tPlxuXG4gICAgPCEtLSBQcm9maWxlcyBvZiBzZXR0aW5ncy4gLS0+XG4gICAgPHByb2ZpbGVzPlxuICAgICAgICA8IS0tIERlZmF1bHQgc2V0dGluZ3MuIC0tPlxuICAgICAgICA8ZGVmYXVsdD5cbiAgICAgICAgICAgIDwhLS0gTWF4aW11bSBtZW1vcnkgdXNhZ2UgZm9yIHByb2Nlc3Npbmcgc2luZ2xlIHF1ZXJ5LCBpbiBieXRlcy4gLS0+XG4gICAgICAgICAgICA8bWF4X21lbW9yeV91c2FnZT4xMDAwMDAwMDAwMDwvbWF4X21lbW9yeV91c2FnZT5cblxuICAgICAgICAgICAgPCEtLSBIb3cgdG8gY2hvb3NlIGJldHdlZW4gcmVwbGljYXMgZHVyaW5nIGRpc3RyaWJ1dGVkIHF1ZXJ5IHByb2Nlc3NpbmcuXG4gICAgICAgICAgICAgICAgcmFuZG9tIC0gY2hvb3NlIHJhbmRvbSByZXBsaWNhIGZyb20gc2V0IG9mIHJlcGxpY2FzIHdpdGggbWluaW11bSBudW1iZXIgb2YgZXJyb3JzXG4gICAgICAgICAgICAgICAgbmVhcmVzdF9ob3N0bmFtZSAtIGZyb20gc2V0IG9mIHJlcGxpY2FzIHdpdGggbWluaW11bSBudW1iZXIgb2YgZXJyb3JzLCBjaG9vc2UgcmVwbGljYVxuICAgICAgICAgICAgICAgICAgd2l0aCBtaW5pbXVtIG51bWJlciBvZiBkaWZmZXJlbnQgc3ltYm9scyBiZXR3ZWVuIHJlcGxpY2EncyBob3N0bmFtZSBhbmQgbG9jYWwgaG9zdG5hbWVcbiAgICAgICAgICAgICAgICAgIChIYW1taW5nIGRpc3RhbmNlKS5cbiAgICAgICAgICAgICAgICBpbl9vcmRlciAtIGZpcnN0IGxpdmUgcmVwbGljYSBpcyBjaG9zZW4gaW4gc3BlY2lmaWVkIG9yZGVyLlxuICAgICAgICAgICAgICAgIGZpcnN0X29yX3JhbmRvbSAtIGlmIGZpcnN0IHJlcGxpY2Egb25lIGhhcyBoaWdoZXIgbnVtYmVyIG9mIGVycm9ycywgcGljayBhIHJhbmRvbSBvbmUgZnJvbSByZXBsaWNhcyB3aXRoIG1pbmltdW0gbnVtYmVyIG9mIGVycm9ycy5cbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPGxvYWRfYmFsYW5jaW5nPnJhbmRvbTwvbG9hZF9iYWxhbmNpbmc+XG4gICAgICAgIDwvZGVmYXVsdD5cblxuICAgICAgICA8IS0tIFByb2ZpbGUgdGhhdCBhbGxvd3Mgb25seSByZWFkIHF1ZXJpZXMuIC0tPlxuICAgICAgICA8cmVhZG9ubHk+XG4gICAgICAgICAgICA8cmVhZG9ubHk+MTwvcmVhZG9ubHk+XG4gICAgICAgIDwvcmVhZG9ubHk+XG4gICAgPC9wcm9maWxlcz5cblxuICAgIDwhLS0gVXNlcnMgYW5kIEFDTC4gLS0+XG4gICAgPHVzZXJzPlxuICAgICAgICA8IS0tIElmIHVzZXIgbmFtZSB3YXMgbm90IHNwZWNpZmllZCwgJ2RlZmF1bHQnIHVzZXIgaXMgdXNlZC4gLS0+XG4gICAgICAgIDxkZWZhdWx0PlxuICAgICAgICAgICAgPCEtLSBTZWUgYWxzbyB0aGUgZmlsZXMgaW4gdXNlcnMuZCBkaXJlY3Rvcnkgd2hlcmUgdGhlIHBhc3N3b3JkIGNhbiBiZSBvdmVycmlkZGVuLlxuXG4gICAgICAgICAgICAgICAgUGFzc3dvcmQgY291bGQgYmUgc3BlY2lmaWVkIGluIHBsYWludGV4dCBvciBpbiBTSEEyNTYgKGluIGhleCBmb3JtYXQpLlxuXG4gICAgICAgICAgICAgICAgSWYgeW91IHdhbnQgdG8gc3BlY2lmeSBwYXNzd29yZCBpbiBwbGFpbnRleHQgKG5vdCByZWNvbW1lbmRlZCksIHBsYWNlIGl0IGluICdwYXNzd29yZCcgZWxlbWVudC5cbiAgICAgICAgICAgICAgICBFeGFtcGxlOiA8cGFzc3dvcmQ+cXdlcnR5PC9wYXNzd29yZD4uXG4gICAgICAgICAgICAgICAgUGFzc3dvcmQgY291bGQgYmUgZW1wdHkuXG5cbiAgICAgICAgICAgICAgICBJZiB5b3Ugd2FudCB0byBzcGVjaWZ5IFNIQTI1NiwgcGxhY2UgaXQgaW4gJ3Bhc3N3b3JkX3NoYTI1Nl9oZXgnIGVsZW1lbnQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPHBhc3N3b3JkX3NoYTI1Nl9oZXg+NjVlODRiZTMzNTMyZmI3ODRjNDgxMjk2NzVmOWVmZjNhNjgyYjI3MTY4YzBlYTc0NGIyY2Y1OGVlMDIzMzdjNTwvcGFzc3dvcmRfc2hhMjU2X2hleD5cbiAgICAgICAgICAgICAgICBSZXN0cmljdGlvbnMgb2YgU0hBMjU2OiBpbXBvc3NpYmlsaXR5IHRvIGNvbm5lY3QgdG8gQ2xpY2tIb3VzZSB1c2luZyBNeVNRTCBKUyBjbGllbnQgKGFzIG9mIEp1bHkgMjAxOSkuXG5cbiAgICAgICAgICAgICAgICBJZiB5b3Ugd2FudCB0byBzcGVjaWZ5IGRvdWJsZSBTSEExLCBwbGFjZSBpdCBpbiAncGFzc3dvcmRfZG91YmxlX3NoYTFfaGV4JyBlbGVtZW50LlxuICAgICAgICAgICAgICAgIEV4YW1wbGU6IDxwYXNzd29yZF9kb3VibGVfc2hhMV9oZXg+ZTM5NTc5NmQ2NTQ2YjFiNjVkYjlkNjY1Y2Q0M2YwZTg1OGRkNDMwMzwvcGFzc3dvcmRfZG91YmxlX3NoYTFfaGV4PlxuXG4gICAgICAgICAgICAgICAgSWYgeW91IHdhbnQgdG8gc3BlY2lmeSBhIHByZXZpb3VzbHkgZGVmaW5lZCBMREFQIHNlcnZlciAoc2VlICdsZGFwX3NlcnZlcnMnIGluIHRoZSBtYWluIGNvbmZpZykgZm9yIGF1dGhlbnRpY2F0aW9uLFxuICAgICAgICAgICAgICAgICAgcGxhY2UgaXRzIG5hbWUgaW4gJ3NlcnZlcicgZWxlbWVudCBpbnNpZGUgJ2xkYXAnIGVsZW1lbnQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGxkYXA+PHNlcnZlcj5teV9sZGFwX3NlcnZlcjwvc2VydmVyPjwvbGRhcD5cblxuICAgICAgICAgICAgICAgIElmIHlvdSB3YW50IHRvIGF1dGhlbnRpY2F0ZSB0aGUgdXNlciB2aWEgS2VyYmVyb3MgKGFzc3VtaW5nIEtlcmJlcm9zIGlzIGVuYWJsZWQsIHNlZSAna2VyYmVyb3MnIGluIHRoZSBtYWluIGNvbmZpZyksXG4gICAgICAgICAgICAgICAgICBwbGFjZSAna2VyYmVyb3MnIGVsZW1lbnQgaW5zdGVhZCBvZiAncGFzc3dvcmQnIChhbmQgc2ltaWxhcikgZWxlbWVudHMuXG4gICAgICAgICAgICAgICAgVGhlIG5hbWUgcGFydCBvZiB0aGUgY2Fub25pY2FsIHByaW5jaXBhbCBuYW1lIG9mIHRoZSBpbml0aWF0b3IgbXVzdCBtYXRjaCB0aGUgdXNlciBuYW1lIGZvciBhdXRoZW50aWNhdGlvbiB0byBzdWNjZWVkLlxuICAgICAgICAgICAgICAgIFlvdSBjYW4gYWxzbyBwbGFjZSAncmVhbG0nIGVsZW1lbnQgaW5zaWRlICdrZXJiZXJvcycgZWxlbWVudCB0byBmdXJ0aGVyIHJlc3RyaWN0IGF1dGhlbnRpY2F0aW9uIHRvIG9ubHkgdGhvc2UgcmVxdWVzdHNcbiAgICAgICAgICAgICAgICAgIHdob3NlIGluaXRpYXRvcidzIHJlYWxtIG1hdGNoZXMgaXQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGtlcmJlcm9zIC8+XG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGtlcmJlcm9zPjxyZWFsbT5FWEFNUExFLkNPTTwvcmVhbG0+PC9rZXJiZXJvcz5cblxuICAgICAgICAgICAgICAgIEhvdyB0byBnZW5lcmF0ZSBkZWNlbnQgcGFzc3dvcmQ6XG4gICAgICAgICAgICAgICAgRXhlY3V0ZTogUEFTU1dPUkQ9JChiYXNlNjQgPCAvZGV2L3VyYW5kb20gfCBoZWFkIC1jOCk7IGVjaG8gXCIkUEFTU1dPUkRcIjsgZWNobyAtbiBcIiRQQVNTV09SRFwiIHwgc2hhMjU2c3VtIHwgdHIgLWQgJy0nXG4gICAgICAgICAgICAgICAgSW4gZmlyc3QgbGluZSB3aWxsIGJlIHBhc3N3b3JkIGFuZCBpbiBzZWNvbmQgLSBjb3JyZXNwb25kaW5nIFNIQTI1Ni5cblxuICAgICAgICAgICAgICAgIEhvdyB0byBnZW5lcmF0ZSBkb3VibGUgU0hBMTpcbiAgICAgICAgICAgICAgICBFeGVjdXRlOiBQQVNTV09SRD0kKGJhc2U2NCA8IC9kZXYvdXJhbmRvbSB8IGhlYWQgLWM4KTsgZWNobyBcIiRQQVNTV09SRFwiOyBlY2hvIC1uIFwiJFBBU1NXT1JEXCIgfCBzaGExc3VtIHwgdHIgLWQgJy0nIHwgeHhkIC1yIC1wIHwgc2hhMXN1bSB8IHRyIC1kICctJ1xuICAgICAgICAgICAgICAgIEluIGZpcnN0IGxpbmUgd2lsbCBiZSBwYXNzd29yZCBhbmQgaW4gc2Vjb25kIC0gY29ycmVzcG9uZGluZyBkb3VibGUgU0hBMS5cbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPHBhc3N3b3JkPjwvcGFzc3dvcmQ+XG5cbiAgICAgICAgICAgIDwhLS0gTGlzdCBvZiBuZXR3b3JrcyB3aXRoIG9wZW4gYWNjZXNzLlxuXG4gICAgICAgICAgICAgICAgVG8gb3BlbiBhY2Nlc3MgZnJvbSBldmVyeXdoZXJlLCBzcGVjaWZ5OlxuICAgICAgICAgICAgICAgICAgICA8aXA+OjovMDwvaXA+XG5cbiAgICAgICAgICAgICAgICBUbyBvcGVuIGFjY2VzcyBvbmx5IGZyb20gbG9jYWxob3N0LCBzcGVjaWZ5OlxuICAgICAgICAgICAgICAgICAgICA8aXA+OjoxPC9pcD5cbiAgICAgICAgICAgICAgICAgICAgPGlwPjEyNy4wLjAuMTwvaXA+XG5cbiAgICAgICAgICAgICAgICBFYWNoIGVsZW1lbnQgb2YgbGlzdCBoYXMgb25lIG9mIHRoZSBmb2xsb3dpbmcgZm9ybXM6XG4gICAgICAgICAgICAgICAgPGlwPiBJUC1hZGRyZXNzIG9yIG5ldHdvcmsgbWFzay4gRXhhbXBsZXM6IDIxMy4xODAuMjA0LjMgb3IgMTAuMC4wLjEvOCBvciAxMC4wLjAuMS8yNTUuMjU1LjI1NS4wXG4gICAgICAgICAgICAgICAgICAgIDJhMDI6NmI4OjozIG9yIDJhMDI6NmI4OjozLzY0IG9yIDJhMDI6NmI4OjozL2ZmZmY6ZmZmZjpmZmZmOmZmZmY6Oi5cbiAgICAgICAgICAgICAgICA8aG9zdD4gSG9zdG5hbWUuIEV4YW1wbGU6IHNlcnZlcjAxLmNsaWNraG91c2UuY29tLlxuICAgICAgICAgICAgICAgICAgICBUbyBjaGVjayBhY2Nlc3MsIEROUyBxdWVyeSBpcyBwZXJmb3JtZWQsIGFuZCBhbGwgcmVjZWl2ZWQgYWRkcmVzc2VzIGNvbXBhcmVkIHRvIHBlZXIgYWRkcmVzcy5cbiAgICAgICAgICAgICAgICA8aG9zdF9yZWdleHA+IFJlZ3VsYXIgZXhwcmVzc2lvbiBmb3IgaG9zdCBuYW1lcy4gRXhhbXBsZSwgXnNlcnZlclxcZFxcZC1cXGRcXGQtXFxkXFwuY2xpY2tob3VzZVxcLmNvbSRcbiAgICAgICAgICAgICAgICAgICAgVG8gY2hlY2sgYWNjZXNzLCBETlMgUFRSIHF1ZXJ5IGlzIHBlcmZvcm1lZCBmb3IgcGVlciBhZGRyZXNzIGFuZCB0aGVuIHJlZ2V4cCBpcyBhcHBsaWVkLlxuICAgICAgICAgICAgICAgICAgICBUaGVuLCBmb3IgcmVzdWx0IG9mIFBUUiBxdWVyeSwgYW5vdGhlciBETlMgcXVlcnkgaXMgcGVyZm9ybWVkIGFuZCBhbGwgcmVjZWl2ZWQgYWRkcmVzc2VzIGNvbXBhcmVkIHRvIHBlZXIgYWRkcmVzcy5cbiAgICAgICAgICAgICAgICAgICAgU3Ryb25nbHkgcmVjb21tZW5kZWQgdGhhdCByZWdleHAgaXMgZW5kcyB3aXRoICRcbiAgICAgICAgICAgICAgICBBbGwgcmVzdWx0cyBvZiBETlMgcmVxdWVzdHMgYXJlIGNhY2hlZCB0aWxsIHNlcnZlciByZXN0YXJ0LlxuICAgICAgICAgICAgLS0+XG4gICAgICAgICAgICA8bmV0d29ya3M+XG4gICAgICAgICAgICAgICAgPGlwPjo6LzA8L2lwPlxuICAgICAgICAgICAgPC9uZXR3b3Jrcz5cblxuICAgICAgICAgICAgPCEtLSBTZXR0aW5ncyBwcm9maWxlIGZvciB1c2VyLiAtLT5cbiAgICAgICAgICAgIDxwcm9maWxlPmRlZmF1bHQ8L3Byb2ZpbGU+XG5cbiAgICAgICAgICAgIDwhLS0gUXVvdGEgZm9yIHVzZXIuIC0tPlxuICAgICAgICAgICAgPHF1b3RhPmRlZmF1bHQ8L3F1b3RhPlxuXG4gICAgICAgICAgICA8IS0tIFVzZXIgY2FuIGNyZWF0ZSBvdGhlciB1c2VycyBhbmQgZ3JhbnQgcmlnaHRzIHRvIHRoZW0uIC0tPlxuICAgICAgICAgICAgPCEtLSA8YWNjZXNzX21hbmFnZW1lbnQ+MTwvYWNjZXNzX21hbmFnZW1lbnQ+IC0tPlxuICAgICAgICA8L2RlZmF1bHQ+XG4gICAgPC91c2Vycz5cblxuICAgIDwhLS0gUXVvdGFzLiAtLT5cbiAgICA8cXVvdGFzPlxuICAgICAgICA8IS0tIE5hbWUgb2YgcXVvdGEuIC0tPlxuICAgICAgICA8ZGVmYXVsdD5cbiAgICAgICAgICAgIDwhLS0gTGltaXRzIGZvciB0aW1lIGludGVydmFsLiBZb3UgY291bGQgc3BlY2lmeSBtYW55IGludGVydmFscyB3aXRoIGRpZmZlcmVudCBsaW1pdHMuIC0tPlxuICAgICAgICAgICAgPGludGVydmFsPlxuICAgICAgICAgICAgICAgIDwhLS0gTGVuZ3RoIG9mIGludGVydmFsLiAtLT5cbiAgICAgICAgICAgICAgICA8ZHVyYXRpb24+MzYwMDwvZHVyYXRpb24+XG5cbiAgICAgICAgICAgICAgICA8IS0tIE5vIGxpbWl0cy4gSnVzdCBjYWxjdWxhdGUgcmVzb3VyY2UgdXNhZ2UgZm9yIHRpbWUgaW50ZXJ2YWwuIC0tPlxuICAgICAgICAgICAgICAgIDxxdWVyaWVzPjA8L3F1ZXJpZXM+XG4gICAgICAgICAgICAgICAgPGVycm9ycz4wPC9lcnJvcnM+XG4gICAgICAgICAgICAgICAgPHJlc3VsdF9yb3dzPjA8L3Jlc3VsdF9yb3dzPlxuICAgICAgICAgICAgICAgIDxyZWFkX3Jvd3M+MDwvcmVhZF9yb3dzPlxuICAgICAgICAgICAgICAgIDxleGVjdXRpb25fdGltZT4wPC9leGVjdXRpb25fdGltZT5cbiAgICAgICAgICAgIDwvaW50ZXJ2YWw+XG4gICAgICAgIDwvZGVmYXVsdD5cbiAgICA8L3F1b3Rhcz5cbjwvY2xpY2tob3VzZT5cbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS9jb25maWcueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy54bWwKICAgICAgICBjb250ZW50OiAiPD94bWwgdmVyc2lvbj1cIjEuMFwiPz5cbjxjbGlja2hvdXNlPlxuICA8bWF4X2Nvbm5lY3Rpb25zPjQwOTY8L21heF9jb25uZWN0aW9ucz5cbiAgPGtlZXBfYWxpdmVfdGltZW91dD4zPC9rZWVwX2FsaXZlX3RpbWVvdXQ+XG4gIDxtYXhfY29uY3VycmVudF9xdWVyaWVzPjEwMDwvbWF4X2NvbmN1cnJlbnRfcXVlcmllcz5cbiAgPG1hcmtfY2FjaGVfc2l6ZT41MzY4NzA5MTIwPC9tYXJrX2NhY2hlX3NpemU+XG4gIDxtbWFwX2NhY2hlX3NpemU+MTAwMDwvbW1hcF9jYWNoZV9zaXplPlxuICA8Y29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9zaXplPjEzNDIxNzcyODwvY29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9zaXplPlxuICA8Y29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9lbGVtZW50c19zaXplPjEwMDAwPC9jb21waWxlZF9leHByZXNzaW9uX2NhY2hlX2VsZW1lbnRzX3NpemU+XG4gIDxjdXN0b21fc2V0dGluZ3NfcHJlZml4ZXM+PC9jdXN0b21fc2V0dGluZ3NfcHJlZml4ZXM+XG4gIDxkaWN0aW9uYXJpZXNfY29uZmlnPipfZGljdGlvbmFyeS54bWw8L2RpY3Rpb25hcmllc19jb25maWc+XG4gIDx1c2VyX2RlZmluZWRfZXhlY3V0YWJsZV9mdW5jdGlvbnNfY29uZmlnPipmdW5jdGlvbi54bWw8L3VzZXJfZGVmaW5lZF9leGVjdXRhYmxlX2Z1bmN0aW9uc19jb25maWc+XG4gIDx1c2VyX3NjcmlwdHNfcGF0aD4vdmFyL2xpYi9jbGlja2hvdXNlL3VzZXJfc2NyaXB0cy88L3VzZXJfc2NyaXB0c19wYXRoPlxuICA8aHR0cF9wb3J0PjgxMjM8L2h0dHBfcG9ydD5cbiAgPHRjcF9wb3J0PjkwMDA8L3RjcF9wb3J0PlxuICA8bXlzcWxfcG9ydD45MDA0PC9teXNxbF9wb3J0PlxuICA8cG9zdGdyZXNxbF9wb3J0PjkwMDU8L3Bvc3RncmVzcWxfcG9ydD5cbiAgPGludGVyc2VydmVyX2h0dHBfcG9ydD45MDA5PC9pbnRlcnNlcnZlcl9odHRwX3BvcnQ+XG4gIDxsb2dnZXI+XG4gICAgPGxldmVsPmluZm9ybWF0aW9uPC9sZXZlbD5cbiAgICA8Zm9ybWF0dGluZz5cbiAgICAgIDx0eXBlPmpzb248L3R5cGU+XG4gICAgPC9mb3JtYXR0aW5nPlxuICA8L2xvZ2dlcj5cbiAgPG1hY3Jvcz5cbiAgICA8c2hhcmQ+MDE8L3NoYXJkPlxuICAgIDxyZXBsaWNhPmV4YW1wbGUwMS0wMS0xPC9yZXBsaWNhPlxuICA8L21hY3Jvcz5cbiAgPHByb21ldGhldXM+XG4gICAgPGVuZHBvaW50Pi9tZXRyaWNzPC9lbmRwb2ludD5cbiAgICA8cG9ydD45MzYzPC9wb3J0PlxuICAgIDxtZXRyaWNzPnRydWU8L21ldHJpY3M+XG4gICAgPGV2ZW50cz50cnVlPC9ldmVudHM+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNzPnRydWU8L2FzeW5jaHJvbm91c19tZXRyaWNzPlxuICAgIDxzdGF0dXNfaW5mbz50cnVlPC9zdGF0dXNfaW5mbz5cbiAgPC9wcm9tZXRoZXVzPlxuICA8b3BlbnRlbGVtZXRyeV9zcGFuX2xvZz5cbiAgICA8ZW5naW5lPmVuZ2luZSBNZXJnZVRyZWVcbiAgICAgICAgICAgIHBhcnRpdGlvbiBieSB0b1lZWVlNTShmaW5pc2hfZGF0ZSlcbiAgICAgICAgICAgIG9yZGVyIGJ5IChmaW5pc2hfZGF0ZSwgZmluaXNoX3RpbWVfdXMsIHRyYWNlX2lkKTwvZW5naW5lPlxuICA8L29wZW50ZWxlbWV0cnlfc3Bhbl9sb2c+XG4gIDxxdWVyeV9tYXNraW5nX3J1bGVzPlxuICAgIDxydWxlPlxuICAgICAgPG5hbWU+aGlkZSBlbmNyeXB0L2RlY3J5cHQgYXJndW1lbnRzPC9uYW1lPlxuICAgICAgPHJlZ2V4cD4oKD86YWVzXyk/KD86ZW5jcnlwdHxkZWNyeXB0KSg/Ol9teXNxbCk/KVxccypcXChcXHMqKD86Jyg/OlxcXFwnfC4pKyd8Lio/KVxccypcXCk8L3JlZ2V4cD5cbiAgICAgIDxyZXBsYWNlPlxcMSg/Pz8pPC9yZXBsYWNlPlxuICAgIDwvcnVsZT5cbiAgPC9xdWVyeV9tYXNraW5nX3J1bGVzPlxuICA8c2VuZF9jcmFzaF9yZXBvcnRzPlxuICAgIDxlbmFibGVkPmZhbHNlPC9lbmFibGVkPlxuICAgIDxhbm9ueW1pemU+ZmFsc2U8L2Fub255bWl6ZT5cbiAgICA8ZW5kcG9pbnQ+aHR0cHM6Ly82ZjMzMDM0Y2ZlNjg0ZGQ3YTNhYjk4NzVlNTdiMWM4ZEBvMzg4ODcwLmluZ2VzdC5zZW50cnkuaW8vNTIyNjI3NzwvZW5kcG9pbnQ+XG4gIDwvc2VuZF9jcmFzaF9yZXBvcnRzPlxuICA8bWVyZ2VfdHJlZV9tZXRhZGF0YV9jYWNoZT5cbiAgICA8bHJ1X2NhY2hlX3NpemU+MjY4NDM1NDU2PC9scnVfY2FjaGVfc2l6ZT5cbiAgICA8Y29udGludWVfaWZfY29ycnVwdGVkPnRydWU8L2NvbnRpbnVlX2lmX2NvcnJ1cHRlZD5cbiAgPC9tZXJnZV90cmVlX21ldGFkYXRhX2NhY2hlPlxuICA8dXNlcl9kaXJlY3Rvcmllcz5cbiAgICA8dXNlcnNfeG1sPlxuICAgICAgICA8IS0tIFBhdGggdG8gY29uZmlndXJhdGlvbiBmaWxlIHdpdGggcHJlZGVmaW5lZCB1c2Vycy4gLS0+XG4gICAgICAgIDxwYXRoPnVzZXJzLnhtbDwvcGF0aD5cbiAgICA8L3VzZXJzX3htbD5cbiAgICA8bG9jYWxfZGlyZWN0b3J5PlxuICAgICAgICA8IS0tIFBhdGggdG8gZm9sZGVyIHdoZXJlIHVzZXJzIGNyZWF0ZWQgYnkgU1FMIGNvbW1hbmRzIGFyZSBzdG9yZWQuIC0tPlxuICAgICAgICA8cGF0aD4vdmFyL2xpYi9jbGlja2hvdXNlL2FjY2Vzcy88L3BhdGg+XG4gICAgPC9sb2NhbF9kaXJlY3Rvcnk+XG4gIDwvdXNlcl9kaXJlY3Rvcmllcz5cbiAgPGRlZmF1bHRfcHJvZmlsZT5kZWZhdWx0PC9kZWZhdWx0X3Byb2ZpbGU+XG4gICAgPGRpc3RyaWJ1dGVkX2RkbD5cbiAgICAgICAgPCEtLSBQYXRoIGluIFpvb0tlZXBlciB0byBxdWV1ZSB3aXRoIERETCBxdWVyaWVzIC0tPlxuICAgICAgICA8cGF0aD4vY2xpY2tob3VzZS90YXNrX3F1ZXVlL2RkbDwvcGF0aD5cbiAgICA8L2Rpc3RyaWJ1dGVkX2RkbD5cbjwvY2xpY2tob3VzZT5cbiIKICBzaWdub3o6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3o6djAuOTcuMScKICAgIGRlcGVuZHNfb246CiAgICAgIGNsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc2NoZW1hLW1pZ3JhdG9yLXN5bmM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2NvbXBsZXRlZF9zdWNjZXNzZnVsbHkKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGNvbW1hbmQ6CiAgICAgIC0gJy0tY29uZmlnPS9yb290L2NvbmZpZy9wcm9tZXRoZXVzLnltbCcKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3Byb21ldGhldXMueW1sCiAgICAgICAgdGFyZ2V0OiAvcm9vdC9jb25maWcvcHJvbWV0aGV1cy55bWwKICAgICAgICBjb250ZW50OiAiIyBteSBnbG9iYWwgY29uZmlnXG5nbG9iYWw6XG4gIHNjcmFwZV9pbnRlcnZhbDogICAgIDVzICMgU2V0IHRoZSBzY3JhcGUgaW50ZXJ2YWwgdG8gZXZlcnkgMTUgc2Vjb25kcy4gRGVmYXVsdCBpcyBldmVyeSAxIG1pbnV0ZS5cbiAgZXZhbHVhdGlvbl9pbnRlcnZhbDogMTVzICMgRXZhbHVhdGUgcnVsZXMgZXZlcnkgMTUgc2Vjb25kcy4gVGhlIGRlZmF1bHQgaXMgZXZlcnkgMSBtaW51dGUuXG4gICMgc2NyYXBlX3RpbWVvdXQgaXMgc2V0IHRvIHRoZSBnbG9iYWwgZGVmYXVsdCAoMTBzKS5cblxuIyBBbGVydG1hbmFnZXIgY29uZmlndXJhdGlvblxuYWxlcnRpbmc6XG4gIGFsZXJ0bWFuYWdlcnM6XG4gIC0gc3RhdGljX2NvbmZpZ3M6XG4gICAgLSB0YXJnZXRzOlxuICAgICAgLSBhbGVydG1hbmFnZXI6OTA5M1xuXG4jIExvYWQgcnVsZXMgb25jZSBhbmQgcGVyaW9kaWNhbGx5IGV2YWx1YXRlIHRoZW0gYWNjb3JkaW5nIHRvIHRoZSBnbG9iYWwgJ2V2YWx1YXRpb25faW50ZXJ2YWwnLlxucnVsZV9maWxlczogW11cbiAgIyAtIFwiZmlyc3RfcnVsZXMueW1sXCJcbiAgIyAtIFwic2Vjb25kX3J1bGVzLnltbFwiXG4gICMgLSAnYWxlcnRzLnltbCdcblxuIyBBIHNjcmFwZSBjb25maWd1cmF0aW9uIGNvbnRhaW5pbmcgZXhhY3RseSBvbmUgZW5kcG9pbnQgdG8gc2NyYXBlOlxuIyBIZXJlIGl0J3MgUHJvbWV0aGV1cyBpdHNlbGYuXG5zY3JhcGVfY29uZmlnczogW11cblxucmVtb3RlX3JlYWQ6XG4gIC0gdXJsOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X21ldHJpY3NcbiIKICAgICAgLQogICAgICAgIHR5cGU6IHZvbHVtZQogICAgICAgIHNvdXJjZTogc3FsaXRlCiAgICAgICAgdGFyZ2V0OiAvdmFyL2xpYi9zaWdub3ovCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fU0lHTk9aXzgwODAKICAgICAgLSAnU0lHTk9aX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1JFQUxCQVNFNjRfSldUU0VDUkVUfScKICAgICAgLSAnU0lHTk9aX1RFTEVNRVRSWVNUT1JFX0NMSUNLSE9VU0VfRFNOPXRjcDovL2NsaWNraG91c2U6OTAwMCcKICAgICAgLSBTSUdOT1pfU1FMU1RPUkVfU1FMSVRFX1BBVEg9L3Zhci9saWIvc2lnbm96L3NpZ25vei5kYgogICAgICAtIERBU0hCT0FSRFNfUEFUSD0vcm9vdC9jb25maWcvZGFzaGJvYXJkcwogICAgICAtIFNUT1JBR0U9Y2xpY2tob3VzZQogICAgICAtIEdPREVCVUc9bmV0ZG5zPWdvCiAgICAgIC0gREVQTE9ZTUVOVF9UWVBFPWRvY2tlci1zdGFuZGFsb25lLWFtZAogICAgICAtICdTSUdOT1pfU1RBVFNSRVBPUlRFUl9FTkFCTEVEPSR7U0lHTk9aX1NUQVRTUkVQT1JURVJfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ1NJR05PWl9FTUFJTElOR19FTkFCTEVEPSR7U0lHTk9aX0VNQUlMSU5HX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnU0lHTk9aX0VNQUlMSU5HX1NNVFBfQUREUkVTUz0ke1NJR05PWl9FTUFJTElOR19TTVRQX0FERFJFU1N9JwogICAgICAtICdTSUdOT1pfRU1BSUxJTkdfU01UUF9GUk9NPSR7U0lHTk9aX0VNQUlMSU5HX1NNVFBfRlJPTX0nCiAgICAgIC0gJ1NJR05PWl9FTUFJTElOR19TTVRQX0FVVEhfVVNFUk5BTUU9JHtTSUdOT1pfRU1BSUxJTkdfU01UUF9BVVRIX1VTRVJOQU1FfScKICAgICAgLSAnU0lHTk9aX0VNQUlMSU5HX1NNVFBfQVVUSF9QQVNTV09SRD0ke1NJR05PWl9FTUFJTElOR19TTVRQX0FVVEhfUEFTU1dPUkR9JwogICAgICAtIFNJR05PWl9BTEVSVE1BTkFHRVJfUFJPVklERVI9c2lnbm96CiAgICAgIC0gJ1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19BVVRIX19QQVNTV09SRD0ke1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19BVVRIX19QQVNTV09SRH0nCiAgICAgIC0gJ1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19BVVRIX19VU0VSTkFNRT0ke1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19BVVRIX19VU0VSTkFNRX0nCiAgICAgIC0gJ1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19GUk9NPSR7U0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0ZST019JwogICAgICAtICdTSUdOT1pfQUxFUlRNQU5BR0VSX1NJR05PWl9HTE9CQUxfU01UUF9fU01BUlRIT1NUPSR7U0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX1NNQVJUSE9TVH0nCiAgICAgIC0gRE9UX01FVFJJQ1NfRU5BQkVMRD10cnVlCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJy1xJwogICAgICAgIC0gJ2xvY2FsaG9zdDo4MDgwL2FwaS92MS9oZWFsdGgnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogIG90ZWwtY29sbGVjdG9yOgogICAgaW1hZ2U6ICdzaWdub3ovc2lnbm96LW90ZWwtY29sbGVjdG9yOnYwLjEyOS43JwogICAgZGVwZW5kc19vbjoKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzY2hlbWEtbWlncmF0b3Itc3luYzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgICBzaWdub3o6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGNvbW1hbmQ6CiAgICAgIC0gJy0tY29uZmlnPS9ldGMvb3RlbC1jb2xsZWN0b3ItY29uZmlnLnlhbWwnCiAgICAgIC0gJy0tbWFuYWdlci1jb25maWc9L2V0Yy9tYW5hZ2VyLWNvbmZpZy55YW1sJwogICAgICAtICctLWNvcHktcGF0aD0vdmFyL3RtcC9jb2xsZWN0b3ItY29uZmlnLnlhbWwnCiAgICAgIC0gJy0tZmVhdHVyZS1nYXRlcz0tcGtnLnRyYW5zbGF0b3IucHJvbWV0aGV1cy5Ob3JtYWxpemVOYW1lJwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vb3RlbC1jb2xsZWN0b3ItY29uZmlnLnlhbWwKICAgICAgICB0YXJnZXQ6IC9ldGMvb3RlbC1jb2xsZWN0b3ItY29uZmlnLnlhbWwKICAgICAgICBjb250ZW50OiAicmVjZWl2ZXJzOlxuICBvdGxwOlxuICAgIHByb3RvY29sczpcbiAgICAgIGdycGM6XG4gICAgICAgIGVuZHBvaW50OiAwLjAuMC4wOjQzMTdcbiAgICAgIGh0dHA6XG4gICAgICAgIGVuZHBvaW50OiAwLjAuMC4wOjQzMThcbiAgcHJvbWV0aGV1czpcbiAgICBjb25maWc6XG4gICAgICBnbG9iYWw6XG4gICAgICAgIHNjcmFwZV9pbnRlcnZhbDogNjBzXG4gICAgICBzY3JhcGVfY29uZmlnczpcbiAgICAgICAgLSBqb2JfbmFtZTogb3RlbC1jb2xsZWN0b3JcbiAgICAgICAgICBzdGF0aWNfY29uZmlnczpcbiAgICAgICAgICAtIHRhcmdldHM6XG4gICAgICAgICAgICAgIC0gbG9jYWxob3N0Ojg4ODhcbiAgICAgICAgICAgIGxhYmVsczpcbiAgICAgICAgICAgICAgam9iX25hbWU6IG90ZWwtY29sbGVjdG9yXG5wcm9jZXNzb3JzOlxuICBiYXRjaDpcbiAgICBzZW5kX2JhdGNoX3NpemU6IDEwMDAwXG4gICAgc2VuZF9iYXRjaF9tYXhfc2l6ZTogMTEwMDBcbiAgICB0aW1lb3V0OiAxMHNcbiAgcmVzb3VyY2VkZXRlY3Rpb246XG4gICAgIyBVc2luZyBPVEVMX1JFU09VUkNFX0FUVFJJQlVURVMgZW52dmFyLCBlbnYgZGV0ZWN0b3IgYWRkcyBjdXN0b20gbGFiZWxzLlxuICAgIGRldGVjdG9yczogW2Vudiwgc3lzdGVtXVxuICAgIHRpbWVvdXQ6IDJzXG4gIHNpZ25venNwYW5tZXRyaWNzL2RlbHRhOlxuICAgIG1ldHJpY3NfZXhwb3J0ZXI6IHNpZ25vemNsaWNraG91c2VtZXRyaWNzXG4gICAgbWV0cmljc19mbHVzaF9pbnRlcnZhbDogNjBzXG4gICAgbGF0ZW5jeV9oaXN0b2dyYW1fYnVja2V0czogWzEwMHVzLCAxbXMsIDJtcywgNm1zLCAxMG1zLCA1MG1zLCAxMDBtcywgMjUwbXMsIDUwMG1zLCAxMDAwbXMsIDE0MDBtcywgMjAwMG1zLCA1cywgMTBzLCAyMHMsIDQwcywgNjBzIF1cbiAgICBkaW1lbnNpb25zX2NhY2hlX3NpemU6IDEwMDAwMFxuICAgIGFnZ3JlZ2F0aW9uX3RlbXBvcmFsaXR5OiBBR0dSRUdBVElPTl9URU1QT1JBTElUWV9ERUxUQVxuICAgIGVuYWJsZV9leHBfaGlzdG9ncmFtOiB0cnVlXG4gICAgZGltZW5zaW9uczpcbiAgICAgIC0gbmFtZTogc2VydmljZS5uYW1lc3BhY2VcbiAgICAgICAgZGVmYXVsdDogZGVmYXVsdFxuICAgICAgLSBuYW1lOiBkZXBsb3ltZW50LmVudmlyb25tZW50XG4gICAgICAgIGRlZmF1bHQ6IGRlZmF1bHRcbiAgICAgICMgVGhpcyBpcyBhZGRlZCB0byBlbnN1cmUgdGhlIHVuaXF1ZW5lc3Mgb2YgdGhlIHRpbWVzZXJpZXNcbiAgICAgICMgT3RoZXJ3aXNlLCBpZGVudGljYWwgdGltZXNlcmllcyBwcm9kdWNlZCBieSBtdWx0aXBsZSByZXBsaWNhcyBvZlxuICAgICAgIyBjb2xsZWN0b3JzIHJlc3VsdCBpbiBpbmNvcnJlY3QgQVBNIG1ldHJpY3NcbiAgICAgIC0gbmFtZTogc2lnbm96LmNvbGxlY3Rvci5pZFxuICAgICAgLSBuYW1lOiBzZXJ2aWNlLnZlcnNpb25cbiAgICAgIC0gbmFtZTogYnJvd3Nlci5wbGF0Zm9ybVxuICAgICAgLSBuYW1lOiBicm93c2VyLm1vYmlsZVxuICAgICAgLSBuYW1lOiBrOHMuY2x1c3Rlci5uYW1lXG4gICAgICAtIG5hbWU6IGs4cy5ub2RlLm5hbWVcbiAgICAgIC0gbmFtZTogazhzLm5hbWVzcGFjZS5uYW1lXG4gICAgICAtIG5hbWU6IGhvc3QubmFtZVxuICAgICAgLSBuYW1lOiBob3N0LnR5cGVcbiAgICAgIC0gbmFtZTogY29udGFpbmVyLm5hbWVcbmV4dGVuc2lvbnM6XG4gIGhlYWx0aF9jaGVjazpcbiAgICBlbmRwb2ludDogMC4wLjAuMDoxMzEzM1xuICBwcHJvZjpcbiAgICBlbmRwb2ludDogMC4wLjAuMDoxNzc3XG5leHBvcnRlcnM6XG4gIGNsaWNraG91c2V0cmFjZXM6XG4gICAgZGF0YXNvdXJjZTogdGNwOi8vY2xpY2tob3VzZTo5MDAwL3NpZ25vel90cmFjZXNcbiAgICBsb3dfY2FyZGluYWxfZXhjZXB0aW9uX2dyb3VwaW5nOiAke2VudjpMT1dfQ0FSRElOQUxfRVhDRVBUSU9OX0dST1VQSU5HfVxuICAgIHVzZV9uZXdfc2NoZW1hOiB0cnVlXG4gIHNpZ25vemNsaWNraG91c2VtZXRyaWNzOlxuICAgIGRzbjogdGNwOi8vY2xpY2tob3VzZTo5MDAwL3NpZ25vel9tZXRyaWNzXG4gIGNsaWNraG91c2Vsb2dzZXhwb3J0ZXI6XG4gICAgZHNuOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X2xvZ3NcbiAgICB0aW1lb3V0OiAxMHNcbiAgICB1c2VfbmV3X3NjaGVtYTogdHJ1ZVxuc2VydmljZTpcbiAgdGVsZW1ldHJ5OlxuICAgIGxvZ3M6XG4gICAgICBlbmNvZGluZzoganNvblxuICBleHRlbnNpb25zOlxuICAgIC0gaGVhbHRoX2NoZWNrXG4gICAgLSBwcHJvZlxuICBwaXBlbGluZXM6XG4gICAgdHJhY2VzOlxuICAgICAgcmVjZWl2ZXJzOiBbb3RscF1cbiAgICAgIHByb2Nlc3NvcnM6IFtzaWdub3pzcGFubWV0cmljcy9kZWx0YSwgYmF0Y2hdXG4gICAgICBleHBvcnRlcnM6IFtjbGlja2hvdXNldHJhY2VzXVxuICAgIG1ldHJpY3M6XG4gICAgICByZWNlaXZlcnM6IFtvdGxwXVxuICAgICAgcHJvY2Vzc29yczogW2JhdGNoXVxuICAgICAgZXhwb3J0ZXJzOiBbc2lnbm96Y2xpY2tob3VzZW1ldHJpY3NdXG4gICAgbWV0cmljcy9wcm9tZXRoZXVzOlxuICAgICAgcmVjZWl2ZXJzOiBbcHJvbWV0aGV1c11cbiAgICAgIHByb2Nlc3NvcnM6IFtiYXRjaF1cbiAgICAgIGV4cG9ydGVyczogW3NpZ25vemNsaWNraG91c2VtZXRyaWNzXVxuICAgIGxvZ3M6XG4gICAgICByZWNlaXZlcnM6IFtvdGxwXVxuICAgICAgcHJvY2Vzc29yczogW2JhdGNoXVxuICAgICAgZXhwb3J0ZXJzOiBbY2xpY2tob3VzZWxvZ3NleHBvcnRlcl0iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL290ZWwtY29sbGVjdG9yLW9wYW1wLWNvbmZpZy55YW1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL21hbmFnZXItY29uZmlnLnlhbWwKICAgICAgICBjb250ZW50OiAic2VydmVyX2VuZHBvaW50OiB3czovL3NpZ25vejo0MzIwL3YxL29wYW1wXG4iCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fT1RFTENPTExFQ1RPUkhUVFBfNDMxOAogICAgICAtICdPVEVMX1JFU09VUkNFX0FUVFJJQlVURVM9aG9zdC5uYW1lPXNpZ25vei1ob3N0LG9zLnR5cGU9bGludXgnCiAgICAgIC0gTE9XX0NBUkRJTkFMX0VYQ0VQVElPTl9HUk9VUElORz1mYWxzZQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdiYXNoIC1jICJleGVjIDY8PiAvZGV2L3RjcC9sb2NhbGhvc3QvMTMxMzMiJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDMKICBzY2hlbWEtbWlncmF0b3Itc3luYzoKICAgIGltYWdlOiAnc2lnbm96L3NpZ25vei1zY2hlbWEtbWlncmF0b3I6djAuMTI5LjcnCiAgICBjb21tYW5kOgogICAgICAtIHN5bmMKICAgICAgLSAnLS1kc249dGNwOi8vY2xpY2tob3VzZTo5MDAwJwogICAgICAtICctLXVwPScKICAgIGRlcGVuZHNfb246CiAgICAgIGNsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIHJlc3RhcnQ6IG9uLWZhaWx1cmUKICAgIGV4Y2x1ZGVfZnJvbV9oYzogdHJ1ZQogICAgbG9nZ2luZzoKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtc2l6ZTogNTBtCiAgICAgICAgbWF4LWZpbGU6ICczJwogIHNjaGVtYS1taWdyYXRvci1hc3luYzoKICAgIGltYWdlOiAnc2lnbm96L3NpZ25vei1zY2hlbWEtbWlncmF0b3I6djAuMTI5LjcnCiAgICBkZXBlbmRzX29uOgogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHNjaGVtYS1taWdyYXRvci1zeW5jOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9jb21wbGV0ZWRfc3VjY2Vzc2Z1bGx5CiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGNvbW1hbmQ6CiAgICAgIC0gYXN5bmMKICAgICAgLSAnLS1kc249dGNwOi8vY2xpY2tob3VzZTo5MDAwJwogICAgICAtICctLXVwPScK", + "compose": "c2VydmljZXM6CiAgaW5pdC1jbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI1LjUuNi1hbHBpbmUnCiAgICBjb21tYW5kOgogICAgICAtIGJhc2gKICAgICAgLSAnLWMnCiAgICAgIC0gInZlcnNpb249XCJ2MC4wLjFcIlxubm9kZV9vcz0kJCh1bmFtZSAtcyB8IHRyICdbOnVwcGVyOl0nICdbOmxvd2VyOl0nKVxubm9kZV9hcmNoPSQkKHVuYW1lIC1tIHwgc2VkIHMvYWFyY2g2NC9hcm02NC8gfCBzZWQgcy94ODZfNjQvYW1kNjQvKVxuZWNobyBcIkZldGNoaW5nIGhpc3RvZ3JhbS1iaW5hcnkgZm9yICQke25vZGVfb3N9LyQke25vZGVfYXJjaH1cIlxuY2QgL3RtcFxud2dldCAtTyBoaXN0b2dyYW0tcXVhbnRpbGUudGFyLmd6IFwiaHR0cHM6Ly9naXRodWIuY29tL1NpZ05vei9zaWdub3ovcmVsZWFzZXMvZG93bmxvYWQvaGlzdG9ncmFtLXF1YW50aWxlJTJGJCR7dmVyc2lvbn0vaGlzdG9ncmFtLXF1YW50aWxlXyQke25vZGVfb3N9XyQke25vZGVfYXJjaH0udGFyLmd6XCJcbnRhciAteHZ6ZiBoaXN0b2dyYW0tcXVhbnRpbGUudGFyLmd6XG5ta2RpciAtcCAvdmFyL2xpYi9jbGlja2hvdXNlL3VzZXJfc2NyaXB0cy9oaXN0b2dyYW1RdWFudGlsZVxubXYgaGlzdG9ncmFtLXF1YW50aWxlIC92YXIvbGliL2NsaWNraG91c2UvdXNlcl9zY3JpcHRzL2hpc3RvZ3JhbVF1YW50aWxlXG4iCiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICB6b29rZWVwZXI6CiAgICBpbWFnZTogJ3NpZ25vei96b29rZWVwZXI6My45LjMnCiAgICB1c2VyOiByb290CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ2N1cmwgLXMgLW0gMiBodHRwOi8vbG9jYWxob3N0OjgwODAvY29tbWFuZHMvcnVvayB8IGdyZXAgZXJyb3IgfCBncmVwIG51bGwnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgbG9nZ2luZzoKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtc2l6ZTogNTBtCiAgICAgICAgbWF4LWZpbGU6ICczJwogICAgdm9sdW1lczoKICAgICAgLSAnem9va2VlcGVyOi9iaXRuYW1pL3pvb2tlZXBlcicKICAgIGVudmlyb25tZW50OgogICAgICAtICdBTExPV19BTk9OWU1PVVNfTE9HSU49JHtaT09fQUxMT1dfQU5PTllNT1VTX0xPR0lOOi15ZXN9JwogICAgICAtICdaT09fQVVUT1BVUkdFX0lOVEVSVkFMPSR7Wk9PX0FVVE9QVVJHRV9JTlRFUlZBTDotMX0nCiAgICAgIC0gJ1pPT19FTkFCTEVfUFJPTUVUSEVVU19NRVRSSUNTPSR7Wk9PX0VOQUJMRV9QUk9NRVRIRVVTX01FVFJJQ1M6LXllc30nCiAgICAgIC0gJ1pPT19QUk9NRVRIRVVTX01FVFJJQ1NfUE9SVF9OVU1CRVI9JHtaT09fUFJPTUVUSEVVU19NRVRSSUNTX1BPUlRfTlVNQkVSOi05MTQxfScKICBjbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI1LjUuNi1hbHBpbmUnCiAgICB0dHk6IHRydWUKICAgIGRlcGVuZHNfb246CiAgICAgIGluaXQtY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgICB6b29rZWVwZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnLXEnCiAgICAgICAgLSAnMC4wLjAuMDo4MTIzL3BpbmcnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgdWxpbWl0czoKICAgICAgbnByb2M6IDY1NTM1CiAgICAgIG5vZmlsZToKICAgICAgICBzb2Z0OiAyNjIxNDQKICAgICAgICBoYXJkOiAyNjIxNDQKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGVudmlyb25tZW50OgogICAgICAtIENMSUNLSE9VU0VfU0tJUF9VU0VSX1NFVFVQPTEKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiB2b2x1bWUKICAgICAgICBzb3VyY2U6IGNsaWNraG91c2UKICAgICAgICB0YXJnZXQ6IC92YXIvbGliL2NsaWNraG91c2UvCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2UvY3VzdG9tLWZ1bmN0aW9uLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jdXN0b20tZnVuY3Rpb24ueG1sCiAgICAgICAgY29udGVudDogIjxmdW5jdGlvbnM+XG4gICAgPGZ1bmN0aW9uPlxuICAgICAgICA8dHlwZT5leGVjdXRhYmxlPC90eXBlPlxuICAgICAgICA8bmFtZT5oaXN0b2dyYW1RdWFudGlsZTwvbmFtZT5cbiAgICAgICAgPHJldHVybl90eXBlPkZsb2F0NjQ8L3JldHVybl90eXBlPlxuICAgICAgICA8YXJndW1lbnQ+XG4gICAgICAgICAgICA8dHlwZT5BcnJheShGbG9hdDY0KTwvdHlwZT5cbiAgICAgICAgICAgIDxuYW1lPmJ1Y2tldHM8L25hbWU+XG4gICAgICAgIDwvYXJndW1lbnQ+XG4gICAgICAgIDxhcmd1bWVudD5cbiAgICAgICAgICAgIDx0eXBlPkFycmF5KEZsb2F0NjQpPC90eXBlPlxuICAgICAgICAgICAgPG5hbWU+Y291bnRzPC9uYW1lPlxuICAgICAgICA8L2FyZ3VtZW50PlxuICAgICAgICA8YXJndW1lbnQ+XG4gICAgICAgICAgICA8dHlwZT5GbG9hdDY0PC90eXBlPlxuICAgICAgICAgICAgPG5hbWU+cXVhbnRpbGU8L25hbWU+XG4gICAgICAgIDwvYXJndW1lbnQ+XG4gICAgICAgIDxmb3JtYXQ+Q1NWPC9mb3JtYXQ+XG4gICAgICAgIDxjb21tYW5kPi4vaGlzdG9ncmFtUXVhbnRpbGU8L2NvbW1hbmQ+XG4gICAgPC9mdW5jdGlvbj5cbjwvZnVuY3Rpb25zPlxuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9jbGlja2hvdXNlL2NsdXN0ZXIueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy5kL2NsdXN0ZXIueG1sCiAgICAgICAgY29udGVudDogIjw/eG1sIHZlcnNpb249XCIxLjBcIj8+XG48Y2xpY2tob3VzZT5cbiAgICA8IS0tIFpvb0tlZXBlciBpcyB1c2VkIHRvIHN0b3JlIG1ldGFkYXRhIGFib3V0IHJlcGxpY2FzLCB3aGVuIHVzaW5nIFJlcGxpY2F0ZWQgdGFibGVzLlxuICAgICAgICBPcHRpb25hbC4gSWYgeW91IGRvbid0IHVzZSByZXBsaWNhdGVkIHRhYmxlcywgeW91IGNvdWxkIG9taXQgdGhhdC5cblxuICAgICAgICBTZWUgaHR0cHM6Ly9jbGlja2hvdXNlLmNvbS9kb2NzL2VuL2VuZ2luZXMvdGFibGUtZW5naW5lcy9tZXJnZXRyZWUtZmFtaWx5L3JlcGxpY2F0aW9uL1xuICAgICAgLS0+XG4gICAgPHpvb2tlZXBlcj5cbiAgICAgICAgPG5vZGUgaW5kZXg9XCIxXCI+XG4gICAgICAgICAgICA8aG9zdD56b29rZWVwZXI8L2hvc3Q+XG4gICAgICAgICAgICA8cG9ydD4yMTgxPC9wb3J0PlxuICAgICAgICA8L25vZGU+XG4gICAgPC96b29rZWVwZXI+XG5cbiAgICA8IS0tIENvbmZpZ3VyYXRpb24gb2YgY2x1c3RlcnMgdGhhdCBjb3VsZCBiZSB1c2VkIGluIERpc3RyaWJ1dGVkIHRhYmxlcy5cbiAgICAgICAgaHR0cHM6Ly9jbGlja2hvdXNlLmNvbS9kb2NzL2VuL29wZXJhdGlvbnMvdGFibGVfZW5naW5lcy9kaXN0cmlidXRlZC9cbiAgICAgIC0tPlxuICAgIDxyZW1vdGVfc2VydmVycz5cbiAgICAgICAgPGNsdXN0ZXI+XG4gICAgICAgICAgICA8IS0tIEludGVyLXNlcnZlciBwZXItY2x1c3RlciBzZWNyZXQgZm9yIERpc3RyaWJ1dGVkIHF1ZXJpZXNcbiAgICAgICAgICAgICAgICBkZWZhdWx0OiBubyBzZWNyZXQgKG5vIGF1dGhlbnRpY2F0aW9uIHdpbGwgYmUgcGVyZm9ybWVkKVxuXG4gICAgICAgICAgICAgICAgSWYgc2V0LCB0aGVuIERpc3RyaWJ1dGVkIHF1ZXJpZXMgd2lsbCBiZSB2YWxpZGF0ZWQgb24gc2hhcmRzLCBzbyBhdCBsZWFzdDpcbiAgICAgICAgICAgICAgICAtIHN1Y2ggY2x1c3RlciBzaG91bGQgZXhpc3Qgb24gdGhlIHNoYXJkLFxuICAgICAgICAgICAgICAgIC0gc3VjaCBjbHVzdGVyIHNob3VsZCBoYXZlIHRoZSBzYW1lIHNlY3JldC5cblxuICAgICAgICAgICAgICAgIEFuZCBhbHNvIChhbmQgd2hpY2ggaXMgbW9yZSBpbXBvcnRhbnQpLCB0aGUgaW5pdGlhbF91c2VyIHdpbGxcbiAgICAgICAgICAgICAgICBiZSB1c2VkIGFzIGN1cnJlbnQgdXNlciBmb3IgdGhlIHF1ZXJ5LlxuXG4gICAgICAgICAgICAgICAgUmlnaHQgbm93IHRoZSBwcm90b2NvbCBpcyBwcmV0dHkgc2ltcGxlIGFuZCBpdCBvbmx5IHRha2VzIGludG8gYWNjb3VudDpcbiAgICAgICAgICAgICAgICAtIGNsdXN0ZXIgbmFtZVxuICAgICAgICAgICAgICAgIC0gcXVlcnlcblxuICAgICAgICAgICAgICAgIEFsc28gaXQgd2lsbCBiZSBuaWNlIGlmIHRoZSBmb2xsb3dpbmcgd2lsbCBiZSBpbXBsZW1lbnRlZDpcbiAgICAgICAgICAgICAgICAtIHNvdXJjZSBob3N0bmFtZSAoc2VlIGludGVyc2VydmVyX2h0dHBfaG9zdCksIGJ1dCB0aGVuIGl0IHdpbGwgZGVwZW5kcyBmcm9tIEROUyxcbiAgICAgICAgICAgICAgICAgIGl0IGNhbiB1c2UgSVAgYWRkcmVzcyBpbnN0ZWFkLCBidXQgdGhlbiB0aGUgeW91IG5lZWQgdG8gZ2V0IGNvcnJlY3Qgb24gdGhlIGluaXRpYXRvciBub2RlLlxuICAgICAgICAgICAgICAgIC0gdGFyZ2V0IGhvc3RuYW1lIC8gaXAgYWRkcmVzcyAoc2FtZSBub3RlcyBhcyBmb3Igc291cmNlIGhvc3RuYW1lKVxuICAgICAgICAgICAgICAgIC0gdGltZS1iYXNlZCBzZWN1cml0eSB0b2tlbnNcbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPCEtLSA8c2VjcmV0Pjwvc2VjcmV0PiAtLT5cbiAgICAgICAgICAgIDxzaGFyZD5cbiAgICAgICAgICAgICAgICA8IS0tIE9wdGlvbmFsLiBXaGV0aGVyIHRvIHdyaXRlIGRhdGEgdG8ganVzdCBvbmUgb2YgdGhlIHJlcGxpY2FzLiBEZWZhdWx0OiBmYWxzZSAod3JpdGUgZGF0YSB0byBhbGwgcmVwbGljYXMpLiAtLT5cbiAgICAgICAgICAgICAgICA8IS0tIDxpbnRlcm5hbF9yZXBsaWNhdGlvbj5mYWxzZTwvaW50ZXJuYWxfcmVwbGljYXRpb24+IC0tPlxuICAgICAgICAgICAgICAgIDwhLS0gT3B0aW9uYWwuIFNoYXJkIHdlaWdodCB3aGVuIHdyaXRpbmcgZGF0YS4gRGVmYXVsdDogMS4gLS0+XG4gICAgICAgICAgICAgICAgPCEtLSA8d2VpZ2h0PjE8L3dlaWdodD4gLS0+XG4gICAgICAgICAgICAgICAgPHJlcGxpY2E+XG4gICAgICAgICAgICAgICAgICAgIDxob3N0PmNsaWNraG91c2U8L2hvc3Q+XG4gICAgICAgICAgICAgICAgICAgIDxwb3J0PjkwMDA8L3BvcnQ+XG4gICAgICAgICAgICAgICAgICAgIDwhLS0gT3B0aW9uYWwuIFByaW9yaXR5IG9mIHRoZSByZXBsaWNhIGZvciBsb2FkX2JhbGFuY2luZy4gRGVmYXVsdDogMSAobGVzcyB2YWx1ZSBoYXMgbW9yZSBwcmlvcml0eSkuIC0tPlxuICAgICAgICAgICAgICAgICAgICA8IS0tIDxwcmlvcml0eT4xPC9wcmlvcml0eT4gLS0+XG4gICAgICAgICAgICAgICAgPC9yZXBsaWNhPlxuICAgICAgICAgICAgPC9zaGFyZD5cbiAgICAgICAgICAgIDwhLS0gPHNoYXJkPlxuICAgICAgICAgICAgICAgIDxyZXBsaWNhPlxuICAgICAgICAgICAgICAgICAgICA8aG9zdD5jbGlja2hvdXNlLTI8L2hvc3Q+XG4gICAgICAgICAgICAgICAgICAgIDxwb3J0PjkwMDA8L3BvcnQ+XG4gICAgICAgICAgICAgICAgPC9yZXBsaWNhPlxuICAgICAgICAgICAgPC9zaGFyZD5cbiAgICAgICAgICAgIDxzaGFyZD5cbiAgICAgICAgICAgICAgICA8cmVwbGljYT5cbiAgICAgICAgICAgICAgICAgICAgPGhvc3Q+Y2xpY2tob3VzZS0zPC9ob3N0PlxuICAgICAgICAgICAgICAgICAgICA8cG9ydD45MDAwPC9wb3J0PlxuICAgICAgICAgICAgICAgIDwvcmVwbGljYT5cbiAgICAgICAgICAgIDwvc2hhcmQ+IC0tPlxuICAgICAgICA8L2NsdXN0ZXI+XG4gICAgPC9yZW1vdGVfc2VydmVycz5cbjwvY2xpY2tob3VzZT5cbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS91c2Vycy54bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvY2xpY2tob3VzZS1zZXJ2ZXIvdXNlcnMueG1sCiAgICAgICAgY29udGVudDogIjw/eG1sIHZlcnNpb249XCIxLjBcIj8+XG48Y2xpY2tob3VzZT5cbiAgICA8IS0tIFNlZSBhbHNvIHRoZSBmaWxlcyBpbiB1c2Vycy5kIGRpcmVjdG9yeSB3aGVyZSB0aGUgc2V0dGluZ3MgY2FuIGJlIG92ZXJyaWRkZW4uIC0tPlxuXG4gICAgPCEtLSBQcm9maWxlcyBvZiBzZXR0aW5ncy4gLS0+XG4gICAgPHByb2ZpbGVzPlxuICAgICAgICA8IS0tIERlZmF1bHQgc2V0dGluZ3MuIC0tPlxuICAgICAgICA8ZGVmYXVsdD5cbiAgICAgICAgICAgIDwhLS0gTWF4aW11bSBtZW1vcnkgdXNhZ2UgZm9yIHByb2Nlc3Npbmcgc2luZ2xlIHF1ZXJ5LCBpbiBieXRlcy4gLS0+XG4gICAgICAgICAgICA8bWF4X21lbW9yeV91c2FnZT4xMDAwMDAwMDAwMDwvbWF4X21lbW9yeV91c2FnZT5cblxuICAgICAgICAgICAgPCEtLSBIb3cgdG8gY2hvb3NlIGJldHdlZW4gcmVwbGljYXMgZHVyaW5nIGRpc3RyaWJ1dGVkIHF1ZXJ5IHByb2Nlc3NpbmcuXG4gICAgICAgICAgICAgICAgcmFuZG9tIC0gY2hvb3NlIHJhbmRvbSByZXBsaWNhIGZyb20gc2V0IG9mIHJlcGxpY2FzIHdpdGggbWluaW11bSBudW1iZXIgb2YgZXJyb3JzXG4gICAgICAgICAgICAgICAgbmVhcmVzdF9ob3N0bmFtZSAtIGZyb20gc2V0IG9mIHJlcGxpY2FzIHdpdGggbWluaW11bSBudW1iZXIgb2YgZXJyb3JzLCBjaG9vc2UgcmVwbGljYVxuICAgICAgICAgICAgICAgICAgd2l0aCBtaW5pbXVtIG51bWJlciBvZiBkaWZmZXJlbnQgc3ltYm9scyBiZXR3ZWVuIHJlcGxpY2EncyBob3N0bmFtZSBhbmQgbG9jYWwgaG9zdG5hbWVcbiAgICAgICAgICAgICAgICAgIChIYW1taW5nIGRpc3RhbmNlKS5cbiAgICAgICAgICAgICAgICBpbl9vcmRlciAtIGZpcnN0IGxpdmUgcmVwbGljYSBpcyBjaG9zZW4gaW4gc3BlY2lmaWVkIG9yZGVyLlxuICAgICAgICAgICAgICAgIGZpcnN0X29yX3JhbmRvbSAtIGlmIGZpcnN0IHJlcGxpY2Egb25lIGhhcyBoaWdoZXIgbnVtYmVyIG9mIGVycm9ycywgcGljayBhIHJhbmRvbSBvbmUgZnJvbSByZXBsaWNhcyB3aXRoIG1pbmltdW0gbnVtYmVyIG9mIGVycm9ycy5cbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPGxvYWRfYmFsYW5jaW5nPnJhbmRvbTwvbG9hZF9iYWxhbmNpbmc+XG4gICAgICAgIDwvZGVmYXVsdD5cblxuICAgICAgICA8IS0tIFByb2ZpbGUgdGhhdCBhbGxvd3Mgb25seSByZWFkIHF1ZXJpZXMuIC0tPlxuICAgICAgICA8cmVhZG9ubHk+XG4gICAgICAgICAgICA8cmVhZG9ubHk+MTwvcmVhZG9ubHk+XG4gICAgICAgIDwvcmVhZG9ubHk+XG4gICAgPC9wcm9maWxlcz5cblxuICAgIDwhLS0gVXNlcnMgYW5kIEFDTC4gLS0+XG4gICAgPHVzZXJzPlxuICAgICAgICA8IS0tIElmIHVzZXIgbmFtZSB3YXMgbm90IHNwZWNpZmllZCwgJ2RlZmF1bHQnIHVzZXIgaXMgdXNlZC4gLS0+XG4gICAgICAgIDxkZWZhdWx0PlxuICAgICAgICAgICAgPCEtLSBTZWUgYWxzbyB0aGUgZmlsZXMgaW4gdXNlcnMuZCBkaXJlY3Rvcnkgd2hlcmUgdGhlIHBhc3N3b3JkIGNhbiBiZSBvdmVycmlkZGVuLlxuXG4gICAgICAgICAgICAgICAgUGFzc3dvcmQgY291bGQgYmUgc3BlY2lmaWVkIGluIHBsYWludGV4dCBvciBpbiBTSEEyNTYgKGluIGhleCBmb3JtYXQpLlxuXG4gICAgICAgICAgICAgICAgSWYgeW91IHdhbnQgdG8gc3BlY2lmeSBwYXNzd29yZCBpbiBwbGFpbnRleHQgKG5vdCByZWNvbW1lbmRlZCksIHBsYWNlIGl0IGluICdwYXNzd29yZCcgZWxlbWVudC5cbiAgICAgICAgICAgICAgICBFeGFtcGxlOiA8cGFzc3dvcmQ+cXdlcnR5PC9wYXNzd29yZD4uXG4gICAgICAgICAgICAgICAgUGFzc3dvcmQgY291bGQgYmUgZW1wdHkuXG5cbiAgICAgICAgICAgICAgICBJZiB5b3Ugd2FudCB0byBzcGVjaWZ5IFNIQTI1NiwgcGxhY2UgaXQgaW4gJ3Bhc3N3b3JkX3NoYTI1Nl9oZXgnIGVsZW1lbnQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPHBhc3N3b3JkX3NoYTI1Nl9oZXg+NjVlODRiZTMzNTMyZmI3ODRjNDgxMjk2NzVmOWVmZjNhNjgyYjI3MTY4YzBlYTc0NGIyY2Y1OGVlMDIzMzdjNTwvcGFzc3dvcmRfc2hhMjU2X2hleD5cbiAgICAgICAgICAgICAgICBSZXN0cmljdGlvbnMgb2YgU0hBMjU2OiBpbXBvc3NpYmlsaXR5IHRvIGNvbm5lY3QgdG8gQ2xpY2tIb3VzZSB1c2luZyBNeVNRTCBKUyBjbGllbnQgKGFzIG9mIEp1bHkgMjAxOSkuXG5cbiAgICAgICAgICAgICAgICBJZiB5b3Ugd2FudCB0byBzcGVjaWZ5IGRvdWJsZSBTSEExLCBwbGFjZSBpdCBpbiAncGFzc3dvcmRfZG91YmxlX3NoYTFfaGV4JyBlbGVtZW50LlxuICAgICAgICAgICAgICAgIEV4YW1wbGU6IDxwYXNzd29yZF9kb3VibGVfc2hhMV9oZXg+ZTM5NTc5NmQ2NTQ2YjFiNjVkYjlkNjY1Y2Q0M2YwZTg1OGRkNDMwMzwvcGFzc3dvcmRfZG91YmxlX3NoYTFfaGV4PlxuXG4gICAgICAgICAgICAgICAgSWYgeW91IHdhbnQgdG8gc3BlY2lmeSBhIHByZXZpb3VzbHkgZGVmaW5lZCBMREFQIHNlcnZlciAoc2VlICdsZGFwX3NlcnZlcnMnIGluIHRoZSBtYWluIGNvbmZpZykgZm9yIGF1dGhlbnRpY2F0aW9uLFxuICAgICAgICAgICAgICAgICAgcGxhY2UgaXRzIG5hbWUgaW4gJ3NlcnZlcicgZWxlbWVudCBpbnNpZGUgJ2xkYXAnIGVsZW1lbnQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGxkYXA+PHNlcnZlcj5teV9sZGFwX3NlcnZlcjwvc2VydmVyPjwvbGRhcD5cblxuICAgICAgICAgICAgICAgIElmIHlvdSB3YW50IHRvIGF1dGhlbnRpY2F0ZSB0aGUgdXNlciB2aWEgS2VyYmVyb3MgKGFzc3VtaW5nIEtlcmJlcm9zIGlzIGVuYWJsZWQsIHNlZSAna2VyYmVyb3MnIGluIHRoZSBtYWluIGNvbmZpZyksXG4gICAgICAgICAgICAgICAgICBwbGFjZSAna2VyYmVyb3MnIGVsZW1lbnQgaW5zdGVhZCBvZiAncGFzc3dvcmQnIChhbmQgc2ltaWxhcikgZWxlbWVudHMuXG4gICAgICAgICAgICAgICAgVGhlIG5hbWUgcGFydCBvZiB0aGUgY2Fub25pY2FsIHByaW5jaXBhbCBuYW1lIG9mIHRoZSBpbml0aWF0b3IgbXVzdCBtYXRjaCB0aGUgdXNlciBuYW1lIGZvciBhdXRoZW50aWNhdGlvbiB0byBzdWNjZWVkLlxuICAgICAgICAgICAgICAgIFlvdSBjYW4gYWxzbyBwbGFjZSAncmVhbG0nIGVsZW1lbnQgaW5zaWRlICdrZXJiZXJvcycgZWxlbWVudCB0byBmdXJ0aGVyIHJlc3RyaWN0IGF1dGhlbnRpY2F0aW9uIHRvIG9ubHkgdGhvc2UgcmVxdWVzdHNcbiAgICAgICAgICAgICAgICAgIHdob3NlIGluaXRpYXRvcidzIHJlYWxtIG1hdGNoZXMgaXQuXG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGtlcmJlcm9zIC8+XG4gICAgICAgICAgICAgICAgRXhhbXBsZTogPGtlcmJlcm9zPjxyZWFsbT5FWEFNUExFLkNPTTwvcmVhbG0+PC9rZXJiZXJvcz5cblxuICAgICAgICAgICAgICAgIEhvdyB0byBnZW5lcmF0ZSBkZWNlbnQgcGFzc3dvcmQ6XG4gICAgICAgICAgICAgICAgRXhlY3V0ZTogUEFTU1dPUkQ9JChiYXNlNjQgPCAvZGV2L3VyYW5kb20gfCBoZWFkIC1jOCk7IGVjaG8gXCIkUEFTU1dPUkRcIjsgZWNobyAtbiBcIiRQQVNTV09SRFwiIHwgc2hhMjU2c3VtIHwgdHIgLWQgJy0nXG4gICAgICAgICAgICAgICAgSW4gZmlyc3QgbGluZSB3aWxsIGJlIHBhc3N3b3JkIGFuZCBpbiBzZWNvbmQgLSBjb3JyZXNwb25kaW5nIFNIQTI1Ni5cblxuICAgICAgICAgICAgICAgIEhvdyB0byBnZW5lcmF0ZSBkb3VibGUgU0hBMTpcbiAgICAgICAgICAgICAgICBFeGVjdXRlOiBQQVNTV09SRD0kKGJhc2U2NCA8IC9kZXYvdXJhbmRvbSB8IGhlYWQgLWM4KTsgZWNobyBcIiRQQVNTV09SRFwiOyBlY2hvIC1uIFwiJFBBU1NXT1JEXCIgfCBzaGExc3VtIHwgdHIgLWQgJy0nIHwgeHhkIC1yIC1wIHwgc2hhMXN1bSB8IHRyIC1kICctJ1xuICAgICAgICAgICAgICAgIEluIGZpcnN0IGxpbmUgd2lsbCBiZSBwYXNzd29yZCBhbmQgaW4gc2Vjb25kIC0gY29ycmVzcG9uZGluZyBkb3VibGUgU0hBMS5cbiAgICAgICAgICAgIC0tPlxuICAgICAgICAgICAgPHBhc3N3b3JkPjwvcGFzc3dvcmQ+XG5cbiAgICAgICAgICAgIDwhLS0gTGlzdCBvZiBuZXR3b3JrcyB3aXRoIG9wZW4gYWNjZXNzLlxuXG4gICAgICAgICAgICAgICAgVG8gb3BlbiBhY2Nlc3MgZnJvbSBldmVyeXdoZXJlLCBzcGVjaWZ5OlxuICAgICAgICAgICAgICAgICAgICA8aXA+OjovMDwvaXA+XG5cbiAgICAgICAgICAgICAgICBUbyBvcGVuIGFjY2VzcyBvbmx5IGZyb20gbG9jYWxob3N0LCBzcGVjaWZ5OlxuICAgICAgICAgICAgICAgICAgICA8aXA+OjoxPC9pcD5cbiAgICAgICAgICAgICAgICAgICAgPGlwPjEyNy4wLjAuMTwvaXA+XG5cbiAgICAgICAgICAgICAgICBFYWNoIGVsZW1lbnQgb2YgbGlzdCBoYXMgb25lIG9mIHRoZSBmb2xsb3dpbmcgZm9ybXM6XG4gICAgICAgICAgICAgICAgPGlwPiBJUC1hZGRyZXNzIG9yIG5ldHdvcmsgbWFzay4gRXhhbXBsZXM6IDIxMy4xODAuMjA0LjMgb3IgMTAuMC4wLjEvOCBvciAxMC4wLjAuMS8yNTUuMjU1LjI1NS4wXG4gICAgICAgICAgICAgICAgICAgIDJhMDI6NmI4OjozIG9yIDJhMDI6NmI4OjozLzY0IG9yIDJhMDI6NmI4OjozL2ZmZmY6ZmZmZjpmZmZmOmZmZmY6Oi5cbiAgICAgICAgICAgICAgICA8aG9zdD4gSG9zdG5hbWUuIEV4YW1wbGU6IHNlcnZlcjAxLmNsaWNraG91c2UuY29tLlxuICAgICAgICAgICAgICAgICAgICBUbyBjaGVjayBhY2Nlc3MsIEROUyBxdWVyeSBpcyBwZXJmb3JtZWQsIGFuZCBhbGwgcmVjZWl2ZWQgYWRkcmVzc2VzIGNvbXBhcmVkIHRvIHBlZXIgYWRkcmVzcy5cbiAgICAgICAgICAgICAgICA8aG9zdF9yZWdleHA+IFJlZ3VsYXIgZXhwcmVzc2lvbiBmb3IgaG9zdCBuYW1lcy4gRXhhbXBsZSwgXnNlcnZlclxcZFxcZC1cXGRcXGQtXFxkXFwuY2xpY2tob3VzZVxcLmNvbSRcbiAgICAgICAgICAgICAgICAgICAgVG8gY2hlY2sgYWNjZXNzLCBETlMgUFRSIHF1ZXJ5IGlzIHBlcmZvcm1lZCBmb3IgcGVlciBhZGRyZXNzIGFuZCB0aGVuIHJlZ2V4cCBpcyBhcHBsaWVkLlxuICAgICAgICAgICAgICAgICAgICBUaGVuLCBmb3IgcmVzdWx0IG9mIFBUUiBxdWVyeSwgYW5vdGhlciBETlMgcXVlcnkgaXMgcGVyZm9ybWVkIGFuZCBhbGwgcmVjZWl2ZWQgYWRkcmVzc2VzIGNvbXBhcmVkIHRvIHBlZXIgYWRkcmVzcy5cbiAgICAgICAgICAgICAgICAgICAgU3Ryb25nbHkgcmVjb21tZW5kZWQgdGhhdCByZWdleHAgaXMgZW5kcyB3aXRoICRcbiAgICAgICAgICAgICAgICBBbGwgcmVzdWx0cyBvZiBETlMgcmVxdWVzdHMgYXJlIGNhY2hlZCB0aWxsIHNlcnZlciByZXN0YXJ0LlxuICAgICAgICAgICAgLS0+XG4gICAgICAgICAgICA8bmV0d29ya3M+XG4gICAgICAgICAgICAgICAgPGlwPjo6LzA8L2lwPlxuICAgICAgICAgICAgPC9uZXR3b3Jrcz5cblxuICAgICAgICAgICAgPCEtLSBTZXR0aW5ncyBwcm9maWxlIGZvciB1c2VyLiAtLT5cbiAgICAgICAgICAgIDxwcm9maWxlPmRlZmF1bHQ8L3Byb2ZpbGU+XG5cbiAgICAgICAgICAgIDwhLS0gUXVvdGEgZm9yIHVzZXIuIC0tPlxuICAgICAgICAgICAgPHF1b3RhPmRlZmF1bHQ8L3F1b3RhPlxuXG4gICAgICAgICAgICA8IS0tIFVzZXIgY2FuIGNyZWF0ZSBvdGhlciB1c2VycyBhbmQgZ3JhbnQgcmlnaHRzIHRvIHRoZW0uIC0tPlxuICAgICAgICAgICAgPCEtLSA8YWNjZXNzX21hbmFnZW1lbnQ+MTwvYWNjZXNzX21hbmFnZW1lbnQ+IC0tPlxuICAgICAgICA8L2RlZmF1bHQ+XG4gICAgPC91c2Vycz5cblxuICAgIDwhLS0gUXVvdGFzLiAtLT5cbiAgICA8cXVvdGFzPlxuICAgICAgICA8IS0tIE5hbWUgb2YgcXVvdGEuIC0tPlxuICAgICAgICA8ZGVmYXVsdD5cbiAgICAgICAgICAgIDwhLS0gTGltaXRzIGZvciB0aW1lIGludGVydmFsLiBZb3UgY291bGQgc3BlY2lmeSBtYW55IGludGVydmFscyB3aXRoIGRpZmZlcmVudCBsaW1pdHMuIC0tPlxuICAgICAgICAgICAgPGludGVydmFsPlxuICAgICAgICAgICAgICAgIDwhLS0gTGVuZ3RoIG9mIGludGVydmFsLiAtLT5cbiAgICAgICAgICAgICAgICA8ZHVyYXRpb24+MzYwMDwvZHVyYXRpb24+XG5cbiAgICAgICAgICAgICAgICA8IS0tIE5vIGxpbWl0cy4gSnVzdCBjYWxjdWxhdGUgcmVzb3VyY2UgdXNhZ2UgZm9yIHRpbWUgaW50ZXJ2YWwuIC0tPlxuICAgICAgICAgICAgICAgIDxxdWVyaWVzPjA8L3F1ZXJpZXM+XG4gICAgICAgICAgICAgICAgPGVycm9ycz4wPC9lcnJvcnM+XG4gICAgICAgICAgICAgICAgPHJlc3VsdF9yb3dzPjA8L3Jlc3VsdF9yb3dzPlxuICAgICAgICAgICAgICAgIDxyZWFkX3Jvd3M+MDwvcmVhZF9yb3dzPlxuICAgICAgICAgICAgICAgIDxleGVjdXRpb25fdGltZT4wPC9leGVjdXRpb25fdGltZT5cbiAgICAgICAgICAgIDwvaW50ZXJ2YWw+XG4gICAgICAgIDwvZGVmYXVsdD5cbiAgICA8L3F1b3Rhcz5cbjwvY2xpY2tob3VzZT5cbiIKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vY2xpY2tob3VzZS9jb25maWcueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy54bWwKICAgICAgICBjb250ZW50OiAiPD94bWwgdmVyc2lvbj1cIjEuMFwiPz5cbjxjbGlja2hvdXNlPlxuICA8bWF4X2Nvbm5lY3Rpb25zPjQwOTY8L21heF9jb25uZWN0aW9ucz5cbiAgPGtlZXBfYWxpdmVfdGltZW91dD4zPC9rZWVwX2FsaXZlX3RpbWVvdXQ+XG4gIDxtYXhfY29uY3VycmVudF9xdWVyaWVzPjEwMDwvbWF4X2NvbmN1cnJlbnRfcXVlcmllcz5cbiAgPG1hcmtfY2FjaGVfc2l6ZT41MzY4NzA5MTIwPC9tYXJrX2NhY2hlX3NpemU+XG4gIDxtbWFwX2NhY2hlX3NpemU+MTAwMDwvbW1hcF9jYWNoZV9zaXplPlxuICA8Y29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9zaXplPjEzNDIxNzcyODwvY29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9zaXplPlxuICA8Y29tcGlsZWRfZXhwcmVzc2lvbl9jYWNoZV9lbGVtZW50c19zaXplPjEwMDAwPC9jb21waWxlZF9leHByZXNzaW9uX2NhY2hlX2VsZW1lbnRzX3NpemU+XG4gIDxjdXN0b21fc2V0dGluZ3NfcHJlZml4ZXM+PC9jdXN0b21fc2V0dGluZ3NfcHJlZml4ZXM+XG4gIDxkaWN0aW9uYXJpZXNfY29uZmlnPipfZGljdGlvbmFyeS54bWw8L2RpY3Rpb25hcmllc19jb25maWc+XG4gIDx1c2VyX2RlZmluZWRfZXhlY3V0YWJsZV9mdW5jdGlvbnNfY29uZmlnPipmdW5jdGlvbi54bWw8L3VzZXJfZGVmaW5lZF9leGVjdXRhYmxlX2Z1bmN0aW9uc19jb25maWc+XG4gIDx1c2VyX3NjcmlwdHNfcGF0aD4vdmFyL2xpYi9jbGlja2hvdXNlL3VzZXJfc2NyaXB0cy88L3VzZXJfc2NyaXB0c19wYXRoPlxuICA8aHR0cF9wb3J0PjgxMjM8L2h0dHBfcG9ydD5cbiAgPHRjcF9wb3J0PjkwMDA8L3RjcF9wb3J0PlxuICA8bXlzcWxfcG9ydD45MDA0PC9teXNxbF9wb3J0PlxuICA8cG9zdGdyZXNxbF9wb3J0PjkwMDU8L3Bvc3RncmVzcWxfcG9ydD5cbiAgPGludGVyc2VydmVyX2h0dHBfcG9ydD45MDA5PC9pbnRlcnNlcnZlcl9odHRwX3BvcnQ+XG4gIDxsb2dnZXI+XG4gICAgPGxldmVsPmluZm9ybWF0aW9uPC9sZXZlbD5cbiAgICA8Zm9ybWF0dGluZz5cbiAgICAgIDx0eXBlPmpzb248L3R5cGU+XG4gICAgPC9mb3JtYXR0aW5nPlxuICA8L2xvZ2dlcj5cbiAgPG1hY3Jvcz5cbiAgICA8c2hhcmQ+MDE8L3NoYXJkPlxuICAgIDxyZXBsaWNhPmV4YW1wbGUwMS0wMS0xPC9yZXBsaWNhPlxuICA8L21hY3Jvcz5cbiAgPHByb21ldGhldXM+XG4gICAgPGVuZHBvaW50Pi9tZXRyaWNzPC9lbmRwb2ludD5cbiAgICA8cG9ydD45MzYzPC9wb3J0PlxuICAgIDxtZXRyaWNzPnRydWU8L21ldHJpY3M+XG4gICAgPGV2ZW50cz50cnVlPC9ldmVudHM+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNzPnRydWU8L2FzeW5jaHJvbm91c19tZXRyaWNzPlxuICAgIDxzdGF0dXNfaW5mbz50cnVlPC9zdGF0dXNfaW5mbz5cbiAgPC9wcm9tZXRoZXVzPlxuICA8b3BlbnRlbGVtZXRyeV9zcGFuX2xvZz5cbiAgICA8ZW5naW5lPmVuZ2luZSBNZXJnZVRyZWVcbiAgICAgICAgICAgIHBhcnRpdGlvbiBieSB0b1lZWVlNTShmaW5pc2hfZGF0ZSlcbiAgICAgICAgICAgIG9yZGVyIGJ5IChmaW5pc2hfZGF0ZSwgZmluaXNoX3RpbWVfdXMsIHRyYWNlX2lkKTwvZW5naW5lPlxuICA8L29wZW50ZWxlbWV0cnlfc3Bhbl9sb2c+XG4gIDxxdWVyeV9tYXNraW5nX3J1bGVzPlxuICAgIDxydWxlPlxuICAgICAgPG5hbWU+aGlkZSBlbmNyeXB0L2RlY3J5cHQgYXJndW1lbnRzPC9uYW1lPlxuICAgICAgPHJlZ2V4cD4oKD86YWVzXyk/KD86ZW5jcnlwdHxkZWNyeXB0KSg/Ol9teXNxbCk/KVxccypcXChcXHMqKD86Jyg/OlxcXFwnfC4pKyd8Lio/KVxccypcXCk8L3JlZ2V4cD5cbiAgICAgIDxyZXBsYWNlPlxcMSg/Pz8pPC9yZXBsYWNlPlxuICAgIDwvcnVsZT5cbiAgPC9xdWVyeV9tYXNraW5nX3J1bGVzPlxuICA8c2VuZF9jcmFzaF9yZXBvcnRzPlxuICAgIDxlbmFibGVkPmZhbHNlPC9lbmFibGVkPlxuICAgIDxhbm9ueW1pemU+ZmFsc2U8L2Fub255bWl6ZT5cbiAgICA8ZW5kcG9pbnQ+aHR0cHM6Ly82ZjMzMDM0Y2ZlNjg0ZGQ3YTNhYjk4NzVlNTdiMWM4ZEBvMzg4ODcwLmluZ2VzdC5zZW50cnkuaW8vNTIyNjI3NzwvZW5kcG9pbnQ+XG4gIDwvc2VuZF9jcmFzaF9yZXBvcnRzPlxuICA8bWVyZ2VfdHJlZV9tZXRhZGF0YV9jYWNoZT5cbiAgICA8bHJ1X2NhY2hlX3NpemU+MjY4NDM1NDU2PC9scnVfY2FjaGVfc2l6ZT5cbiAgICA8Y29udGludWVfaWZfY29ycnVwdGVkPnRydWU8L2NvbnRpbnVlX2lmX2NvcnJ1cHRlZD5cbiAgPC9tZXJnZV90cmVlX21ldGFkYXRhX2NhY2hlPlxuICA8dXNlcl9kaXJlY3Rvcmllcz5cbiAgICA8dXNlcnNfeG1sPlxuICAgICAgICA8IS0tIFBhdGggdG8gY29uZmlndXJhdGlvbiBmaWxlIHdpdGggcHJlZGVmaW5lZCB1c2Vycy4gLS0+XG4gICAgICAgIDxwYXRoPnVzZXJzLnhtbDwvcGF0aD5cbiAgICA8L3VzZXJzX3htbD5cbiAgICA8bG9jYWxfZGlyZWN0b3J5PlxuICAgICAgICA8IS0tIFBhdGggdG8gZm9sZGVyIHdoZXJlIHVzZXJzIGNyZWF0ZWQgYnkgU1FMIGNvbW1hbmRzIGFyZSBzdG9yZWQuIC0tPlxuICAgICAgICA8cGF0aD4vdmFyL2xpYi9jbGlja2hvdXNlL2FjY2Vzcy88L3BhdGg+XG4gICAgPC9sb2NhbF9kaXJlY3Rvcnk+XG4gIDwvdXNlcl9kaXJlY3Rvcmllcz5cbiAgPGRlZmF1bHRfcHJvZmlsZT5kZWZhdWx0PC9kZWZhdWx0X3Byb2ZpbGU+XG4gICAgPGRpc3RyaWJ1dGVkX2RkbD5cbiAgICAgICAgPCEtLSBQYXRoIGluIFpvb0tlZXBlciB0byBxdWV1ZSB3aXRoIERETCBxdWVyaWVzIC0tPlxuICAgICAgICA8cGF0aD4vY2xpY2tob3VzZS90YXNrX3F1ZXVlL2RkbDwvcGF0aD5cbiAgICA8L2Rpc3RyaWJ1dGVkX2RkbD5cbjwvY2xpY2tob3VzZT5cbiIKICBzaWdub3o6CiAgICBpbWFnZTogJ3NpZ25vei9zaWdub3o6djAuOTcuMScKICAgIGRlcGVuZHNfb246CiAgICAgIGNsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc2NoZW1hLW1pZ3JhdG9yLXN5bmM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2NvbXBsZXRlZF9zdWNjZXNzZnVsbHkKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGNvbW1hbmQ6CiAgICAgIC0gJy0tY29uZmlnPS9yb290L2NvbmZpZy9wcm9tZXRoZXVzLnltbCcKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL3Byb21ldGhldXMueW1sCiAgICAgICAgdGFyZ2V0OiAvcm9vdC9jb25maWcvcHJvbWV0aGV1cy55bWwKICAgICAgICBjb250ZW50OiAiIyBteSBnbG9iYWwgY29uZmlnXG5nbG9iYWw6XG4gIHNjcmFwZV9pbnRlcnZhbDogICAgIDVzICMgU2V0IHRoZSBzY3JhcGUgaW50ZXJ2YWwgdG8gZXZlcnkgMTUgc2Vjb25kcy4gRGVmYXVsdCBpcyBldmVyeSAxIG1pbnV0ZS5cbiAgZXZhbHVhdGlvbl9pbnRlcnZhbDogMTVzICMgRXZhbHVhdGUgcnVsZXMgZXZlcnkgMTUgc2Vjb25kcy4gVGhlIGRlZmF1bHQgaXMgZXZlcnkgMSBtaW51dGUuXG4gICMgc2NyYXBlX3RpbWVvdXQgaXMgc2V0IHRvIHRoZSBnbG9iYWwgZGVmYXVsdCAoMTBzKS5cblxuIyBBbGVydG1hbmFnZXIgY29uZmlndXJhdGlvblxuYWxlcnRpbmc6XG4gIGFsZXJ0bWFuYWdlcnM6XG4gIC0gc3RhdGljX2NvbmZpZ3M6XG4gICAgLSB0YXJnZXRzOlxuICAgICAgLSBhbGVydG1hbmFnZXI6OTA5M1xuXG4jIExvYWQgcnVsZXMgb25jZSBhbmQgcGVyaW9kaWNhbGx5IGV2YWx1YXRlIHRoZW0gYWNjb3JkaW5nIHRvIHRoZSBnbG9iYWwgJ2V2YWx1YXRpb25faW50ZXJ2YWwnLlxucnVsZV9maWxlczogW11cbiAgIyAtIFwiZmlyc3RfcnVsZXMueW1sXCJcbiAgIyAtIFwic2Vjb25kX3J1bGVzLnltbFwiXG4gICMgLSAnYWxlcnRzLnltbCdcblxuIyBBIHNjcmFwZSBjb25maWd1cmF0aW9uIGNvbnRhaW5pbmcgZXhhY3RseSBvbmUgZW5kcG9pbnQgdG8gc2NyYXBlOlxuIyBIZXJlIGl0J3MgUHJvbWV0aGV1cyBpdHNlbGYuXG5zY3JhcGVfY29uZmlnczogW11cblxucmVtb3RlX3JlYWQ6XG4gIC0gdXJsOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X21ldHJpY3NcbiIKICAgICAgLQogICAgICAgIHR5cGU6IHZvbHVtZQogICAgICAgIHNvdXJjZTogc3FsaXRlCiAgICAgICAgdGFyZ2V0OiAvdmFyL2xpYi9zaWdub3ovCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fU0lHTk9aXzgwODAKICAgICAgLSAnU0lHTk9aX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1JFQUxCQVNFNjRfSldUU0VDUkVUfScKICAgICAgLSAnU0lHTk9aX1RFTEVNRVRSWVNUT1JFX0NMSUNLSE9VU0VfRFNOPXRjcDovL2NsaWNraG91c2U6OTAwMCcKICAgICAgLSBTSUdOT1pfU1FMU1RPUkVfU1FMSVRFX1BBVEg9L3Zhci9saWIvc2lnbm96L3NpZ25vei5kYgogICAgICAtIERBU0hCT0FSRFNfUEFUSD0vcm9vdC9jb25maWcvZGFzaGJvYXJkcwogICAgICAtIFNUT1JBR0U9Y2xpY2tob3VzZQogICAgICAtIEdPREVCVUc9bmV0ZG5zPWdvCiAgICAgIC0gREVQTE9ZTUVOVF9UWVBFPWRvY2tlci1zdGFuZGFsb25lLWFtZAogICAgICAtICdTSUdOT1pfU1RBVFNSRVBPUlRFUl9FTkFCTEVEPSR7U0lHTk9aX1NUQVRTUkVQT1JURVJfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ1NJR05PWl9FTUFJTElOR19FTkFCTEVEPSR7U0lHTk9aX0VNQUlMSU5HX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnU0lHTk9aX0VNQUlMSU5HX1NNVFBfQUREUkVTUz0ke1NJR05PWl9FTUFJTElOR19TTVRQX0FERFJFU1N9JwogICAgICAtICdTSUdOT1pfRU1BSUxJTkdfU01UUF9GUk9NPSR7U0lHTk9aX0VNQUlMSU5HX1NNVFBfRlJPTX0nCiAgICAgIC0gJ1NJR05PWl9FTUFJTElOR19TTVRQX0FVVEhfVVNFUk5BTUU9JHtTSUdOT1pfRU1BSUxJTkdfU01UUF9BVVRIX1VTRVJOQU1FfScKICAgICAgLSAnU0lHTk9aX0VNQUlMSU5HX1NNVFBfQVVUSF9QQVNTV09SRD0ke1NJR05PWl9FTUFJTElOR19TTVRQX0FVVEhfUEFTU1dPUkR9JwogICAgICAtIFNJR05PWl9BTEVSVE1BTkFHRVJfUFJPVklERVI9c2lnbm96CiAgICAgIC0gJ1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19BVVRIX19QQVNTV09SRD0ke1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19BVVRIX19QQVNTV09SRH0nCiAgICAgIC0gJ1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19BVVRIX19VU0VSTkFNRT0ke1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19BVVRIX19VU0VSTkFNRX0nCiAgICAgIC0gJ1NJR05PWl9BTEVSVE1BTkFHRVJfU0lHTk9aX0dMT0JBTF9TTVRQX19GUk9NPSR7U0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX0ZST019JwogICAgICAtICdTSUdOT1pfQUxFUlRNQU5BR0VSX1NJR05PWl9HTE9CQUxfU01UUF9fU01BUlRIT1NUPSR7U0lHTk9aX0FMRVJUTUFOQUdFUl9TSUdOT1pfR0xPQkFMX1NNVFBfX1NNQVJUSE9TVH0nCiAgICAgIC0gRE9UX01FVFJJQ1NfRU5BQkxFRD10cnVlCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJy1xJwogICAgICAgIC0gJ2xvY2FsaG9zdDo4MDgwL2FwaS92MS9oZWFsdGgnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogIG90ZWwtY29sbGVjdG9yOgogICAgaW1hZ2U6ICdzaWdub3ovc2lnbm96LW90ZWwtY29sbGVjdG9yOnYwLjEyOS43JwogICAgZGVwZW5kc19vbjoKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzY2hlbWEtbWlncmF0b3Itc3luYzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgICBzaWdub3o6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGNvbW1hbmQ6CiAgICAgIC0gJy0tY29uZmlnPS9ldGMvb3RlbC1jb2xsZWN0b3ItY29uZmlnLnlhbWwnCiAgICAgIC0gJy0tbWFuYWdlci1jb25maWc9L2V0Yy9tYW5hZ2VyLWNvbmZpZy55YW1sJwogICAgICAtICctLWNvcHktcGF0aD0vdmFyL3RtcC9jb2xsZWN0b3ItY29uZmlnLnlhbWwnCiAgICAgIC0gJy0tZmVhdHVyZS1nYXRlcz0tcGtnLnRyYW5zbGF0b3IucHJvbWV0aGV1cy5Ob3JtYWxpemVOYW1lJwogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vb3RlbC1jb2xsZWN0b3ItY29uZmlnLnlhbWwKICAgICAgICB0YXJnZXQ6IC9ldGMvb3RlbC1jb2xsZWN0b3ItY29uZmlnLnlhbWwKICAgICAgICBjb250ZW50OiAicmVjZWl2ZXJzOlxuICBvdGxwOlxuICAgIHByb3RvY29sczpcbiAgICAgIGdycGM6XG4gICAgICAgIGVuZHBvaW50OiAwLjAuMC4wOjQzMTdcbiAgICAgIGh0dHA6XG4gICAgICAgIGVuZHBvaW50OiAwLjAuMC4wOjQzMThcbiAgcHJvbWV0aGV1czpcbiAgICBjb25maWc6XG4gICAgICBnbG9iYWw6XG4gICAgICAgIHNjcmFwZV9pbnRlcnZhbDogNjBzXG4gICAgICBzY3JhcGVfY29uZmlnczpcbiAgICAgICAgLSBqb2JfbmFtZTogb3RlbC1jb2xsZWN0b3JcbiAgICAgICAgICBzdGF0aWNfY29uZmlnczpcbiAgICAgICAgICAtIHRhcmdldHM6XG4gICAgICAgICAgICAgIC0gbG9jYWxob3N0Ojg4ODhcbiAgICAgICAgICAgIGxhYmVsczpcbiAgICAgICAgICAgICAgam9iX25hbWU6IG90ZWwtY29sbGVjdG9yXG5wcm9jZXNzb3JzOlxuICBiYXRjaDpcbiAgICBzZW5kX2JhdGNoX3NpemU6IDEwMDAwXG4gICAgc2VuZF9iYXRjaF9tYXhfc2l6ZTogMTEwMDBcbiAgICB0aW1lb3V0OiAxMHNcbiAgcmVzb3VyY2VkZXRlY3Rpb246XG4gICAgIyBVc2luZyBPVEVMX1JFU09VUkNFX0FUVFJJQlVURVMgZW52dmFyLCBlbnYgZGV0ZWN0b3IgYWRkcyBjdXN0b20gbGFiZWxzLlxuICAgIGRldGVjdG9yczogW2Vudiwgc3lzdGVtXVxuICAgIHRpbWVvdXQ6IDJzXG4gIHNpZ25venNwYW5tZXRyaWNzL2RlbHRhOlxuICAgIG1ldHJpY3NfZXhwb3J0ZXI6IHNpZ25vemNsaWNraG91c2VtZXRyaWNzXG4gICAgbWV0cmljc19mbHVzaF9pbnRlcnZhbDogNjBzXG4gICAgbGF0ZW5jeV9oaXN0b2dyYW1fYnVja2V0czogWzEwMHVzLCAxbXMsIDJtcywgNm1zLCAxMG1zLCA1MG1zLCAxMDBtcywgMjUwbXMsIDUwMG1zLCAxMDAwbXMsIDE0MDBtcywgMjAwMG1zLCA1cywgMTBzLCAyMHMsIDQwcywgNjBzIF1cbiAgICBkaW1lbnNpb25zX2NhY2hlX3NpemU6IDEwMDAwMFxuICAgIGFnZ3JlZ2F0aW9uX3RlbXBvcmFsaXR5OiBBR0dSRUdBVElPTl9URU1QT1JBTElUWV9ERUxUQVxuICAgIGVuYWJsZV9leHBfaGlzdG9ncmFtOiB0cnVlXG4gICAgZGltZW5zaW9uczpcbiAgICAgIC0gbmFtZTogc2VydmljZS5uYW1lc3BhY2VcbiAgICAgICAgZGVmYXVsdDogZGVmYXVsdFxuICAgICAgLSBuYW1lOiBkZXBsb3ltZW50LmVudmlyb25tZW50XG4gICAgICAgIGRlZmF1bHQ6IGRlZmF1bHRcbiAgICAgICMgVGhpcyBpcyBhZGRlZCB0byBlbnN1cmUgdGhlIHVuaXF1ZW5lc3Mgb2YgdGhlIHRpbWVzZXJpZXNcbiAgICAgICMgT3RoZXJ3aXNlLCBpZGVudGljYWwgdGltZXNlcmllcyBwcm9kdWNlZCBieSBtdWx0aXBsZSByZXBsaWNhcyBvZlxuICAgICAgIyBjb2xsZWN0b3JzIHJlc3VsdCBpbiBpbmNvcnJlY3QgQVBNIG1ldHJpY3NcbiAgICAgIC0gbmFtZTogc2lnbm96LmNvbGxlY3Rvci5pZFxuICAgICAgLSBuYW1lOiBzZXJ2aWNlLnZlcnNpb25cbiAgICAgIC0gbmFtZTogYnJvd3Nlci5wbGF0Zm9ybVxuICAgICAgLSBuYW1lOiBicm93c2VyLm1vYmlsZVxuICAgICAgLSBuYW1lOiBrOHMuY2x1c3Rlci5uYW1lXG4gICAgICAtIG5hbWU6IGs4cy5ub2RlLm5hbWVcbiAgICAgIC0gbmFtZTogazhzLm5hbWVzcGFjZS5uYW1lXG4gICAgICAtIG5hbWU6IGhvc3QubmFtZVxuICAgICAgLSBuYW1lOiBob3N0LnR5cGVcbiAgICAgIC0gbmFtZTogY29udGFpbmVyLm5hbWVcbmV4dGVuc2lvbnM6XG4gIGhlYWx0aF9jaGVjazpcbiAgICBlbmRwb2ludDogMC4wLjAuMDoxMzEzM1xuICBwcHJvZjpcbiAgICBlbmRwb2ludDogMC4wLjAuMDoxNzc3XG5leHBvcnRlcnM6XG4gIGNsaWNraG91c2V0cmFjZXM6XG4gICAgZGF0YXNvdXJjZTogdGNwOi8vY2xpY2tob3VzZTo5MDAwL3NpZ25vel90cmFjZXNcbiAgICBsb3dfY2FyZGluYWxfZXhjZXB0aW9uX2dyb3VwaW5nOiAke2VudjpMT1dfQ0FSRElOQUxfRVhDRVBUSU9OX0dST1VQSU5HfVxuICAgIHVzZV9uZXdfc2NoZW1hOiB0cnVlXG4gIHNpZ25vemNsaWNraG91c2VtZXRyaWNzOlxuICAgIGRzbjogdGNwOi8vY2xpY2tob3VzZTo5MDAwL3NpZ25vel9tZXRyaWNzXG4gIGNsaWNraG91c2Vsb2dzZXhwb3J0ZXI6XG4gICAgZHNuOiB0Y3A6Ly9jbGlja2hvdXNlOjkwMDAvc2lnbm96X2xvZ3NcbiAgICB0aW1lb3V0OiAxMHNcbiAgICB1c2VfbmV3X3NjaGVtYTogdHJ1ZVxuc2VydmljZTpcbiAgdGVsZW1ldHJ5OlxuICAgIGxvZ3M6XG4gICAgICBlbmNvZGluZzoganNvblxuICBleHRlbnNpb25zOlxuICAgIC0gaGVhbHRoX2NoZWNrXG4gICAgLSBwcHJvZlxuICBwaXBlbGluZXM6XG4gICAgdHJhY2VzOlxuICAgICAgcmVjZWl2ZXJzOiBbb3RscF1cbiAgICAgIHByb2Nlc3NvcnM6IFtzaWdub3pzcGFubWV0cmljcy9kZWx0YSwgYmF0Y2hdXG4gICAgICBleHBvcnRlcnM6IFtjbGlja2hvdXNldHJhY2VzXVxuICAgIG1ldHJpY3M6XG4gICAgICByZWNlaXZlcnM6IFtvdGxwXVxuICAgICAgcHJvY2Vzc29yczogW2JhdGNoXVxuICAgICAgZXhwb3J0ZXJzOiBbc2lnbm96Y2xpY2tob3VzZW1ldHJpY3NdXG4gICAgbWV0cmljcy9wcm9tZXRoZXVzOlxuICAgICAgcmVjZWl2ZXJzOiBbcHJvbWV0aGV1c11cbiAgICAgIHByb2Nlc3NvcnM6IFtiYXRjaF1cbiAgICAgIGV4cG9ydGVyczogW3NpZ25vemNsaWNraG91c2VtZXRyaWNzXVxuICAgIGxvZ3M6XG4gICAgICByZWNlaXZlcnM6IFtvdGxwXVxuICAgICAgcHJvY2Vzc29yczogW2JhdGNoXVxuICAgICAgZXhwb3J0ZXJzOiBbY2xpY2tob3VzZWxvZ3NleHBvcnRlcl0iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL290ZWwtY29sbGVjdG9yLW9wYW1wLWNvbmZpZy55YW1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL21hbmFnZXItY29uZmlnLnlhbWwKICAgICAgICBjb250ZW50OiAic2VydmVyX2VuZHBvaW50OiB3czovL3NpZ25vejo0MzIwL3YxL29wYW1wXG4iCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fT1RFTENPTExFQ1RPUkhUVFBfNDMxOAogICAgICAtICdPVEVMX1JFU09VUkNFX0FUVFJJQlVURVM9aG9zdC5uYW1lPXNpZ25vei1ob3N0LG9zLnR5cGU9bGludXgnCiAgICAgIC0gTE9XX0NBUkRJTkFMX0VYQ0VQVElPTl9HUk9VUElORz1mYWxzZQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdiYXNoIC1jICJleGVjIDY8PiAvZGV2L3RjcC9sb2NhbGhvc3QvMTMxMzMiJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDMKICBzY2hlbWEtbWlncmF0b3Itc3luYzoKICAgIGltYWdlOiAnc2lnbm96L3NpZ25vei1zY2hlbWEtbWlncmF0b3I6djAuMTI5LjcnCiAgICBjb21tYW5kOgogICAgICAtIHN5bmMKICAgICAgLSAnLS1kc249dGNwOi8vY2xpY2tob3VzZTo5MDAwJwogICAgICAtICctLXVwPScKICAgIGRlcGVuZHNfb246CiAgICAgIGNsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIHJlc3RhcnQ6IG9uLWZhaWx1cmUKICAgIGV4Y2x1ZGVfZnJvbV9oYzogdHJ1ZQogICAgbG9nZ2luZzoKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtc2l6ZTogNTBtCiAgICAgICAgbWF4LWZpbGU6ICczJwogIHNjaGVtYS1taWdyYXRvci1hc3luYzoKICAgIGltYWdlOiAnc2lnbm96L3NpZ25vei1zY2hlbWEtbWlncmF0b3I6djAuMTI5LjcnCiAgICBkZXBlbmRzX29uOgogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHNjaGVtYS1taWdyYXRvci1zeW5jOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9jb21wbGV0ZWRfc3VjY2Vzc2Z1bGx5CiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIGxvZ2dpbmc6CiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LXNpemU6IDUwbQogICAgICAgIG1heC1maWxlOiAnMycKICAgIGNvbW1hbmQ6CiAgICAgIC0gYXN5bmMKICAgICAgLSAnLS1kc249dGNwOi8vY2xpY2tob3VzZTo5MDAwJwogICAgICAtICctLXVwPScK", "tags": [ "telemetry", "server", From 33d3f196cc87618e5545d0f55059edf9d17c0ef3 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:42:35 +0100 Subject: [PATCH 026/434] chore(api): improve current request error message --- app/Http/Controllers/Api/ServicesController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index ddd63d60c..cfe96edbe 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -68,7 +68,7 @@ private function applyServiceUrls(Service $service, array $urlsArray, string $te $duplicates = $urls->duplicates()->unique()->values(); if ($duplicates->isNotEmpty() && ! $forceDomainOverride) { - $errors[] = 'The current request contains conflicting URLs across containers: '.implode(', ', $duplicates->toArray()); + $errors[] = 'The current request contains conflicting URLs across containers: '.implode(', ', $duplicates->toArray()).'. Use force_domain_override=true to proceed.'; } if (count($errors) > 0) { From ae9d0ec817ba354a862fd92a56a20bdf8f0b2967 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:50:48 +0100 Subject: [PATCH 027/434] docs(api): change domains to urls --- app/Http/Controllers/Api/ServicesController.php | 4 ++-- openapi.json | 4 ++-- openapi.yaml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index cfe96edbe..a93a4c6d3 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -217,7 +217,7 @@ public function services(Request $request) type: 'object', properties: [ 'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'], - 'url' => ['type' => 'string', 'description' => 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").'], + 'url' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").'], ], ), ], @@ -807,7 +807,7 @@ public function delete_by_uuid(Request $request) type: 'object', properties: [ 'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'], - 'url' => ['type' => 'string', 'description' => 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").'], + 'url' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").'], ], ), ], diff --git a/openapi.json b/openapi.json index 46a5b4bc3..a94ef79b1 100644 --- a/openapi.json +++ b/openapi.json @@ -8899,7 +8899,7 @@ }, "url": { "type": "string", - "description": "Comma-separated list of domains (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")." + "description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")." } }, "type": "object" @@ -9225,7 +9225,7 @@ }, "url": { "type": "string", - "description": "Comma-separated list of domains (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")." + "description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")." } }, "type": "object" diff --git a/openapi.yaml b/openapi.yaml index ae2999610..75ccb69fe 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -5609,7 +5609,7 @@ paths: urls: type: array description: 'Array of URLs to be applied to containers of a service.' - items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object } + items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object } force_domain_override: type: boolean default: false @@ -5794,7 +5794,7 @@ paths: urls: type: array description: 'Array of URLs to be applied to containers of a service.' - items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of domains (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object } + items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object } force_domain_override: type: boolean default: false From f4acf7ca108de1fc21093376bf305b18bedd73a2 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:21:18 +0100 Subject: [PATCH 028/434] refactor(api): application urls validation - rename fqdn to urls as that is what it actually is - improve URL validation to allow urls without a TLD - improve error messages to make it clear that URLs are needed - improve code by combining some actions --- .../Api/ApplicationsController.php | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 60fd45ef4..25b98c465 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -2411,18 +2411,24 @@ public function update_by_uuid(Request $request) $requestHasDomains = $request->has('domains'); if ($requestHasDomains && $server->isProxyShouldRun()) { $uuid = $request->uuid; - $fqdn = $request->domains; - $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); - $fqdn = str($fqdn)->replaceStart(',', '')->trim(); + $urls = $request->domains; + $urls = str($urls)->replaceStart(',', '')->replaceEnd(',', '')->trim(); $errors = []; - $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { - $domain = trim($domain); - if (filter_var($domain, FILTER_VALIDATE_URL) === false || ! preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/', $domain)) { - $errors[] = 'Invalid domain: '.$domain; + $urls = str($urls)->trim()->explode(',')->map(function ($url) use (&$errors) { + $url = trim($url); + if (! filter_var($url, FILTER_VALIDATE_URL)) { + $errors[] = 'Invalid URL: '.$url; + + return $url; + } + $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; + if (! in_array(strtolower($scheme), ['http', 'https'])) { + $errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; } - return $domain; + return str($url)->lower(); }); + if (count($errors) > 0) { return response()->json([ 'message' => 'Validation failed.', @@ -2430,7 +2436,7 @@ public function update_by_uuid(Request $request) ], 422); } // Check for domain conflicts - $result = checkIfDomainIsAlreadyUsedViaAPI($fqdn, $teamId, $uuid); + $result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $uuid); if (isset($result['error'])) { return response()->json([ 'message' => 'Validation failed.', @@ -3626,17 +3632,23 @@ private function validateDataApplications(Request $request, Server $server) } if ($request->has('domains') && $server->isProxyShouldRun()) { $uuid = $request->uuid; - $fqdn = $request->domains; - $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); - $fqdn = str($fqdn)->replaceStart(',', '')->trim(); + $urls = $request->domains; + $urls = str($urls)->replaceEnd(',', '')->trim(); + $urls = str($urls)->replaceStart(',', '')->trim(); $errors = []; - $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { - $domain = trim($domain); - if (filter_var($domain, FILTER_VALIDATE_URL) === false) { - $errors[] = 'Invalid domain: '.$domain; + $urls = str($urls)->trim()->explode(',')->map(function ($url) use (&$errors) { + $url = trim($url); + if (! filter_var($url, FILTER_VALIDATE_URL)) { + $errors[] = 'Invalid URL: '.$url; + + return str($url)->lower(); + } + $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; + if (! in_array(strtolower($scheme), ['http', 'https'])) { + $errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; } - return str($domain)->lower(); + return str($url)->lower(); }); if (count($errors) > 0) { return response()->json([ @@ -3645,7 +3657,7 @@ private function validateDataApplications(Request $request, Server $server) ], 422); } // Check for domain conflicts - $result = checkIfDomainIsAlreadyUsedViaAPI($fqdn, $teamId, $uuid); + $result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $uuid); if (isset($result['error'])) { return response()->json([ 'message' => 'Validation failed.', From c66b6490e65639300d1584fef83ded90c2eace93 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:24:27 +0100 Subject: [PATCH 029/434] docs(api): improve domains API docs --- app/Http/Controllers/Api/ApplicationsController.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 25b98c465..d655a9d1d 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -153,7 +153,7 @@ public function applications(Request $request) 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'], 'name' => ['type' => 'string', 'description' => 'The application name.'], 'description' => ['type' => 'string', 'description' => 'The application description.'], - 'domains' => ['type' => 'string', 'description' => 'The application domains.'], + 'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'], 'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'], 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], @@ -315,7 +315,7 @@ public function create_public_application(Request $request) 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], 'name' => ['type' => 'string', 'description' => 'The application name.'], 'description' => ['type' => 'string', 'description' => 'The application description.'], - 'domains' => ['type' => 'string', 'description' => 'The application domains.'], + 'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'], 'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'], 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], @@ -476,7 +476,7 @@ public function create_private_gh_app_application(Request $request) 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], 'name' => ['type' => 'string', 'description' => 'The application name.'], 'description' => ['type' => 'string', 'description' => 'The application description.'], - 'domains' => ['type' => 'string', 'description' => 'The application domains.'], + 'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'], 'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'], 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], @@ -635,7 +635,7 @@ public function create_private_deploy_key_application(Request $request) 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'], 'name' => ['type' => 'string', 'description' => 'The application name.'], 'description' => ['type' => 'string', 'description' => 'The application description.'], - 'domains' => ['type' => 'string', 'description' => 'The application domains.'], + 'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'], 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], 'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'], @@ -771,7 +771,7 @@ public function create_dockerfile_application(Request $request) 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'], 'name' => ['type' => 'string', 'description' => 'The application name.'], 'description' => ['type' => 'string', 'description' => 'The application description.'], - 'domains' => ['type' => 'string', 'description' => 'The application domains.'], + 'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'], 'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'], 'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'], 'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'], @@ -2150,7 +2150,7 @@ public function delete_by_uuid(Request $request) 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], 'name' => ['type' => 'string', 'description' => 'The application name.'], 'description' => ['type' => 'string', 'description' => 'The application description.'], - 'domains' => ['type' => 'string', 'description' => 'The application domains.'], + 'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'], 'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'], 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], From 754448d9d447c8bc333dc61daf49bf7bdde46ce0 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:35:37 +0100 Subject: [PATCH 030/434] feat(api): improve docker_compose_domains - add url conflict checking and force_domain_override support - refactor docker_compose_domains URL validation function --- .../Api/ApplicationsController.php | 268 +++++++++++------- 1 file changed, 172 insertions(+), 96 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index d655a9d1d..24e8394a4 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1130,39 +1130,58 @@ private function create_application(Request $request, $type) $dockerComposeDomainsJson = collect(); if ($request->has('docker_compose_domains')) { $dockerComposeDomains = collect($request->docker_compose_domains); - $domainErrors = []; - foreach ($dockerComposeDomains as $index => $item) { + // Collect all URLs from all docker_compose_domains items + $urls = $dockerComposeDomains->flatMap(function ($item) { $domainValue = data_get($item, 'domain'); - if (filled($domainValue)) { - $urls = str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim(); - str($urls)->explode(',')->each(function ($url) use (&$domainErrors) { - $url = trim($url); - if (empty($url)) { - return; - } - if (! filter_var($url, FILTER_VALIDATE_URL)) { - $domainErrors[] = "Invalid URL: {$url}"; - - return; - } - $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; - if (! in_array(strtolower($scheme), ['http', 'https'])) { - $domainErrors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; - } - }); + if (blank($domainValue)) { + return []; } - } - if (! empty($domainErrors)) { + return str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter(); + }); + + $errors = []; + $urls = $urls->map(function ($url) use (&$errors) { + if (! filter_var($url, FILTER_VALIDATE_URL)) { + $errors[] = "Invalid URL: {$url}"; + + return $url; + } + $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; + if (! in_array(strtolower($scheme), ['http', 'https'])) { + $errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; + } + + return $url; + }); + + if (count($errors) > 0) { return response()->json([ 'message' => 'Validation failed.', - 'errors' => [ - 'docker_compose_domains' => $domainErrors, - ], + 'errors' => ['docker_compose_domains' => $errors], ], 422); } + // Check for domain conflicts + if ($urls->isNotEmpty()) { + $result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId); + if (isset($result['error'])) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => ['docker_compose_domains' => $result['error']], + ], 422); + } + + if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) { + return response()->json([ + 'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.', + 'conflicts' => $result['conflicts'], + 'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.', + ], 409); + } + } + $dockerComposeDomains->each(function ($domain) use ($dockerComposeDomainsJson) { $dockerComposeDomainsJson->put(data_get($domain, 'name'), ['domain' => data_get($domain, 'domain')]); }); @@ -1319,39 +1338,58 @@ private function create_application(Request $request, $type) $dockerComposeDomainsJson = collect(); if ($request->has('docker_compose_domains')) { $dockerComposeDomains = collect($request->docker_compose_domains); - $domainErrors = []; - foreach ($dockerComposeDomains as $index => $item) { + // Collect all URLs from all docker_compose_domains items + $urls = $dockerComposeDomains->flatMap(function ($item) { $domainValue = data_get($item, 'domain'); - if (filled($domainValue)) { - $urls = str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim(); - str($urls)->explode(',')->each(function ($url) use (&$domainErrors) { - $url = trim($url); - if (empty($url)) { - return; - } - if (! filter_var($url, FILTER_VALIDATE_URL)) { - $domainErrors[] = "Invalid URL: {$url}"; - - return; - } - $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; - if (! in_array(strtolower($scheme), ['http', 'https'])) { - $domainErrors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; - } - }); + if (blank($domainValue)) { + return []; } - } - if (! empty($domainErrors)) { + return str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter(); + }); + + $errors = []; + $urls = $urls->map(function ($url) use (&$errors) { + if (! filter_var($url, FILTER_VALIDATE_URL)) { + $errors[] = "Invalid URL: {$url}"; + + return $url; + } + $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; + if (! in_array(strtolower($scheme), ['http', 'https'])) { + $errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; + } + + return $url; + }); + + if (count($errors) > 0) { return response()->json([ 'message' => 'Validation failed.', - 'errors' => [ - 'docker_compose_domains' => $domainErrors, - ], + 'errors' => ['docker_compose_domains' => $errors], ], 422); } + // Check for domain conflicts + if ($urls->isNotEmpty()) { + $result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId); + if (isset($result['error'])) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => ['docker_compose_domains' => $result['error']], + ], 422); + } + + if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) { + return response()->json([ + 'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.', + 'conflicts' => $result['conflicts'], + 'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.', + ], 409); + } + } + $dockerComposeDomains->each(function ($domain) use ($dockerComposeDomainsJson) { $dockerComposeDomainsJson->put(data_get($domain, 'name'), ['domain' => data_get($domain, 'domain')]); }); @@ -1476,39 +1514,58 @@ private function create_application(Request $request, $type) $dockerComposeDomainsJson = collect(); if ($request->has('docker_compose_domains')) { $dockerComposeDomains = collect($request->docker_compose_domains); - $domainErrors = []; - foreach ($dockerComposeDomains as $index => $item) { + // Collect all URLs from all docker_compose_domains items + $urls = $dockerComposeDomains->flatMap(function ($item) { $domainValue = data_get($item, 'domain'); - if (filled($domainValue)) { - $urls = str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim(); - str($urls)->explode(',')->each(function ($url) use (&$domainErrors) { - $url = trim($url); - if (empty($url)) { - return; - } - if (! filter_var($url, FILTER_VALIDATE_URL)) { - $domainErrors[] = "Invalid URL: {$url}"; - - return; - } - $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; - if (! in_array(strtolower($scheme), ['http', 'https'])) { - $domainErrors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; - } - }); + if (blank($domainValue)) { + return []; } - } - if (! empty($domainErrors)) { + return str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter(); + }); + + $errors = []; + $urls = $urls->map(function ($url) use (&$errors) { + if (! filter_var($url, FILTER_VALIDATE_URL)) { + $errors[] = "Invalid URL: {$url}"; + + return $url; + } + $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; + if (! in_array(strtolower($scheme), ['http', 'https'])) { + $errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; + } + + return $url; + }); + + if (count($errors) > 0) { return response()->json([ 'message' => 'Validation failed.', - 'errors' => [ - 'docker_compose_domains' => $domainErrors, - ], + 'errors' => ['docker_compose_domains' => $errors], ], 422); } + // Check for domain conflicts + if ($urls->isNotEmpty()) { + $result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId); + if (isset($result['error'])) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => ['docker_compose_domains' => $result['error']], + ], 422); + } + + if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) { + return response()->json([ + 'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.', + 'conflicts' => $result['conflicts'], + 'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.', + ], 409); + } + } + $dockerComposeDomains->each(function ($domain) use ($dockerComposeDomainsJson) { $dockerComposeDomainsJson->put(data_get($domain, 'name'), ['domain' => data_get($domain, 'domain')]); }); @@ -2466,39 +2523,58 @@ public function update_by_uuid(Request $request) } $dockerComposeDomains = collect($request->docker_compose_domains); - $domainErrors = []; - foreach ($dockerComposeDomains as $item) { + // Collect all URLs from all docker_compose_domains items + $urls = $dockerComposeDomains->flatMap(function ($item) { $domainValue = data_get($item, 'domain'); - if (filled($domainValue)) { - $urls = str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim(); - str($urls)->explode(',')->each(function ($url) use (&$domainErrors) { - $url = trim($url); - if (empty($url)) { - return; - } - if (! filter_var($url, FILTER_VALIDATE_URL)) { - $domainErrors[] = "Invalid URL: {$url}"; - - return; - } - $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; - if (! in_array(strtolower($scheme), ['http', 'https'])) { - $domainErrors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; - } - }); + if (blank($domainValue)) { + return []; } - } - if (! empty($domainErrors)) { + return str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter(); + }); + + $errors = []; + $urls = $urls->map(function ($url) use (&$errors) { + if (! filter_var($url, FILTER_VALIDATE_URL)) { + $errors[] = "Invalid URL: {$url}"; + + return $url; + } + $scheme = parse_url($url, PHP_URL_SCHEME) ?? ''; + if (! in_array(strtolower($scheme), ['http', 'https'])) { + $errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported."; + } + + return $url; + }); + + if (count($errors) > 0) { return response()->json([ 'message' => 'Validation failed.', - 'errors' => [ - 'docker_compose_domains' => $domainErrors, - ], + 'errors' => ['docker_compose_domains' => $errors], ], 422); } + // Check for domain conflicts + if ($urls->isNotEmpty()) { + $result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $request->uuid); + if (isset($result['error'])) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => ['docker_compose_domains' => $result['error']], + ], 422); + } + + if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) { + return response()->json([ + 'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.', + 'conflicts' => $result['conflicts'], + 'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.', + ], 409); + } + } + $yaml = Yaml::parse($application->docker_compose_raw); $services = data_get($yaml, 'services', []); $dockerComposeDomains->each(function ($domain) use ($services, $dockerComposeDomainsJson) { From 5f5c26d8417c1c5acd2746d400c2636b8a6e95ab Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 13 Jan 2026 20:27:24 +0100 Subject: [PATCH 031/434] fix(api): check domain conflicts within the request --- .../Api/ApplicationsController.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 24e8394a4..ba8df932b 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1156,6 +1156,11 @@ private function create_application(Request $request, $type) return $url; }); + $duplicates = $urls->duplicates()->unique()->values(); + if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) { + $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()); + } + if (count($errors) > 0) { return response()->json([ 'message' => 'Validation failed.', @@ -1364,6 +1369,11 @@ private function create_application(Request $request, $type) return $url; }); + $duplicates = $urls->duplicates()->unique()->values(); + if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) { + $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()); + } + if (count($errors) > 0) { return response()->json([ 'message' => 'Validation failed.', @@ -1540,6 +1550,11 @@ private function create_application(Request $request, $type) return $url; }); + $duplicates = $urls->duplicates()->unique()->values(); + if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) { + $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()); + } + if (count($errors) > 0) { return response()->json([ 'message' => 'Validation failed.', @@ -2549,6 +2564,11 @@ public function update_by_uuid(Request $request) return $url; }); + $duplicates = $urls->duplicates()->unique()->values(); + if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) { + $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()); + } + if (count($errors) > 0) { return response()->json([ 'message' => 'Validation failed.', From fb5695941808db1eab3ad99a81fc71fb51da90cd Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 13 Jan 2026 20:28:08 +0100 Subject: [PATCH 032/434] fix(api): include docker_compose_domains in domain conflict check --- bootstrap/helpers/domains.php | 63 +++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/bootstrap/helpers/domains.php b/bootstrap/helpers/domains.php index 5b665890c..ff77a78e2 100644 --- a/bootstrap/helpers/domains.php +++ b/bootstrap/helpers/domains.php @@ -158,8 +158,7 @@ function checkIfDomainIsAlreadyUsedViaAPI(Collection|array $domains, ?string $te return str($domain); }); - // Check applications within the same team - $applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid', 'name', 'id']); + $applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid', 'name', 'id', 'docker_compose_domains', 'build_pack']); $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->with('service:id,name')->get(['fqdn', 'uuid', 'id', 'service_id']); if ($uuid) { @@ -168,23 +167,51 @@ function checkIfDomainIsAlreadyUsedViaAPI(Collection|array $domains, ?string $te } foreach ($applications as $app) { - if (is_null($app->fqdn)) { - continue; - } - $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('/'); + if (! is_null($app->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('/'); + } + $naked_domain = str($domain)->value(); + if ($domains->contains($naked_domain)) { + $conflicts[] = [ + 'domain' => $naked_domain, + 'resource_name' => $app->name, + 'resource_uuid' => $app->uuid, + 'resource_type' => 'application', + 'message' => "Domain $naked_domain is already in use by application '{$app->name}'", + ]; + } } - $naked_domain = str($domain)->value(); - if ($domains->contains($naked_domain)) { - $conflicts[] = [ - 'domain' => $naked_domain, - 'resource_name' => $app->name, - 'resource_uuid' => $app->uuid, - 'resource_type' => 'application', - 'message' => "Domain $naked_domain is already in use by application '{$app->name}'", - ]; + } + + if ($app->build_pack === 'dockercompose' && ! empty($app->docker_compose_domains)) { + $dockerComposeDomains = json_decode($app->docker_compose_domains, true); + if (is_array($dockerComposeDomains)) { + foreach ($dockerComposeDomains as $serviceName => $domainConfig) { + $domainValue = data_get($domainConfig, 'domain'); + if (empty($domainValue)) { + continue; + } + $list_of_domains = collect(explode(',', $domainValue))->filter(fn ($fqdn) => $fqdn !== ''); + foreach ($list_of_domains as $domain) { + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->value(); + if ($domains->contains($naked_domain)) { + $conflicts[] = [ + 'domain' => $naked_domain, + 'resource_name' => $app->name, + 'resource_uuid' => $app->uuid, + 'resource_type' => 'application', + 'service_name' => $serviceName, + 'message' => "Domain $naked_domain is already in use by application '{$app->name}' (service: {$serviceName})", + ]; + } + } + } } } } From 8a1d76cd99121d4aa4bf57dc906a93beaee15ab6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 13 Jan 2026 20:48:11 +0100 Subject: [PATCH 033/434] fix(api): is_static and docker network missing - GitHub App and Private Deploy Key where missing is_static and connect_to_docker_network --- .../Api/ApplicationsController.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index ba8df932b..74542ac67 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1424,6 +1424,14 @@ private function create_application(Request $request, $type) $application->fqdn = generateUrl(server: $server, random: $application->uuid); $application->save(); } + if (isset($isStatic)) { + $application->settings->is_static = $isStatic; + $application->settings->save(); + } + if (isset($connectToDockerNetwork)) { + $application->settings->connect_to_docker_network = $connectToDockerNetwork; + $application->settings->save(); + } if (isset($useBuildServer)) { $application->settings->is_build_server_enabled = $useBuildServer; $application->settings->save(); @@ -1601,6 +1609,14 @@ private function create_application(Request $request, $type) $application->fqdn = generateUrl(server: $server, random: $application->uuid); $application->save(); } + if (isset($isStatic)) { + $application->settings->is_static = $isStatic; + $application->settings->save(); + } + if (isset($connectToDockerNetwork)) { + $application->settings->connect_to_docker_network = $connectToDockerNetwork; + $application->settings->save(); + } if (isset($useBuildServer)) { $application->settings->is_build_server_enabled = $useBuildServer; $application->settings->save(); @@ -1703,6 +1719,9 @@ private function create_application(Request $request, $type) if ($autogenerateDomain && blank($fqdn)) { $application->fqdn = generateUrl(server: $server, random: $application->uuid); $application->save(); + if (isset($connectToDockerNetwork)) { + $application->settings->connect_to_docker_network = $connectToDockerNetwork; + $application->settings->save(); } if (isset($useBuildServer)) { $application->settings->is_build_server_enabled = $useBuildServer; @@ -1805,6 +1824,9 @@ private function create_application(Request $request, $type) if ($autogenerateDomain && blank($fqdn)) { $application->fqdn = generateUrl(server: $server, random: $application->uuid); $application->save(); + if (isset($connectToDockerNetwork)) { + $application->settings->connect_to_docker_network = $connectToDockerNetwork; + $application->settings->save(); } if (isset($useBuildServer)) { $application->settings->is_build_server_enabled = $useBuildServer; From 6ca04b561373291b368bfccbe1b111fd2676c229 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:16:40 +0100 Subject: [PATCH 034/434] feat(api): add more allowed fields - added dockerfile_location as it is needed for Dockerfile deployments to work properly - added is_spa as it makes sense together with is_static - added is_auto_deploy_enabled and is_force_https_enabled --- .../Api/ApplicationsController.php | 89 ++++++++++++++++++- bootstrap/helpers/api.php | 8 ++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 74542ac67..143ba64d3 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -158,6 +158,9 @@ public function applications(Request $request) 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], 'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'], + 'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'], + 'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'], + 'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'], 'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'], 'install_command' => ['type' => 'string', 'description' => 'The install command.'], 'build_command' => ['type' => 'string', 'description' => 'The build command.'], @@ -198,6 +201,7 @@ public function applications(Request $request) // 'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'], + 'dockerfile_location' => ['type' => 'string', 'description' => 'The Dockerfile location in the repository.'], 'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'], 'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'], 'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'], @@ -320,6 +324,9 @@ public function create_public_application(Request $request) 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], 'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'], + 'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'], + 'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'], + 'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'], 'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'], 'install_command' => ['type' => 'string', 'description' => 'The install command.'], 'build_command' => ['type' => 'string', 'description' => 'The build command.'], @@ -359,6 +366,7 @@ public function create_public_application(Request $request) 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'], + 'dockerfile_location' => ['type' => 'string', 'description' => 'The Dockerfile location in the repository'], 'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'], 'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'], 'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'], @@ -481,6 +489,9 @@ public function create_private_gh_app_application(Request $request) 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], 'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'], + 'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'], + 'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'], + 'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'], 'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'], 'install_command' => ['type' => 'string', 'description' => 'The install command.'], 'build_command' => ['type' => 'string', 'description' => 'The build command.'], @@ -520,6 +531,7 @@ public function create_private_gh_app_application(Request $request) 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'], + 'dockerfile_location' => ['type' => 'string', 'description' => 'The Dockerfile location in the repository.'], 'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'], 'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'], 'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'], @@ -671,6 +683,7 @@ public function create_private_deploy_key_application(Request $request) 'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'], 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], + 'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], @@ -804,6 +817,7 @@ public function create_dockerfile_application(Request $request) 'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'], 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], + 'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], @@ -987,7 +1001,7 @@ private function create_application(Request $request, $type) if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'autogenerate_domain', 'is_container_label_escape_enabled']; + $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'dockerfile_location', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'autogenerate_domain', 'is_container_label_escape_enabled']; $validator = customApiValidator($request->all(), [ 'name' => 'string|max:255', @@ -1030,6 +1044,9 @@ private function create_application(Request $request, $type) $githubAppUuid = $request->github_app_uuid; $useBuildServer = $request->use_build_server; $isStatic = $request->is_static; + $isSpa = $request->is_spa; + $isAutoDeployEnabled = $request->is_auto_deploy_enabled; + $isForceHttpsEnabled = $request->is_force_https_enabled; $connectToDockerNetwork = $request->connect_to_docker_network; $customNginxConfiguration = $request->custom_nginx_configuration; $isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled', true); @@ -1211,6 +1228,18 @@ private function create_application(Request $request, $type) $application->settings->is_static = $isStatic; $application->settings->save(); } + if (isset($isSpa)) { + $application->settings->is_spa = $isSpa; + $application->settings->save(); + } + if (isset($isAutoDeployEnabled)) { + $application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled; + $application->settings->save(); + } + if (isset($isForceHttpsEnabled)) { + $application->settings->is_force_https_enabled = $isForceHttpsEnabled; + $application->settings->save(); + } if (isset($connectToDockerNetwork)) { $application->settings->connect_to_docker_network = $connectToDockerNetwork; $application->settings->save(); @@ -1428,6 +1457,18 @@ private function create_application(Request $request, $type) $application->settings->is_static = $isStatic; $application->settings->save(); } + if (isset($isSpa)) { + $application->settings->is_spa = $isSpa; + $application->settings->save(); + } + if (isset($isAutoDeployEnabled)) { + $application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled; + $application->settings->save(); + } + if (isset($isForceHttpsEnabled)) { + $application->settings->is_force_https_enabled = $isForceHttpsEnabled; + $application->settings->save(); + } if (isset($connectToDockerNetwork)) { $application->settings->connect_to_docker_network = $connectToDockerNetwork; $application->settings->save(); @@ -1613,6 +1654,18 @@ private function create_application(Request $request, $type) $application->settings->is_static = $isStatic; $application->settings->save(); } + if (isset($isSpa)) { + $application->settings->is_spa = $isSpa; + $application->settings->save(); + } + if (isset($isAutoDeployEnabled)) { + $application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled; + $application->settings->save(); + } + if (isset($isForceHttpsEnabled)) { + $application->settings->is_force_https_enabled = $isForceHttpsEnabled; + $application->settings->save(); + } if (isset($connectToDockerNetwork)) { $application->settings->connect_to_docker_network = $connectToDockerNetwork; $application->settings->save(); @@ -1719,6 +1772,11 @@ private function create_application(Request $request, $type) if ($autogenerateDomain && blank($fqdn)) { $application->fqdn = generateUrl(server: $server, random: $application->uuid); $application->save(); + } + if (isset($isForceHttpsEnabled)) { + $application->settings->is_force_https_enabled = $isForceHttpsEnabled; + $application->settings->save(); + } if (isset($connectToDockerNetwork)) { $application->settings->connect_to_docker_network = $connectToDockerNetwork; $application->settings->save(); @@ -1824,6 +1882,11 @@ private function create_application(Request $request, $type) if ($autogenerateDomain && blank($fqdn)) { $application->fqdn = generateUrl(server: $server, random: $application->uuid); $application->save(); + } + if (isset($isForceHttpsEnabled)) { + $application->settings->is_force_https_enabled = $isForceHttpsEnabled; + $application->settings->save(); + } if (isset($connectToDockerNetwork)) { $application->settings->connect_to_docker_network = $connectToDockerNetwork; $application->settings->save(); @@ -2249,6 +2312,9 @@ public function delete_by_uuid(Request $request) 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], 'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'], + 'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'], + 'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'], + 'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'], 'install_command' => ['type' => 'string', 'description' => 'The install command.'], 'build_command' => ['type' => 'string', 'description' => 'The build command.'], 'start_command' => ['type' => 'string', 'description' => 'The start command.'], @@ -2287,6 +2353,7 @@ public function delete_by_uuid(Request $request) 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'], + 'dockerfile_location' => ['type' => 'string', 'description' => 'The Dockerfile location in the repository.'], 'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'], 'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'], 'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'], @@ -2391,7 +2458,7 @@ public function update_by_uuid(Request $request) $this->authorize('update', $application); $server = $application->destination->server; - $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings','custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled']; + $allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'dockerfile_location', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled']; $validationRules = [ 'name' => 'string|max:255', @@ -2629,6 +2696,9 @@ public function update_by_uuid(Request $request) } $instantDeploy = $request->instant_deploy; $isStatic = $request->is_static; + $isSpa = $request->is_spa; + $isAutoDeployEnabled = $request->is_auto_deploy_enabled; + $isForceHttpsEnabled = $request->is_force_https_enabled; $connectToDockerNetwork = $request->connect_to_docker_network; $useBuildServer = $request->use_build_server; $isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled'); @@ -2643,6 +2713,21 @@ public function update_by_uuid(Request $request) $application->settings->save(); } + if (isset($isSpa)) { + $application->settings->is_spa = $isSpa; + $application->settings->save(); + } + + if (isset($isAutoDeployEnabled)) { + $application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled; + $application->settings->save(); + } + + if (isset($isForceHttpsEnabled)) { + $application->settings->is_force_https_enabled = $isForceHttpsEnabled; + $application->settings->save(); + } + if (isset($connectToDockerNetwork)) { $application->settings->connect_to_docker_network = $connectToDockerNetwork; $application->settings->save(); diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index 55cff42d0..c23f55c12 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -86,6 +86,9 @@ function sharedDataApplications() 'git_branch' => 'string', 'build_pack' => Rule::enum(BuildPackTypes::class), 'is_static' => 'boolean', + 'is_spa' => 'boolean', + 'is_auto_deploy_enabled' => 'boolean', + 'is_force_https_enabled' => 'boolean', 'static_image' => Rule::enum(StaticImageTypes::class), 'domains' => 'string', 'redirect' => Rule::enum(RedirectTypes::class), @@ -129,6 +132,7 @@ function sharedDataApplications() 'manual_webhook_secret_gitlab' => 'string|nullable', 'manual_webhook_secret_bitbucket' => 'string|nullable', 'manual_webhook_secret_gitea' => 'string|nullable', + 'dockerfile_location' => 'string|nullable', 'docker_compose_location' => 'string', 'docker_compose' => 'string|nullable', 'docker_compose_domains' => 'array|nullable', @@ -177,6 +181,10 @@ function removeUnnecessaryFieldsFromRequest(Request $request) $request->offsetUnset('private_key_uuid'); $request->offsetUnset('use_build_server'); $request->offsetUnset('is_static'); + $request->offsetUnset('is_spa'); + $request->offsetUnset('is_auto_deploy_enabled'); + $request->offsetUnset('is_force_https_enabled'); + $request->offsetUnset('connect_to_docker_network'); $request->offsetUnset('force_domain_override'); $request->offsetUnset('autogenerate_domain'); $request->offsetUnset('is_container_label_escape_enabled'); From 161e0d2b0586711f75a41cc25380806bcd0b41df Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:37:02 +0100 Subject: [PATCH 035/434] chore(api): improve current request error message --- app/Http/Controllers/Api/ApplicationsController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 143ba64d3..dfc67bfae 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1175,7 +1175,7 @@ private function create_application(Request $request, $type) $duplicates = $urls->duplicates()->unique()->values(); if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) { - $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()); + $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()).' Use force_domain_override=true to proceed.'; } if (count($errors) > 0) { @@ -1400,7 +1400,7 @@ private function create_application(Request $request, $type) $duplicates = $urls->duplicates()->unique()->values(); if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) { - $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()); + $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()).' Use force_domain_override=true to proceed. '; } if (count($errors) > 0) { @@ -1601,7 +1601,7 @@ private function create_application(Request $request, $type) $duplicates = $urls->duplicates()->unique()->values(); if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) { - $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()); + $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()).' Use force_domain_override=true to proceed.'; } if (count($errors) > 0) { @@ -2655,7 +2655,7 @@ public function update_by_uuid(Request $request) $duplicates = $urls->duplicates()->unique()->values(); if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) { - $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()); + $errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()).' Use force_domain_override=true to proceed.'; } if (count($errors) > 0) { From e53c71908f46207b1b23d0df273ebdc149598e2a Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:12:49 +0100 Subject: [PATCH 036/434] fix(api): if domains field is empty clear the fqdn column - providing an empty string for `domains` allows the ability to remove all URLs from the domains field --- app/Http/Controllers/Api/ApplicationsController.php | 12 ++++++++++++ bootstrap/helpers/api.php | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index dfc67bfae..701c92d4d 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -2577,6 +2577,12 @@ public function update_by_uuid(Request $request) $errors = []; $urls = str($urls)->trim()->explode(',')->map(function ($url) use (&$errors) { $url = trim($url); + + // If "domains" is empty clear all URLs from the fqdn column + if (blank($url)) { + return null; + } + if (! filter_var($url, FILTER_VALIDATE_URL)) { $errors[] = 'Invalid URL: '.$url; @@ -3841,6 +3847,12 @@ private function validateDataApplications(Request $request, Server $server) $errors = []; $urls = str($urls)->trim()->explode(',')->map(function ($url) use (&$errors) { $url = trim($url); + + // If "domains" is empty clear all URLs from the fqdn column + if (blank($url)) { + return null; + } + if (! filter_var($url, FILTER_VALIDATE_URL)) { $errors[] = 'Invalid URL: '.$url; diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index c23f55c12..d5c2c996b 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -90,7 +90,7 @@ function sharedDataApplications() 'is_auto_deploy_enabled' => 'boolean', 'is_force_https_enabled' => 'boolean', 'static_image' => Rule::enum(StaticImageTypes::class), - 'domains' => 'string', + 'domains' => 'string|nullable', 'redirect' => Rule::enum(RedirectTypes::class), 'git_commit_sha' => 'string', 'docker_registry_image_name' => 'string|nullable', From a05c19855457e2571333222c7ed14ccaf4c3edab Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:44:27 +0100 Subject: [PATCH 037/434] chore(api): update openapi files --- openapi.json | 84 ++++++++++++++++++++++++++++++++++++++++++++++++---- openapi.yaml | 66 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 138 insertions(+), 12 deletions(-) diff --git a/openapi.json b/openapi.json index a94ef79b1..7bb1ff8f0 100644 --- a/openapi.json +++ b/openapi.json @@ -135,7 +135,7 @@ }, "domains": { "type": "string", - "description": "The application domains." + "description": "The application URLs in a comma-separated list." }, "git_commit_sha": { "type": "string", @@ -153,6 +153,18 @@ "type": "boolean", "description": "The flag to indicate if the application is static." }, + "is_spa": { + "type": "boolean", + "description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true." + }, + "is_auto_deploy_enabled": { + "type": "boolean", + "description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true." + }, + "is_force_https_enabled": { + "type": "boolean", + "description": "The flag to indicate if HTTPS is forced. Defaults to true." + }, "static_image": { "type": "string", "enum": [ @@ -322,6 +334,10 @@ "type": "string", "description": "The Dockerfile content." }, + "dockerfile_location": { + "type": "string", + "description": "The Dockerfile location in the repository." + }, "docker_compose_location": { "type": "string", "description": "The Docker Compose location." @@ -564,7 +580,7 @@ }, "domains": { "type": "string", - "description": "The application domains." + "description": "The application URLs in a comma-separated list." }, "git_commit_sha": { "type": "string", @@ -582,6 +598,18 @@ "type": "boolean", "description": "The flag to indicate if the application is static." }, + "is_spa": { + "type": "boolean", + "description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true." + }, + "is_auto_deploy_enabled": { + "type": "boolean", + "description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true." + }, + "is_force_https_enabled": { + "type": "boolean", + "description": "The flag to indicate if HTTPS is forced. Defaults to true." + }, "static_image": { "type": "string", "enum": [ @@ -751,6 +779,10 @@ "type": "string", "description": "The Dockerfile content." }, + "dockerfile_location": { + "type": "string", + "description": "The Dockerfile location in the repository" + }, "docker_compose_location": { "type": "string", "description": "The Docker Compose location." @@ -993,7 +1025,7 @@ }, "domains": { "type": "string", - "description": "The application domains." + "description": "The application URLs in a comma-separated list." }, "git_commit_sha": { "type": "string", @@ -1011,6 +1043,18 @@ "type": "boolean", "description": "The flag to indicate if the application is static." }, + "is_spa": { + "type": "boolean", + "description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true." + }, + "is_auto_deploy_enabled": { + "type": "boolean", + "description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true." + }, + "is_force_https_enabled": { + "type": "boolean", + "description": "The flag to indicate if HTTPS is forced. Defaults to true." + }, "static_image": { "type": "string", "enum": [ @@ -1180,6 +1224,10 @@ "type": "string", "description": "The Dockerfile content." }, + "dockerfile_location": { + "type": "string", + "description": "The Dockerfile location in the repository." + }, "docker_compose_location": { "type": "string", "description": "The Docker Compose location." @@ -1410,7 +1458,7 @@ }, "domains": { "type": "string", - "description": "The application domains." + "description": "The application URLs in a comma-separated list." }, "docker_registry_image_name": { "type": "string", @@ -1562,6 +1610,10 @@ "type": "boolean", "description": "The flag to indicate if the application should be deployed instantly." }, + "is_force_https_enabled": { + "type": "boolean", + "description": "The flag to indicate if HTTPS is forced. Defaults to true." + }, "use_build_server": { "type": "boolean", "nullable": true, @@ -1754,7 +1806,7 @@ }, "domains": { "type": "string", - "description": "The application domains." + "description": "The application URLs in a comma-separated list." }, "ports_mappings": { "type": "string", @@ -1894,6 +1946,10 @@ "type": "boolean", "description": "The flag to indicate if the application should be deployed instantly." }, + "is_force_https_enabled": { + "type": "boolean", + "description": "The flag to indicate if HTTPS is forced. Defaults to true." + }, "use_build_server": { "type": "boolean", "nullable": true, @@ -2402,7 +2458,7 @@ }, "domains": { "type": "string", - "description": "The application domains." + "description": "The application URLs in a comma-separated list." }, "git_commit_sha": { "type": "string", @@ -2420,6 +2476,18 @@ "type": "boolean", "description": "The flag to indicate if the application is static." }, + "is_spa": { + "type": "boolean", + "description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true." + }, + "is_auto_deploy_enabled": { + "type": "boolean", + "description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true." + }, + "is_force_https_enabled": { + "type": "boolean", + "description": "The flag to indicate if HTTPS is forced. Defaults to true." + }, "install_command": { "type": "string", "description": "The install command." @@ -2582,6 +2650,10 @@ "type": "string", "description": "The Dockerfile content." }, + "dockerfile_location": { + "type": "string", + "description": "The Dockerfile location in the repository." + }, "docker_compose_location": { "type": "string", "description": "The Docker Compose location." diff --git a/openapi.yaml b/openapi.yaml index 75ccb69fe..5d7adec32 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -97,7 +97,7 @@ paths: description: 'The application description.' domains: type: string - description: 'The application domains.' + description: 'The application URLs in a comma-separated list.' git_commit_sha: type: string description: 'The git commit SHA.' @@ -110,6 +110,15 @@ paths: is_static: type: boolean description: 'The flag to indicate if the application is static.' + is_spa: + type: boolean + description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.' + is_auto_deploy_enabled: + type: boolean + description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.' + is_force_https_enabled: + type: boolean + description: 'The flag to indicate if HTTPS is forced. Defaults to true.' static_image: type: string enum: ['nginx:alpine'] @@ -234,6 +243,9 @@ paths: dockerfile: type: string description: 'The Dockerfile content.' + dockerfile_location: + type: string + description: 'The Dockerfile location in the repository.' docker_compose_location: type: string description: 'The Docker Compose location.' @@ -369,7 +381,7 @@ paths: description: 'The application description.' domains: type: string - description: 'The application domains.' + description: 'The application URLs in a comma-separated list.' git_commit_sha: type: string description: 'The git commit SHA.' @@ -382,6 +394,15 @@ paths: is_static: type: boolean description: 'The flag to indicate if the application is static.' + is_spa: + type: boolean + description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.' + is_auto_deploy_enabled: + type: boolean + description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.' + is_force_https_enabled: + type: boolean + description: 'The flag to indicate if HTTPS is forced. Defaults to true.' static_image: type: string enum: ['nginx:alpine'] @@ -506,6 +527,9 @@ paths: dockerfile: type: string description: 'The Dockerfile content.' + dockerfile_location: + type: string + description: 'The Dockerfile location in the repository' docker_compose_location: type: string description: 'The Docker Compose location.' @@ -641,7 +665,7 @@ paths: description: 'The application description.' domains: type: string - description: 'The application domains.' + description: 'The application URLs in a comma-separated list.' git_commit_sha: type: string description: 'The git commit SHA.' @@ -654,6 +678,15 @@ paths: is_static: type: boolean description: 'The flag to indicate if the application is static.' + is_spa: + type: boolean + description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.' + is_auto_deploy_enabled: + type: boolean + description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.' + is_force_https_enabled: + type: boolean + description: 'The flag to indicate if HTTPS is forced. Defaults to true.' static_image: type: string enum: ['nginx:alpine'] @@ -778,6 +811,9 @@ paths: dockerfile: type: string description: 'The Dockerfile content.' + dockerfile_location: + type: string + description: 'The Dockerfile location in the repository.' docker_compose_location: type: string description: 'The Docker Compose location.' @@ -903,7 +939,7 @@ paths: description: 'The application description.' domains: type: string - description: 'The application domains.' + description: 'The application URLs in a comma-separated list.' docker_registry_image_name: type: string description: 'The docker registry image name.' @@ -1015,6 +1051,9 @@ paths: instant_deploy: type: boolean description: 'The flag to indicate if the application should be deployed instantly.' + is_force_https_enabled: + type: boolean + description: 'The flag to indicate if HTTPS is forced. Defaults to true.' use_build_server: type: boolean nullable: true @@ -1124,7 +1163,7 @@ paths: description: 'The application description.' domains: type: string - description: 'The application domains.' + description: 'The application URLs in a comma-separated list.' ports_mappings: type: string description: 'The ports mappings.' @@ -1227,6 +1266,9 @@ paths: instant_deploy: type: boolean description: 'The flag to indicate if the application should be deployed instantly.' + is_force_https_enabled: + type: boolean + description: 'The flag to indicate if HTTPS is forced. Defaults to true.' use_build_server: type: boolean nullable: true @@ -1524,7 +1566,7 @@ paths: description: 'The application description.' domains: type: string - description: 'The application domains.' + description: 'The application URLs in a comma-separated list.' git_commit_sha: type: string description: 'The git commit SHA.' @@ -1537,6 +1579,15 @@ paths: is_static: type: boolean description: 'The flag to indicate if the application is static.' + is_spa: + type: boolean + description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.' + is_auto_deploy_enabled: + type: boolean + description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.' + is_force_https_enabled: + type: boolean + description: 'The flag to indicate if HTTPS is forced. Defaults to true.' install_command: type: string description: 'The install command.' @@ -1657,6 +1708,9 @@ paths: dockerfile: type: string description: 'The Dockerfile content.' + dockerfile_location: + type: string + description: 'The Dockerfile location in the repository.' docker_compose_location: type: string description: 'The Docker Compose location.' From 650186b1abca9d600cb307612fc690851eb83723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?= <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 15 Jan 2026 14:03:10 +0100 Subject: [PATCH 038/434] fix(preview): docker compose preview URLs (#7959) --- app/Jobs/ApplicationPullRequestUpdateJob.php | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/Jobs/ApplicationPullRequestUpdateJob.php b/app/Jobs/ApplicationPullRequestUpdateJob.php index 05453b6a3..025daa12b 100755 --- a/app/Jobs/ApplicationPullRequestUpdateJob.php +++ b/app/Jobs/ApplicationPullRequestUpdateJob.php @@ -47,7 +47,7 @@ public function handle() match ($this->status) { ProcessStatus::QUEUED => $this->body = "The preview deployment for **{$serviceName}** is queued. ⏳\n\n", ProcessStatus::IN_PROGRESS => $this->body = "The preview deployment for **{$serviceName}** is in progress. 🟡\n\n", - ProcessStatus::FINISHED => $this->body = "The preview deployment for **{$serviceName}** is ready. 🟢\n\n".($this->preview->fqdn ? "[Open Preview]({$this->preview->fqdn}) | " : ''), + ProcessStatus::FINISHED => $this->body = "The preview deployment for **{$serviceName}** is ready. 🟢\n\n".$this->getPreviewLinks(), ProcessStatus::ERROR => $this->body = "The preview deployment for **{$serviceName}** failed. 🔴\n\n", ProcessStatus::KILLED => $this->body = "The preview deployment for **{$serviceName}** was killed. ⚫\n\n", ProcessStatus::CANCELLED => $this->body = "The preview deployment for **{$serviceName}** was cancelled. 🚫\n\n", @@ -91,4 +91,27 @@ private function delete_comment() { githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete'); } + + private function getPreviewLinks(): string + { + if ($this->application->build_pack === 'dockercompose') { + $dockerComposeDomains = json_decode($this->preview->docker_compose_domains, true) ?? []; + $links = []; + + foreach ($dockerComposeDomains as $serviceName => $config) { + $domain = data_get($config, 'domain'); + if (! empty($domain)) { + $firstDomain = str($domain)->explode(',')->first(); + $firstDomain = trim($firstDomain); + if (! empty($firstDomain)) { + $links[] = "[Open {$serviceName}]({$firstDomain})"; + } + } + } + + return ! empty($links) ? implode(' | ', $links).' | ' : ''; + } + + return $this->preview->fqdn ? "[Open Preview]({$this->preview->fqdn}) | " : ''; + } } From 51301fd12ee04351ecf2187d2e7d73df4ce8f0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?= <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 15 Jan 2026 21:59:51 +0100 Subject: [PATCH 039/434] feat(notifications): add mattermost notifications (#7963) --- app/Jobs/SendMessageToSlackJob.php | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/app/Jobs/SendMessageToSlackJob.php b/app/Jobs/SendMessageToSlackJob.php index dd5335850..fcd87a9dd 100644 --- a/app/Jobs/SendMessageToSlackJob.php +++ b/app/Jobs/SendMessageToSlackJob.php @@ -22,6 +22,36 @@ public function __construct( } public function handle(): void + { + if ($this->isSlackWebhook()) { + $this->sendToSlack(); + + return; + } + + /** + * This works with Mattermost and as a fallback also with Slack, the notifications just look slightly different and advanced formatting for slack is not supported with Mattermost. + * + * @see https://github.com/coollabsio/coolify/pull/6139#issuecomment-3756777708 + */ + $this->sendToMattermost(); + } + + private function isSlackWebhook(): bool + { + $parsedUrl = parse_url($this->webhookUrl); + + if ($parsedUrl === false) { + return false; + } + + $scheme = $parsedUrl['scheme'] ?? ''; + $host = $parsedUrl['host'] ?? ''; + + return $scheme === 'https' && $host === 'hooks.slack.com'; + } + + private function sendToSlack(): void { Http::post($this->webhookUrl, [ 'text' => $this->message->title, @@ -57,4 +87,24 @@ public function handle(): void ], ]); } + + /** + * @todo v5 refactor: Extract this into a separate SendMessageToMattermostJob.php triggered via the "mattermost" notification channel type. + */ + private function sendToMattermost(): void + { + $username = config('app.name'); + + Http::post($this->webhookUrl, [ + 'username' => $username, + 'attachments' => [ + [ + 'title' => $this->message->title, + 'color' => $this->message->color, + 'text' => $this->message->description, + 'footer' => $username, + ], + ], + ]); + } } From 95091e918ffd1623abb9f177ae6f73e36b30a3f0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:51:26 +0100 Subject: [PATCH 040/434] fix: optimize queries and caching for projects and environments --- app/Livewire/Project/Resource/Index.php | 55 +++++++++++++++---- app/Models/InstanceSettings.php | 6 +- app/Models/Server.php | 40 ++++++++++++++ app/Models/StandaloneDocker.php | 22 ++++++++ app/Models/SwarmDocker.php | 22 ++++++++ .../resources/breadcrumbs.blade.php | 44 ++++++++++++--- .../views/livewire/project/index.blade.php | 2 +- .../livewire/project/resource/index.blade.php | 28 +++++----- tests/Pest.php | 19 +++++++ 9 files changed, 203 insertions(+), 35 deletions(-) diff --git a/app/Livewire/Project/Resource/Index.php b/app/Livewire/Project/Resource/Index.php index 2b199dcfd..be6e3e98f 100644 --- a/app/Livewire/Project/Resource/Index.php +++ b/app/Livewire/Project/Resource/Index.php @@ -33,6 +33,10 @@ class Index extends Component public Collection $services; + public Collection $allProjects; + + public Collection $allEnvironments; + public array $parameters; public function mount() @@ -50,6 +54,33 @@ public function mount() ->firstOrFail(); $this->project = $project; + + // Load projects and environments for breadcrumb navigation (avoids inline queries in view) + $this->allProjects = Project::ownedByCurrentTeamCached(); + $this->allEnvironments = $project->environments() + ->with([ + 'applications.additional_servers', + 'applications.destination.server', + 'services', + 'services.destination.server', + 'postgresqls', + 'postgresqls.destination.server', + 'redis', + 'redis.destination.server', + 'mongodbs', + 'mongodbs.destination.server', + 'mysqls', + 'mysqls.destination.server', + 'mariadbs', + 'mariadbs.destination.server', + 'keydbs', + 'keydbs.destination.server', + 'dragonflies', + 'dragonflies.destination.server', + 'clickhouses', + 'clickhouses.destination.server', + ])->get(); + $this->environment = $environment->loadCount([ 'applications', 'redis', @@ -71,11 +102,13 @@ public function mount() 'destination.server.settings', 'settings', ])->get()->sortBy('name'); - $this->applications = $this->applications->map(function ($application) { + $projectUuid = $this->project->uuid; + $environmentUuid = $this->environment->uuid; + $this->applications = $this->applications->map(function ($application) use ($projectUuid, $environmentUuid) { $application->hrefLink = route('project.application.configuration', [ - 'project_uuid' => data_get($application, 'environment.project.uuid'), - 'environment_uuid' => data_get($application, 'environment.uuid'), - 'application_uuid' => data_get($application, 'uuid'), + 'project_uuid' => $projectUuid, + 'environment_uuid' => $environmentUuid, + 'application_uuid' => $application->uuid, ]); return $application; @@ -98,11 +131,11 @@ public function mount() 'tags', 'destination.server.settings', ])->get()->sortBy('name'); - $this->{$property} = $this->{$property}->map(function ($db) { + $this->{$property} = $this->{$property}->map(function ($db) use ($projectUuid, $environmentUuid) { $db->hrefLink = route('project.database.configuration', [ - 'project_uuid' => $this->project->uuid, + 'project_uuid' => $projectUuid, 'database_uuid' => $db->uuid, - 'environment_uuid' => data_get($this->environment, 'uuid'), + 'environment_uuid' => $environmentUuid, ]); return $db; @@ -114,11 +147,11 @@ public function mount() 'tags', 'destination.server.settings', ])->get()->sortBy('name'); - $this->services = $this->services->map(function ($service) { + $this->services = $this->services->map(function ($service) use ($projectUuid, $environmentUuid) { $service->hrefLink = route('project.service.configuration', [ - 'project_uuid' => data_get($service, 'environment.project.uuid'), - 'environment_uuid' => data_get($service, 'environment.uuid'), - 'service_uuid' => data_get($service, 'uuid'), + 'project_uuid' => $projectUuid, + 'environment_uuid' => $environmentUuid, + 'service_uuid' => $service->uuid, ]); return $service; diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index 376242ca0..ccc361d67 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Once; use Spatie\Url\Url; class InstanceSettings extends Model @@ -35,6 +36,9 @@ class InstanceSettings extends Model protected static function booted(): void { static::updated(function ($settings) { + // Clear once() cache so subsequent calls get fresh data + Once::flush(); + // Clear trusted hosts cache when FQDN changes if ($settings->wasChanged('fqdn')) { \Cache::forget('instance_settings_fqdn_host'); @@ -82,7 +86,7 @@ public function autoUpdateFrequency(): Attribute public static function get() { - return InstanceSettings::findOrFail(0); + return once(fn () => InstanceSettings::findOrFail(0)); } // public function getRecipients($notification) diff --git a/app/Models/Server.php b/app/Models/Server.php index 2319e0303..d693aea6d 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -108,6 +108,12 @@ class Server extends BaseModel public static $batch_counter = 0; + /** + * Identity map cache for request-scoped Server lookups. + * Prevents N+1 queries when the same Server is accessed multiple times. + */ + private static ?array $identityMapCache = null; + protected $appends = ['is_coolify_host']; protected static function booted() @@ -186,6 +192,40 @@ protected static function booted() $server->settings()->delete(); $server->sslCertificates()->delete(); }); + + static::updated(function () { + static::flushIdentityMap(); + }); + } + + /** + * Find a Server by ID using the identity map cache. + * This prevents N+1 queries when the same Server is accessed multiple times. + */ + public static function findCached($id): ?static + { + if ($id === null) { + return null; + } + + if (static::$identityMapCache === null) { + static::$identityMapCache = []; + } + + if (! isset(static::$identityMapCache[$id])) { + static::$identityMapCache[$id] = static::query()->find($id); + } + + return static::$identityMapCache[$id]; + } + + /** + * Flush the identity map cache. + * Called automatically on update, and should be called in tests. + */ + public static function flushIdentityMap(): void + { + static::$identityMapCache = null; } protected $casts = [ diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index 9f5f0b33e..62ef68434 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -73,6 +73,28 @@ public function server() return $this->belongsTo(Server::class); } + /** + * Get the server attribute using identity map caching. + * This intercepts lazy-loading to use cached Server lookups. + */ + public function getServerAttribute(): ?Server + { + // Use eager loaded data if available + if ($this->relationLoaded('server')) { + return $this->getRelation('server'); + } + + // Use identity map for lazy loading + $server = Server::findCached($this->server_id); + + // Cache in relation for future access on this instance + if ($server) { + $this->setRelation('server', $server); + } + + return $server; + } + public function services() { return $this->morphMany(Service::class, 'destination'); diff --git a/app/Models/SwarmDocker.php b/app/Models/SwarmDocker.php index e0fe349c7..08be81970 100644 --- a/app/Models/SwarmDocker.php +++ b/app/Models/SwarmDocker.php @@ -56,6 +56,28 @@ public function server() return $this->belongsTo(Server::class); } + /** + * Get the server attribute using identity map caching. + * This intercepts lazy-loading to use cached Server lookups. + */ + public function getServerAttribute(): ?Server + { + // Use eager loaded data if available + if ($this->relationLoaded('server')) { + return $this->getRelation('server'); + } + + // Use identity map for lazy loading + $server = Server::findCached($this->server_id); + + // Cache in relation for future access on this instance + if ($server) { + $this->setRelation('server', $server); + } + + return $server; + } + public function services() { return $this->morphMany(Service::class, 'destination'); diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php index 380c3270a..135cad3a7 100644 --- a/resources/views/components/resources/breadcrumbs.blade.php +++ b/resources/views/components/resources/breadcrumbs.blade.php @@ -2,12 +2,28 @@ 'lastDeploymentInfo' => null, 'lastDeploymentLink' => null, 'resource' => null, + 'projects' => null, + 'environments' => null, ]) @php - $projects = auth()->user()->currentTeam()->projects()->get(); - $environments = $resource->environment->project + use App\Models\Project; + + // Use passed props if available, otherwise query (backwards compatible) + $projects = $projects ?? Project::ownedByCurrentTeamCached(); + $environments = $environments ?? $resource->environment->project ->environments() - ->with(['applications', 'services']) + ->with([ + 'applications', + 'services', + 'postgresqls', + 'redis', + 'mongodbs', + 'mysqls', + 'mariadbs', + 'keydbs', + 'dragonflies', + 'clickhouses', + ]) ->get(); $currentProjectUuid = data_get($resource, 'environment.project.uuid'); $currentEnvironmentUuid = data_get($resource, 'environment.uuid'); @@ -74,6 +90,16 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar"> @foreach ($environments as $environment) @php + // Use pre-loaded relations instead of databases() method to avoid N+1 queries + $envDatabases = collect() + ->merge($environment->postgresqls ?? collect()) + ->merge($environment->redis ?? collect()) + ->merge($environment->mongodbs ?? collect()) + ->merge($environment->mysqls ?? collect()) + ->merge($environment->mariadbs ?? collect()) + ->merge($environment->keydbs ?? collect()) + ->merge($environment->dragonflies ?? collect()) + ->merge($environment->clickhouses ?? collect()); $envResources = collect() ->merge( $environment->applications->map( @@ -81,9 +107,7 @@ class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 bor ), ) ->merge( - $environment - ->databases() - ->map(fn($db) => ['type' => 'database', 'resource' => $db]), + $envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db]), ) ->merge( $environment->services->map( @@ -173,7 +197,9 @@ class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 bor ]), }; $isCurrentResource = $res->uuid === $currentResourceUuid; - $resHasMultipleServers = $resType === 'application' && method_exists($res, 'additional_servers') && $res->additional_servers()->count() > 0; + // Use loaded relation count if available, otherwise check additional_servers_count attribute + $resHasMultipleServers = $resType === 'application' && method_exists($res, 'additional_servers') && + ($res->relationLoaded('additional_servers') ? $res->additional_servers->count() > 0 : ($res->additional_servers_count ?? 0) > 0); $resServerName = $resHasMultipleServers ? null : data_get($res, 'destination.server.name'); @endphp
@foreach ($projects as $project)
- +
{{ $project->name }}
diff --git a/resources/views/livewire/project/resource/index.blade.php b/resources/views/livewire/project/resource/index.blade.php index 6df6db214..7d69e8b0b 100644 --- a/resources/views/livewire/project/resource/index.blade.php +++ b/resources/views/livewire/project/resource/index.blade.php @@ -29,9 +29,6 @@ @endcan
- @php - $projects = auth()->user()->currentTeam()->projects()->get(); - @endphp
- @php - $allEnvironments = $project - ->environments() - ->with(['applications', 'services']) - ->get(); - @endphp
  • @foreach ($allEnvironments as $env) @php + // Use pre-loaded relations instead of databases() method to avoid N+1 queries + $envDatabases = collect() + ->merge($env->postgresqls ?? collect()) + ->merge($env->redis ?? collect()) + ->merge($env->mongodbs ?? collect()) + ->merge($env->mysqls ?? collect()) + ->merge($env->mariadbs ?? collect()) + ->merge($env->keydbs ?? collect()) + ->merge($env->dragonflies ?? collect()) + ->merge($env->clickhouses ?? collect()); $envResources = collect() ->merge( $env->applications->map( @@ -169,9 +170,7 @@ class="flex items-center gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover ), ) ->merge( - $env - ->databases() - ->map(fn($db) => ['type' => 'database', 'resource' => $db]), + $envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db]), ) ->merge( $env->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc]), @@ -208,10 +207,11 @@ class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 bor 'database_uuid' => $res->uuid, ]), }; + // Use loaded relation to check additional_servers (avoids N+1 query) $resHasMultipleServers = $resType === 'application' && method_exists($res, 'additional_servers') && - $res->additional_servers()->count() > 0; + ($res->relationLoaded('additional_servers') ? $res->additional_servers->count() > 0 : false); $resServerName = $resHasMultipleServers ? null : data_get($res, 'destination.server.name'); diff --git a/tests/Pest.php b/tests/Pest.php index 236ac497e..619dea153 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,8 @@ in('Feature'); +/* +|-------------------------------------------------------------------------- +| Test Hooks +|-------------------------------------------------------------------------- +| +| Global hooks that run before/after each test. +| +*/ +beforeEach(function () { + // Flush the Once memoization cache to ensure tests get fresh data + Once::flush(); + + // Flush the Server identity map cache to ensure tests get fresh data + Server::flushIdentityMap(); +}); + /* |-------------------------------------------------------------------------- | Expectations From b971440202eba652006be1437e5dccecb30a10b1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:03:31 +0100 Subject: [PATCH 041/434] fix: update version numbers to 4.0.0-beta.462 and 4.0.0-beta.463 --- config/constants.php | 2 +- other/nightly/versions.json | 4 ++-- versions.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/constants.php b/config/constants.php index 499076e85..465428ab6 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.461', + 'version' => '4.0.0-beta.462', 'helper_version' => '1.0.12', 'realtime_version' => '1.0.10', 'self_hosted' => env('SELF_HOSTED', true), diff --git a/other/nightly/versions.json b/other/nightly/versions.json index c177b79ca..b63fe5c71 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.461" + "version": "4.0.0-beta.462" }, "nightly": { - "version": "4.0.0-beta.462" + "version": "4.0.0-beta.463" }, "helper": { "version": "1.0.12" diff --git a/versions.json b/versions.json index c177b79ca..b63fe5c71 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.461" + "version": "4.0.0-beta.462" }, "nightly": { - "version": "4.0.0-beta.462" + "version": "4.0.0-beta.463" }, "helper": { "version": "1.0.12" From 0a30e273c7dd5ca76525c53ab26e6f31a87656b6 Mon Sep 17 00:00:00 2001 From: Romain ROCHAS <46826777+yipfram@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:21:56 +0100 Subject: [PATCH 042/434] fix(service): update seaweedfs logo (#7971) Co-authored-by: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> --- public/seaweedfs.png | Bin 78638 -> 0 bytes public/svgs/seaweedfs.svg | 317 +++++++++++++++++++++++++++++++ templates/compose/seaweedfs.yaml | 2 +- 3 files changed, 318 insertions(+), 1 deletion(-) delete mode 100644 public/seaweedfs.png create mode 100644 public/svgs/seaweedfs.svg diff --git a/public/seaweedfs.png b/public/seaweedfs.png deleted file mode 100644 index 74bfb12a0d20cf54080980e7d4fe1a7d6597c36a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78638 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 Ds*%E} diff --git a/public/svgs/seaweedfs.svg b/public/svgs/seaweedfs.svg new file mode 100644 index 000000000..61fce5681 --- /dev/null +++ b/public/svgs/seaweedfs.svg @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/compose/seaweedfs.yaml b/templates/compose/seaweedfs.yaml index a1147da9c..d8b57906b 100644 --- a/templates/compose/seaweedfs.yaml +++ b/templates/compose/seaweedfs.yaml @@ -2,7 +2,7 @@ # slogan: SeaweedFS is a simple and highly scalable distributed file system. Compatible with S3, with an admin web interface. # category: storage # tags: object, storage, server, s3, api -# logo: svgs/garage.svg +# logo: svgs/seaweedfs.svg # port: 8333 services: From b02e64beda3b65384de3800940c2c5732733c111 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:24:12 +0100 Subject: [PATCH 043/434] docs(api): improve app endpoint deprecation description --- app/Http/Controllers/Api/ApplicationsController.php | 4 ++-- openapi.json | 4 ++-- openapi.yaml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 701c92d4d..799a622db 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -893,8 +893,8 @@ public function create_dockerimage_application(Request $request) * @deprecated Use POST /api/v1/services instead. This endpoint creates a Service, not an Application and is an unstable duplicate of POST /api/v1/services. */ #[OA\Post( - summary: 'Create (Docker Compose) (Deprecated)', - description: 'Create new application based on a docker-compose file (without git).', + summary: 'Create (Docker Compose)', + description: 'Deprecated: Use POST /api/v1/services instead.', path: '/applications/dockercompose', operationId: 'create-dockercompose-application', deprecated: true, diff --git a/openapi.json b/openapi.json index 7bb1ff8f0..bd502865a 100644 --- a/openapi.json +++ b/openapi.json @@ -2082,8 +2082,8 @@ "tags": [ "Applications" ], - "summary": "Create (Docker Compose) (Deprecated)", - "description": "Create new application based on a docker-compose file (without git).", + "summary": "Create (Docker Compose)", + "description": "Deprecated: Use POST \/api\/v1\/services instead.", "operationId": "create-dockercompose-application", "requestBody": { "description": "Application object that needs to be created.", diff --git a/openapi.yaml b/openapi.yaml index 5d7adec32..11148f43b 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1329,8 +1329,8 @@ paths: post: tags: - Applications - summary: 'Create (Docker Compose) (Deprecated)' - description: 'Create new application based on a docker-compose file (without git).' + summary: 'Create (Docker Compose)' + description: 'Deprecated: Use POST /api/v1/services instead.' operationId: create-dockercompose-application requestBody: description: 'Application object that needs to be created.' From 4131290719061187dd43114757ff54f6967297fc Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:24:30 +0100 Subject: [PATCH 044/434] chore(service): update service templates json --- templates/service-templates-latest.json | 2 +- templates/service-templates.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json index 540a47bbe..52223ce0c 100644 --- a/templates/service-templates-latest.json +++ b/templates/service-templates-latest.json @@ -4123,7 +4123,7 @@ "api" ], "category": "storage", - "logo": "svgs/garage.svg", + "logo": "svgs/seaweedfs.svg", "minversion": "0.0.0", "port": "8333" }, diff --git a/templates/service-templates.json b/templates/service-templates.json index 9ad5d9be8..e33dbbfb9 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -4123,7 +4123,7 @@ "api" ], "category": "storage", - "logo": "svgs/garage.svg", + "logo": "svgs/seaweedfs.svg", "minversion": "0.0.0", "port": "8333" }, From d8ee5ff91e72b828f3eacff4c3745448b59b00f3 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 16 Jan 2026 22:26:19 +0100 Subject: [PATCH 045/434] fix(service): soju svg --- {svgs => public/svgs}/soju.svg | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {svgs => public/svgs}/soju.svg (100%) diff --git a/svgs/soju.svg b/public/svgs/soju.svg similarity index 100% rename from svgs/soju.svg rename to public/svgs/soju.svg From d76b1fe1151094c4882e262937a5b82367f085a9 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 16 Jan 2026 22:27:17 +0100 Subject: [PATCH 046/434] feat(service): add back soketi-app-manager - I think it was accidentally removed in some version --- public/svgs/soketi-app-manager.svg | 9 +++++++ templates/compose/soketi-app-manager.yaml | 32 +++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 public/svgs/soketi-app-manager.svg create mode 100644 templates/compose/soketi-app-manager.yaml diff --git a/public/svgs/soketi-app-manager.svg b/public/svgs/soketi-app-manager.svg new file mode 100644 index 000000000..a9e31c968 --- /dev/null +++ b/public/svgs/soketi-app-manager.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/templates/compose/soketi-app-manager.yaml b/templates/compose/soketi-app-manager.yaml new file mode 100644 index 000000000..730bce6c6 --- /dev/null +++ b/templates/compose/soketi-app-manager.yaml @@ -0,0 +1,32 @@ +# documentation: https://github.com/rahulhaque/soketi-app-manager-filament +# slogan: Manage soketi websocket server and apps with ease. +# tags: soketi,websockets,app-manager,dashboard +# logo: svgs/soketi-app-manager.svg +# port: 8080 + +services: + soketi-app-manager: + image: ghcr.io/rahulhaque/soketi-app-manager-filament-alpine:latest + environment: + - SERVICE_FQDN_SOKETIAPPMANAGER_8080 + - AUTORUN_ENABLED=true + - AUTORUN_LARAVEL_MIGRATION=${AUTORUN_LARAVEL_MIGRATION:-true} + - APP_DEBUG=${APP_DEBUG:-false} + - APP_URL=$SERVICE_FQDN_SOKETIAPPMANAGER + - APP_KEY=$APP_KEY + - DB_CONNECTION=$DB_CONNECTION + - DB_HOST=$DB_HOST + - DB_PORT=$DB_PORT + - DB_DATABASE=$DB_DATABASE + - DB_USERNAME=$DB_USERNAME + - DB_PASSWORD=$DB_PASSWORD + - PUSHER_HOST=$PUSHER_HOST + - PUSHER_PORT=$PUSHER_PORT + - PUSHER_SCHEME=$PUSHER_SCHEME + - PUSHER_APP_CLUSTER=$PUSHER_APP_CLUSTER + - SOKETI_DB_REDIS_USERNAME=$SOKETI_DB_REDIS_USERNAME + - SOKETI_DB_REDIS_PASSWORD=$SOKETI_DB_REDIS_PASSWORD + - METRICS_HOST=$METRICS_HOST + healthcheck: + test: ["CMD", "php-fpm-healthcheck"] + start_period: 10s \ No newline at end of file From 3b3e90b55e7340aaeb9912a02ebd13860acfed7c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 16 Jan 2026 22:27:56 +0100 Subject: [PATCH 047/434] refactor(services): improve some service slogans --- templates/compose/hatchet.yaml | 2 +- templates/compose/redmine.yaml | 2 +- templates/compose/wings.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/compose/hatchet.yaml b/templates/compose/hatchet.yaml index 0ee1b1c3d..92e307734 100644 --- a/templates/compose/hatchet.yaml +++ b/templates/compose/hatchet.yaml @@ -1,5 +1,5 @@ # documentation: https://docs.hatchet.run/self-hosting/docker-compose -# slogan: Hatchet is a high-throughput, low-latency computing service. It's built on an open-source, fault-tolerant queue, allowing work to be delivered as fast as your system can handle +# slogan: Hatchet allows you to run background tasks at scale with a high-throughput, low-latency computing service built on an open-source, fault-tolerant queue. # tags: ai-agents,background-tasks,data-pipelines,scheduling # logo: svgs/hatchet.svg # port: 80 diff --git a/templates/compose/redmine.yaml b/templates/compose/redmine.yaml index 23fe07bbe..cdbf1f8ae 100644 --- a/templates/compose/redmine.yaml +++ b/templates/compose/redmine.yaml @@ -1,5 +1,5 @@ # documentation: https://www.redmine.org/ -# slogan: Redmine is a flexible project management web application. Written using the Ruby on Rails framework, it is cross-platform and cross-database. +# slogan: Redmine is a flexible project management web application. # tags: redmine,project management # logo: svgs/redmine.svg # port: 3000 diff --git a/templates/compose/wings.yaml b/templates/compose/wings.yaml index eaa34b0d3..059f7b0b6 100644 --- a/templates/compose/wings.yaml +++ b/templates/compose/wings.yaml @@ -1,5 +1,5 @@ # documentation: https://pterodactyl.io/ -# slogan: Wings is Pterodactyl's server control plane +# slogan: The server control plane for Pterodactyl Panel. Written from the ground-up with security, speed, and stability in mind. # category: devtools # tags: game, game server, management, panel, minecraft # logo: svgs/pterodactyl.png From d020dee9de72d8970f6285c78b1e8d8b8f27eedf Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 16 Jan 2026 22:29:09 +0100 Subject: [PATCH 048/434] chore(services): update service template json files --- templates/service-templates-latest.json | 21 ++++++++++++++++++--- templates/service-templates.json | 21 ++++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json index 52223ce0c..5a0f2efcf 100644 --- a/templates/service-templates-latest.json +++ b/templates/service-templates-latest.json @@ -1883,7 +1883,7 @@ }, "hatchet": { "documentation": "https://docs.hatchet.run/self-hosting/docker-compose?utm_source=coolify.io", - "slogan": "Hatchet is a high-throughput, low-latency computing service. It's built on an open-source, fault-tolerant queue, allowing work to be delivered as fast as your system can handle", + "slogan": "Hatchet allows you to run background tasks at scale with a high-throughput, low-latency computing service built on an open-source, fault-tolerant queue.", "compose": "c2VydmljZXM6CiAgaGF0Y2hldC1kYXNoYm9hcmQ6CiAgICBpbWFnZTogJ2doY3IuaW8vaGF0Y2hldC1kZXYvaGF0Y2hldC9oYXRjaGV0LWRhc2hib2FyZDpsYXRlc3QnCiAgICBjb21tYW5kOiAnc2ggLi9lbnRyeXBvaW50LnNoIC0tY29uZmlnIC9oYXRjaGV0L2NvbmZpZycKICAgIGVudmlyb25tZW50OgogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBvc3RncmVzOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtIFNFUlZJQ0VfVVJMX0hBVENIRVRfODAKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJhYmJpdG1xOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHNldHVwLWNvbmZpZzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgICBtaWdyYXRpb246CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2NvbXBsZXRlZF9zdWNjZXNzZnVsbHkKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2hhdGNoZXQtY2VydHM6L2hhdGNoZXQvY2VydHMnCiAgICAgIC0gJ2hhdGNoZXQtY29uZmlnOi9oYXRjaGV0L2NvbmZpZycKICBoYXRjaGV0LWVuZ2luZToKICAgIGltYWdlOiAnZ2hjci5pby9oYXRjaGV0LWRldi9oYXRjaGV0L2hhdGNoZXQtZW5naW5lOmxhdGVzdCcKICAgIGNvbW1hbmQ6ICcvaGF0Y2hldC9oYXRjaGV0LWVuZ2luZSAtLWNvbmZpZyAvaGF0Y2hldC9jb25maWcnCiAgICByZXN0YXJ0OiBvbi1mYWlsdXJlCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByYWJiaXRtcToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzZXR1cC1jb25maWc6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2NvbXBsZXRlZF9zdWNjZXNzZnVsbHkKICAgICAgbWlncmF0aW9uOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9jb21wbGV0ZWRfc3VjY2Vzc2Z1bGx5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlczo1NDMyLyRQT1NUR1JFU19EQicKICAgICAgLSAnU0VSVkVSX0dSUENfQklORF9BRERSRVNTPSR7U0VSVkVSX0dSUENfQklORF9BRERSRVNTOi0wLjAuMC4wfScKICAgICAgLSAnU0VSVkVSX0dSUENfSU5TRUNVUkU9JHtTRVJWRVJfR1JQQ19JTlNFQ1VSRTotdH0nCiAgICB2b2x1bWVzOgogICAgICAtICdoYXRjaGV0LWNlcnRzOi9oYXRjaGV0L2NlcnRzJwogICAgICAtICdoYXRjaGV0LWNvbmZpZzovaGF0Y2hldC9jb25maWcnCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3RncmVzcWwtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19VU0VSPSRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1oYXRjaGV0fScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICByYWJiaXRtcToKICAgIGltYWdlOiAncmFiYml0bXE6My1tYW5hZ2VtZW50JwogICAgaG9zdG5hbWU6IHJhYmJpdG1xCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9SQUJCSVRNUV8xNTY3MgogICAgICAtIFJBQkJJVE1RX0RFRkFVTFRfVVNFUj0kU0VSVklDRV9VU0VSX1JBQkJJVE1RCiAgICAgIC0gUkFCQklUTVFfREVGQVVMVF9QQVNTPSRTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1RCiAgICAgIC0gJ1BPUlQ9JHtSQUJCSVRNUV9QT1JUOi01NjcyfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiAncmFiYml0bXEtZGlhZ25vc3RpY3MgLXEgcGluZycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDMwcwogICAgICByZXRyaWVzOiAxMAogICAgdm9sdW1lczoKICAgICAgLSAncmFiYml0bXEtZGF0YTovdmFyL2xpYi9yYWJiaXRtcS8nCiAgbWlncmF0aW9uOgogICAgaW1hZ2U6ICdnaGNyLmlvL2hhdGNoZXQtZGV2L2hhdGNoZXQvaGF0Y2hldC1taWdyYXRlOmxhdGVzdCcKICAgIGNvbW1hbmQ6IC9oYXRjaGV0L2hhdGNoZXQtbWlncmF0ZQogICAgcmVzdGFydDogJ25vJwogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlczo1NDMyLyRQT1NUR1JFU19EQicKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgc2V0dXAtY29uZmlnOgogICAgaW1hZ2U6ICdnaGNyLmlvL2hhdGNoZXQtZGV2L2hhdGNoZXQvaGF0Y2hldC1hZG1pbjpsYXRlc3QnCiAgICBjb21tYW5kOiAnL2hhdGNoZXQvaGF0Y2hldC1hZG1pbiBxdWlja3N0YXJ0IC0tc2tpcCBjZXJ0cyAtLWdlbmVyYXRlZC1jb25maWctZGlyIC9oYXRjaGV0L2NvbmZpZyAtLW92ZXJ3cml0ZT1mYWxzZScKICAgIHJlc3RhcnQ6ICdubycKICAgIGVudmlyb25tZW50OgogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBvc3RncmVzOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtICdTRVJWRVJfVEFTS1FVRVVFX1JBQkJJVE1RX1VSTD1hbXFwOi8vJFNFUlZJQ0VfVVNFUl9SQUJCSVRNUTokU0VSVklDRV9QQVNTV09SRF9SQUJCSVRNUUByYWJiaXRtcTo1NjcyLycKICAgICAgLSAnU0VSVkVSX0FVVEhfQ09PS0lFX0RPTUFJTj0ke1NFUlZFUl9BVVRIX0NPT0tJRV9ET01BSU46LWxvY2FsaG9zdDo4MDgwfScKICAgICAgLSAnU0VSVkVSX0FVVEhfQ09PS0lFX0lOU0VDVVJFPSR7U0VSVkVSX0FVVEhfQ09PS0lFX0lOU0VDVVJFOi10fScKICAgICAgLSAnU0VSVkVSX0dSUENfQklORF9BRERSRVNTPSR7U0VSVkVSX0dSUENfQklORF9BRERSRVNTOi0wLjAuMC4wfScKICAgICAgLSAnU0VSVkVSX0dSUENfSU5TRUNVUkU9JHtTRVJWRVJfR1JQQ19JTlNFQ1VSRTotdH0nCiAgICAgIC0gJ1NFUlZFUl9HUlBDX0JST0FEQ0FTVF9BRERSRVNTPSR7U0VSVkVSX0dSUENfQlJPQURDQVNUX0FERFJFU1M6LWxvY2FsaG9zdDo3MDc3fScKICAgICAgLSBTRVJWRVJfREVGQVVMVF9FTkdJTkVfVkVSU0lPTj1WMQogICAgICAtICdTRVJWRVJfSU5URVJOQUxfQ0xJRU5UX0lOVEVSTkFMX0dSUENfQlJPQURDQVNUX0FERFJFU1M9JHtTRVJWRVJfSU5URVJOQUxfQ0xJRU5UX0lOVEVSTkFMX0dSUENfQlJPQURDQVNUX0FERFJFU1M6LWhhdGNoZXQtZW5naW5lOjcwNzd9JwogICAgdm9sdW1lczoKICAgICAgLSAnaGF0Y2hldF9jZXJ0czovaGF0Y2hldC9jZXJ0cycKICAgICAgLSAnaGF0Y2hldF9jb25maWc6L2hhdGNoZXQvY29uZmlnJwogICAgZGVwZW5kc19vbjoKICAgICAgbWlncmF0aW9uOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9jb21wbGV0ZWRfc3VjY2Vzc2Z1bGx5CiAgICAgIHJhYmJpdG1xOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5Cg==", "tags": [ "ai-agents", @@ -3995,7 +3995,7 @@ }, "redmine": { "documentation": "https://www.redmine.org/?utm_source=coolify.io", - "slogan": "Redmine is a flexible project management web application. Written using the Ruby on Rails framework, it is cross-platform and cross-database.", + "slogan": "Redmine is a flexible project management web application.", "compose": "c2VydmljZXM6CiAgcmVkbWluZToKICAgIGltYWdlOiAncmVkbWluZTo2LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9SRURNSU5FXzMwMDAKICAgICAgLSAnU0VDUkVUX0tFWV9CQVNFPSR7U0VSVklDRV9QQVNTV09SRF82NF9SRURNSU5FfScKICAgICAgLSBSRURNSU5FX0RCX1BPU1RHUkVTPXBvc3RncmVzcWwKICAgICAgLSBSRURNSU5FX0RCX1BPUlQ9NTQzMgogICAgICAtICdSRURNSU5FX0RCX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUkVETUlORV9EQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfUE9TVEdSRVN9JwogICAgICAtICdSRURNSU5FX0RCX0RBVEFCQVNFPSR7UE9TVEdSRVNfREFUQUJBU0U6LXJlZG1pbmUtZGJ9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICd3Z2V0IC1xTy0gaHR0cDovLzEyNy4wLjAuMTozMDAwLycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3JlZG1pbmUtY29uZmlnOi91c3Ivc3JjL3JlZG1pbmUvY29uZmlnJwogICAgICAtICdyZWRtaW5lLWZpbGVzOi91c3Ivc3JjL3JlZG1pbmUvZmlsZXMnCiAgICAgIC0gJ3JlZG1pbmUtdGhlbWVzOi91c3Ivc3JjL3JlZG1pbmUvdGhlbWVzJwogICAgICAtICdyZWRtaW5lLXBsdWdpbnM6L3Vzci9zcmMvcmVkbWluZS9wbHVnaW5zJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIHBvc3RncmVzcWw6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE3LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQVRBQkFTRTotcmVkbWluZS1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUK", "tags": [ "redmine", @@ -4287,6 +4287,21 @@ "minversion": "0.0.0", "port": "80" }, + "soketi-app-manager": { + "documentation": "https://github.com/rahulhaque/soketi-app-manager-filament?utm_source=coolify.io", + "slogan": "Manage soketi websocket server and apps with ease.", + "compose": "c2VydmljZXM6CiAgc29rZXRpLWFwcC1tYW5hZ2VyOgogICAgaW1hZ2U6ICdnaGNyLmlvL3JhaHVsaGFxdWUvc29rZXRpLWFwcC1tYW5hZ2VyLWZpbGFtZW50LWFscGluZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fU09LRVRJQVBQTUFOQUdFUl84MDgwCiAgICAgIC0gQVVUT1JVTl9FTkFCTEVEPXRydWUKICAgICAgLSAnQVVUT1JVTl9MQVJBVkVMX01JR1JBVElPTj0ke0FVVE9SVU5fTEFSQVZFTF9NSUdSQVRJT046LXRydWV9JwogICAgICAtICdBUFBfREVCVUc9JHtBUFBfREVCVUc6LWZhbHNlfScKICAgICAgLSBBUFBfVVJMPSRTRVJWSUNFX0ZRRE5fU09LRVRJQVBQTUFOQUdFUgogICAgICAtIEFQUF9LRVk9JEFQUF9LRVkKICAgICAgLSBEQl9DT05ORUNUSU9OPSREQl9DT05ORUNUSU9OCiAgICAgIC0gREJfSE9TVD0kREJfSE9TVAogICAgICAtIERCX1BPUlQ9JERCX1BPUlQKICAgICAgLSBEQl9EQVRBQkFTRT0kREJfREFUQUJBU0UKICAgICAgLSBEQl9VU0VSTkFNRT0kREJfVVNFUk5BTUUKICAgICAgLSBEQl9QQVNTV09SRD0kREJfUEFTU1dPUkQKICAgICAgLSBQVVNIRVJfSE9TVD0kUFVTSEVSX0hPU1QKICAgICAgLSBQVVNIRVJfUE9SVD0kUFVTSEVSX1BPUlQKICAgICAgLSBQVVNIRVJfU0NIRU1FPSRQVVNIRVJfU0NIRU1FCiAgICAgIC0gUFVTSEVSX0FQUF9DTFVTVEVSPSRQVVNIRVJfQVBQX0NMVVNURVIKICAgICAgLSBTT0tFVElfREJfUkVESVNfVVNFUk5BTUU9JFNPS0VUSV9EQl9SRURJU19VU0VSTkFNRQogICAgICAtIFNPS0VUSV9EQl9SRURJU19QQVNTV09SRD0kU09LRVRJX0RCX1JFRElTX1BBU1NXT1JECiAgICAgIC0gTUVUUklDU19IT1NUPSRNRVRSSUNTX0hPU1QKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBwaHAtZnBtLWhlYWx0aGNoZWNrCiAgICAgIHN0YXJ0X3BlcmlvZDogMTBzCg==", + "tags": [ + "soketi", + "websockets", + "app-manager", + "dashboard" + ], + "category": null, + "logo": "svgs/soketi-app-manager.svg", + "minversion": "0.0.0", + "port": "8080" + }, "soketi": { "documentation": "https://docs.soketi.app?utm_source=coolify.io", "slogan": "Soketi is your simple, fast, and resilient open-source WebSockets server.", @@ -5034,7 +5049,7 @@ }, "wings": { "documentation": "https://pterodactyl.io/?utm_source=coolify.io", - "slogan": "Wings is Pterodactyl's server control plane", + "slogan": "The server control plane for Pterodactyl Panel. Written from the ground-up with security, speed, and stability in mind.", "compose": "c2VydmljZXM6CiAgd2luZ3M6CiAgICBpbWFnZTogJ2doY3IuaW8vcHRlcm9kYWN0eWwvd2luZ3M6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfV0lOR1NfODQ0MwogICAgICAtICdUWj0ke1RJTUVaT05FOi1VVEN9JwogICAgICAtIFdJTkdTX1VTRVJOQU1FPSRTRVJWSUNFX1VTRVJfV0lOR1MKICAgIHZvbHVtZXM6CiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrJwogICAgICAtICcvdmFyL2xpYi9kb2NrZXIvY29udGFpbmVycy86L3Zhci9saWIvZG9ja2VyL2NvbnRhaW5lcnMvJwogICAgICAtICcvdmFyL2xpYi9wdGVyb2RhY3R5bC92b2x1bWVzOi92YXIvbGliL3B0ZXJvZGFjdHlsL3ZvbHVtZXMnCiAgICAgIC0gJy90bXAvcHRlcm9kYWN0eWw6L3RtcC9wdGVyb2RhY3R5bCcKICAgICAgLSAnd2luZ3NfbGliOi92YXIvbGliL3B0ZXJvZGFjdHlsLycKICAgICAgLSAnd2luZ3NfbG9nczovdmFyL2xvZy9wdGVyb2RhY3R5bC8nCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2V0Yy9jb25maWcueW1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL3B0ZXJvZGFjdHlsL2NvbmZpZy55bWwKICAgICAgICBjb250ZW50OiAiZGVidWc6IGZhbHNlXG51dWlkOiBSZXBsYWNlQ29uZmlnXG50b2tlbl9pZDogUmVwbGFjZUNvbmZpZ1xudG9rZW46IFJlcGxhY2VDb25maWdcbmFwaTpcbiAgaG9zdDogMC4wLjAuMFxuICBwb3J0OiA4NDQzICMgV2FybmluZywgcGFuZWwgbXVzdCBoYXZlIDQ0MyBhcyBkYWVtb24gcG9ydCwgd2hpbGUgaGVyZSBpdCBzaG91bGQgc2hvdWxkIGJlIDg0NDMsIEZRRE4gaW4gQ29vbGlmeSBmb3IgdGhpcyBzZXJ2aWNlIHNob3VsZCBiZSBodHRwczovLyo6ODQ0M1xuICBzc2w6XG4gICAgZW5hYmxlZDogZmFsc2VcbiAgICBjZXJ0OiBSZXBsYWNlQ29uZmlnXG4gICAga2V5OiBSZXBsYWNlQ29uZmlnXG4gIHVwbG9hZF9saW1pdDogMTAwXG5zeXN0ZW06XG4gIGRhdGE6IC92YXIvbGliL3B0ZXJvZGFjdHlsL3ZvbHVtZXNcbiAgc2Z0cDpcbiAgICBiaW5kX3BvcnQ6IDIwMjJcbmFsbG93ZWRfbW91bnRzOiBbXVxucmVtb3RlOiAnJyIKICAgIHBvcnRzOgogICAgICAtICcyMDIyOjIwMjInCg==", "tags": [ "game", diff --git a/templates/service-templates.json b/templates/service-templates.json index e33dbbfb9..be856db45 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -1883,7 +1883,7 @@ }, "hatchet": { "documentation": "https://docs.hatchet.run/self-hosting/docker-compose?utm_source=coolify.io", - "slogan": "Hatchet is a high-throughput, low-latency computing service. It's built on an open-source, fault-tolerant queue, allowing work to be delivered as fast as your system can handle", + "slogan": "Hatchet allows you to run background tasks at scale with a high-throughput, low-latency computing service built on an open-source, fault-tolerant queue.", "compose": "c2VydmljZXM6CiAgaGF0Y2hldC1kYXNoYm9hcmQ6CiAgICBpbWFnZTogJ2doY3IuaW8vaGF0Y2hldC1kZXYvaGF0Y2hldC9oYXRjaGV0LWRhc2hib2FyZDpsYXRlc3QnCiAgICBjb21tYW5kOiAnc2ggLi9lbnRyeXBvaW50LnNoIC0tY29uZmlnIC9oYXRjaGV0L2NvbmZpZycKICAgIGVudmlyb25tZW50OgogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBvc3RncmVzOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtIFNFUlZJQ0VfRlFETl9IQVRDSEVUXzgwCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByYWJiaXRtcToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBzZXR1cC1jb25maWc6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2NvbXBsZXRlZF9zdWNjZXNzZnVsbHkKICAgICAgbWlncmF0aW9uOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9jb21wbGV0ZWRfc3VjY2Vzc2Z1bGx5CiAgICB2b2x1bWVzOgogICAgICAtICdoYXRjaGV0LWNlcnRzOi9oYXRjaGV0L2NlcnRzJwogICAgICAtICdoYXRjaGV0LWNvbmZpZzovaGF0Y2hldC9jb25maWcnCiAgaGF0Y2hldC1lbmdpbmU6CiAgICBpbWFnZTogJ2doY3IuaW8vaGF0Y2hldC1kZXYvaGF0Y2hldC9oYXRjaGV0LWVuZ2luZTpsYXRlc3QnCiAgICBjb21tYW5kOiAnL2hhdGNoZXQvaGF0Y2hldC1lbmdpbmUgLS1jb25maWcgL2hhdGNoZXQvY29uZmlnJwogICAgcmVzdGFydDogb24tZmFpbHVyZQogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmFiYml0bXE6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgc2V0dXAtY29uZmlnOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9jb21wbGV0ZWRfc3VjY2Vzc2Z1bGx5CiAgICAgIG1pZ3JhdGlvbjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfY29tcGxldGVkX3N1Y2Nlc3NmdWxseQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVM6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNAcG9zdGdyZXM6NTQzMi8kUE9TVEdSRVNfREInCiAgICAgIC0gJ1NFUlZFUl9HUlBDX0JJTkRfQUREUkVTUz0ke1NFUlZFUl9HUlBDX0JJTkRfQUREUkVTUzotMC4wLjAuMH0nCiAgICAgIC0gJ1NFUlZFUl9HUlBDX0lOU0VDVVJFPSR7U0VSVkVSX0dSUENfSU5TRUNVUkU6LXR9JwogICAgdm9sdW1lczoKICAgICAgLSAnaGF0Y2hldC1jZXJ0czovaGF0Y2hldC9jZXJ0cycKICAgICAgLSAnaGF0Y2hldC1jb25maWc6L2hhdGNoZXQvY29uZmlnJwogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gUE9TVEdSRVNfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotaGF0Y2hldH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcmFiYml0bXE6CiAgICBpbWFnZTogJ3JhYmJpdG1xOjMtbWFuYWdlbWVudCcKICAgIGhvc3RuYW1lOiByYWJiaXRtcQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1JBQkJJVE1RXzE1NjcyCiAgICAgIC0gUkFCQklUTVFfREVGQVVMVF9VU0VSPSRTRVJWSUNFX1VTRVJfUkFCQklUTVEKICAgICAgLSBSQUJCSVRNUV9ERUZBVUxUX1BBU1M9JFNFUlZJQ0VfUEFTU1dPUkRfUkFCQklUTVEKICAgICAgLSAnUE9SVD0ke1JBQkJJVE1RX1BPUlQ6LTU2NzJ9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6ICdyYWJiaXRtcS1kaWFnbm9zdGljcyAtcSBwaW5nJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMzBzCiAgICAgIHJldHJpZXM6IDEwCiAgICB2b2x1bWVzOgogICAgICAtICdyYWJiaXRtcS1kYXRhOi92YXIvbGliL3JhYmJpdG1xLycKICBtaWdyYXRpb246CiAgICBpbWFnZTogJ2doY3IuaW8vaGF0Y2hldC1kZXYvaGF0Y2hldC9oYXRjaGV0LW1pZ3JhdGU6bGF0ZXN0JwogICAgY29tbWFuZDogL2hhdGNoZXQvaGF0Y2hldC1taWdyYXRlCiAgICByZXN0YXJ0OiAnbm8nCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBvc3RncmVzOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBzZXR1cC1jb25maWc6CiAgICBpbWFnZTogJ2doY3IuaW8vaGF0Y2hldC1kZXYvaGF0Y2hldC9oYXRjaGV0LWFkbWluOmxhdGVzdCcKICAgIGNvbW1hbmQ6ICcvaGF0Y2hldC9oYXRjaGV0LWFkbWluIHF1aWNrc3RhcnQgLS1za2lwIGNlcnRzIC0tZ2VuZXJhdGVkLWNvbmZpZy1kaXIgL2hhdGNoZXQvY29uZmlnIC0tb3ZlcndyaXRlPWZhbHNlJwogICAgcmVzdGFydDogJ25vJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVM6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNAcG9zdGdyZXM6NTQzMi8kUE9TVEdSRVNfREInCiAgICAgIC0gJ1NFUlZFUl9UQVNLUVVFVUVfUkFCQklUTVFfVVJMPWFtcXA6Ly8kU0VSVklDRV9VU0VSX1JBQkJJVE1ROiRTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1RQHJhYmJpdG1xOjU2NzIvJwogICAgICAtICdTRVJWRVJfQVVUSF9DT09LSUVfRE9NQUlOPSR7U0VSVkVSX0FVVEhfQ09PS0lFX0RPTUFJTjotbG9jYWxob3N0OjgwODB9JwogICAgICAtICdTRVJWRVJfQVVUSF9DT09LSUVfSU5TRUNVUkU9JHtTRVJWRVJfQVVUSF9DT09LSUVfSU5TRUNVUkU6LXR9JwogICAgICAtICdTRVJWRVJfR1JQQ19CSU5EX0FERFJFU1M9JHtTRVJWRVJfR1JQQ19CSU5EX0FERFJFU1M6LTAuMC4wLjB9JwogICAgICAtICdTRVJWRVJfR1JQQ19JTlNFQ1VSRT0ke1NFUlZFUl9HUlBDX0lOU0VDVVJFOi10fScKICAgICAgLSAnU0VSVkVSX0dSUENfQlJPQURDQVNUX0FERFJFU1M9JHtTRVJWRVJfR1JQQ19CUk9BRENBU1RfQUREUkVTUzotbG9jYWxob3N0OjcwNzd9JwogICAgICAtIFNFUlZFUl9ERUZBVUxUX0VOR0lORV9WRVJTSU9OPVYxCiAgICAgIC0gJ1NFUlZFUl9JTlRFUk5BTF9DTElFTlRfSU5URVJOQUxfR1JQQ19CUk9BRENBU1RfQUREUkVTUz0ke1NFUlZFUl9JTlRFUk5BTF9DTElFTlRfSU5URVJOQUxfR1JQQ19CUk9BRENBU1RfQUREUkVTUzotaGF0Y2hldC1lbmdpbmU6NzA3N30nCiAgICB2b2x1bWVzOgogICAgICAtICdoYXRjaGV0X2NlcnRzOi9oYXRjaGV0L2NlcnRzJwogICAgICAtICdoYXRjaGV0X2NvbmZpZzovaGF0Y2hldC9jb25maWcnCiAgICBkZXBlbmRzX29uOgogICAgICBtaWdyYXRpb246CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2NvbXBsZXRlZF9zdWNjZXNzZnVsbHkKICAgICAgcmFiYml0bXE6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkK", "tags": [ "ai-agents", @@ -3995,7 +3995,7 @@ }, "redmine": { "documentation": "https://www.redmine.org/?utm_source=coolify.io", - "slogan": "Redmine is a flexible project management web application. Written using the Ruby on Rails framework, it is cross-platform and cross-database.", + "slogan": "Redmine is a flexible project management web application.", "compose": "c2VydmljZXM6CiAgcmVkbWluZToKICAgIGltYWdlOiAncmVkbWluZTo2LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9SRURNSU5FXzMwMDAKICAgICAgLSAnU0VDUkVUX0tFWV9CQVNFPSR7U0VSVklDRV9QQVNTV09SRF82NF9SRURNSU5FfScKICAgICAgLSBSRURNSU5FX0RCX1BPU1RHUkVTPXBvc3RncmVzcWwKICAgICAgLSBSRURNSU5FX0RCX1BPUlQ9NTQzMgogICAgICAtICdSRURNSU5FX0RCX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUkVETUlORV9EQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfUE9TVEdSRVN9JwogICAgICAtICdSRURNSU5FX0RCX0RBVEFCQVNFPSR7UE9TVEdSRVNfREFUQUJBU0U6LXJlZG1pbmUtZGJ9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICd3Z2V0IC1xTy0gaHR0cDovLzEyNy4wLjAuMTozMDAwLycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3JlZG1pbmUtY29uZmlnOi91c3Ivc3JjL3JlZG1pbmUvY29uZmlnJwogICAgICAtICdyZWRtaW5lLWZpbGVzOi91c3Ivc3JjL3JlZG1pbmUvZmlsZXMnCiAgICAgIC0gJ3JlZG1pbmUtdGhlbWVzOi91c3Ivc3JjL3JlZG1pbmUvdGhlbWVzJwogICAgICAtICdyZWRtaW5lLXBsdWdpbnM6L3Vzci9zcmMvcmVkbWluZS9wbHVnaW5zJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIHBvc3RncmVzcWw6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE3LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQVRBQkFTRTotcmVkbWluZS1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUK", "tags": [ "redmine", @@ -4287,6 +4287,21 @@ "minversion": "0.0.0", "port": "80" }, + "soketi-app-manager": { + "documentation": "https://github.com/rahulhaque/soketi-app-manager-filament?utm_source=coolify.io", + "slogan": "Manage soketi websocket server and apps with ease.", + "compose": "c2VydmljZXM6CiAgc29rZXRpLWFwcC1tYW5hZ2VyOgogICAgaW1hZ2U6ICdnaGNyLmlvL3JhaHVsaGFxdWUvc29rZXRpLWFwcC1tYW5hZ2VyLWZpbGFtZW50LWFscGluZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fU09LRVRJQVBQTUFOQUdFUl84MDgwCiAgICAgIC0gQVVUT1JVTl9FTkFCTEVEPXRydWUKICAgICAgLSAnQVVUT1JVTl9MQVJBVkVMX01JR1JBVElPTj0ke0FVVE9SVU5fTEFSQVZFTF9NSUdSQVRJT046LXRydWV9JwogICAgICAtICdBUFBfREVCVUc9JHtBUFBfREVCVUc6LWZhbHNlfScKICAgICAgLSBBUFBfVVJMPSRTRVJWSUNFX0ZRRE5fU09LRVRJQVBQTUFOQUdFUgogICAgICAtIEFQUF9LRVk9JEFQUF9LRVkKICAgICAgLSBEQl9DT05ORUNUSU9OPSREQl9DT05ORUNUSU9OCiAgICAgIC0gREJfSE9TVD0kREJfSE9TVAogICAgICAtIERCX1BPUlQ9JERCX1BPUlQKICAgICAgLSBEQl9EQVRBQkFTRT0kREJfREFUQUJBU0UKICAgICAgLSBEQl9VU0VSTkFNRT0kREJfVVNFUk5BTUUKICAgICAgLSBEQl9QQVNTV09SRD0kREJfUEFTU1dPUkQKICAgICAgLSBQVVNIRVJfSE9TVD0kUFVTSEVSX0hPU1QKICAgICAgLSBQVVNIRVJfUE9SVD0kUFVTSEVSX1BPUlQKICAgICAgLSBQVVNIRVJfU0NIRU1FPSRQVVNIRVJfU0NIRU1FCiAgICAgIC0gUFVTSEVSX0FQUF9DTFVTVEVSPSRQVVNIRVJfQVBQX0NMVVNURVIKICAgICAgLSBTT0tFVElfREJfUkVESVNfVVNFUk5BTUU9JFNPS0VUSV9EQl9SRURJU19VU0VSTkFNRQogICAgICAtIFNPS0VUSV9EQl9SRURJU19QQVNTV09SRD0kU09LRVRJX0RCX1JFRElTX1BBU1NXT1JECiAgICAgIC0gTUVUUklDU19IT1NUPSRNRVRSSUNTX0hPU1QKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBwaHAtZnBtLWhlYWx0aGNoZWNrCiAgICAgIHN0YXJ0X3BlcmlvZDogMTBzCg==", + "tags": [ + "soketi", + "websockets", + "app-manager", + "dashboard" + ], + "category": null, + "logo": "svgs/soketi-app-manager.svg", + "minversion": "0.0.0", + "port": "8080" + }, "soketi": { "documentation": "https://docs.soketi.app?utm_source=coolify.io", "slogan": "Soketi is your simple, fast, and resilient open-source WebSockets server.", @@ -5034,7 +5049,7 @@ }, "wings": { "documentation": "https://pterodactyl.io/?utm_source=coolify.io", - "slogan": "Wings is Pterodactyl's server control plane", + "slogan": "The server control plane for Pterodactyl Panel. Written from the ground-up with security, speed, and stability in mind.", "compose": "c2VydmljZXM6CiAgd2luZ3M6CiAgICBpbWFnZTogJ2doY3IuaW8vcHRlcm9kYWN0eWwvd2luZ3M6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1dJTkdTXzg0NDMKICAgICAgLSAnVFo9JHtUSU1FWk9ORTotVVRDfScKICAgICAgLSBXSU5HU19VU0VSTkFNRT0kU0VSVklDRV9VU0VSX1dJTkdTCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnL3Zhci9saWIvZG9ja2VyL2NvbnRhaW5lcnMvOi92YXIvbGliL2RvY2tlci9jb250YWluZXJzLycKICAgICAgLSAnL3Zhci9saWIvcHRlcm9kYWN0eWwvdm9sdW1lczovdmFyL2xpYi9wdGVyb2RhY3R5bC92b2x1bWVzJwogICAgICAtICcvdG1wL3B0ZXJvZGFjdHlsOi90bXAvcHRlcm9kYWN0eWwnCiAgICAgIC0gJ3dpbmdzX2xpYjovdmFyL2xpYi9wdGVyb2RhY3R5bC8nCiAgICAgIC0gJ3dpbmdzX2xvZ3M6L3Zhci9sb2cvcHRlcm9kYWN0eWwvJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9ldGMvY29uZmlnLnltbAogICAgICAgIHRhcmdldDogL2V0Yy9wdGVyb2RhY3R5bC9jb25maWcueW1sCiAgICAgICAgY29udGVudDogImRlYnVnOiBmYWxzZVxudXVpZDogUmVwbGFjZUNvbmZpZ1xudG9rZW5faWQ6IFJlcGxhY2VDb25maWdcbnRva2VuOiBSZXBsYWNlQ29uZmlnXG5hcGk6XG4gIGhvc3Q6IDAuMC4wLjBcbiAgcG9ydDogODQ0MyAjIFdhcm5pbmcsIHBhbmVsIG11c3QgaGF2ZSA0NDMgYXMgZGFlbW9uIHBvcnQsIHdoaWxlIGhlcmUgaXQgc2hvdWxkIHNob3VsZCBiZSA4NDQzLCBGUUROIGluIENvb2xpZnkgZm9yIHRoaXMgc2VydmljZSBzaG91bGQgYmUgaHR0cHM6Ly8qOjg0NDNcbiAgc3NsOlxuICAgIGVuYWJsZWQ6IGZhbHNlXG4gICAgY2VydDogUmVwbGFjZUNvbmZpZ1xuICAgIGtleTogUmVwbGFjZUNvbmZpZ1xuICB1cGxvYWRfbGltaXQ6IDEwMFxuc3lzdGVtOlxuICBkYXRhOiAvdmFyL2xpYi9wdGVyb2RhY3R5bC92b2x1bWVzXG4gIHNmdHA6XG4gICAgYmluZF9wb3J0OiAyMDIyXG5hbGxvd2VkX21vdW50czogW11cbnJlbW90ZTogJyciCiAgICBwb3J0czoKICAgICAgLSAnMjAyMjoyMDIyJwo=", "tags": [ "game", From fe89bd30a33b0594db3701a154f24317fbe9f73b Mon Sep 17 00:00:00 2001 From: Andreas Date: Mon, 19 Jan 2026 18:13:51 +0100 Subject: [PATCH 049/434] fix(service): autobase database is not persisted correctly (#7978) --- templates/compose/autobase.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/autobase.yaml b/templates/compose/autobase.yaml index caafa8cce..bef8fdc05 100644 --- a/templates/compose/autobase.yaml +++ b/templates/compose/autobase.yaml @@ -27,7 +27,7 @@ services: environment: - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} volumes: - - autobase-db-data:/var/lib/postgresql/data + - autobase-db-data:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s From bd5696db1c955af074b07305db9fcc5ad67a887e Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:17:12 +0100 Subject: [PATCH 050/434] fix(ui): make tooltips a bit wider --- resources/css/utilities.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/css/utilities.css b/resources/css/utilities.css index c7b77022f..02be0c0c4 100644 --- a/resources/css/utilities.css +++ b/resources/css/utilities.css @@ -281,7 +281,7 @@ @utility info-helper { } @utility info-helper-popup { - @apply hidden absolute z-40 text-xs rounded-sm text-neutral-700 group-hover:block dark:border-coolgray-500 border-neutral-900 dark:bg-coolgray-400 bg-neutral-200 dark:text-neutral-300 max-w-xs whitespace-normal break-words; + @apply hidden absolute z-40 text-xs rounded-sm text-neutral-700 group-hover:block dark:border-coolgray-500 border-neutral-900 dark:bg-coolgray-400 bg-neutral-200 dark:text-neutral-300 max-w-sm whitespace-normal break-words; } @utility buyme { From 7aa41675ab95bdd351446ecae41e72b3be8de734 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:19:35 +0100 Subject: [PATCH 051/434] fix(ui): modal issues - tooltips can not extend outside the modal causing a scrollbar to appear - modals are to wide - remove unused minWidth and maxWidth props --- resources/views/components/modal-input.blade.php | 7 ++----- .../views/livewire/project/service/configuration.blade.php | 3 +-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/resources/views/components/modal-input.blade.php b/resources/views/components/modal-input.blade.php index d65162d36..bdc9d9ac7 100644 --- a/resources/views/components/modal-input.blade.php +++ b/resources/views/components/modal-input.blade.php @@ -7,8 +7,6 @@ 'action' => 'delete', 'content' => null, 'closeOutside' => true, - 'minWidth' => '36rem', - 'maxWidth' => '48rem', 'isFullWidth' => false, ]) @@ -16,7 +14,6 @@ $modalId = 'modal-' . uniqid(); @endphp -
    x-transition:leave="ease-in duration-100" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95" - class="relative w-full min-w-full lg:min-w-[{{ $minWidth }}] max-w-[{{ $maxWidth }}] max-h-[calc(100vh-2rem)] border rounded-sm drop-shadow-sm bg-white border-neutral-200 dark:bg-base dark:border-coolgray-300 flex flex-col"> + class="relative w-full lg:w-auto lg:min-w-2xl lg:max-w-4xl border rounded-sm drop-shadow-sm bg-white border-neutral-200 dark:bg-base dark:border-coolgray-300 flex flex-col">

    {{ $title }}

    diff --git a/resources/views/livewire/project/service/configuration.blade.php b/resources/views/livewire/project/service/configuration.blade.php index b5de2a6a3..c54c537ba 100644 --- a/resources/views/livewire/project/service/configuration.blade.php +++ b/resources/views/livewire/project/service/configuration.blade.php @@ -71,8 +71,7 @@ @if ($application->fqdn) {{ Str::limit($application->fqdn, 60) }} @can('update', $service) - + Date: Mon, 19 Jan 2026 18:28:33 +0100 Subject: [PATCH 052/434] feat(service): upgrade checkmate to v3 (#7995) --- templates/compose/checkmate.yaml | 73 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/templates/compose/checkmate.yaml b/templates/compose/checkmate.yaml index 70b1fee70..e52d6a35e 100644 --- a/templates/compose/checkmate.yaml +++ b/templates/compose/checkmate.yaml @@ -1,47 +1,48 @@ -# documentation: https://bluewavelabs.gitbook.io/checkmate -# slogan: An open source server monitoring application +# documentation: https://docs.checkmate.so/ +# slogan: An open source server and websites monitoring application # category: monitoring # tags: monitoring,server,uptime,healthcheck # logo: svgs/checkmate.png -# port: 80 +# port: 52345 services: - client: - image: bluewaveuptime/uptime_client:latest + checkmate: + image: 'ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:v3.2.0' environment: - - SERVICE_URL_CHECKMATE_80 - - UPTIME_APP_API_BASE_URL=${SERVICE_URL_CHECKMATESERVER_5000}/api/v1 + - SERVICE_URL_CHECKMATE_52345 + - 'UPTIME_APP_API_BASE_URL=${SERVICE_URL_CHECKMATE}/api/v1' + - 'UPTIME_APP_CLIENT_HOST=${SERVICE_URL_CHECKMATE}' + - 'DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db' + - 'CLIENT_HOST=${SERVICE_URL_CHECKMATE}' + - 'JWT_SECRET=${SERVICE_PASSWORD_64_JWT}' depends_on: - - server - server: - image: bluewaveuptime/uptime_server:latest - environment: - - SERVICE_URL_CHECKMATESERVER_5000 - - JWT_SECRET=${SERVICE_PASSWORD_64_JWT} - - REFRESH_TOKEN_SECRET=${SERVICE_PASSWORD_64_REFRESH} - - SYSTEM_EMAIL_ADDRESS=${SYSTEM_EMAIL_ADDRESS:-test@example.com} - - SYSTEM_EMAIL_PASSWORD=${SERVICE_PASSWORD_64_EMAIL} - - SYSTEM_EMAIL_HOST=${SYSTEM_EMAIL_HOST} - - SYSTEM_EMAIL_PORT=${SYSTEM_EMAIL_PORT} - - PAGESPEED_API_KEY=${PAGESPEED_API_KEY} - - DB_CONNECTION_STRING=${DB_CONNECTION_STRING:-mongodb://mongodb:27017/uptime_db} - - REDIS_HOST=${REDIS_HOST:-redis} - - REDIS_PORT=${REDIS_PORT:-6379} - - DB_TYPE=${DB_TYPE:-MongoDB} - - TOKEN_TTL=${TOKEN_TTL:-99d} - - REFRESH_TOKEN_TTL=${REFRESH_TOKEN_TTL:-99d} - volumes: - - /var/run/docker.sock:/var/run/docker.sock - depends_on: - - redis - mongodb - redis: - image: bluewaveuptime/uptime_redis:latest - volumes: - - redis:/data + healthcheck: + test: + - CMD + - node + - '-e' + - "require('http').get('http://127.0.0.1:52345/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))" + interval: 30s + timeout: 5s + retries: 3 mongodb: - image: bluewaveuptime/uptime_database_mongo:latest + image: 'ghcr.io/bluewave-labs/checkmate-mongo:v3.2.0' + command: + - mongod + - '--quiet' + - '--bind_ip_all' volumes: - - mongodb:/data/db - command: ["mongod", "--quiet"] + - 'checkmate-mongo:/data/db' + healthcheck: + test: + - CMD + - mongosh + - '--eval' + - "db.adminCommand('ping')" + - '--quiet' + interval: 5s + timeout: 30s + start_period: 10s + From 24ff75bb7f031411d71272fdcb1b999fdd213db2 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:50:56 +0100 Subject: [PATCH 053/434] fix(validation): add @, / and & support to names and descriptions --- app/Support/ValidationPatterns.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Support/ValidationPatterns.php b/app/Support/ValidationPatterns.php index 379f44af3..a2da0fc46 100644 --- a/app/Support/ValidationPatterns.php +++ b/app/Support/ValidationPatterns.php @@ -10,12 +10,12 @@ class ValidationPatterns /** * Pattern for names excluding all dangerous characters */ - public const NAME_PATTERN = '/^[\p{L}\p{M}\p{N}\s\-_.]+$/u'; + public const NAME_PATTERN = '/^[\p{L}\p{M}\p{N}\s\-_.@\/&]+$/u'; /** * Pattern for descriptions excluding all dangerous characters with some additional allowed characters */ - public const DESCRIPTION_PATTERN = '/^[\p{L}\p{M}\p{N}\s\-_.,!?()\'\"+=*]+$/u'; + public const DESCRIPTION_PATTERN = '/^[\p{L}\p{M}\p{N}\s\-_.,!?()\'\"+=*@\/&]+$/u'; /** * Get validation rules for name fields @@ -64,7 +64,7 @@ public static function descriptionRules(bool $required = false, int $maxLength = public static function nameMessages(): array { return [ - 'name.regex' => "The name may only contain letters (including Unicode), numbers, spaces, dashes (-), underscores (_) and dots (.).", + 'name.regex' => "The name may only contain letters (including Unicode), numbers, spaces, and these characters: - _ . / @ &", 'name.min' => 'The name must be at least :min characters.', 'name.max' => 'The name may not be greater than :max characters.', ]; @@ -76,7 +76,7 @@ public static function nameMessages(): array public static function descriptionMessages(): array { return [ - 'description.regex' => "The description may only contain letters (including Unicode), numbers, spaces, and common punctuation (- _ . , ! ? ( ) ' \" + = *).", + 'description.regex' => "The description may only contain letters (including Unicode), numbers, spaces, and common punctuation: - _ . , ! ? ( ) ' \" + = * / @ &", 'description.max' => 'The description may not be greater than :max characters.', ]; } From dd9e59932167edb9c057e025e986d61726b80bcd Mon Sep 17 00:00:00 2001 From: Raphael Afonso <34760118+Raphael-Afonso@users.noreply.github.com> Date: Mon, 19 Jan 2026 15:08:32 -0300 Subject: [PATCH 054/434] fix(backup): postgres restore arithmetic syntax error (#7997) --- app/Livewire/Project/Database/Import.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php index 946368a39..7d37bd473 100644 --- a/app/Livewire/Project/Database/Import.php +++ b/app/Livewire/Project/Database/Import.php @@ -147,7 +147,7 @@ private function validateServerPath(string $path): bool public ?int $activityId = null; - public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d ${POSTGRES_DB:\${POSTGRES_USER:-postgres}}'; + public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d ${POSTGRES_DB:-${POSTGRES_USER:-postgres}}'; public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE'; @@ -261,11 +261,11 @@ public function updatedDumpAll($value) $this->postgresqlRestoreCommand = <<<'EOD' psql -U ${POSTGRES_USER} -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IS NOT NULL AND pid <> pg_backend_pid()" && \ psql -U ${POSTGRES_USER} -t -c "SELECT datname FROM pg_database WHERE NOT datistemplate" | xargs -I {} dropdb -U ${POSTGRES_USER} --if-exists {} && \ -createdb -U ${POSTGRES_USER} ${POSTGRES_DB:\${POSTGRES_USER:-postgres}} +createdb -U ${POSTGRES_USER} ${POSTGRES_DB:-${POSTGRES_USER:-postgres}} EOD; - $this->restoreCommandText = $this->postgresqlRestoreCommand.' && (gunzip -cf 2>/dev/null || cat ) | psql -U ${POSTGRES_USER} -d ${POSTGRES_DB:\${POSTGRES_USER:-postgres}}'; + $this->restoreCommandText = $this->postgresqlRestoreCommand.' && (gunzip -cf 2>/dev/null || cat ) | psql -U ${POSTGRES_USER} -d ${POSTGRES_DB:-${POSTGRES_USER:-postgres}}'; } else { - $this->postgresqlRestoreCommand = 'pg_restore -U ${POSTGRES_USER} -d ${POSTGRES_DB:\${POSTGRES_USER:-postgres}}'; + $this->postgresqlRestoreCommand = 'pg_restore -U ${POSTGRES_USER} -d ${POSTGRES_DB:-${POSTGRES_USER:-postgres}}'; } break; } @@ -757,7 +757,7 @@ public function buildRestoreCommand(string $tmpPath): string case 'postgresql': $restoreCommand = $this->postgresqlRestoreCommand; if ($this->dumpAll) { - $restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | psql -U \${POSTGRES_USER} -d \${POSTGRES_DB:\${POSTGRES_USER:-postgres}}"; + $restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | psql -U \${POSTGRES_USER} -d \${POSTGRES_DB:-\${POSTGRES_USER:-postgres}}"; } else { $restoreCommand .= " {$tmpPath}"; } From 5d4374bd8f763a380137099421c551ddb0e75057 Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:14:41 +0100 Subject: [PATCH 055/434] feat(service): update pterodactyl version (#7981) --- templates/compose/pterodactyl-panel.yaml | 2 +- templates/compose/pterodactyl-with-wings.yaml | 4 ++-- templates/compose/wings.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/compose/pterodactyl-panel.yaml b/templates/compose/pterodactyl-panel.yaml index fbd88bbfb..9a3f6c779 100644 --- a/templates/compose/pterodactyl-panel.yaml +++ b/templates/compose/pterodactyl-panel.yaml @@ -32,7 +32,7 @@ services: retries: 3 pterodactyl: - image: ghcr.io/pterodactyl/panel:latest + image: ghcr.io/pterodactyl/panel:v1.12.0 volumes: - "panel-var:/app/var/" - "panel-nginx:/etc/nginx/http.d/" diff --git a/templates/compose/pterodactyl-with-wings.yaml b/templates/compose/pterodactyl-with-wings.yaml index adda9e3c9..6e1e3614c 100644 --- a/templates/compose/pterodactyl-with-wings.yaml +++ b/templates/compose/pterodactyl-with-wings.yaml @@ -33,7 +33,7 @@ services: timeout: 1s retries: 3 pterodactyl: - image: 'ghcr.io/pterodactyl/panel:v1.11.11' + image: 'ghcr.io/pterodactyl/panel:v1.12.0' volumes: - 'panel-var:/app/var/' - 'panel-nginx:/etc/nginx/http.d/' @@ -109,7 +109,7 @@ services: - MAIL_PASSWORD=$MAIL_PASSWORD - MAIL_ENCRYPTION=$MAIL_ENCRYPTION wings: - image: 'ghcr.io/pterodactyl/wings:v1.11.13' + image: 'ghcr.io/pterodactyl/wings:v1.12.1' restart: unless-stopped ports: - "2022:2022" diff --git a/templates/compose/wings.yaml b/templates/compose/wings.yaml index 059f7b0b6..96c47d95f 100644 --- a/templates/compose/wings.yaml +++ b/templates/compose/wings.yaml @@ -7,7 +7,7 @@ services: wings: - image: "ghcr.io/pterodactyl/wings:latest" + image: "ghcr.io/pterodactyl/wings:v1.12.1" environment: - SERVICE_URL_WINGS_8443 - "TZ=${TIMEZONE:-UTC}" From da535042820d589dc2e3d1d15decbc83471bf72b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20M=C3=B6nckemeyer?= Date: Mon, 19 Jan 2026 19:22:25 +0100 Subject: [PATCH 056/434] fix(service): users unable to create their first ente account without SMTP (#7986) --- templates/compose/ente-photos.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/compose/ente-photos.yaml b/templates/compose/ente-photos.yaml index a765e4a7e..effeeeb4a 100644 --- a/templates/compose/ente-photos.yaml +++ b/templates/compose/ente-photos.yaml @@ -39,13 +39,13 @@ services: - ENTE_S3_B2_EU_CEN_REGION=${S3_STORAGE_REGION:-us-east-1} - ENTE_S3_B2_EU_CEN_BUCKET=${S3_STORAGE_BUCKET:?} - - ENTE_SMTP_HOST=${ENTE_SMTP_HOST:-smtp.gmail.com} - - ENTE_SMTP_PORT=${ENTE_SMTP_PORT:-587} + - ENTE_SMTP_HOST=${ENTE_SMTP_HOST} + - ENTE_SMTP_PORT=${ENTE_SMTP_PORT} - ENTE_SMTP_USERNAME=${ENTE_SMTP_USERNAME} - ENTE_SMTP_PASSWORD=${ENTE_SMTP_PASSWORD} - ENTE_SMTP_EMAIL=${ENTE_SMTP_EMAIL} - ENTE_SMTP_SENDER_NAME=${ENTE_SMTP_SENDER_NAME} - - ENTE_SMTP_ENCRYPTION=${ENTE_SMTP_ENCRYPTION:-tls} + - ENTE_SMTP_ENCRYPTION=${ENTE_SMTP_ENCRYPTION} depends_on: postgres: From 3a60561a34c5d39470da76d13e5783da868df5f4 Mon Sep 17 00:00:00 2001 From: Mailo Date: Mon, 19 Jan 2026 19:27:19 +0100 Subject: [PATCH 057/434] fix(ui): horizontal overflow on application and service headings (#7970) --- resources/css/app.css | 14 ++++++++++++-- .../views/components/notification/navbar.blade.php | 2 +- .../livewire/project/application/heading.blade.php | 2 +- .../livewire/project/resource/index.blade.php | 2 +- .../livewire/project/service/heading.blade.php | 2 +- resources/views/livewire/server/navbar.blade.php | 4 ++-- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/resources/css/app.css b/resources/css/app.css index 30371d307..eeba1ee01 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -96,7 +96,17 @@ body { } body { - @apply min-h-screen text-sm antialiased scrollbar; + @apply min-h-screen text-sm antialiased scrollbar overflow-x-hidden; +} + +.coolify-monaco-editor { + @apply min-w-0 w-full; + overflow-x: hidden; +} + +.coolify-monaco-editor .monaco-editor, +.coolify-monaco-editor .overflow-guard { + max-width: 100%; } option { @@ -196,4 +206,4 @@ .log-highlight { .dark .log-highlight { background-color: rgba(234, 179, 8, 0.3); -} \ 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 256c4d528..0ee3b8ee4 100644 --- a/resources/views/components/notification/navbar.blade.php +++ b/resources/views/components/notification/navbar.blade.php @@ -2,7 +2,7 @@

    Notifications

    Get notified about your infrastructure.