From e8d985211e19b881abe721d54a2d6df8e23b961a Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Wed, 24 Dec 2025 11:30:16 +0100 Subject: [PATCH 001/602] feat: shared server environment variables --- app/Jobs/ApplicationDeploymentJob.php | 24 +-- app/Livewire/SharedVariables/Server/Index.php | 22 +++ app/Livewire/SharedVariables/Server/Show.php | 169 ++++++++++++++++++ app/Models/EnvironmentVariable.php | 65 ++++++- app/Models/Server.php | 5 + app/Models/SharedEnvironmentVariable.php | 5 + app/View/Components/Forms/EnvVarInput.php | 4 + bootstrap/helpers/constants.php | 2 +- ..._to_shared_environment_variables_table.php | 35 ++++ .../livewire/shared-variables/index.blade.php | 48 ++--- .../shared-variables/server/index.blade.php | 25 +++ .../shared-variables/server/show.blade.php | 36 ++++ routes/web.php | 4 + 13 files changed, 411 insertions(+), 33 deletions(-) create mode 100644 app/Livewire/SharedVariables/Server/Index.php create mode 100644 app/Livewire/SharedVariables/Server/Show.php create mode 100644 database/migrations/2025_12_24_095507_add_server_to_shared_environment_variables_table.php create mode 100644 resources/views/livewire/shared-variables/server/index.blade.php create mode 100644 resources/views/livewire/shared-variables/server/show.blade.php diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 56a29276b..b37eb9833 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1234,7 +1234,7 @@ private function generate_runtime_environment_variables() }); foreach ($runtime_environment_variables as $env) { - $envs->push($env->key.'='.$env->real_value); + $envs->push($env->key.'='.$env->getResolvedValueWithServer($this->server)); } // Check for PORT environment variable mismatch with ports_exposes @@ -1300,7 +1300,7 @@ private function generate_runtime_environment_variables() }); foreach ($runtime_environment_variables_preview as $env) { - $envs->push($env->key.'='.$env->real_value); + $envs->push($env->key.'='.$env->getResolvedValueWithServer($this->server)); } // Add PORT if not exists, use the first port as default if ($this->build_pack !== 'dockercompose') { @@ -2304,14 +2304,16 @@ private function generate_nixpacks_env_variables() $this->env_nixpacks_args = collect([]); if ($this->pull_request_id === 0) { foreach ($this->application->nixpacks_environment_variables as $env) { - if (! is_null($env->real_value) && $env->real_value !== '') { - $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); + $resolvedValue = $env->getResolvedValueWithServer($this->server); + if (! is_null($resolvedValue) && $resolvedValue !== '') { + $this->env_nixpacks_args->push("--env {$env->key}={$resolvedValue}"); } } } else { foreach ($this->application->nixpacks_environment_variables_preview as $env) { - if (! is_null($env->real_value) && $env->real_value !== '') { - $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); + $resolvedValue = $env->getResolvedValueWithServer($this->server); + if (! is_null($resolvedValue) && $resolvedValue !== '') { + $this->env_nixpacks_args->push("--env {$env->key}={$resolvedValue}"); } } } @@ -2447,8 +2449,9 @@ private function generate_env_variables() ->get(); foreach ($envs as $env) { - if (! is_null($env->real_value)) { - $this->env_args->put($env->key, $env->real_value); + $resolvedValue = $env->getResolvedValueWithServer($this->server); + if (! is_null($resolvedValue)) { + $this->env_args->put($env->key, $resolvedValue); } } } else { @@ -2458,8 +2461,9 @@ private function generate_env_variables() ->get(); foreach ($envs as $env) { - if (! is_null($env->real_value)) { - $this->env_args->put($env->key, $env->real_value); + $resolvedValue = $env->getResolvedValueWithServer($this->server); + if (! is_null($resolvedValue)) { + $this->env_args->put($env->key, $resolvedValue); } } } diff --git a/app/Livewire/SharedVariables/Server/Index.php b/app/Livewire/SharedVariables/Server/Index.php new file mode 100644 index 000000000..cd10e510a --- /dev/null +++ b/app/Livewire/SharedVariables/Server/Index.php @@ -0,0 +1,22 @@ +servers = Server::ownedByCurrentTeamCached(); + } + + public function render() + { + return view('livewire.shared-variables.server.index'); + } +} diff --git a/app/Livewire/SharedVariables/Server/Show.php b/app/Livewire/SharedVariables/Server/Show.php new file mode 100644 index 000000000..6aa34f242 --- /dev/null +++ b/app/Livewire/SharedVariables/Server/Show.php @@ -0,0 +1,169 @@ + 'refreshEnvs', 'saveKey' => 'saveKey', 'environmentVariableDeleted' => 'refreshEnvs']; + + public function saveKey($data) + { + try { + $this->authorize('update', $this->server); + + $found = $this->server->environment_variables()->where('key', $data['key'])->first(); + if ($found) { + throw new \Exception('Variable already exists.'); + } + $this->server->environment_variables()->create([ + 'key' => $data['key'], + 'value' => $data['value'], + 'is_multiline' => $data['is_multiline'], + 'is_literal' => $data['is_literal'], + 'type' => 'server', + 'team_id' => currentTeam()->id, + ]); + $this->server->refresh(); + $this->getDevView(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function mount() + { + $serverUuid = request()->route('server_uuid'); + $teamId = currentTeam()->id; + $server = Server::where('team_id', $teamId)->where('uuid', $serverUuid)->first(); + if (!$server) { + return redirect()->route('dashboard'); + } + $this->server = $server; + $this->getDevView(); + } + + public function switch() + { + $this->authorize('view', $this->server); + $this->view = $this->view === 'normal' ? 'dev' : 'normal'; + $this->getDevView(); + } + + public function getDevView() + { + $this->variables = $this->formatEnvironmentVariables($this->server->environment_variables->sortBy('key')); + } + + private function formatEnvironmentVariables($variables) + { + return $variables->map(function ($item) { + if ($item->is_shown_once) { + return "$item->key=(Locked Secret, delete and add again to change)"; + } + if ($item->is_multiline) { + return "$item->key=(Multiline environment variable, edit in normal view)"; + } + + return "$item->key=$item->value"; + })->join("\n"); + } + + public function submit() + { + try { + $this->authorize('update', $this->server); + $this->handleBulkSubmit(); + $this->getDevView(); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->refreshEnvs(); + } + } + + private function handleBulkSubmit() + { + $variables = parseEnvFormatToArray($this->variables); + + $changesMade = DB::transaction(function () use ($variables) { + // Delete removed variables + $deletedCount = $this->deleteRemovedVariables($variables); + + // Update or create variables + $updatedCount = $this->updateOrCreateVariables($variables); + + return $deletedCount > 0 || $updatedCount > 0; + }); + + if ($changesMade) { + $this->dispatch('success', 'Environment variables updated.'); + } + } + + private function deleteRemovedVariables($variables) + { + $variablesToDelete = $this->server->environment_variables()->whereNotIn('key', array_keys($variables))->get(); + + if ($variablesToDelete->isEmpty()) { + return 0; + } + + $this->server->environment_variables()->whereNotIn('key', array_keys($variables))->delete(); + + return $variablesToDelete->count(); + } + + private function updateOrCreateVariables($variables) + { + $count = 0; + foreach ($variables as $key => $value) { + $found = $this->server->environment_variables()->where('key', $key)->first(); + + if ($found) { + if (! $found->is_shown_once && ! $found->is_multiline) { + if ($found->value !== $value) { + $found->value = $value; + $found->save(); + $count++; + } + } + } else { + $this->server->environment_variables()->create([ + 'key' => $key, + 'value' => $value, + 'is_multiline' => false, + 'is_literal' => false, + 'type' => 'server', + 'team_id' => currentTeam()->id, + ]); + $count++; + } + } + + return $count; + } + + public function refreshEnvs() + { + $this->server->refresh(); + $this->getDevView(); + } + + public function render() + { + return view('livewire.shared-variables.server.show'); + } +} \ No newline at end of file diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 895dc1c43..9308b9ce6 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -122,6 +122,17 @@ public function realValue(): Attribute return null; } + // Load relationships needed for shared variable resolution + if (! $resource->relationLoaded('environment')) { + $resource->load('environment'); + } + if (! $resource->relationLoaded('server') && method_exists($resource, 'server')) { + $resource->load('server'); + } + if (! $resource->relationLoaded('destination') && method_exists($resource, 'destination')) { + $resource->load('destination.server'); + } + $real_value = $this->get_real_environment_variables($this->value, $resource); if ($this->is_literal || $this->is_multiline) { $real_value = '\''.$real_value.'\''; @@ -181,7 +192,43 @@ protected function isShared(): Attribute ); } - private function get_real_environment_variables(?string $environment_variable = null, $resource = null) + public function get_real_environment_variables_with_server(?string $environment_variable = null, $resource = null, $server = null) + { + return $this->get_real_environment_variables_internal($environment_variable, $resource, $server); + } + + public function getResolvedValueWithServer($server = null) + { + if (! $this->relationLoaded('resourceable')) { + $this->load('resourceable'); + } + $resource = $this->resourceable; + if (! $resource) { + return null; + } + + // Load relationships needed for shared variable resolution + if (! $resource->relationLoaded('environment')) { + $resource->load('environment'); + } + if (! $resource->relationLoaded('server') && method_exists($resource, 'server')) { + $resource->load('server'); + } + if (! $resource->relationLoaded('destination') && method_exists($resource, 'destination')) { + $resource->load('destination.server'); + } + + $real_value = $this->get_real_environment_variables_internal($this->value, $resource, $server); + if ($this->is_literal || $this->is_multiline) { + $real_value = '\''.$real_value.'\''; + } else { + $real_value = escapeEnvVariables($real_value); + } + + return $real_value; + } + + private function get_real_environment_variables_internal(?string $environment_variable = null, $resource = null, $serverOverride = null) { if ((is_null($environment_variable) && $environment_variable === '') || is_null($resource)) { return null; @@ -203,6 +250,17 @@ private function get_real_environment_variables(?string $environment_variable = $id = $resource->environment->project->id; } elseif ($type->value() === 'team') { $id = $resource->team()->id; + } elseif ($type->value() === 'server') { + // Use server override if provided (for deployment context), otherwise use resource's server + if ($serverOverride) { + $id = $serverOverride->id; + } elseif (isset($resource->server) && $resource->server) { + $id = $resource->server->id; + } elseif (isset($resource->destination) && $resource->destination && isset($resource->destination->server)) { + $id = $resource->destination->server->id; + } else { + $id = null; + } } if (is_null($id)) { continue; @@ -216,6 +274,11 @@ private function get_real_environment_variables(?string $environment_variable = return str($environment_variable)->value(); } + private function get_real_environment_variables(?string $environment_variable = null, $resource = null) + { + return $this->get_real_environment_variables_internal($environment_variable, $resource); + } + private function get_environment_variables(?string $environment_variable = null): ?string { if (! $environment_variable) { diff --git a/app/Models/Server.php b/app/Models/Server.php index be39e3f8d..31d4f6440 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1016,6 +1016,11 @@ public function team() return $this->belongsTo(Team::class); } + public function environment_variables() + { + return $this->hasMany(SharedEnvironmentVariable::class)->where('type', 'server'); + } + public function isProxyShouldRun() { // TODO: Do we need "|| $this->proxy->force_stop" here? diff --git a/app/Models/SharedEnvironmentVariable.php b/app/Models/SharedEnvironmentVariable.php index 7956f006a..7b68bbac9 100644 --- a/app/Models/SharedEnvironmentVariable.php +++ b/app/Models/SharedEnvironmentVariable.php @@ -27,4 +27,9 @@ public function environment() { return $this->belongsTo(Environment::class); } + + public function server() + { + return $this->belongsTo(Server::class); + } } diff --git a/app/View/Components/Forms/EnvVarInput.php b/app/View/Components/Forms/EnvVarInput.php index 4a98e4a51..faef64a36 100644 --- a/app/View/Components/Forms/EnvVarInput.php +++ b/app/View/Components/Forms/EnvVarInput.php @@ -38,6 +38,7 @@ public function __construct( public array $availableVars = [], public ?string $projectUuid = null, public ?string $environmentUuid = null, + public ?string $serverUuid = null, ) { // Handle authorization-based disabling if ($this->canGate && $this->canResource && $this->autoDisable) { @@ -86,6 +87,9 @@ public function render(): View|Closure|string 'environment_uuid' => $this->environmentUuid, ]) : route('shared-variables.environment.index'), + 'server' => $this->serverUuid + ? route('shared-variables.server.show', ['server_uuid' => $this->serverUuid]) + : route('shared-variables.server.index'), 'default' => route('shared-variables.index'), ]; diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index bbbe2bc05..9c103aaac 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -81,4 +81,4 @@ const NEEDS_TO_DISABLE_STRIPPREFIX = [ 'appwrite' => ['appwrite', 'appwrite-console', 'appwrite-realtime'], ]; -const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment']; +const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment', 'server']; diff --git a/database/migrations/2025_12_24_095507_add_server_to_shared_environment_variables_table.php b/database/migrations/2025_12_24_095507_add_server_to_shared_environment_variables_table.php new file mode 100644 index 000000000..0207ed955 --- /dev/null +++ b/database/migrations/2025_12_24_095507_add_server_to_shared_environment_variables_table.php @@ -0,0 +1,35 @@ +foreignId('server_id')->nullable()->constrained()->onDelete('cascade'); + $table->unique(['key', 'server_id', 'team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('shared_environment_variables', function (Blueprint $table) { + $table->dropUnique(['key', 'server_id', 'team_id']); + $table->dropForeign(['server_id']); + $table->dropColumn('server_id'); + }); + DB::statement("ALTER TABLE shared_environment_variables DROP CONSTRAINT shared_environment_variables_type_check"); + DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment'))"); + } +}; diff --git a/resources/views/livewire/shared-variables/index.blade.php b/resources/views/livewire/shared-variables/index.blade.php index 3e19e5f1a..5064b75ba 100644 --- a/resources/views/livewire/shared-variables/index.blade.php +++ b/resources/views/livewire/shared-variables/index.blade.php @@ -5,27 +5,33 @@

Shared Variables

-
Set Team / Project / Environment wide variables.
+
Set Team / Project / Environment / Server wide variables.
-
- -
-
Team wide
-
Usable for all resources in a team.
-
-
- -
-
Project wide
-
Usable for all resources in a project.
-
-
- -
-
Environment wide
-
Usable for all resources in an environment.
-
-
+
+ +
+
Team wide
+
Usable for all resources in a team.
+
+
+ +
+
Project wide
+
Usable for all resources in a project.
+
+
+ +
+
Environment wide
+
Usable for all resources in an environment.
+
+
+ +
+
Server wide
+
Usable for all resources in a server.
+
+
-
+
diff --git a/resources/views/livewire/shared-variables/server/index.blade.php b/resources/views/livewire/shared-variables/server/index.blade.php new file mode 100644 index 000000000..4183fee5b --- /dev/null +++ b/resources/views/livewire/shared-variables/server/index.blade.php @@ -0,0 +1,25 @@ +
+ + Server Variables | Coolify + +
+

Servers

+
+
List of your servers.
+
+ @forelse ($servers as $server) + +
+
{{ $server->name }}
+
+ {{ $server->description }}
+
+
+ @empty +
+
No server found.
+
+ @endforelse +
+
\ No newline at end of file diff --git a/resources/views/livewire/shared-variables/server/show.blade.php b/resources/views/livewire/shared-variables/server/show.blade.php new file mode 100644 index 000000000..44ceeae7f --- /dev/null +++ b/resources/views/livewire/shared-variables/server/show.blade.php @@ -0,0 +1,36 @@ +
+ + Server Variable | Coolify + +
+

Shared Variables for {{ data_get($server, 'name') }}

+ @can('update', $server) + + + + @endcan + {{ $view === 'normal' ? 'Developer view' : 'Normal view' }} +
+
+
You can use these variables anywhere with
+
@{{ server.VARIABLENAME }}
+ +
+ @if ($view === 'normal') +
+ @forelse ($server->environment_variables->sort()->sortBy('key') as $env) + + @empty +
No environment variables found.
+ @endforelse +
+ @else +
+ + Save All Environment Variables +
+ @endif +
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 2a9072299..c2c9293fd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -70,6 +70,8 @@ use App\Livewire\SharedVariables\Index as SharedVariablesIndex; use App\Livewire\SharedVariables\Project\Index as ProjectSharedVariablesIndex; use App\Livewire\SharedVariables\Project\Show as ProjectSharedVariablesShow; +use App\Livewire\SharedVariables\Server\Index as ServerSharedVariablesIndex; +use App\Livewire\SharedVariables\Server\Show as ServerSharedVariablesShow; use App\Livewire\SharedVariables\Team\Index as TeamSharedVariablesIndex; use App\Livewire\Source\Github\Change as GitHubChange; use App\Livewire\Storage\Index as StorageIndex; @@ -145,6 +147,8 @@ Route::get('/project/{project_uuid}', ProjectSharedVariablesShow::class)->name('shared-variables.project.show'); Route::get('/environments', EnvironmentSharedVariablesIndex::class)->name('shared-variables.environment.index'); Route::get('/environments/project/{project_uuid}/environment/{environment_uuid}', EnvironmentSharedVariablesShow::class)->name('shared-variables.environment.show'); + Route::get('/servers', ServerSharedVariablesIndex::class)->name('shared-variables.server.index'); + Route::get('/server/{server_uuid}', ServerSharedVariablesShow::class)->name('shared-variables.server.show'); }); Route::prefix('team')->group(function () { From 81009c29cf58b024605bc75d6163af9964f5e5dd Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Wed, 24 Dec 2025 13:31:40 +0100 Subject: [PATCH 002/602] fix: server env shows not found on application variables input field on autocomplete --- .../Shared/EnvironmentVariable/Add.php | 41 +++++++++++++++++++ .../Shared/EnvironmentVariable/Show.php | 41 +++++++++++++++++++ .../components/forms/env-var-input.blade.php | 2 +- .../shared/environment-variable/add.blade.php | 3 +- .../environment-variable/show.blade.php | 9 ++-- 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php index fa65e8bd2..f1b92c5db 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php @@ -67,6 +67,7 @@ public function availableSharedVariables(): array 'team' => [], 'project' => [], 'environment' => [], + 'server' => [], ]; // Early return if no team @@ -122,6 +123,46 @@ public function availableSharedVariables(): array } } + // Get server variables + $serverUuid = data_get($this->parameters, 'server_uuid'); + if ($serverUuid) { + // If we have a specific server_uuid, show variables for that server + $server = \App\Models\Server::where('team_id', $team->id) + ->where('uuid', $serverUuid) + ->first(); + + if ($server) { + try { + $this->authorize('view', $server); + $result['server'] = $server->environment_variables() + ->pluck('key') + ->toArray(); + } catch (\Illuminate\Auth\Access\AuthorizationException $e) { + // User not authorized to view server variables + } + } + } else { + // For application environment variables, try to use the application's destination server + $applicationUuid = data_get($this->parameters, 'application_uuid'); + if ($applicationUuid) { + $application = \App\Models\Application::whereRelation('environment.project.team', 'id', $team->id) + ->where('uuid', $applicationUuid) + ->with('destination.server') + ->first(); + + if ($application && $application->destination && $application->destination->server) { + try { + $this->authorize('view', $application->destination->server); + $result['server'] = $application->destination->server->environment_variables() + ->pluck('key') + ->toArray(); + } catch (\Illuminate\Auth\Access\AuthorizationException $e) { + // User not authorized to view server variables + } + } + } + } + return $result; } diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 2030f631e..a14adb83f 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -204,6 +204,7 @@ public function availableSharedVariables(): array 'team' => [], 'project' => [], 'environment' => [], + 'server' => [], ]; // Early return if no team @@ -259,6 +260,46 @@ public function availableSharedVariables(): array } } + // Get server variables + $serverUuid = data_get($this->parameters, 'server_uuid'); + if ($serverUuid) { + // If we have a specific server_uuid, show variables for that server + $server = \App\Models\Server::where('team_id', $team->id) + ->where('uuid', $serverUuid) + ->first(); + + if ($server) { + try { + $this->authorize('view', $server); + $result['server'] = $server->environment_variables() + ->pluck('key') + ->toArray(); + } catch (\Illuminate\Auth\Access\AuthorizationException $e) { + // User not authorized to view server variables + } + } + } else { + // For application environment variables, try to use the application's destination server + $applicationUuid = data_get($this->parameters, 'application_uuid'); + if ($applicationUuid) { + $application = \App\Models\Application::whereRelation('environment.project.team', 'id', $team->id) + ->where('uuid', $applicationUuid) + ->with('destination.server') + ->first(); + + if ($application && $application->destination && $application->destination->server) { + try { + $this->authorize('view', $application->destination->server); + $result['server'] = $application->destination->server->environment_variables() + ->pluck('key') + ->toArray(); + } catch (\Illuminate\Auth\Access\AuthorizationException $e) { + // User not authorized to view server variables + } + } + } + } + return $result; } diff --git a/resources/views/components/forms/env-var-input.blade.php b/resources/views/components/forms/env-var-input.blade.php index 2466a57f9..dde535f19 100644 --- a/resources/views/components/forms/env-var-input.blade.php +++ b/resources/views/components/forms/env-var-input.blade.php @@ -17,7 +17,7 @@ selectedIndex: 0, cursorPosition: 0, currentScope: null, - availableScopes: ['team', 'project', 'environment'], + availableScopes: ['team', 'project', 'environment', 'server'], availableVars: @js($availableVars), scopeUrls: @js($scopeUrls), 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..daf808c5e 100644 --- a/resources/views/livewire/project/shared/environment-variable/add.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/add.blade.php @@ -6,7 +6,8 @@ + :environmentUuid="data_get($parameters, 'environment_uuid')" + :serverUuid="data_get($parameters, 'server_uuid')" /> @endif @if (!$shared && !$is_multiline) 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..d2195c2af 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -111,7 +111,8 @@ id="value" :availableVars="$this->availableSharedVariables" :projectUuid="data_get($parameters, 'project_uuid')" - :environmentUuid="data_get($parameters, 'environment_uuid')" /> + :environmentUuid="data_get($parameters, 'environment_uuid')" + :serverUuid="data_get($parameters, 'server_uuid')" /> @if ($is_shared) @endif @@ -129,7 +130,8 @@ id="value" :availableVars="$this->availableSharedVariables" :projectUuid="data_get($parameters, 'project_uuid')" - :environmentUuid="data_get($parameters, 'environment_uuid')" /> + :environmentUuid="data_get($parameters, 'environment_uuid')" + :serverUuid="data_get($parameters, 'server_uuid')" /> @endif @if ($is_shared) @@ -145,7 +147,8 @@ id="value" :availableVars="$this->availableSharedVariables" :projectUuid="data_get($parameters, 'project_uuid')" - :environmentUuid="data_get($parameters, 'environment_uuid')" /> + :environmentUuid="data_get($parameters, 'environment_uuid')" + :serverUuid="data_get($parameters, 'server_uuid')" /> @if ($is_shared) @endif From 5ed308dcf01dba904c4a131cbcc8ac92c2b7a9d2 Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Wed, 24 Dec 2025 13:58:50 +0100 Subject: [PATCH 003/602] feat: predefined server variables (COOLIFY_SERVER_NAME, COOLIFY_SERVER_UUID) These are not visible on shared env page but user can use these variables like they use the COOLIFY_RESOURCE_UUID --- app/Livewire/SharedVariables/Server/Show.php | 20 +++++++++++++--- app/Models/Environment.php | 2 +- app/Models/Project.php | 2 +- app/Models/Server.php | 19 ++++++++++++++- app/Models/Team.php | 2 +- .../SharedEnvironmentVariableSeeder.php | 23 +++++++++++++++++++ .../shared-variables/server/show.blade.php | 2 +- 7 files changed, 62 insertions(+), 8 deletions(-) diff --git a/app/Livewire/SharedVariables/Server/Show.php b/app/Livewire/SharedVariables/Server/Show.php index 6aa34f242..1dd9f9d46 100644 --- a/app/Livewire/SharedVariables/Server/Show.php +++ b/app/Livewire/SharedVariables/Server/Show.php @@ -24,6 +24,10 @@ public function saveKey($data) try { $this->authorize('update', $this->server); + if (in_array($data['key'], ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])) { + throw new \Exception('Cannot create predefined variable.'); + } + $found = $this->server->environment_variables()->where('key', $data['key'])->first(); if ($found) { throw new \Exception('Variable already exists.'); @@ -64,7 +68,7 @@ public function switch() public function getDevView() { - $this->variables = $this->formatEnvironmentVariables($this->server->environment_variables->sortBy('key')); + $this->variables = $this->formatEnvironmentVariables($this->server->environment_variables->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])->sortBy('key')); } private function formatEnvironmentVariables($variables) @@ -115,13 +119,19 @@ private function handleBulkSubmit() private function deleteRemovedVariables($variables) { - $variablesToDelete = $this->server->environment_variables()->whereNotIn('key', array_keys($variables))->get(); + $variablesToDelete = $this->server->environment_variables() + ->whereNotIn('key', array_keys($variables)) + ->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME']) + ->get(); if ($variablesToDelete->isEmpty()) { return 0; } - $this->server->environment_variables()->whereNotIn('key', array_keys($variables))->delete(); + $this->server->environment_variables() + ->whereNotIn('key', array_keys($variables)) + ->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME']) + ->delete(); return $variablesToDelete->count(); } @@ -130,6 +140,10 @@ private function updateOrCreateVariables($variables) { $count = 0; foreach ($variables as $key => $value) { + // Skip predefined variables + if (in_array($key, ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])) { + continue; + } $found = $this->server->environment_variables()->where('key', $key)->first(); if ($found) { diff --git a/app/Models/Environment.php b/app/Models/Environment.php index c2ad9d2cb..38138da1e 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -56,7 +56,7 @@ public function isEmpty() public function environment_variables() { - return $this->hasMany(SharedEnvironmentVariable::class); + return $this->hasMany(SharedEnvironmentVariable::class)->where('type', 'environment'); } public function applications() diff --git a/app/Models/Project.php b/app/Models/Project.php index 8b26672f0..c1d7dc82a 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -73,7 +73,7 @@ protected static function booted() public function environment_variables() { - return $this->hasMany(SharedEnvironmentVariable::class); + return $this->hasMany(SharedEnvironmentVariable::class)->where('type', 'project'); } public function environments() diff --git a/app/Models/Server.php b/app/Models/Server.php index 31d4f6440..46587e7bc 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -19,6 +19,7 @@ use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; @@ -168,9 +169,25 @@ protected static function booted() $standaloneDocker->saveQuietly(); } } - if (! isset($server->proxy->redirect_enabled)) { + if (! isset($server->proxy->redirect_enabled)) { $server->proxy->redirect_enabled = true; } + + // Create predefined server shared variables + SharedEnvironmentVariable::create([ + 'key' => 'COOLIFY_SERVER_UUID', + 'value' => $server->uuid, + 'type' => 'server', + 'server_id' => $server->id, + 'team_id' => $server->team_id, + ]); + SharedEnvironmentVariable::create([ + 'key' => 'COOLIFY_SERVER_NAME', + 'value' => $server->name, + 'type' => 'server', + 'server_id' => $server->id, + 'team_id' => $server->team_id, + ]); }); static::retrieved(function ($server) { if (! isset($server->proxy->redirect_enabled)) { diff --git a/app/Models/Team.php b/app/Models/Team.php index 5cb186942..b98ae08ff 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -214,7 +214,7 @@ public function subscriptionEnded() public function environment_variables() { - return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id'); + return $this->hasMany(SharedEnvironmentVariable::class)->where('type', 'team'); } public function members() diff --git a/database/seeders/SharedEnvironmentVariableSeeder.php b/database/seeders/SharedEnvironmentVariableSeeder.php index 54643fe3b..b55d13a17 100644 --- a/database/seeders/SharedEnvironmentVariableSeeder.php +++ b/database/seeders/SharedEnvironmentVariableSeeder.php @@ -2,6 +2,7 @@ namespace Database\Seeders; +use App\Models\Server; use App\Models\SharedEnvironmentVariable; use Illuminate\Database\Seeder; @@ -32,5 +33,27 @@ public function run(): void 'project_id' => 1, 'team_id' => 0, ]); + + // Add predefined server variables to all existing servers + $servers = \App\Models\Server::all(); + foreach ($servers as $server) { + SharedEnvironmentVariable::firstOrCreate([ + 'key' => 'COOLIFY_SERVER_UUID', + 'type' => 'server', + 'server_id' => $server->id, + 'team_id' => $server->team_id, + ], [ + 'value' => $server->uuid, + ]); + + SharedEnvironmentVariable::firstOrCreate([ + 'key' => 'COOLIFY_SERVER_NAME', + 'type' => 'server', + 'server_id' => $server->id, + 'team_id' => $server->team_id, + ], [ + 'value' => $server->name, + ]); + } } } diff --git a/resources/views/livewire/shared-variables/server/show.blade.php b/resources/views/livewire/shared-variables/server/show.blade.php index 44ceeae7f..cddde9c76 100644 --- a/resources/views/livewire/shared-variables/server/show.blade.php +++ b/resources/views/livewire/shared-variables/server/show.blade.php @@ -19,7 +19,7 @@ @if ($view === 'normal')
- @forelse ($server->environment_variables->sort()->sortBy('key') as $env) + @forelse ($server->environment_variables->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])->sort()->sortBy('key') as $env) @empty From 09e14d2f516a426822b5c441ceda12bafba475cf Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:40:00 +0100 Subject: [PATCH 004/602] fix: server env not showing for services --- .../Shared/EnvironmentVariable/Add.php | 20 +++++++++++++++++++ .../Shared/EnvironmentVariable/Show.php | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php index f1b92c5db..fdcb39270 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php @@ -160,6 +160,26 @@ public function availableSharedVariables(): array // User not authorized to view server variables } } + } else { + // For service environment variables, try to use the service's server + $serviceUuid = data_get($this->parameters, 'service_uuid'); + if ($serviceUuid) { + $service = \App\Models\Service::whereRelation('environment.project.team', 'id', $team->id) + ->where('uuid', $serviceUuid) + ->with('server') + ->first(); + + if ($service && $service->server) { + try { + $this->authorize('view', $service->server); + $result['server'] = $service->server->environment_variables() + ->pluck('key') + ->toArray(); + } catch (\Illuminate\Auth\Access\AuthorizationException $e) { + // User not authorized to view server variables + } + } + } } } diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index a14adb83f..ac7549f18 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -297,6 +297,26 @@ public function availableSharedVariables(): array // User not authorized to view server variables } } + } else { + // For service environment variables, try to use the service's server + $serviceUuid = data_get($this->parameters, 'service_uuid'); + if ($serviceUuid) { + $service = \App\Models\Service::whereRelation('environment.project.team', 'id', $team->id) + ->where('uuid', $serviceUuid) + ->with('server') + ->first(); + + if ($service && $service->server) { + try { + $this->authorize('view', $service->server); + $result['server'] = $service->server->environment_variables() + ->pluck('key') + ->toArray(); + } catch (\Illuminate\Auth\Access\AuthorizationException $e) { + // User not authorized to view server variables + } + } + } } } From 82b19e59214826f557f294352bc66fa9a0525246 Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:41:08 +0100 Subject: [PATCH 005/602] fix: predefined server env were not generated for existing servers --- app/Models/SharedEnvironmentVariable.php | 2 +- ...d_server_variables_to_existing_servers.php | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php diff --git a/app/Models/SharedEnvironmentVariable.php b/app/Models/SharedEnvironmentVariable.php index 7b68bbac9..de6b23425 100644 --- a/app/Models/SharedEnvironmentVariable.php +++ b/app/Models/SharedEnvironmentVariable.php @@ -10,7 +10,7 @@ class SharedEnvironmentVariable extends Model protected $casts = [ 'key' => 'string', - 'value' => 'encrypted', + 'value' => 'string', ]; public function team() diff --git a/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php b/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php new file mode 100644 index 000000000..d31b57ca7 --- /dev/null +++ b/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php @@ -0,0 +1,68 @@ +get(); + + foreach ($servers as $server) { + // Check if COOLIFY_SERVER_UUID already exists + $uuidExists = DB::table('shared_environment_variables') + ->where('type', 'server') + ->where('server_id', $server->id) + ->where('key', 'COOLIFY_SERVER_UUID') + ->exists(); + + if (!$uuidExists) { + DB::table('shared_environment_variables')->insert([ + 'key' => 'COOLIFY_SERVER_UUID', + 'value' => $server->uuid, + 'type' => 'server', + 'server_id' => $server->id, + 'team_id' => $server->team_id, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + // Check if COOLIFY_SERVER_NAME already exists + $nameExists = DB::table('shared_environment_variables') + ->where('type', 'server') + ->where('server_id', $server->id) + ->where('key', 'COOLIFY_SERVER_NAME') + ->exists(); + + if (!$nameExists) { + DB::table('shared_environment_variables')->insert([ + 'key' => 'COOLIFY_SERVER_NAME', + 'value' => $server->name, + 'type' => 'server', + 'server_id' => $server->id, + 'team_id' => $server->team_id, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Remove predefined server variables + DB::table('shared_environment_variables') + ->where('type', 'server') + ->whereIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME']) + ->delete(); + } +}; From 50b589aeff3820d000b741335e46e437c985efb7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 26 Dec 2025 12:02:24 +0100 Subject: [PATCH 006/602] fix(ui): Initialize latestVersion in Upgrade component mount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upgrade modal was displaying "0.0.0" as the target version because $latestVersion was not initialized during component mount. The Blade template rendered before Alpine's x-init could populate the value. Fixed by calling get_latest_version_of_coolify() in mount() to ensure the version is available at initial render time. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 --- app/Livewire/Upgrade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index 7948ad6a9..25cedc71b 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -24,6 +24,7 @@ class Upgrade extends Component public function mount() { $this->currentVersion = config('constants.coolify.version'); + $this->latestVersion = get_latest_version_of_coolify(); $this->devMode = isDev(); } From e3df380a04b4cb16fd01b0c05d084509ab668f8f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:30:53 +0100 Subject: [PATCH 007/602] fix: change value cast to encrypted for shared environment variables --- app/Models/SharedEnvironmentVariable.php | 2 +- templates/service-templates-latest.json | 6 +++--- templates/service-templates.json | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/Models/SharedEnvironmentVariable.php b/app/Models/SharedEnvironmentVariable.php index de6b23425..7b68bbac9 100644 --- a/app/Models/SharedEnvironmentVariable.php +++ b/app/Models/SharedEnvironmentVariable.php @@ -10,7 +10,7 @@ class SharedEnvironmentVariable extends Model protected $casts = [ 'key' => 'string', - 'value' => 'string', + 'value' => 'encrypted', ]; public function team() 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 99d22ae7d68b0fde62bfcc78b7693287ec744d22 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:31:00 +0100 Subject: [PATCH 008/602] fix: filter available scopes based on existing variables in env var input --- .../views/components/forms/env-var-input.blade.php | 14 +++++++++++++- .../shared/environment-variable/show.blade.php | 6 +++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/resources/views/components/forms/env-var-input.blade.php b/resources/views/components/forms/env-var-input.blade.php index dde535f19..61e308a83 100644 --- a/resources/views/components/forms/env-var-input.blade.php +++ b/resources/views/components/forms/env-var-input.blade.php @@ -17,8 +17,15 @@ selectedIndex: 0, cursorPosition: 0, currentScope: null, - availableScopes: ['team', 'project', 'environment', 'server'], availableVars: @js($availableVars), + get availableScopes() { + // Only include scopes that have at least one variable + const allScopes = ['team', 'project', 'environment', 'server']; + return allScopes.filter(scope => { + const vars = this.availableVars[scope]; + return vars && vars.length > 0; + }); + }, scopeUrls: @js($scopeUrls), handleInput() { @@ -54,6 +61,11 @@ if (content === '') { this.currentScope = null; + // Only show dropdown if there are available scopes with variables + if (this.availableScopes.length === 0) { + this.showDropdown = false; + return; + } this.suggestions = this.availableScopes.map(scope => ({ type: 'scope', value: scope, 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 d2195c2af..4dc46bbbb 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -109,7 +109,7 @@ disabled type="password" id="value" - :availableVars="$this->availableSharedVariables" + :availableVars="$isSharedVariable ? [] : $this->availableSharedVariables" :projectUuid="data_get($parameters, 'project_uuid')" :environmentUuid="data_get($parameters, 'environment_uuid')" :serverUuid="data_get($parameters, 'server_uuid')" /> @@ -128,7 +128,7 @@ :required="$is_redis_credential" type="password" id="value" - :availableVars="$this->availableSharedVariables" + :availableVars="$isSharedVariable ? [] : $this->availableSharedVariables" :projectUuid="data_get($parameters, 'project_uuid')" :environmentUuid="data_get($parameters, 'environment_uuid')" :serverUuid="data_get($parameters, 'server_uuid')" /> @@ -145,7 +145,7 @@ disabled type="password" id="value" - :availableVars="$this->availableSharedVariables" + :availableVars="$isSharedVariable ? [] : $this->availableSharedVariables" :projectUuid="data_get($parameters, 'project_uuid')" :environmentUuid="data_get($parameters, 'environment_uuid')" :serverUuid="data_get($parameters, 'server_uuid')" /> From 510fb2256bc88f625c26e2602943cc23578e07d2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:46:39 +0100 Subject: [PATCH 009/602] fix: add 'is_literal' flag to shared environment variables for servers --- app/Models/Server.php | 5 +++-- ..._add_predefined_server_variables_to_existing_servers.php | 6 ++++-- database/seeders/SharedEnvironmentVariableSeeder.php | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index 46587e7bc..bb75a414b 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -19,7 +19,6 @@ use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; @@ -169,7 +168,7 @@ protected static function booted() $standaloneDocker->saveQuietly(); } } - if (! isset($server->proxy->redirect_enabled)) { + if (! isset($server->proxy->redirect_enabled)) { $server->proxy->redirect_enabled = true; } @@ -180,6 +179,7 @@ protected static function booted() 'type' => 'server', 'server_id' => $server->id, 'team_id' => $server->team_id, + 'is_literal' => true, ]); SharedEnvironmentVariable::create([ 'key' => 'COOLIFY_SERVER_NAME', @@ -187,6 +187,7 @@ protected static function booted() 'type' => 'server', 'server_id' => $server->id, 'team_id' => $server->team_id, + 'is_literal' => true, ]); }); static::retrieved(function ($server) { diff --git a/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php b/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php index d31b57ca7..3d09197b4 100644 --- a/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php +++ b/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php @@ -21,13 +21,14 @@ public function up(): void ->where('key', 'COOLIFY_SERVER_UUID') ->exists(); - if (!$uuidExists) { + if (! $uuidExists) { DB::table('shared_environment_variables')->insert([ 'key' => 'COOLIFY_SERVER_UUID', 'value' => $server->uuid, 'type' => 'server', 'server_id' => $server->id, 'team_id' => $server->team_id, + 'is_literal' => true, 'created_at' => now(), 'updated_at' => now(), ]); @@ -40,13 +41,14 @@ public function up(): void ->where('key', 'COOLIFY_SERVER_NAME') ->exists(); - if (!$nameExists) { + if (! $nameExists) { DB::table('shared_environment_variables')->insert([ 'key' => 'COOLIFY_SERVER_NAME', 'value' => $server->name, 'type' => 'server', 'server_id' => $server->id, 'team_id' => $server->team_id, + 'is_literal' => true, 'created_at' => now(), 'updated_at' => now(), ]); diff --git a/database/seeders/SharedEnvironmentVariableSeeder.php b/database/seeders/SharedEnvironmentVariableSeeder.php index b55d13a17..7a17fbd10 100644 --- a/database/seeders/SharedEnvironmentVariableSeeder.php +++ b/database/seeders/SharedEnvironmentVariableSeeder.php @@ -44,6 +44,7 @@ public function run(): void 'team_id' => $server->team_id, ], [ 'value' => $server->uuid, + 'is_literal' => true, ]); SharedEnvironmentVariable::firstOrCreate([ @@ -53,6 +54,7 @@ public function run(): void 'team_id' => $server->team_id, ], [ 'value' => $server->name, + 'is_literal' => true, ]); } } From 9cd8dff5bf5092d6b9826f9b7a5baf9337454c84 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:46:44 +0100 Subject: [PATCH 010/602] fix: remove redundant sort call in environment variables display --- resources/views/livewire/shared-variables/server/show.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/shared-variables/server/show.blade.php b/resources/views/livewire/shared-variables/server/show.blade.php index cddde9c76..c39b647fa 100644 --- a/resources/views/livewire/shared-variables/server/show.blade.php +++ b/resources/views/livewire/shared-variables/server/show.blade.php @@ -19,7 +19,7 @@
@if ($view === 'normal')
- @forelse ($server->environment_variables->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])->sort()->sortBy('key') as $env) + @forelse ($server->environment_variables->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])->sortBy('key') as $env) @empty From 5661c136f57502957eb284c47fea40e3c51d837c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 2 Jan 2026 19:51:09 +0100 Subject: [PATCH 011/602] fix: ensure authorization check for server view in mount method --- app/Livewire/SharedVariables/Server/Show.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Livewire/SharedVariables/Server/Show.php b/app/Livewire/SharedVariables/Server/Show.php index 1dd9f9d46..6078ef36f 100644 --- a/app/Livewire/SharedVariables/Server/Show.php +++ b/app/Livewire/SharedVariables/Server/Show.php @@ -52,9 +52,10 @@ public function mount() $serverUuid = request()->route('server_uuid'); $teamId = currentTeam()->id; $server = Server::where('team_id', $teamId)->where('uuid', $serverUuid)->first(); - if (!$server) { + if (! $server) { return redirect()->route('dashboard'); } + $this->authorize('view', $server); $this->server = $server; $this->getDevView(); } @@ -180,4 +181,4 @@ public function render() { return view('livewire.shared-variables.server.show'); } -} \ No newline at end of file +} From 54c28710d9b1ebfd93a334fb97fe79674a9e6683 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 2 Jan 2026 19:51:16 +0100 Subject: [PATCH 012/602] fix: streamline migration for adding predefined server variables to existing servers --- ...d_server_variables_to_existing_servers.php | 76 ++++++++----------- 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php b/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php index 3d09197b4..c67987e67 100644 --- a/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php +++ b/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php @@ -1,7 +1,8 @@ get(); + Server::query()->chunk(100, function ($servers) { + foreach ($servers as $server) { + $existingKeys = SharedEnvironmentVariable::where('type', 'server') + ->where('server_id', $server->id) + ->whereIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME']) + ->pluck('key') + ->toArray(); - foreach ($servers as $server) { - // Check if COOLIFY_SERVER_UUID already exists - $uuidExists = DB::table('shared_environment_variables') - ->where('type', 'server') - ->where('server_id', $server->id) - ->where('key', 'COOLIFY_SERVER_UUID') - ->exists(); + if (! in_array('COOLIFY_SERVER_UUID', $existingKeys)) { + SharedEnvironmentVariable::create([ + 'key' => 'COOLIFY_SERVER_UUID', + 'value' => $server->uuid, + 'type' => 'server', + 'server_id' => $server->id, + 'team_id' => $server->team_id, + 'is_literal' => true, + ]); + } - if (! $uuidExists) { - DB::table('shared_environment_variables')->insert([ - 'key' => 'COOLIFY_SERVER_UUID', - 'value' => $server->uuid, - 'type' => 'server', - 'server_id' => $server->id, - 'team_id' => $server->team_id, - 'is_literal' => true, - 'created_at' => now(), - 'updated_at' => now(), - ]); + if (! in_array('COOLIFY_SERVER_NAME', $existingKeys)) { + SharedEnvironmentVariable::create([ + 'key' => 'COOLIFY_SERVER_NAME', + 'value' => $server->name, + 'type' => 'server', + 'server_id' => $server->id, + 'team_id' => $server->team_id, + 'is_literal' => true, + ]); + } } - - // Check if COOLIFY_SERVER_NAME already exists - $nameExists = DB::table('shared_environment_variables') - ->where('type', 'server') - ->where('server_id', $server->id) - ->where('key', 'COOLIFY_SERVER_NAME') - ->exists(); - - if (! $nameExists) { - DB::table('shared_environment_variables')->insert([ - 'key' => 'COOLIFY_SERVER_NAME', - 'value' => $server->name, - 'type' => 'server', - 'server_id' => $server->id, - 'team_id' => $server->team_id, - 'is_literal' => true, - 'created_at' => now(), - 'updated_at' => now(), - ]); - } - } + }); } /** @@ -61,9 +49,7 @@ public function up(): void */ public function down(): void { - // Remove predefined server variables - DB::table('shared_environment_variables') - ->where('type', 'server') + SharedEnvironmentVariable::where('type', 'server') ->whereIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME']) ->delete(); } From 9b65b82ccba58af4691f58ee095298007854bf96 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 31 Jan 2026 01:56:17 +1100 Subject: [PATCH 013/602] feat(template): cloudflare-ddns --- public/svgs/cloudflare-ddns.svg | 8 ++++++++ templates/compose/cloudflare-ddns.yaml | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 public/svgs/cloudflare-ddns.svg create mode 100644 templates/compose/cloudflare-ddns.yaml diff --git a/public/svgs/cloudflare-ddns.svg b/public/svgs/cloudflare-ddns.svg new file mode 100644 index 000000000..efe800bcc --- /dev/null +++ b/public/svgs/cloudflare-ddns.svg @@ -0,0 +1,8 @@ + + + + + + DDNS + + diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml new file mode 100644 index 000000000..874f2cffb --- /dev/null +++ b/templates/compose/cloudflare-ddns.yaml @@ -0,0 +1,20 @@ +# documentation: https://github.com/favonia/cloudflare-ddns +# slogan: A small, feature-rich, and robust Cloudflare DDNS updater. +# category: automation +# tags: cloud, ddns +# logo: svgs/cloudflare-ddns.svg + +services: + cloudflare-ddns: + image: favonia/cloudflare-ddns:1 + network_mode: host + restart: unless-stopped + user: "1000:1000" + read_only: true + cap_drop: [all] + security_opt: [no-new-privileges:true] + environment: + - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN} + - DOMAINS=${DOMAINS} + - PROXIED=false + - IP6_PROVIDER=none From 90449d2bb5e7b8370dadb0899f511bb9c5b6c2cc Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Date: Sun, 1 Feb 2026 13:55:44 +1100 Subject: [PATCH 014/602] fix: remove restart: unless-stopped Update templates/compose/cloudflare-ddns.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/cloudflare-ddns.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml index 874f2cffb..b44828a70 100644 --- a/templates/compose/cloudflare-ddns.yaml +++ b/templates/compose/cloudflare-ddns.yaml @@ -8,7 +8,6 @@ services: cloudflare-ddns: image: favonia/cloudflare-ddns:1 network_mode: host - restart: unless-stopped user: "1000:1000" read_only: true cap_drop: [all] From 96b9cd3fa543c4e78554af27607ab231d7122e60 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Date: Sun, 1 Feb 2026 13:56:09 +1100 Subject: [PATCH 015/602] fix: mark the API token env as required, and other env as configurable from the UI Update templates/compose/cloudflare-ddns.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/cloudflare-ddns.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml index b44828a70..92f857c41 100644 --- a/templates/compose/cloudflare-ddns.yaml +++ b/templates/compose/cloudflare-ddns.yaml @@ -13,7 +13,7 @@ services: cap_drop: [all] security_opt: [no-new-privileges:true] environment: - - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN} + - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:?} - DOMAINS=${DOMAINS} - - PROXIED=false - - IP6_PROVIDER=none + - PROXIED=${PROXIED:-false} + - IP6_PROVIDER=${IP6_PROVIDER:-none} From b65f6399df736777a48498aca17c43f9609a5a1f Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:52:14 +1100 Subject: [PATCH 016/602] fix: make domains env compulsory Update templates/compose/cloudflare-ddns.yaml --- templates/compose/cloudflare-ddns.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml index 92f857c41..4f29a98d4 100644 --- a/templates/compose/cloudflare-ddns.yaml +++ b/templates/compose/cloudflare-ddns.yaml @@ -14,6 +14,6 @@ services: security_opt: [no-new-privileges:true] environment: - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:?} - - DOMAINS=${DOMAINS} + - DOMAINS=${DOMAINS:?} - PROXIED=${PROXIED:-false} - IP6_PROVIDER=${IP6_PROVIDER:-none} From 960ceddf1543e162cd5cc24e8b8aa7447dbd889a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Feb 2026 18:52:40 +0000 Subject: [PATCH 017/602] docs: update changelog --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e8ae806..76c548627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1190,7 +1190,16 @@ ### 🚀 Features - *(service)* Update autobase to version 2.5 (#7923) - *(service)* Add chibisafe template (#5808) - *(ui)* Improve sidebar menu items styling (#7928) -- *(service)* Improve open-archiver +- *(template)* Add open archiver template (#6593) +- *(service)* Add linkding template (#6651) +- *(service)* Add glip template (#7937) +- *(templates)* Add Sessy docker compose template (#7951) +- *(api)* Add update urls support to services api +- *(api)* Improve service urls update +- *(api)* Add url update support to services api (#7929) +- *(api)* Improve docker_compose_domains +- *(api)* Add more allowed fields +- *(notifications)* Add mattermost notifications (#7963) ### 🐛 Bug Fixes @@ -3773,6 +3782,7 @@ ### 🐛 Bug Fixes - *(scheduling)* Change redis cleanup command frequency from hourly to weekly for better resource management - *(versions)* Update coolify version numbers in versions.json and constants.php to 4.0.0-beta.420.5 and 4.0.0-beta.420.6 - *(database)* Ensure internal port defaults correctly for unsupported database types in StartDatabaseProxy +- *(git)* Tracking issue due to case sensitivity - *(versions)* Update coolify version numbers in versions.json and constants.php to 4.0.0-beta.420.6 and 4.0.0-beta.420.7 - *(scheduling)* Remove unnecessary padding from scheduled task form layout for improved UI consistency - *(horizon)* Update queue configuration to use environment variable for dynamic queue management @@ -3798,7 +3808,6 @@ ### 🐛 Bug Fixes - *(application)* Add option to suppress toast notifications when loading compose file - *(git)* Tracking issue due to case sensitivity - *(git)* Tracking issue due to case sensitivity -- *(git)* Tracking issue due to case sensitivity - *(ui)* Delete button width on small screens (#6308) - *(service)* Matrix entrypoint - *(ui)* Add flex-wrap to prevent overflow on small screens (#6307) @@ -4422,6 +4431,23 @@ ### 🐛 Bug Fixes - *(api)* Deprecate applications compose endpoint - *(api)* Applications post and patch endpoints - *(api)* Applications create and patch endpoints (#7917) +- *(service)* Sftpgo port +- *(env)* Only cat .env file in dev +- *(api)* Encoding checks (#7944) +- *(env)* Only show nixpacks plan variables section in dev +- Switch custom labels check to UTF-8 +- *(api)* One click service name and description cannot be set during creation +- *(ui)* Improve volume mount warning for compose applications (#7947) +- *(api)* Show an error if the same 2 urls are provided +- *(preview)* Docker compose preview URLs (#7959) +- *(api)* Check domain conflicts within the request +- *(api)* Include docker_compose_domains in domain conflict check +- *(api)* Is_static and docker network missing +- *(api)* If domains field is empty clear the fqdn column +- *(api)* Application endpoint issues part 2 (#7948) +- Optimize queries and caching for projects and environments +- *(perf)* Eliminate N+1 queries from InstanceSettings and Server lookups (#7966) +- Update version numbers to 4.0.0-beta.462 and 4.0.0-beta.463 ### 💼 Other @@ -5510,6 +5536,7 @@ ### 🚜 Refactor - Move all env sorting to one place - *(api)* Make docker_compose_raw description more clear - *(api)* Update application create endpoints docs +- *(api)* Application urls validation ### 📚 Documentation @@ -5616,7 +5643,6 @@ ### 📚 Documentation - Update changelog - *(tests)* Update testing guidelines for unit and feature tests - *(sync)* Create AI Instructions Synchronization Guide and update CLAUDE.md references -- Update changelog - *(database-patterns)* Add critical note on mass assignment protection for new columns - Clarify cloud-init script compatibility - Update changelog @@ -5647,7 +5673,9 @@ ### 📚 Documentation - Update application architecture and database patterns for request-level caching best practices - Remove git worktree symlink instructions from CLAUDE.md - Remove git worktree symlink instructions from CLAUDE.md (#7908) -- Update changelog +- Add transcript lol link and logo to readme (#7331) +- *(api)* Change domains to urls +- *(api)* Improve domains API docs ### ⚡ Performance @@ -6293,10 +6321,10 @@ ### ⚙️ Miscellaneous Tasks - *(versions)* Update Coolify versions to 4.0.0-beta.420.2 and 4.0.0-beta.420.3 in multiple files - *(versions)* Bump coolify and nightly versions to 4.0.0-beta.420.3 and 4.0.0-beta.420.4 respectively - *(versions)* Update coolify and nightly versions to 4.0.0-beta.420.4 and 4.0.0-beta.420.5 respectively -- *(service)* Update Nitropage template (#6181) -- *(versions)* Update all version - *(bump)* Update composer deps - *(version)* Bump Coolify version to 4.0.0-beta.420.6 +- *(service)* Update Nitropage template (#6181) +- *(versions)* Update all version - *(service)* Improve matrix service - *(service)* Format runner service - *(service)* Improve sequin @@ -6399,6 +6427,10 @@ ### ⚙️ Miscellaneous Tasks - *(services)* Upgrade service template json files - *(api)* Update openapi json and yaml - *(api)* Regenerate openapi docs +- Prepare for PR +- *(api)* Improve current request error message +- *(api)* Improve current request error message +- *(api)* Update openapi files ### ◀️ Revert From 32e1fd97aefe440b03ee1f750761c7e46df87ba8 Mon Sep 17 00:00:00 2001 From: Matteo Gassend Date: Sat, 7 Feb 2026 20:35:51 +0100 Subject: [PATCH 018/602] feat(templates): add ElectricSQL docker compose template --- public/svgs/electricsql.svg | 4 ++++ templates/compose/electricsql.yaml | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 public/svgs/electricsql.svg create mode 100644 templates/compose/electricsql.yaml diff --git a/public/svgs/electricsql.svg b/public/svgs/electricsql.svg new file mode 100644 index 000000000..bbffe200a --- /dev/null +++ b/public/svgs/electricsql.svg @@ -0,0 +1,4 @@ + + + + diff --git a/templates/compose/electricsql.yaml b/templates/compose/electricsql.yaml new file mode 100644 index 000000000..b1ae2ff96 --- /dev/null +++ b/templates/compose/electricsql.yaml @@ -0,0 +1,27 @@ +# documentation: https://electric-sql.com/docs/guides/deployment +# slogan: Sync shape-based subsets of your Postgres data over HTTP. +# category: backend +# tags: electric,electricsql,realtime,sync,postgresql +# logo: svgs/electricsql.svg +# port: 3000 + +## This template intentionally does not deploy PostgreSQL. +## Set DATABASE_URL to an existing Postgres instance with logical replication enabled. +## If ELECTRIC_SECRET is set, your own backend/proxy must append it to shape requests. + +services: + electric: + image: electricsql/electric:1.4.2 + environment: + - SERVICE_URL_ELECTRIC_3000 + - DATABASE_URL=${DATABASE_URL:?} + - ELECTRIC_SECRET=${SERVICE_PASSWORD_64_ELECTRIC} + - ELECTRIC_STORAGE_DIR=/app/persistent + - ELECTRIC_USAGE_REPORTING=${ELECTRIC_USAGE_REPORTING:-false} + volumes: + - electric_data:/app/persistent + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/v1/health"] + interval: 10s + timeout: 5s + retries: 5 From c180947d6b3664e81927e8d68a85df0caac85c0b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Feb 2026 19:36:24 +0000 Subject: [PATCH 019/602] docs: update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c548627..1cc1e0649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1200,6 +1200,7 @@ ### 🚀 Features - *(api)* Improve docker_compose_domains - *(api)* Add more allowed fields - *(notifications)* Add mattermost notifications (#7963) +- *(templates)* Add ElectricSQL docker compose template ### 🐛 Bug Fixes @@ -5676,6 +5677,7 @@ ### 📚 Documentation - Add transcript lol link and logo to readme (#7331) - *(api)* Change domains to urls - *(api)* Improve domains API docs +- Update changelog ### ⚡ Performance From 93a95bdf4391abd1e44dbb3093ab5ea9a5f3f169 Mon Sep 17 00:00:00 2001 From: James Peters Date: Sat, 14 Feb 2026 00:03:12 +0000 Subject: [PATCH 020/602] Added extra documentation on format for port + path when configuring domain rewrites --- .../views/livewire/project/application/general.blade.php | 4 ++-- .../views/livewire/project/service/edit-domain.blade.php | 2 +- resources/views/livewire/project/service/index.blade.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index f576a4a12..be59fa884 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -54,7 +54,7 @@ @if (!isDatabaseImage(data_get($service, 'image')))
@@ -106,7 +106,7 @@ x-bind:disabled="!canUpdate" /> @else @can('update', $application) Generate Domain diff --git a/resources/views/livewire/project/service/edit-domain.blade.php b/resources/views/livewire/project/service/edit-domain.blade.php index 0691146f6..7ca7a9685 100644 --- a/resources/views/livewire/project/service/edit-domain.blade.php +++ b/resources/views/livewire/project/service/edit-domain.blade.php @@ -10,7 +10,7 @@ + helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.

Example
- http://app.coolify.io,https://cloud.coolify.io/dashboard
- http://app.coolify.io/api/v3
- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.
- http://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container.">
Save diff --git a/resources/views/livewire/project/service/index.blade.php b/resources/views/livewire/project/service/index.blade.php index 04d30ae60..6d9cd3ee2 100644 --- a/resources/views/livewire/project/service/index.blade.php +++ b/resources/views/livewire/project/service/index.blade.php @@ -66,11 +66,11 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ @if ($serviceApplication->required_fqdn) + helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.

Example
- http://app.coolify.io,https://cloud.coolify.io/dashboard
- http://app.coolify.io/api/v3
- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.
- http://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."> @else + helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.

Example
- http://app.coolify.io,https://cloud.coolify.io/dashboard
- http://app.coolify.io/api/v3
- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.
- http://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."> @endif @endif Date: Sat, 14 Feb 2026 13:11:23 +0000 Subject: [PATCH 021/602] docs: update changelog --- CHANGELOG.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cc1e0649..223fad638 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1201,6 +1201,30 @@ ### 🚀 Features - *(api)* Add more allowed fields - *(notifications)* Add mattermost notifications (#7963) - *(templates)* Add ElectricSQL docker compose template +- *(service)* Add back soketi-app-manager +- *(service)* Upgrade checkmate to v3 (#7995) +- *(service)* Update pterodactyl version (#7981) +- *(service)* Add langflow template (#8006) +- *(service)* Upgrade listmonk to v6 +- *(service)* Add alexandrie template (#8021) +- *(service)* Upgrade formbricks to v4 (#8022) +- *(service)* Add goatcounter template (#8029) +- *(installer)* Add tencentos as a supported os +- *(installer)* Update nightly install script +- Update pr template to remove unnecessary quote blocks +- *(service)* Add satisfactory game server (#8056) +- *(service)* Disable mautic (#8088) +- *(service)* Add bento-pdf (#8095) +- *(ui)* Add official postgres 18 support +- *(database)* Add official postgres 18 support +- *(ui)* Use 2 column layout +- *(database)* Add official postgres 18 and pgvector 18 support (#8143) +- *(ui)* Improve global search with uuid and pr support (#7901) +- *(openclaw)* Add Openclaw service with environment variables and health checks +- *(service)* Disable maybe +- *(service)* Disable maybe (#8167) +- *(service)* Add sure +- *(service)* Add sure (#8157) ### 🐛 Bug Fixes @@ -4449,6 +4473,41 @@ ### 🐛 Bug Fixes - Optimize queries and caching for projects and environments - *(perf)* Eliminate N+1 queries from InstanceSettings and Server lookups (#7966) - Update version numbers to 4.0.0-beta.462 and 4.0.0-beta.463 +- *(service)* Update seaweedfs logo (#7971) +- *(service)* Soju svg +- *(service)* Autobase database is not persisted correctly (#7978) +- *(ui)* Make tooltips a bit wider +- *(ui)* Modal issues +- *(validation)* Add @, / and & support to names and descriptions +- *(backup)* Postgres restore arithmetic syntax error (#7997) +- *(service)* Users unable to create their first ente account without SMTP (#7986) +- *(ui)* Horizontal overflow on application and service headings (#7970) +- *(service)* Supabase studio settings redirect loop (#7828) +- *(env)* Skip escaping for valid JSON in environment variables (#6160) +- *(service)* Disable kong response buffering and increase timeouts (#7864) +- *(service)* Rocketchat fails to start due to database version incompatibility (#7999) +- *(service)* N8n v2 with worker timeout error +- *(service)* Elasticsearch-with-kibana not generating account token +- *(service)* Elasticsearch-with-kibana not generating account token (#8067) +- *(service)* Kimai fails to start (#8027) +- *(service)* Reactive-resume template (#8048) +- *(api)* Infinite loop with github app with many repos (#8052) +- *(env)* Skip escaping for valid JSON in environment variables (#8080) +- *(docker)* Update PostgreSQL version to 16 in Dockerfile +- *(validation)* Enforce url validation for instance domain (#8078) +- *(service)* Bluesky pds invite code doesn't generate (#8081) +- *(service)* Bugsink login fails due to cors (#8083) +- *(service)* Strapi doesn't start (#8084) +- *(service)* Activepieces postgres 18 volume mount (#8098) +- *(service)* Forgejo login failure (#8145) +- *(database)* Pgvector 18 version is not parsed properly +- *(labels)* Make sure name is slugified +- *(parser)* Replace dashes and dots in auto generated envs +- Stop database proxy when is_public changes to false (#8138) +- *(docs)* Update documentation link for Openclaw service +- *(api-docs)* Use proper schema references for environment variable endpoints (#8239) +- *(ui)* Fix datalist border color and add repository selection watcher (#8240) +- *(server)* Improve IP uniqueness validation with team-specific error messages ### 💼 Other @@ -4913,6 +4972,7 @@ ### 💼 Other - CVE-2025-55182 React2shell infected supabase/studio:2025.06.02-sha-8f2993d - Bump superset to 6.0.0 - Trim whitespace from domain input in instance settings (#7837) +- Upgrade postgres client to fix build error ### 🚜 Refactor @@ -5538,6 +5598,7 @@ ### 🚜 Refactor - *(api)* Make docker_compose_raw description more clear - *(api)* Update application create endpoints docs - *(api)* Application urls validation +- *(services)* Improve some service slogans ### 📚 Documentation @@ -5678,6 +5739,10 @@ ### 📚 Documentation - *(api)* Change domains to urls - *(api)* Improve domains API docs - Update changelog +- Update changelog +- *(api)* Improve app endpoint deprecation description +- Add Coolify design system reference +- Add Coolify design system reference (#8237) ### ⚡ Performance @@ -6433,6 +6498,14 @@ ### ⚙️ Miscellaneous Tasks - *(api)* Improve current request error message - *(api)* Improve current request error message - *(api)* Update openapi files +- *(service)* Update service templates json +- *(services)* Update service template json files +- *(service)* Use major version for openpanel (#8053) +- Prepare for PR +- *(services)* Update service template json files +- Bump coolify version +- Prepare for PR +- Prepare for PR ### ◀️ Revert From a0077de12cb9e05e6fb7c90ea223abaf329f2684 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Sun, 15 Feb 2026 22:21:26 +0300 Subject: [PATCH 022/602] feat: add 'is_preserve_repository_enabled' option to application controler for PATCH, POST --- .../Api/ApplicationsController.php | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 1e045ff5a..bad0adac3 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -226,6 +226,7 @@ public function applications(Request $request) 'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'], 'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'], 'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'], + 'is_preserve_repository_enabled' => ['type' => 'boolean', 'default' => false, 'description' => 'Preserve repository during deployment.'], ], ) ), @@ -391,6 +392,7 @@ public function create_public_application(Request $request) 'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'], 'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'], 'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'], + 'is_preserve_repository_enabled' => ['type' => 'boolean', 'default' => false, 'description' => 'Preserve repository during deployment.'], ], ) ), @@ -556,6 +558,7 @@ public function create_private_gh_app_application(Request $request) 'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'], 'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'], 'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'], + 'is_preserve_repository_enabled' => ['type' => 'boolean', 'default' => false, 'description' => 'Preserve repository during deployment.'], ], ) ), @@ -1002,7 +1005,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', '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']; + $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', 'is_preserve_repository_enabled']; $validator = customApiValidator($request->all(), [ 'name' => 'string|max:255', @@ -1051,6 +1054,7 @@ private function create_application(Request $request, $type) $connectToDockerNetwork = $request->connect_to_docker_network; $customNginxConfiguration = $request->custom_nginx_configuration; $isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled', true); + $isPreserveRepositoryEnabled = $request->boolean('is_preserve_repository_enabled',false); if (! is_null($customNginxConfiguration)) { if (! isBase64Encoded($customNginxConfiguration)) { @@ -1253,6 +1257,10 @@ private function create_application(Request $request, $type) $application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled; $application->settings->save(); } + if (isset($isPreserveRepositoryEnabled)) { + $application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled; + $application->settings->save(); + } $application->refresh(); // Auto-generate domain if requested and no custom domain provided if ($autogenerateDomain && blank($fqdn)) { @@ -1486,6 +1494,10 @@ private function create_application(Request $request, $type) $application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled; $application->settings->save(); } + if (isset($isPreserveRepositoryEnabled)) { + $application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled; + $application->settings->save(); + } if ($application->settings->is_container_label_readonly_enabled) { $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); $application->save(); @@ -1683,6 +1695,10 @@ private function create_application(Request $request, $type) $application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled; $application->settings->save(); } + if (isset($isPreserveRepositoryEnabled)) { + $application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled; + $application->settings->save(); + } if ($application->settings->is_container_label_readonly_enabled) { $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); $application->save(); @@ -2378,6 +2394,7 @@ public function delete_by_uuid(Request $request) 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'], 'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'], + 'is_preserve_repository_enabled' => ['type' => 'boolean', 'description' => 'Preserve git repository during application update. If false, the existing repository will be removed and replaced with the new one. If true, the existing repository will be kept and the new one will be ignored. Default is false.'], ], ) ), @@ -2463,7 +2480,7 @@ public function update_by_uuid(Request $request) $this->authorize('update', $application); $server = $application->destination->server; - $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']; + $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', 'is_preserve_repository_enabled']; $validationRules = [ 'name' => 'string|max:255', @@ -2713,7 +2730,7 @@ public function update_by_uuid(Request $request) $connectToDockerNetwork = $request->connect_to_docker_network; $useBuildServer = $request->use_build_server; $isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled'); - + $isPreserveRepositoryEnabled = $request->boolean('is_preserve_repository_enabled'); if (isset($useBuildServer)) { $application->settings->is_build_server_enabled = $useBuildServer; $application->settings->save(); @@ -2748,7 +2765,10 @@ public function update_by_uuid(Request $request) $application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled; $application->settings->save(); } - + if ($request->has('is_preserve_repository_enabled')) { + $application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled; + $application->settings->save(); + } removeUnnecessaryFieldsFromRequest($request); $data = $request->all(); From 53c1d5bcbb41bd03684b9ca4ae1f2e3a57a0dfca Mon Sep 17 00:00:00 2001 From: Ahmed Date: Sun, 15 Feb 2026 22:24:41 +0300 Subject: [PATCH 023/602] feat: add 'is_preserve_repository_enabled' field to shared data applications and remove from request --- bootstrap/helpers/api.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index d5c2c996b..da2eb6f21 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -139,6 +139,7 @@ function sharedDataApplications() 'docker_compose_custom_start_command' => 'string|nullable', 'docker_compose_custom_build_command' => 'string|nullable', 'is_container_label_escape_enabled' => 'boolean', + 'is_preserve_repository_enabled' => 'boolean' ]; } @@ -188,5 +189,6 @@ function removeUnnecessaryFieldsFromRequest(Request $request) $request->offsetUnset('force_domain_override'); $request->offsetUnset('autogenerate_domain'); $request->offsetUnset('is_container_label_escape_enabled'); + $request->offsetUnset('is_preserve_repository_enabled'); $request->offsetUnset('docker_compose_raw'); } From 20563e23ff338cdf7e288ee217f32b4ffaac21c8 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Sun, 15 Feb 2026 22:53:26 +0300 Subject: [PATCH 024/602] feat: add 'is_preserve_repository_enabled' field to openapi specifications for deployment --- openapi.json | 20 ++++++++++++++++++++ openapi.yaml | 16 ++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/openapi.json b/openapi.json index bd502865a..a9e16ca55 100644 --- a/openapi.json +++ b/openapi.json @@ -407,6 +407,11 @@ "type": "boolean", "default": true, "description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off." + }, + "is_preserve_repository_enabled": { + "type": "boolean", + "default": false, + "description": "Preserve repository during deployment." } }, "type": "object" @@ -852,6 +857,11 @@ "type": "boolean", "default": true, "description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off." + }, + "is_preserve_repository_enabled": { + "type": "boolean", + "default": false, + "description": "Preserve repository during deployment." } }, "type": "object" @@ -1297,6 +1307,11 @@ "type": "boolean", "default": true, "description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off." + }, + "is_preserve_repository_enabled": { + "type": "boolean", + "default": false, + "description": "Preserve repository during deployment." } }, "type": "object" @@ -2704,6 +2719,11 @@ "type": "boolean", "default": true, "description": "Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off." + }, + "is_preserve_repository_enabled": { + "type": "boolean", + "default": false, + "description": "Preserve repository during deployment." } }, "type": "object" diff --git a/openapi.yaml b/openapi.yaml index 11148f43b..79ad73320 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -291,6 +291,10 @@ paths: type: boolean default: true description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.' + is_preserve_repository_enabled: + type: boolean + default: false + description: 'Preserve repository during deployment.' type: object responses: '201': @@ -575,6 +579,10 @@ paths: type: boolean default: true description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.' + is_preserve_repository_enabled: + type: boolean + default: false + description: 'Preserve repository during deployment.' type: object responses: '201': @@ -859,6 +867,10 @@ paths: type: boolean default: true description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.' + is_preserve_repository_enabled: + type: boolean + default: false + description: 'Preserve repository during deployment.' type: object responses: '201': @@ -1741,6 +1753,10 @@ paths: type: boolean default: true description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.' + is_preserve_repository_enabled: + type: boolean + default: false + description: 'Preserve repository during deployment.' type: object responses: '200': From 17f5259f3238c269e5aa2c3fde116050967c7675 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Feb 2026 08:32:31 +0000 Subject: [PATCH 025/602] docs: update changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 223fad638..21d6da7cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4508,6 +4508,12 @@ ### 🐛 Bug Fixes - *(api-docs)* Use proper schema references for environment variable endpoints (#8239) - *(ui)* Fix datalist border color and add repository selection watcher (#8240) - *(server)* Improve IP uniqueness validation with team-specific error messages +- *(jobs)* Initialize status variable in checkHetznerStatus (#8359) +- *(jobs)* Handle queue timeouts gracefully in Horizon (#8360) +- *(push-server-job)* Skip containers with empty service subId (#8361) +- *(database)* Disable proxy on port allocation failure (#8362) +- *(sentry)* Use withScope for SSH retry event tracking (#8363) +- *(api)* Add a newline to openapi.json ### 💼 Other @@ -5599,6 +5605,7 @@ ### 🚜 Refactor - *(api)* Update application create endpoints docs - *(api)* Application urls validation - *(services)* Improve some service slogans +- *(ssh-retry)* Remove Sentry tracking from retry logic ### 📚 Documentation @@ -5743,6 +5750,7 @@ ### 📚 Documentation - *(api)* Improve app endpoint deprecation description - Add Coolify design system reference - Add Coolify design system reference (#8237) +- Update changelog ### ⚡ Performance @@ -6506,6 +6514,11 @@ ### ⚙️ Miscellaneous Tasks - Bump coolify version - Prepare for PR - Prepare for PR +- Prepare for PR +- Prepare for PR +- Prepare for PR +- Prepare for PR +- Prepare for PR ### ◀️ Revert From dfd0edff6439a1441d307e4f1726bb6fb30ed401 Mon Sep 17 00:00:00 2001 From: Cinzya Date: Mon, 16 Feb 2026 08:47:00 +0100 Subject: [PATCH 026/602] feat(services): add architecture warning --- app/Console/Commands/Generate/Services.php | 24 ++++++++++++++++ app/Livewire/GlobalSearch.php | 5 +++- .../views/livewire/global-search.blade.php | 14 ++++++++++ .../livewire/project/new/select.blade.php | 28 +++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/Generate/Services.php b/app/Console/Commands/Generate/Services.php index 42f9360bb..e316fc391 100644 --- a/app/Console/Commands/Generate/Services.php +++ b/app/Console/Commands/Generate/Services.php @@ -88,6 +88,14 @@ private function processFile(string $file): false|array $payload['envs'] = base64_encode($envFileContent); } + if (str($data->get('amd_only'))->toBoolean()) { + $payload['amd_only'] = true; + } + + if (str($data->get('arm_only'))->toBoolean()) { + $payload['arm_only'] = true; + } + return $payload; } @@ -160,6 +168,14 @@ private function processFileWithFqdn(string $file): false|array $payload['envs'] = base64_encode($modifiedEnvContent); } + if (str($data->get('amd_only'))->toBoolean()) { + $payload['amd_only'] = true; + } + + if (str($data->get('arm_only'))->toBoolean()) { + $payload['arm_only'] = true; + } + return $payload; } @@ -229,6 +245,14 @@ private function processFileWithFqdnRaw(string $file): false|array $payload['envs'] = $modifiedEnvContent; } + if (str($data->get('amd_only'))->toBoolean()) { + $payload['amd_only'] = true; + } + + if (str($data->get('arm_only'))->toBoolean()) { + $payload['arm_only'] = true; + } + return $payload; } } diff --git a/app/Livewire/GlobalSearch.php b/app/Livewire/GlobalSearch.php index ed9115093..56804d823 100644 --- a/app/Livewire/GlobalSearch.php +++ b/app/Livewire/GlobalSearch.php @@ -1495,7 +1495,10 @@ public function getServicesProperty() 'type' => 'one-click-service-'.$serviceKey, 'category' => 'Services', 'resourceType' => 'service', - ]); + ] + array_filter([ + 'amd_only' => data_get($service, 'amd_only') ? true : null, + 'arm_only' => data_get($service, 'arm_only') ? true : null, + ])); } $cachedServices = $items->toArray(); diff --git a/resources/views/livewire/global-search.blade.php b/resources/views/livewire/global-search.blade.php index 27b8d2821..8a5b19e92 100644 --- a/resources/views/livewire/global-search.blade.php +++ b/resources/views/livewire/global-search.blade.php @@ -822,6 +822,20 @@ class="h-5 w-5 text-warning-600 dark:text-warning-400"
+ + +
+ + AMD only + +
+
+ This service only supports AMD64/x86_64 architecture. It will not work + on ARM-based servers (e.g., Apple Silicon, Raspberry Pi, AWS Graviton). +
+
+
+ + - {{-- Billing, Refund & Cancellation --}} + {{-- Manage Subscription --}}

Manage Subscription

- {{-- Billing --}} @@ -207,8 +211,13 @@ class="w-20 px-2 py-1 text-xl font-bold text-center rounded border dark:bg-coolg Manage Billing on Stripe +
+
- {{-- Resume or Cancel --}} + {{-- Cancel Subscription --}} +
+

Cancel Subscription

+
@if (currentTeam()->subscription->stripe_cancel_at_period_end) Resume Subscription @else @@ -231,10 +240,18 @@ class="w-20 px-2 py-1 text-xl font-bold text-center rounded border dark:bg-coolg confirmationLabel="Enter your team name to confirm" shortConfirmationLabel="Team Name" step2ButtonText="Permanently Cancel" /> @endif +
+ @if (currentTeam()->subscription->stripe_cancel_at_period_end) +

Your subscription is set to cancel at the end of the billing period.

+ @endif +
- {{-- Refund --}} + {{-- Refund --}} +
+

Refund

+
@if ($refundCheckLoading) - + Request Full Refund @elseif ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end) + @else + Request Full Refund @endif
- - {{-- Contextual notes --}} - @if ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end) -

Eligible for a full refund — {{ $refundDaysRemaining }} days remaining.

- @elseif ($refundAlreadyUsed) -

Refund already processed. Each team is eligible for one refund only.

- @endif - @if (currentTeam()->subscription->stripe_cancel_at_period_end) -

Your subscription is set to cancel at the end of the billing period.

- @endif +

+ @if ($refundCheckLoading) + Checking refund eligibility... + @elseif ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end) + Eligible for a full refund — {{ $refundDaysRemaining }} days remaining. + @elseif ($refundAlreadyUsed) + Refund already processed. Each team is eligible for one refund only. + @else + Not eligible for a refund. + @endif +

diff --git a/tests/Feature/Subscription/RefundSubscriptionTest.php b/tests/Feature/Subscription/RefundSubscriptionTest.php index b6c2d4064..2447a0716 100644 --- a/tests/Feature/Subscription/RefundSubscriptionTest.php +++ b/tests/Feature/Subscription/RefundSubscriptionTest.php @@ -43,9 +43,11 @@ describe('checkEligibility', function () { test('returns eligible when subscription is within 30 days', function () { + $periodEnd = now()->addDays(20)->timestamp; $stripeSubscription = (object) [ 'status' => 'active', 'start_date' => now()->subDays(10)->timestamp, + 'current_period_end' => $periodEnd, ]; $this->mockSubscriptions @@ -58,12 +60,15 @@ expect($result['eligible'])->toBeTrue(); expect($result['days_remaining'])->toBe(20); + expect($result['current_period_end'])->toBe($periodEnd); }); test('returns ineligible when subscription is past 30 days', function () { + $periodEnd = now()->addDays(25)->timestamp; $stripeSubscription = (object) [ 'status' => 'active', 'start_date' => now()->subDays(35)->timestamp, + 'current_period_end' => $periodEnd, ]; $this->mockSubscriptions @@ -77,12 +82,15 @@ expect($result['eligible'])->toBeFalse(); expect($result['days_remaining'])->toBe(0); expect($result['reason'])->toContain('30-day refund window has expired'); + expect($result['current_period_end'])->toBe($periodEnd); }); test('returns ineligible when subscription is not active', function () { + $periodEnd = now()->addDays(25)->timestamp; $stripeSubscription = (object) [ 'status' => 'canceled', 'start_date' => now()->subDays(5)->timestamp, + 'current_period_end' => $periodEnd, ]; $this->mockSubscriptions @@ -94,6 +102,7 @@ $result = $action->checkEligibility($this->team); expect($result['eligible'])->toBeFalse(); + expect($result['current_period_end'])->toBe($periodEnd); }); test('returns ineligible when no subscription exists', function () { @@ -104,6 +113,7 @@ expect($result['eligible'])->toBeFalse(); expect($result['reason'])->toContain('No active subscription'); + expect($result['current_period_end'])->toBeNull(); }); test('returns ineligible when invoice is not paid', function () { @@ -114,6 +124,7 @@ expect($result['eligible'])->toBeFalse(); expect($result['reason'])->toContain('not paid'); + expect($result['current_period_end'])->toBeNull(); }); test('returns ineligible when team has already been refunded', function () { @@ -145,6 +156,7 @@ $stripeSubscription = (object) [ 'status' => 'active', 'start_date' => now()->subDays(10)->timestamp, + 'current_period_end' => now()->addDays(20)->timestamp, ]; $this->mockSubscriptions @@ -205,6 +217,7 @@ $stripeSubscription = (object) [ 'status' => 'active', 'start_date' => now()->subDays(10)->timestamp, + 'current_period_end' => now()->addDays(20)->timestamp, ]; $this->mockSubscriptions @@ -229,6 +242,7 @@ $stripeSubscription = (object) [ 'status' => 'active', 'start_date' => now()->subDays(10)->timestamp, + 'current_period_end' => now()->addDays(20)->timestamp, ]; $this->mockSubscriptions @@ -255,6 +269,7 @@ $stripeSubscription = (object) [ 'status' => 'active', 'start_date' => now()->subDays(35)->timestamp, + 'current_period_end' => now()->addDays(25)->timestamp, ]; $this->mockSubscriptions From 566744b2e036897fc8200dd3622ba097aff1bf6c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:21:59 +0100 Subject: [PATCH 077/602] fix(stripe): add error handling and resilience to subscription operations - Record refunds immediately before cancellation to prevent retry issues if cancel fails - Wrap Stripe API calls in try-catch for refunds and quantity reverts with internal notifications - Add null check in Team.subscriptionEnded() to prevent NPE when subscription doesn't exist - Fix control flow bug in StripeProcessJob (add missing break statement) - Cap dynamic server limit with MAX_SERVER_LIMIT in subscription updates - Add comprehensive tests for refund failures, event handling, and null safety --- app/Actions/Stripe/RefundSubscription.php | 19 ++- .../Stripe/UpdateSubscriptionQuantity.php | 19 ++- app/Jobs/StripeProcessJob.php | 6 +- .../VerifyStripeSubscriptionStatusJob.php | 9 +- app/Models/Team.php | 4 + .../Subscription/RefundSubscriptionTest.php | 50 ++++++ .../Subscription/StripeProcessJobTest.php | 143 ++++++++++++++++++ .../TeamSubscriptionEndedTest.php | 16 ++ .../VerifyStripeSubscriptionStatusJobTest.php | 102 +++++++++++++ 9 files changed, 350 insertions(+), 18 deletions(-) create mode 100644 tests/Feature/Subscription/StripeProcessJobTest.php create mode 100644 tests/Feature/Subscription/TeamSubscriptionEndedTest.php create mode 100644 tests/Feature/Subscription/VerifyStripeSubscriptionStatusJobTest.php diff --git a/app/Actions/Stripe/RefundSubscription.php b/app/Actions/Stripe/RefundSubscription.php index 021cba13e..fc8191f5b 100644 --- a/app/Actions/Stripe/RefundSubscription.php +++ b/app/Actions/Stripe/RefundSubscription.php @@ -99,16 +99,27 @@ public function execute(Team $team): array 'payment_intent' => $paymentIntentId, ]); - $this->stripe->subscriptions->cancel($subscription->stripe_subscription_id); + // Record refund immediately so it cannot be retried if cancel fails + $subscription->update([ + 'stripe_refunded_at' => now(), + 'stripe_feedback' => 'Refund requested by user', + 'stripe_comment' => 'Full refund processed within 30-day window at '.now()->toDateTimeString(), + ]); + + try { + $this->stripe->subscriptions->cancel($subscription->stripe_subscription_id); + } catch (\Exception $e) { + \Log::critical("Refund succeeded but subscription cancel failed for team {$team->id}: ".$e->getMessage()); + send_internal_notification( + "CRITICAL: Refund succeeded but cancel failed for subscription {$subscription->stripe_subscription_id}, team {$team->id}. Manual intervention required." + ); + } $subscription->update([ 'stripe_cancel_at_period_end' => false, 'stripe_invoice_paid' => false, 'stripe_trial_already_ended' => false, 'stripe_past_due' => false, - 'stripe_feedback' => 'Refund requested by user', - 'stripe_comment' => 'Full refund processed within 30-day window at '.now()->toDateTimeString(), - 'stripe_refunded_at' => now(), ]); $team->subscriptionEnded(); diff --git a/app/Actions/Stripe/UpdateSubscriptionQuantity.php b/app/Actions/Stripe/UpdateSubscriptionQuantity.php index c181e988d..a3eab4dca 100644 --- a/app/Actions/Stripe/UpdateSubscriptionQuantity.php +++ b/app/Actions/Stripe/UpdateSubscriptionQuantity.php @@ -153,12 +153,19 @@ public function execute(Team $team, int $quantity): array \Log::warning("Subscription {$subscription->stripe_subscription_id} quantity updated but invoice not paid (status: {$latestInvoice->status}) for team {$team->name}. Reverting to {$previousQuantity}."); // Revert subscription quantity on Stripe - $this->stripe->subscriptions->update($subscription->stripe_subscription_id, [ - 'items' => [ - ['id' => $item->id, 'quantity' => $previousQuantity], - ], - 'proration_behavior' => 'none', - ]); + try { + $this->stripe->subscriptions->update($subscription->stripe_subscription_id, [ + 'items' => [ + ['id' => $item->id, 'quantity' => $previousQuantity], + ], + 'proration_behavior' => 'none', + ]); + } catch (\Exception $revertException) { + \Log::critical("Failed to revert Stripe quantity for subscription {$subscription->stripe_subscription_id}, team {$team->id}. Stripe may have quantity {$quantity} but local is {$previousQuantity}. Error: ".$revertException->getMessage()); + send_internal_notification( + "CRITICAL: Stripe quantity revert failed for subscription {$subscription->stripe_subscription_id}, team {$team->id}. Manual reconciliation required." + ); + } // Void the unpaid invoice if ($latestInvoice->id) { diff --git a/app/Jobs/StripeProcessJob.php b/app/Jobs/StripeProcessJob.php index e61ac81e4..f5d52f29c 100644 --- a/app/Jobs/StripeProcessJob.php +++ b/app/Jobs/StripeProcessJob.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Actions\Stripe\UpdateSubscriptionQuantity; use App\Models\Subscription; use App\Models\Team; use Illuminate\Contracts\Queue\ShouldBeEncrypted; @@ -238,6 +239,7 @@ public function handle(): void 'stripe_invoice_paid' => false, ]); } + break; case 'customer.subscription.updated': $teamId = data_get($data, 'metadata.team_id'); $userId = data_get($data, 'metadata.user_id'); @@ -272,14 +274,14 @@ public function handle(): void $comment = data_get($data, 'cancellation_details.comment'); $lookup_key = data_get($data, 'items.data.0.price.lookup_key'); if (str($lookup_key)->contains('dynamic')) { - $quantity = data_get($data, 'items.data.0.quantity', 2); + $quantity = min((int) data_get($data, 'items.data.0.quantity', 2), UpdateSubscriptionQuantity::MAX_SERVER_LIMIT); $team = data_get($subscription, 'team'); if ($team) { $team->update([ 'custom_server_limit' => $quantity, ]); + ServerLimitCheckJob::dispatch($team); } - ServerLimitCheckJob::dispatch($team); } $subscription->update([ 'stripe_feedback' => $feedback, diff --git a/app/Jobs/VerifyStripeSubscriptionStatusJob.php b/app/Jobs/VerifyStripeSubscriptionStatusJob.php index cf7c3c0ea..f7addacf1 100644 --- a/app/Jobs/VerifyStripeSubscriptionStatusJob.php +++ b/app/Jobs/VerifyStripeSubscriptionStatusJob.php @@ -82,12 +82,9 @@ public function handle(): void 'stripe_past_due' => false, ]); - // Trigger subscription ended logic if canceled - if ($stripeSubscription->status === 'canceled') { - $team = $this->subscription->team; - if ($team) { - $team->subscriptionEnded(); - } + $team = $this->subscription->team; + if ($team) { + $team->subscriptionEnded(); } break; diff --git a/app/Models/Team.php b/app/Models/Team.php index e32526169..10b22b4e1 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -197,6 +197,10 @@ public function isAnyNotificationEnabled() public function subscriptionEnded() { + if (! $this->subscription) { + return; + } + $this->subscription->update([ 'stripe_subscription_id' => null, 'stripe_cancel_at_period_end' => false, diff --git a/tests/Feature/Subscription/RefundSubscriptionTest.php b/tests/Feature/Subscription/RefundSubscriptionTest.php index b6c2d4064..144cdad09 100644 --- a/tests/Feature/Subscription/RefundSubscriptionTest.php +++ b/tests/Feature/Subscription/RefundSubscriptionTest.php @@ -251,6 +251,56 @@ expect($result['error'])->toContain('No payment intent'); }); + test('records refund and proceeds when cancel fails', function () { + $stripeSubscription = (object) [ + 'status' => 'active', + 'start_date' => now()->subDays(10)->timestamp, + ]; + + $this->mockSubscriptions + ->shouldReceive('retrieve') + ->with('sub_test_123') + ->andReturn($stripeSubscription); + + $invoiceCollection = (object) ['data' => [ + (object) ['payment_intent' => 'pi_test_123'], + ]]; + + $this->mockInvoices + ->shouldReceive('all') + ->with([ + 'subscription' => 'sub_test_123', + 'status' => 'paid', + 'limit' => 1, + ]) + ->andReturn($invoiceCollection); + + $this->mockRefunds + ->shouldReceive('create') + ->with(['payment_intent' => 'pi_test_123']) + ->andReturn((object) ['id' => 're_test_123']); + + // Cancel throws — simulating Stripe failure after refund + $this->mockSubscriptions + ->shouldReceive('cancel') + ->with('sub_test_123') + ->andThrow(new \Exception('Stripe cancel API error')); + + $action = new RefundSubscription($this->mockStripe); + $result = $action->execute($this->team); + + // Should still succeed — refund went through + expect($result['success'])->toBeTrue(); + expect($result['error'])->toBeNull(); + + $this->subscription->refresh(); + // Refund timestamp must be recorded + expect($this->subscription->stripe_refunded_at)->not->toBeNull(); + // Subscription should still be marked as ended locally + expect($this->subscription->stripe_invoice_paid)->toBeFalsy(); + expect($this->subscription->stripe_subscription_id)->toBeNull(); + }); + test('fails when subscription is past refund window', function () { $stripeSubscription = (object) [ 'status' => 'active', diff --git a/tests/Feature/Subscription/StripeProcessJobTest.php b/tests/Feature/Subscription/StripeProcessJobTest.php new file mode 100644 index 000000000..95cff188a --- /dev/null +++ b/tests/Feature/Subscription/StripeProcessJobTest.php @@ -0,0 +1,143 @@ +set('constants.coolify.self_hosted', false); + config()->set('subscription.provider', 'stripe'); + config()->set('subscription.stripe_api_key', 'sk_test_fake'); + config()->set('subscription.stripe_excluded_plans', ''); + + $this->team = Team::factory()->create(); + $this->user = User::factory()->create(); + $this->team->members()->attach($this->user->id, ['role' => 'owner']); +}); + +describe('customer.subscription.created does not fall through to updated', function () { + test('created event creates subscription without setting stripe_invoice_paid to true', function () { + Queue::fake(); + + $event = [ + 'type' => 'customer.subscription.created', + 'data' => [ + 'object' => [ + 'customer' => 'cus_new_123', + 'id' => 'sub_new_123', + 'metadata' => [ + 'team_id' => $this->team->id, + 'user_id' => $this->user->id, + ], + ], + ], + ]; + + $job = new StripeProcessJob($event); + $job->handle(); + + $subscription = Subscription::where('team_id', $this->team->id)->first(); + + expect($subscription)->not->toBeNull(); + expect($subscription->stripe_subscription_id)->toBe('sub_new_123'); + expect($subscription->stripe_customer_id)->toBe('cus_new_123'); + // Critical: stripe_invoice_paid must remain false — payment not yet confirmed + expect($subscription->stripe_invoice_paid)->toBeFalsy(); + }); +}); + +describe('customer.subscription.updated clamps quantity to MAX_SERVER_LIMIT', function () { + test('quantity exceeding MAX is clamped to 100', function () { + Queue::fake(); + + Subscription::create([ + 'team_id' => $this->team->id, + 'stripe_subscription_id' => 'sub_existing', + 'stripe_customer_id' => 'cus_clamp_test', + 'stripe_invoice_paid' => true, + ]); + + $event = [ + 'type' => 'customer.subscription.updated', + 'data' => [ + 'object' => [ + 'customer' => 'cus_clamp_test', + 'id' => 'sub_existing', + 'status' => 'active', + 'metadata' => [ + 'team_id' => $this->team->id, + 'user_id' => $this->user->id, + ], + 'items' => [ + 'data' => [[ + 'subscription' => 'sub_existing', + 'plan' => ['id' => 'price_dynamic_monthly'], + 'price' => ['lookup_key' => 'dynamic_monthly'], + 'quantity' => 999, + ]], + ], + 'cancel_at_period_end' => false, + 'cancellation_details' => ['feedback' => null, 'comment' => null], + ], + ], + ]; + + $job = new StripeProcessJob($event); + $job->handle(); + + $this->team->refresh(); + expect($this->team->custom_server_limit)->toBe(100); + + Queue::assertPushed(ServerLimitCheckJob::class); + }); +}); + +describe('ServerLimitCheckJob dispatch is guarded by team check', function () { + test('does not dispatch ServerLimitCheckJob when team is null', function () { + Queue::fake(); + + // Create subscription without a valid team relationship + $subscription = Subscription::create([ + 'team_id' => 99999, + 'stripe_subscription_id' => 'sub_orphan', + 'stripe_customer_id' => 'cus_orphan_test', + 'stripe_invoice_paid' => true, + ]); + + $event = [ + 'type' => 'customer.subscription.updated', + 'data' => [ + 'object' => [ + 'customer' => 'cus_orphan_test', + 'id' => 'sub_orphan', + 'status' => 'active', + 'metadata' => [ + 'team_id' => null, + 'user_id' => null, + ], + 'items' => [ + 'data' => [[ + 'subscription' => 'sub_orphan', + 'plan' => ['id' => 'price_dynamic_monthly'], + 'price' => ['lookup_key' => 'dynamic_monthly'], + 'quantity' => 5, + ]], + ], + 'cancel_at_period_end' => false, + 'cancellation_details' => ['feedback' => null, 'comment' => null], + ], + ], + ]; + + $job = new StripeProcessJob($event); + $job->handle(); + + Queue::assertNotPushed(ServerLimitCheckJob::class); + }); +}); diff --git a/tests/Feature/Subscription/TeamSubscriptionEndedTest.php b/tests/Feature/Subscription/TeamSubscriptionEndedTest.php new file mode 100644 index 000000000..55d59e0e6 --- /dev/null +++ b/tests/Feature/Subscription/TeamSubscriptionEndedTest.php @@ -0,0 +1,16 @@ +create(); + + // Should return early without error — no NPE + $team->subscriptionEnded(); + + // If we reach here, no exception was thrown + expect(true)->toBeTrue(); +}); diff --git a/tests/Feature/Subscription/VerifyStripeSubscriptionStatusJobTest.php b/tests/Feature/Subscription/VerifyStripeSubscriptionStatusJobTest.php new file mode 100644 index 000000000..be8661b6c --- /dev/null +++ b/tests/Feature/Subscription/VerifyStripeSubscriptionStatusJobTest.php @@ -0,0 +1,102 @@ +set('constants.coolify.self_hosted', false); + config()->set('subscription.provider', 'stripe'); + config()->set('subscription.stripe_api_key', 'sk_test_fake'); + + $this->team = Team::factory()->create(); + $this->user = User::factory()->create(); + $this->team->members()->attach($this->user->id, ['role' => 'owner']); + + $this->subscription = Subscription::create([ + 'team_id' => $this->team->id, + 'stripe_subscription_id' => 'sub_verify_123', + 'stripe_customer_id' => 'cus_verify_123', + 'stripe_invoice_paid' => true, + 'stripe_past_due' => false, + ]); +}); + +test('subscriptionEnded is called for unpaid status', function () { + $mockStripe = Mockery::mock(StripeClient::class); + $mockSubscriptions = Mockery::mock(SubscriptionService::class); + $mockStripe->subscriptions = $mockSubscriptions; + + $mockSubscriptions + ->shouldReceive('retrieve') + ->with('sub_verify_123') + ->andReturn((object) [ + 'status' => 'unpaid', + 'cancel_at_period_end' => false, + ]); + + app()->bind(StripeClient::class, fn () => $mockStripe); + + // Create a server to verify it gets disabled + $server = Server::factory()->create(['team_id' => $this->team->id]); + + $job = new VerifyStripeSubscriptionStatusJob($this->subscription); + $job->handle(); + + $this->subscription->refresh(); + expect($this->subscription->stripe_invoice_paid)->toBeFalsy(); + expect($this->subscription->stripe_subscription_id)->toBeNull(); +}); + +test('subscriptionEnded is called for incomplete_expired status', function () { + $mockStripe = Mockery::mock(StripeClient::class); + $mockSubscriptions = Mockery::mock(SubscriptionService::class); + $mockStripe->subscriptions = $mockSubscriptions; + + $mockSubscriptions + ->shouldReceive('retrieve') + ->with('sub_verify_123') + ->andReturn((object) [ + 'status' => 'incomplete_expired', + 'cancel_at_period_end' => false, + ]); + + app()->bind(StripeClient::class, fn () => $mockStripe); + + $job = new VerifyStripeSubscriptionStatusJob($this->subscription); + $job->handle(); + + $this->subscription->refresh(); + expect($this->subscription->stripe_invoice_paid)->toBeFalsy(); + expect($this->subscription->stripe_subscription_id)->toBeNull(); +}); + +test('subscriptionEnded is called for canceled status', function () { + $mockStripe = Mockery::mock(StripeClient::class); + $mockSubscriptions = Mockery::mock(SubscriptionService::class); + $mockStripe->subscriptions = $mockSubscriptions; + + $mockSubscriptions + ->shouldReceive('retrieve') + ->with('sub_verify_123') + ->andReturn((object) [ + 'status' => 'canceled', + 'cancel_at_period_end' => false, + ]); + + app()->bind(StripeClient::class, fn () => $mockStripe); + + $job = new VerifyStripeSubscriptionStatusJob($this->subscription); + $job->handle(); + + $this->subscription->refresh(); + expect($this->subscription->stripe_invoice_paid)->toBeFalsy(); + expect($this->subscription->stripe_subscription_id)->toBeNull(); +}); From ca3ae289eb7f48bac23ae143ff5c1416b14ab72e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:42:29 +0100 Subject: [PATCH 078/602] feat(storage): add resources tab and improve S3 deletion handling Add new Resources tab to storage show page displaying backup schedules using that storage. Refactor storage show layout with navigation tabs for General and Resources sections. Move delete action from form to show component. Implement cascade deletion in S3Storage model to automatically disable S3 backups when storage is deleted. Improve error handling in DatabaseBackupJob to throw exception when S3 storage is missing instead of silently returning. - New Storage/Resources Livewire component - Add resources.blade.php view - Add storage.resources route - Move delete() method from Form to Show component - Add deleting event listener to S3Storage model - Track backup count and current route in Show component - Add #[On('submitStorage')] attribute to form submission --- app/Jobs/DatabaseBackupJob.php | 12 +- app/Livewire/Storage/Form.php | 15 +-- app/Livewire/Storage/Resources.php | 23 ++++ app/Livewire/Storage/Show.php | 20 ++++ app/Models/S3Storage.php | 12 ++ .../views/livewire/storage/form.blade.php | 31 ----- .../livewire/storage/resources.blade.php | 62 ++++++++++ .../views/livewire/storage/show.blade.php | 48 +++++++- routes/web.php | 1 + tests/Feature/DatabaseBackupJobTest.php | 109 ++++++++++++++++++ 10 files changed, 285 insertions(+), 48 deletions(-) create mode 100644 app/Livewire/Storage/Resources.php create mode 100644 resources/views/livewire/storage/resources.blade.php diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 5fc9f6cd8..b55c324be 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -625,10 +625,16 @@ private function calculate_size() private function upload_to_s3(): void { + if (is_null($this->s3)) { + $this->backup->update([ + 'save_s3' => false, + 's3_storage_id' => null, + ]); + + throw new \Exception('S3 storage configuration is missing or has been deleted (S3 storage ID: '.($this->backup->s3_storage_id ?? 'null').'). S3 backup has been disabled for this schedule.'); + } + try { - if (is_null($this->s3)) { - return; - } $key = $this->s3->key; $secret = $this->s3->secret; // $region = $this->s3->region; diff --git a/app/Livewire/Storage/Form.php b/app/Livewire/Storage/Form.php index 4dc0b6ae2..791226334 100644 --- a/app/Livewire/Storage/Form.php +++ b/app/Livewire/Storage/Form.php @@ -6,6 +6,7 @@ use App\Support\ValidationPatterns; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Support\Facades\DB; +use Livewire\Attributes\On; use Livewire\Component; class Form extends Component @@ -131,19 +132,7 @@ public function testConnection() } } - public function delete() - { - try { - $this->authorize('delete', $this->storage); - - $this->storage->delete(); - - return redirect()->route('storage.index'); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } - + #[On('submitStorage')] public function submit() { try { diff --git a/app/Livewire/Storage/Resources.php b/app/Livewire/Storage/Resources.php new file mode 100644 index 000000000..f17175013 --- /dev/null +++ b/app/Livewire/Storage/Resources.php @@ -0,0 +1,23 @@ +storage->id) + ->with('database') + ->get(); + + return view('livewire.storage.resources', [ + 'backups' => $backups, + ]); + } +} diff --git a/app/Livewire/Storage/Show.php b/app/Livewire/Storage/Show.php index fdf3d0d28..dc5121e94 100644 --- a/app/Livewire/Storage/Show.php +++ b/app/Livewire/Storage/Show.php @@ -3,6 +3,7 @@ namespace App\Livewire\Storage; use App\Models\S3Storage; +use App\Models\ScheduledDatabaseBackup; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; @@ -12,6 +13,10 @@ class Show extends Component public $storage = null; + public string $currentRoute = ''; + + public int $backupCount = 0; + public function mount() { $this->storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->first(); @@ -19,6 +24,21 @@ public function mount() abort(404); } $this->authorize('view', $this->storage); + $this->currentRoute = request()->route()->getName(); + $this->backupCount = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id)->count(); + } + + public function delete() + { + try { + $this->authorize('delete', $this->storage); + + $this->storage->delete(); + + return redirect()->route('storage.index'); + } catch (\Throwable $e) { + return handleError($e, $this); + } } public function render() diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index 3aae55966..f395a065c 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -40,6 +40,13 @@ protected static function boot(): void $storage->secret = trim($storage->secret); } }); + + static::deleting(function (S3Storage $storage) { + ScheduledDatabaseBackup::where('s3_storage_id', $storage->id)->update([ + 'save_s3' => false, + 's3_storage_id' => null, + ]); + }); } public static function ownedByCurrentTeam(array $select = ['*']) @@ -59,6 +66,11 @@ public function team() return $this->belongsTo(Team::class); } + public function scheduledBackups() + { + return $this->hasMany(ScheduledDatabaseBackup::class, 's3_storage_id'); + } + public function awsUrl() { return "{$this->endpoint}/{$this->bucket}"; diff --git a/resources/views/livewire/storage/form.blade.php b/resources/views/livewire/storage/form.blade.php index 850d7735f..3d9fd322b 100644 --- a/resources/views/livewire/storage/form.blade.php +++ b/resources/views/livewire/storage/form.blade.php @@ -1,36 +1,5 @@
-
-
-

Storage Details

-
{{ $storage->name }}
-
-
Current Status:
- @if ($isUsable) - - Usable - - @else - - Not Usable - - @endif -
-
- Save - - @can('delete', $storage) - - @endcan -
diff --git a/resources/views/livewire/storage/resources.blade.php b/resources/views/livewire/storage/resources.blade.php new file mode 100644 index 000000000..29d26d4f5 --- /dev/null +++ b/resources/views/livewire/storage/resources.blade.php @@ -0,0 +1,62 @@ +
+
+ @forelse ($backups as $backup) + @php + $database = $backup->database; + $databaseName = $database?->name ?? 'Deleted database'; + $link = null; + if ($database && $database instanceof \App\Models\ServiceDatabase) { + $service = $database->service; + if ($service) { + $environment = $service->environment; + $project = $environment?->project; + if ($project && $environment) { + $link = route('project.service.configuration', [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $environment->uuid, + 'service_uuid' => $service->uuid, + ]); + } + } + } elseif ($database) { + $environment = $database->environment; + $project = $environment?->project; + if ($project && $environment) { + $link = route('project.database.backup.index', [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $environment->uuid, + 'database_uuid' => $database->uuid, + ]); + } + } + @endphp + @if ($link) + + @else + + @endif + @empty +
+
No backup schedules are using this storage.
+
+ @endforelse +
+
diff --git a/resources/views/livewire/storage/show.blade.php b/resources/views/livewire/storage/show.blade.php index 1c3a11a69..e54de37f3 100644 --- a/resources/views/livewire/storage/show.blade.php +++ b/resources/views/livewire/storage/show.blade.php @@ -2,5 +2,51 @@ {{ data_get_str($storage, 'name')->limit(10) }} >Storages | Coolify - + +
+

Storage Details

+ @if ($storage->is_usable) + + Usable + + @else + + Not Usable + + @endif + Save + @can('delete', $storage) + + @endcan +
+
{{ $storage->name }}
+ + + +
+ @if ($currentRoute === 'storage.show') + + @elseif ($currentRoute === 'storage.resources') + + @endif +
diff --git a/routes/web.php b/routes/web.php index 26863aa17..27763f121 100644 --- a/routes/web.php +++ b/routes/web.php @@ -140,6 +140,7 @@ Route::prefix('storages')->group(function () { Route::get('/', StorageIndex::class)->name('storage.index'); Route::get('/{storage_uuid}', StorageShow::class)->name('storage.show'); + Route::get('/{storage_uuid}/resources', StorageShow::class)->name('storage.resources'); }); Route::prefix('shared-variables')->group(function () { Route::get('/', SharedVariablesIndex::class)->name('shared-variables.index'); diff --git a/tests/Feature/DatabaseBackupJobTest.php b/tests/Feature/DatabaseBackupJobTest.php index d7efc2bcd..37c377dab 100644 --- a/tests/Feature/DatabaseBackupJobTest.php +++ b/tests/Feature/DatabaseBackupJobTest.php @@ -1,6 +1,10 @@ toHaveKey('s3_storage_deleted'); expect($casts['s3_storage_deleted'])->toBe('boolean'); }); + +test('upload_to_s3 throws exception and disables s3 when storage is null', function () { + $backup = ScheduledDatabaseBackup::create([ + 'frequency' => '0 0 * * *', + 'save_s3' => true, + 's3_storage_id' => 99999, + 'database_type' => 'App\Models\StandalonePostgresql', + 'database_id' => 1, + 'team_id' => Team::factory()->create()->id, + ]); + + $job = new DatabaseBackupJob($backup); + + $reflection = new ReflectionClass($job); + $s3Property = $reflection->getProperty('s3'); + $s3Property->setValue($job, null); + + $method = $reflection->getMethod('upload_to_s3'); + + expect(fn () => $method->invoke($job)) + ->toThrow(Exception::class, 'S3 storage configuration is missing or has been deleted'); + + $backup->refresh(); + expect($backup->save_s3)->toBeFalsy(); + expect($backup->s3_storage_id)->toBeNull(); +}); + +test('deleting s3 storage disables s3 on linked backups', function () { + $team = Team::factory()->create(); + + $s3 = S3Storage::create([ + 'name' => 'Test S3', + 'region' => 'us-east-1', + 'key' => 'test-key', + 'secret' => 'test-secret', + 'bucket' => 'test-bucket', + 'endpoint' => 'https://s3.example.com', + 'team_id' => $team->id, + ]); + + $backup1 = ScheduledDatabaseBackup::create([ + 'frequency' => '0 0 * * *', + 'save_s3' => true, + 's3_storage_id' => $s3->id, + 'database_type' => 'App\Models\StandalonePostgresql', + 'database_id' => 1, + 'team_id' => $team->id, + ]); + + $backup2 = ScheduledDatabaseBackup::create([ + 'frequency' => '0 0 * * *', + 'save_s3' => true, + 's3_storage_id' => $s3->id, + 'database_type' => 'App\Models\StandaloneMysql', + 'database_id' => 2, + 'team_id' => $team->id, + ]); + + // Unrelated backup should not be affected + $unrelatedBackup = ScheduledDatabaseBackup::create([ + 'frequency' => '0 0 * * *', + 'save_s3' => true, + 's3_storage_id' => null, + 'database_type' => 'App\Models\StandalonePostgresql', + 'database_id' => 3, + 'team_id' => $team->id, + ]); + + $s3->delete(); + + $backup1->refresh(); + $backup2->refresh(); + $unrelatedBackup->refresh(); + + expect($backup1->save_s3)->toBeFalsy(); + expect($backup1->s3_storage_id)->toBeNull(); + expect($backup2->save_s3)->toBeFalsy(); + expect($backup2->s3_storage_id)->toBeNull(); + expect($unrelatedBackup->save_s3)->toBeTruthy(); +}); + +test('s3 storage has scheduled backups relationship', function () { + $team = Team::factory()->create(); + + $s3 = S3Storage::create([ + 'name' => 'Test S3', + 'region' => 'us-east-1', + 'key' => 'test-key', + 'secret' => 'test-secret', + 'bucket' => 'test-bucket', + 'endpoint' => 'https://s3.example.com', + 'team_id' => $team->id, + ]); + + ScheduledDatabaseBackup::create([ + 'frequency' => '0 0 * * *', + 'save_s3' => true, + 's3_storage_id' => $s3->id, + 'database_type' => 'App\Models\StandalonePostgresql', + 'database_id' => 1, + 'team_id' => $team->id, + ]); + + expect($s3->scheduledBackups()->count())->toBe(1); +}); From 86c8ec9c20b7f2f4dcc8ed1b75efa39b7b2b2763 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:04:16 +0100 Subject: [PATCH 079/602] feat(storage): group backups by database and filter by s3 status Group backup schedules by their parent database (type + ID) for better organization in the UI. Filter to only display backups with save_s3 enabled. Restructure the template to show database name as a header with nested backups underneath, allowing clearer visualization of which backups belong to each database. Add key binding to livewire component to ensure proper re-rendering when resources change. --- app/Livewire/Storage/Resources.php | 6 +- .../livewire/storage/resources.blade.php | 123 ++++++++++-------- .../views/livewire/storage/show.blade.php | 2 +- 3 files changed, 76 insertions(+), 55 deletions(-) diff --git a/app/Livewire/Storage/Resources.php b/app/Livewire/Storage/Resources.php index f17175013..30ac67066 100644 --- a/app/Livewire/Storage/Resources.php +++ b/app/Livewire/Storage/Resources.php @@ -13,11 +13,13 @@ class Resources extends Component public function render() { $backups = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id) + ->where('save_s3', true) ->with('database') - ->get(); + ->get() + ->groupBy(fn ($backup) => $backup->database_type.'-'.$backup->database_id); return view('livewire.storage.resources', [ - 'backups' => $backups, + 'groupedBackups' => $backups, ]); } } diff --git a/resources/views/livewire/storage/resources.blade.php b/resources/views/livewire/storage/resources.blade.php index 29d26d4f5..4fdef1e4b 100644 --- a/resources/views/livewire/storage/resources.blade.php +++ b/resources/views/livewire/storage/resources.blade.php @@ -1,62 +1,81 @@
-
- @forelse ($backups as $backup) - @php - $database = $backup->database; - $databaseName = $database?->name ?? 'Deleted database'; - $link = null; - if ($database && $database instanceof \App\Models\ServiceDatabase) { - $service = $database->service; - if ($service) { - $environment = $service->environment; - $project = $environment?->project; - if ($project && $environment) { - $link = route('project.service.configuration', [ - 'project_uuid' => $project->uuid, - 'environment_uuid' => $environment->uuid, - 'service_uuid' => $service->uuid, - ]); - } - } - } elseif ($database) { - $environment = $database->environment; + @forelse ($groupedBackups as $backups) + @php + $firstBackup = $backups->first(); + $database = $firstBackup->database; + $databaseName = $database?->name ?? 'Deleted database'; + $resourceLink = null; + $backupParams = null; + if ($database && $database instanceof \App\Models\ServiceDatabase) { + $service = $database->service; + if ($service) { + $environment = $service->environment; $project = $environment?->project; if ($project && $environment) { - $link = route('project.database.backup.index', [ + $resourceLink = route('project.service.configuration', [ 'project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, - 'database_uuid' => $database->uuid, + 'service_uuid' => $service->uuid, ]); } } - @endphp - @if ($link) - - @else - - @endif - @empty -
-
No backup schedules are using this storage.
+ } elseif ($database) { + $environment = $database->environment; + $project = $environment?->project; + if ($project && $environment) { + $resourceLink = route('project.database.backup.index', [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $environment->uuid, + 'database_uuid' => $database->uuid, + ]); + $backupParams = [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $environment->uuid, + 'database_uuid' => $database->uuid, + ]; + } + } + @endphp +
+
+ @if ($resourceLink) + {{ $databaseName }} + @else + {{ $databaseName }} + @endif
- @endforelse -
+
+ @foreach ($backups as $backup) + @php + $backupLink = null; + if ($backupParams) { + $backupLink = route('project.database.backup.execution', array_merge($backupParams, [ + 'backup_uuid' => $backup->uuid, + ])); + } + @endphp + @if ($backupLink) + + @else + + @endif + @endforeach +
+
+ @empty +
No backup schedules are using this storage.
+ @endforelse
diff --git a/resources/views/livewire/storage/show.blade.php b/resources/views/livewire/storage/show.blade.php index e54de37f3..0d580486e 100644 --- a/resources/views/livewire/storage/show.blade.php +++ b/resources/views/livewire/storage/show.blade.php @@ -46,7 +46,7 @@ @if ($currentRoute === 'storage.show') @elseif ($currentRoute === 'storage.resources') - + @endif
From ce5e736b00d493ab2863b3ab666d50d8a8c1e0e8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:48:52 +0100 Subject: [PATCH 080/602] feat(storage): add storage management for backup schedules Add ability to move backups between S3 storages and disable S3 backups. Refactor storage resources view from cards to table layout with search functionality and storage selection dropdowns. --- app/Livewire/Storage/Resources.php | 60 ++++++ resources/css/app.css | 2 +- .../livewire/storage/resources.blade.php | 182 ++++++++++-------- 3 files changed, 165 insertions(+), 79 deletions(-) diff --git a/app/Livewire/Storage/Resources.php b/app/Livewire/Storage/Resources.php index 30ac67066..643ecb3eb 100644 --- a/app/Livewire/Storage/Resources.php +++ b/app/Livewire/Storage/Resources.php @@ -10,6 +10,61 @@ class Resources extends Component { public S3Storage $storage; + public array $selectedStorages = []; + + public function mount(): void + { + $backups = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id) + ->where('save_s3', true) + ->get(); + + foreach ($backups as $backup) { + $this->selectedStorages[$backup->id] = $this->storage->id; + } + } + + public function disableS3(int $backupId): void + { + $backup = ScheduledDatabaseBackup::findOrFail($backupId); + + $backup->update([ + 'save_s3' => false, + 's3_storage_id' => null, + ]); + + unset($this->selectedStorages[$backupId]); + + $this->dispatch('success', 'S3 disabled.', 'S3 backup has been disabled for this schedule.'); + } + + public function moveBackup(int $backupId): void + { + $backup = ScheduledDatabaseBackup::findOrFail($backupId); + $newStorageId = $this->selectedStorages[$backupId] ?? null; + + if (! $newStorageId || (int) $newStorageId === $this->storage->id) { + $this->dispatch('error', 'No change.', 'The backup is already using this storage.'); + + return; + } + + $newStorage = S3Storage::where('id', $newStorageId) + ->where('team_id', $this->storage->team_id) + ->first(); + + if (! $newStorage) { + $this->dispatch('error', 'Storage not found.'); + + return; + } + + $backup->update(['s3_storage_id' => $newStorage->id]); + + unset($this->selectedStorages[$backupId]); + + $this->dispatch('success', 'Backup moved.', "Moved to {$newStorage->name}."); + } + public function render() { $backups = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id) @@ -18,8 +73,13 @@ public function render() ->get() ->groupBy(fn ($backup) => $backup->database_type.'-'.$backup->database_id); + $allStorages = S3Storage::where('team_id', $this->storage->team_id) + ->orderBy('name') + ->get(['id', 'name', 'is_usable']); + return view('livewire.storage.resources', [ 'groupedBackups' => $backups, + 'allStorages' => $allStorages, ]); } } diff --git a/resources/css/app.css b/resources/css/app.css index eeba1ee01..3cfa03dae 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -163,7 +163,7 @@ tbody { } tr { - @apply text-black dark:text-neutral-400 dark:hover:bg-black hover:bg-neutral-200; + @apply text-black dark:text-neutral-400 dark:hover:bg-coolgray-300 hover:bg-neutral-100; } tr th { diff --git a/resources/views/livewire/storage/resources.blade.php b/resources/views/livewire/storage/resources.blade.php index 4fdef1e4b..481e7ccab 100644 --- a/resources/views/livewire/storage/resources.blade.php +++ b/resources/views/livewire/storage/resources.blade.php @@ -1,81 +1,107 @@ -
- @forelse ($groupedBackups as $backups) - @php - $firstBackup = $backups->first(); - $database = $firstBackup->database; - $databaseName = $database?->name ?? 'Deleted database'; - $resourceLink = null; - $backupParams = null; - if ($database && $database instanceof \App\Models\ServiceDatabase) { - $service = $database->service; - if ($service) { - $environment = $service->environment; - $project = $environment?->project; - if ($project && $environment) { - $resourceLink = route('project.service.configuration', [ - 'project_uuid' => $project->uuid, - 'environment_uuid' => $environment->uuid, - 'service_uuid' => $service->uuid, - ]); - } - } - } elseif ($database) { - $environment = $database->environment; - $project = $environment?->project; - if ($project && $environment) { - $resourceLink = route('project.database.backup.index', [ - 'project_uuid' => $project->uuid, - 'environment_uuid' => $environment->uuid, - 'database_uuid' => $database->uuid, - ]); - $backupParams = [ - 'project_uuid' => $project->uuid, - 'environment_uuid' => $environment->uuid, - 'database_uuid' => $database->uuid, - ]; - } - } - @endphp -
-
- @if ($resourceLink) - {{ $databaseName }} - @else - {{ $databaseName }} - @endif -
-
- @foreach ($backups as $backup) - @php - $backupLink = null; - if ($backupParams) { - $backupLink = route('project.database.backup.execution', array_merge($backupParams, [ - 'backup_uuid' => $backup->uuid, - ])); - } - @endphp - @if ($backupLink) - - @else - - @endif - @endforeach +
+ + @if ($groupedBackups->count() > 0) +
+
+
+ + + + + + + + + + + @foreach ($groupedBackups as $backups) + @php + $firstBackup = $backups->first(); + $database = $firstBackup->database; + $databaseName = $database?->name ?? 'Deleted database'; + $resourceLink = null; + $backupParams = null; + if ($database && $database instanceof \App\Models\ServiceDatabase) { + $service = $database->service; + if ($service) { + $environment = $service->environment; + $project = $environment?->project; + if ($project && $environment) { + $resourceLink = route('project.service.configuration', [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $environment->uuid, + 'service_uuid' => $service->uuid, + ]); + } + } + } elseif ($database) { + $environment = $database->environment; + $project = $environment?->project; + if ($project && $environment) { + $resourceLink = route('project.database.backup.index', [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $environment->uuid, + 'database_uuid' => $database->uuid, + ]); + $backupParams = [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $environment->uuid, + 'database_uuid' => $database->uuid, + ]; + } + } + @endphp + @foreach ($backups as $backup) + + + + + + + @endforeach + @endforeach + +
DatabaseFrequencyStatusS3 Storage
+ @if ($resourceLink) + {{ $databaseName }} + @else + {{ $databaseName }} + @endif + + @php + $backupLink = null; + if ($backupParams) { + $backupLink = route('project.database.backup.execution', array_merge($backupParams, [ + 'backup_uuid' => $backup->uuid, + ])); + } + @endphp + @if ($backupLink) + {{ $backup->frequency }} + @else + {{ $backup->frequency }} + @endif + + @if ($backup->enabled) + Enabled + @else + Disabled + @endif + +
+ + Save + Disable S3 +
+
+
- @empty -
No backup schedules are using this storage.
- @endforelse + @else +
No backup schedules are using this storage.
+ @endif
From 3afc1d46dea06ebe1a7e931ff12d000b1f269a76 Mon Sep 17 00:00:00 2001 From: Mattia Trapani Date: Thu, 19 Mar 2026 16:08:39 +0100 Subject: [PATCH 081/602] Fix environment variable defaults in rallly.yaml - SERVICE_URL already includes protocol - SMTP_SECURE and SMTP_TLS_ENABLED need a default value --- templates/compose/rallly.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/compose/rallly.yaml b/templates/compose/rallly.yaml index 0dfc84c56..477dd6a5d 100644 --- a/templates/compose/rallly.yaml +++ b/templates/compose/rallly.yaml @@ -32,15 +32,15 @@ services: - SERVICE_URL_RALLLY_3000 - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@rallly_db:5432/${POSTGRES_DB:-rallly} - SECRET_PASSWORD=${SERVICE_PASSWORD_64_RALLLY} - - NEXT_PUBLIC_BASE_URL=https://${SERVICE_URL_RALLLY} + - NEXT_PUBLIC_BASE_URL=${SERVICE_URL_RALLLY} - ALLOWED_EMAILS=${ALLOWED_EMAILS} - SUPPORT_EMAIL=${SUPPORT_EMAIL:-support@example.com} - SMTP_HOST=${SMTP_HOST} - SMTP_PORT=${SMTP_PORT} - - SMTP_SECURE=${SMTP_SECURE} + - SMTP_SECURE=${SMTP_SECURE:-false} - SMTP_USER=${SMTP_USER} - SMTP_PWD=${SMTP_PWD} - - SMTP_TLS_ENABLED=${SMTP_TLS_ENABLED} + - SMTP_TLS_ENABLED=${SMTP_TLS_ENABLED:-false} healthcheck: test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/3000' || exit 1"] interval: 5s From 485a57fb1b2319f3b02991bb13f28cb40f8bbe01 Mon Sep 17 00:00:00 2001 From: Mattia Trapani Date: Thu, 19 Mar 2026 16:11:50 +0100 Subject: [PATCH 082/602] SMTP_TLS_ENABLED deprecated --- templates/compose/rallly.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/compose/rallly.yaml b/templates/compose/rallly.yaml index 477dd6a5d..45cb51aff 100644 --- a/templates/compose/rallly.yaml +++ b/templates/compose/rallly.yaml @@ -40,7 +40,6 @@ services: - SMTP_SECURE=${SMTP_SECURE:-false} - SMTP_USER=${SMTP_USER} - SMTP_PWD=${SMTP_PWD} - - SMTP_TLS_ENABLED=${SMTP_TLS_ENABLED:-false} healthcheck: test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/3000' || exit 1"] interval: 5s From 0627dce4da77e6165d780cf61773243cfa9ad60c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 17:15:02 +0000 Subject: [PATCH 083/602] build(deps): bump phpseclib/phpseclib from 3.0.49 to 3.0.50 Bumps [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib) from 3.0.49 to 3.0.50. - [Release notes](https://github.com/phpseclib/phpseclib/releases) - [Changelog](https://github.com/phpseclib/phpseclib/blob/master/CHANGELOG.md) - [Commits](https://github.com/phpseclib/phpseclib/compare/3.0.49...3.0.50) --- updated-dependencies: - dependency-name: phpseclib/phpseclib dependency-version: 3.0.50 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 993835a42..3b1f4eded 100644 --- a/composer.lock +++ b/composer.lock @@ -5061,16 +5061,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.49", + "version": "3.0.50", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9" + "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9", - "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", + "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", "shasum": "" }, "require": { @@ -5151,7 +5151,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.49" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.50" }, "funding": [ { @@ -5167,7 +5167,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T09:17:28+00:00" + "time": "2026-03-19T02:57:58+00:00" }, { "name": "phpstan/phpdoc-parser", From 8a164735cb3bb046aa8d5a10e3f6d077bd961dce Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:56:58 +0100 Subject: [PATCH 084/602] fix(api): extract resource UUIDs from route parameters Extract resource UUIDs from route parameters instead of request body in ApplicationsController and ServicesController environment variable endpoints. This prevents UUID parameters from being spoofed in the request body. - Replace $request->uuid with $request->route('uuid') - Replace $request->env_uuid with $request->route('env_uuid') - Add tests verifying route parameters are used and body UUIDs ignored --- .../Api/ApplicationsController.php | 10 +-- .../Controllers/Api/ServicesController.php | 10 +-- .../EnvironmentVariableUpdateApiTest.php | 62 ++++++++++++++++++- 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 6f34b43bf..5819441b7 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -2957,7 +2957,7 @@ public function update_env_by_uuid(Request $request) if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->route('uuid'))->first(); if (! $application) { return response()->json([ @@ -3158,7 +3158,7 @@ public function create_bulk_envs(Request $request) if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->route('uuid'))->first(); if (! $application) { return response()->json([ @@ -3352,7 +3352,7 @@ public function create_env(Request $request) if (is_null($teamId)) { return invalidTokenResponse(); } - $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->route('uuid'))->first(); if (! $application) { return response()->json([ @@ -3509,7 +3509,7 @@ public function delete_env_by_uuid(Request $request) if (is_null($teamId)) { return invalidTokenResponse(); } - $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->route('uuid'))->first(); if (! $application) { return response()->json([ @@ -3519,7 +3519,7 @@ public function delete_env_by_uuid(Request $request) $this->authorize('manageEnvironment', $application); - $found_env = EnvironmentVariable::where('uuid', $request->env_uuid) + $found_env = EnvironmentVariable::where('uuid', $request->route('env_uuid')) ->where('resourceable_type', Application::class) ->where('resourceable_id', $application->id) ->first(); diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 32097443e..de504c1a4 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -1207,7 +1207,7 @@ public function update_env_by_uuid(Request $request) return invalidTokenResponse(); } - $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->route('uuid'))->first(); if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } @@ -1342,7 +1342,7 @@ public function create_bulk_envs(Request $request) return invalidTokenResponse(); } - $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->route('uuid'))->first(); if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } @@ -1461,7 +1461,7 @@ public function create_env(Request $request) return invalidTokenResponse(); } - $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->route('uuid'))->first(); if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } @@ -1570,14 +1570,14 @@ public function delete_env_by_uuid(Request $request) return invalidTokenResponse(); } - $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->route('uuid'))->first(); if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } $this->authorize('manageEnvironment', $service); - $env = EnvironmentVariable::where('uuid', $request->env_uuid) + $env = EnvironmentVariable::where('uuid', $request->route('env_uuid')) ->where('resourceable_type', Service::class) ->where('resourceable_id', $service->id) ->first(); diff --git a/tests/Feature/EnvironmentVariableUpdateApiTest.php b/tests/Feature/EnvironmentVariableUpdateApiTest.php index 9c45dc5ae..1ff528bbf 100644 --- a/tests/Feature/EnvironmentVariableUpdateApiTest.php +++ b/tests/Feature/EnvironmentVariableUpdateApiTest.php @@ -3,6 +3,7 @@ use App\Models\Application; use App\Models\Environment; use App\Models\EnvironmentVariable; +use App\Models\InstanceSettings; use App\Models\Project; use App\Models\Server; use App\Models\Service; @@ -14,6 +15,8 @@ uses(RefreshDatabase::class); beforeEach(function () { + InstanceSettings::updateOrCreate(['id' => 0]); + $this->team = Team::factory()->create(); $this->user = User::factory()->create(); $this->team->members()->attach($this->user->id, ['role' => 'owner']); @@ -24,7 +27,7 @@ $this->bearerToken = $this->token->plainTextToken; $this->server = Server::factory()->create(['team_id' => $this->team->id]); - $this->destination = StandaloneDocker::factory()->create(['server_id' => $this->server->id]); + $this->destination = StandaloneDocker::where('server_id', $this->server->id)->first(); $this->project = Project::factory()->create(['team_id' => $this->team->id]); $this->environment = Environment::factory()->create(['project_id' => $this->project->id]); }); @@ -117,6 +120,35 @@ $response->assertStatus(422); }); + + test('uses route uuid and ignores uuid in request body', function () { + $service = Service::factory()->create([ + 'server_id' => $this->server->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + 'environment_id' => $this->environment->id, + ]); + + EnvironmentVariable::create([ + 'key' => 'TEST_KEY', + 'value' => 'old-value', + 'resourceable_type' => Service::class, + 'resourceable_id' => $service->id, + 'is_preview' => false, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/services/{$service->uuid}/envs", [ + 'key' => 'TEST_KEY', + 'value' => 'new-value', + 'uuid' => 'bogus-uuid-from-body', + ]); + + $response->assertStatus(201); + $response->assertJsonFragment(['key' => 'TEST_KEY']); + }); }); describe('PATCH /api/v1/applications/{uuid}/envs', function () { @@ -191,4 +223,32 @@ $response->assertStatus(422); }); + + test('rejects unknown fields in request body', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + EnvironmentVariable::create([ + 'key' => 'TEST_KEY', + 'value' => 'old-value', + 'resourceable_type' => Application::class, + 'resourceable_id' => $application->id, + 'is_preview' => false, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/applications/{$application->uuid}/envs", [ + 'key' => 'TEST_KEY', + 'value' => 'new-value', + 'uuid' => 'bogus-uuid-from-body', + ]); + + $response->assertStatus(422); + $response->assertJsonFragment(['uuid' => ['This field is not allowed.']]); + }); }); From fb76b68c0822df5616320d8fbc8624fd0d8b3d17 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:17:55 +0100 Subject: [PATCH 085/602] feat(api): support comments in bulk environment variable endpoints Add support for optional comment field on environment variables created or updated through the bulk API endpoints. Comments are validated to a maximum of 256 characters and are nullable. Updates preserve existing comments when not provided in the request. --- .../Api/ApplicationsController.php | 11 +- .../Controllers/Api/ServicesController.php | 1 + .../EnvironmentVariableBulkCommentApiTest.php | 244 ++++++++++++++++++ 3 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/EnvironmentVariableBulkCommentApiTest.php diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 5819441b7..3444f9f14 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -3175,7 +3175,7 @@ public function create_bulk_envs(Request $request) ], 400); } $bulk_data = collect($bulk_data)->map(function ($item) { - return collect($item)->only(['key', 'value', 'is_preview', 'is_literal', 'is_multiline', 'is_shown_once', 'is_runtime', 'is_buildtime']); + return collect($item)->only(['key', 'value', 'is_preview', 'is_literal', 'is_multiline', 'is_shown_once', 'is_runtime', 'is_buildtime', 'comment']); }); $returnedEnvs = collect(); foreach ($bulk_data as $item) { @@ -3188,6 +3188,7 @@ public function create_bulk_envs(Request $request) 'is_shown_once' => 'boolean', 'is_runtime' => 'boolean', 'is_buildtime' => 'boolean', + 'comment' => 'string|nullable|max:256', ]); if ($validator->fails()) { return response()->json([ @@ -3220,6 +3221,9 @@ public function create_bulk_envs(Request $request) if ($item->has('is_buildtime') && $env->is_buildtime != $item->get('is_buildtime')) { $env->is_buildtime = $item->get('is_buildtime'); } + if ($item->has('comment') && $env->comment != $item->get('comment')) { + $env->comment = $item->get('comment'); + } $env->save(); } else { $env = $application->environment_variables()->create([ @@ -3231,6 +3235,7 @@ public function create_bulk_envs(Request $request) 'is_shown_once' => $is_shown_once, 'is_runtime' => $item->get('is_runtime', true), 'is_buildtime' => $item->get('is_buildtime', true), + 'comment' => $item->get('comment'), 'resourceable_type' => get_class($application), 'resourceable_id' => $application->id, ]); @@ -3254,6 +3259,9 @@ public function create_bulk_envs(Request $request) if ($item->has('is_buildtime') && $env->is_buildtime != $item->get('is_buildtime')) { $env->is_buildtime = $item->get('is_buildtime'); } + if ($item->has('comment') && $env->comment != $item->get('comment')) { + $env->comment = $item->get('comment'); + } $env->save(); } else { $env = $application->environment_variables()->create([ @@ -3265,6 +3273,7 @@ public function create_bulk_envs(Request $request) 'is_shown_once' => $is_shown_once, 'is_runtime' => $item->get('is_runtime', true), 'is_buildtime' => $item->get('is_buildtime', true), + 'comment' => $item->get('comment'), '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 de504c1a4..4caee26dd 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -1362,6 +1362,7 @@ public function create_bulk_envs(Request $request) 'is_literal' => 'boolean', 'is_multiline' => 'boolean', 'is_shown_once' => 'boolean', + 'comment' => 'string|nullable|max:256', ]); if ($validator->fails()) { diff --git a/tests/Feature/EnvironmentVariableBulkCommentApiTest.php b/tests/Feature/EnvironmentVariableBulkCommentApiTest.php new file mode 100644 index 000000000..f038ad682 --- /dev/null +++ b/tests/Feature/EnvironmentVariableBulkCommentApiTest.php @@ -0,0 +1,244 @@ + 0]); + + $this->team = Team::factory()->create(); + $this->user = User::factory()->create(); + $this->team->members()->attach($this->user->id, ['role' => 'owner']); + + session(['currentTeam' => $this->team]); + + $this->token = $this->user->createToken('test-token', ['*']); + $this->bearerToken = $this->token->plainTextToken; + + $this->server = Server::factory()->create(['team_id' => $this->team->id]); + $this->destination = StandaloneDocker::where('server_id', $this->server->id)->first(); + $this->project = Project::factory()->create(['team_id' => $this->team->id]); + $this->environment = Environment::factory()->create(['project_id' => $this->project->id]); +}); + +describe('PATCH /api/v1/applications/{uuid}/envs/bulk', function () { + test('creates environment variables with comments', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/applications/{$application->uuid}/envs/bulk", [ + 'data' => [ + [ + 'key' => 'DB_HOST', + 'value' => 'localhost', + 'comment' => 'Database host for production', + ], + [ + 'key' => 'DB_PORT', + 'value' => '5432', + ], + ], + ]); + + $response->assertStatus(201); + + $envWithComment = EnvironmentVariable::where('key', 'DB_HOST') + ->where('resourceable_id', $application->id) + ->where('is_preview', false) + ->first(); + + $envWithoutComment = EnvironmentVariable::where('key', 'DB_PORT') + ->where('resourceable_id', $application->id) + ->where('is_preview', false) + ->first(); + + expect($envWithComment->comment)->toBe('Database host for production'); + expect($envWithoutComment->comment)->toBeNull(); + }); + + test('updates existing environment variable comment', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + EnvironmentVariable::create([ + 'key' => 'API_KEY', + 'value' => 'old-key', + 'comment' => 'Old comment', + 'resourceable_type' => Application::class, + 'resourceable_id' => $application->id, + 'is_preview' => false, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/applications/{$application->uuid}/envs/bulk", [ + 'data' => [ + [ + 'key' => 'API_KEY', + 'value' => 'new-key', + 'comment' => 'Updated comment', + ], + ], + ]); + + $response->assertStatus(201); + + $env = EnvironmentVariable::where('key', 'API_KEY') + ->where('resourceable_id', $application->id) + ->where('is_preview', false) + ->first(); + + expect($env->value)->toBe('new-key'); + expect($env->comment)->toBe('Updated comment'); + }); + + test('preserves existing comment when not provided in bulk update', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + EnvironmentVariable::create([ + 'key' => 'SECRET', + 'value' => 'old-secret', + 'comment' => 'Keep this comment', + 'resourceable_type' => Application::class, + 'resourceable_id' => $application->id, + 'is_preview' => false, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/applications/{$application->uuid}/envs/bulk", [ + 'data' => [ + [ + 'key' => 'SECRET', + 'value' => 'new-secret', + ], + ], + ]); + + $response->assertStatus(201); + + $env = EnvironmentVariable::where('key', 'SECRET') + ->where('resourceable_id', $application->id) + ->where('is_preview', false) + ->first(); + + expect($env->value)->toBe('new-secret'); + expect($env->comment)->toBe('Keep this comment'); + }); + + test('rejects comment exceeding 256 characters', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/applications/{$application->uuid}/envs/bulk", [ + 'data' => [ + [ + 'key' => 'TEST_VAR', + 'value' => 'value', + 'comment' => str_repeat('a', 257), + ], + ], + ]); + + $response->assertStatus(422); + }); +}); + +describe('PATCH /api/v1/services/{uuid}/envs/bulk', function () { + test('creates environment variables with comments', function () { + $service = Service::factory()->create([ + 'server_id' => $this->server->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + 'environment_id' => $this->environment->id, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/services/{$service->uuid}/envs/bulk", [ + 'data' => [ + [ + 'key' => 'REDIS_HOST', + 'value' => 'redis', + 'comment' => 'Redis cache host', + ], + [ + 'key' => 'REDIS_PORT', + 'value' => '6379', + ], + ], + ]); + + $response->assertStatus(201); + + $envWithComment = EnvironmentVariable::where('key', 'REDIS_HOST') + ->where('resourceable_id', $service->id) + ->where('resourceable_type', Service::class) + ->first(); + + $envWithoutComment = EnvironmentVariable::where('key', 'REDIS_PORT') + ->where('resourceable_id', $service->id) + ->where('resourceable_type', Service::class) + ->first(); + + expect($envWithComment->comment)->toBe('Redis cache host'); + expect($envWithoutComment->comment)->toBeNull(); + }); + + test('rejects comment exceeding 256 characters', function () { + $service = Service::factory()->create([ + 'server_id' => $this->server->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + 'environment_id' => $this->environment->id, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/services/{$service->uuid}/envs/bulk", [ + 'data' => [ + [ + 'key' => 'TEST_VAR', + 'value' => 'value', + 'comment' => str_repeat('a', 257), + ], + ], + ]); + + $response->assertStatus(422); + }); +}); From c00d5de03e9a943270db8bebe7e360b444da24b8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 19 Mar 2026 23:29:50 +0100 Subject: [PATCH 086/602] feat(api): add database environment variable management endpoints Add CRUD API endpoints for managing environment variables on databases: - GET /databases/{uuid}/envs - list environment variables - POST /databases/{uuid}/envs - create environment variable - PATCH /databases/{uuid}/envs - update environment variable - PATCH /databases/{uuid}/envs/bulk - bulk create environment variables - DELETE /databases/{uuid}/envs/{env_uuid} - delete environment variable Includes comprehensive test suite and OpenAPI documentation. --- .../Controllers/Api/DatabasesController.php | 548 ++++++++++++++++++ openapi.json | 381 ++++++++++++ openapi.yaml | 236 ++++++++ routes/api.php | 6 + .../DatabaseEnvironmentVariableApiTest.php | 346 +++++++++++ 5 files changed, 1517 insertions(+) create mode 100644 tests/Feature/DatabaseEnvironmentVariableApiTest.php diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index f7a62cf90..6ad18d872 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -11,6 +11,7 @@ use App\Http\Controllers\Controller; use App\Jobs\DatabaseBackupJob; use App\Jobs\DeleteResourceJob; +use App\Models\EnvironmentVariable; use App\Models\Project; use App\Models\S3Storage; use App\Models\ScheduledDatabaseBackup; @@ -2750,4 +2751,551 @@ public function action_restart(Request $request) 200 ); } + + private function removeSensitiveEnvData($env) + { + $env->makeHidden([ + 'id', + 'resourceable', + 'resourceable_id', + 'resourceable_type', + ]); + if (request()->attributes->get('can_read_sensitive', false) === false) { + $env->makeHidden([ + 'value', + 'real_value', + ]); + } + + return serializeApiResponse($env); + } + + #[OA\Get( + summary: 'List Envs', + description: 'List all envs by database UUID.', + path: '/databases/{uuid}/envs', + operationId: 'list-envs-by-database-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Databases'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the database.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Environment variables.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable') + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function envs(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + $database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId); + if (! $database) { + return response()->json(['message' => 'Database not found.'], 404); + } + + $this->authorize('view', $database); + + $envs = $database->environment_variables->map(function ($env) { + return $this->removeSensitiveEnvData($env); + }); + + return response()->json($envs); + } + + #[OA\Patch( + summary: 'Update Env', + description: 'Update env by database UUID.', + path: '/databases/{uuid}/envs', + operationId: 'update-env-by-database-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Databases'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the database.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Env updated.', + required: true, + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + required: ['key', 'value'], + properties: [ + 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'], + 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'], + 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'], + 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'], + 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'], + ], + ), + ), + ], + ), + responses: [ + new OA\Response( + response: 201, + description: 'Environment variable updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + ref: '#/components/schemas/EnvironmentVariable' + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), + ] + )] + public function update_env_by_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $database = queryDatabaseByUuidWithinTeam($request->route('uuid'), $teamId); + if (! $database) { + return response()->json(['message' => 'Database not found.'], 404); + } + + $this->authorize('manageEnvironment', $database); + + $validator = customApiValidator($request->all(), [ + 'key' => 'string|required', + 'value' => 'string|nullable', + 'is_literal' => 'boolean', + 'is_multiline' => 'boolean', + 'is_shown_once' => 'boolean', + 'comment' => 'string|nullable|max:256', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $key = str($request->key)->trim()->replace(' ', '_')->value; + $env = $database->environment_variables()->where('key', $key)->first(); + if (! $env) { + return response()->json(['message' => 'Environment variable not found.'], 404); + } + + $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->removeSensitiveEnvData($env))->setStatusCode(201); + } + + #[OA\Patch( + summary: 'Update Envs (Bulk)', + description: 'Update multiple envs by database UUID.', + path: '/databases/{uuid}/envs/bulk', + operationId: 'update-envs-by-database-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Databases'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the database.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Bulk envs updated.', + required: true, + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + required: ['data'], + properties: [ + 'data' => [ + 'type' => 'array', + 'items' => new OA\Schema( + type: 'object', + properties: [ + 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'], + 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'], + 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'], + 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'], + 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'], + ], + ), + ], + ], + ), + ), + ], + ), + responses: [ + new OA\Response( + response: 201, + description: 'Environment variables updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable') + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), + ] + )] + public function create_bulk_envs(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $database = queryDatabaseByUuidWithinTeam($request->route('uuid'), $teamId); + if (! $database) { + return response()->json(['message' => 'Database not found.'], 404); + } + + $this->authorize('manageEnvironment', $database); + + $bulk_data = $request->get('data'); + if (! $bulk_data) { + return response()->json(['message' => 'Bulk data is required.'], 400); + } + + $updatedEnvs = collect(); + foreach ($bulk_data as $item) { + $validator = customApiValidator($item, [ + 'key' => 'string|required', + 'value' => 'string|nullable', + 'is_literal' => 'boolean', + 'is_multiline' => 'boolean', + 'is_shown_once' => 'boolean', + 'comment' => 'string|nullable|max:256', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + $key = str($item['key'])->trim()->replace(' ', '_')->value; + $env = $database->environment_variables()->updateOrCreate( + ['key' => $key], + $item + ); + + $updatedEnvs->push($this->removeSensitiveEnvData($env)); + } + + return response()->json($updatedEnvs)->setStatusCode(201); + } + + #[OA\Post( + summary: 'Create Env', + description: 'Create env by database UUID.', + path: '/databases/{uuid}/envs', + operationId: 'create-env-by-database-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Databases'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the database.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + requestBody: new OA\RequestBody( + required: true, + description: 'Env created.', + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'], + 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'], + 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'], + 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'], + 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'], + ], + ), + ), + ), + responses: [ + new OA\Response( + response: 201, + description: 'Environment variable created.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'uuid' => ['type' => 'string', 'example' => 'nc0k04gk8g0cgsk440g0koko'], + ] + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), + ] + )] + public function create_env(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $database = queryDatabaseByUuidWithinTeam($request->route('uuid'), $teamId); + if (! $database) { + return response()->json(['message' => 'Database not found.'], 404); + } + + $this->authorize('manageEnvironment', $database); + + $validator = customApiValidator($request->all(), [ + 'key' => 'string|required', + 'value' => 'string|nullable', + 'is_literal' => 'boolean', + 'is_multiline' => 'boolean', + 'is_shown_once' => 'boolean', + 'comment' => 'string|nullable|max:256', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $key = str($request->key)->trim()->replace(' ', '_')->value; + $existingEnv = $database->environment_variables()->where('key', $key)->first(); + if ($existingEnv) { + return response()->json([ + 'message' => 'Environment variable already exists. Use PATCH request to update it.', + ], 409); + } + + $env = $database->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->removeSensitiveEnvData($env))->setStatusCode(201); + } + + #[OA\Delete( + summary: 'Delete Env', + description: 'Delete env by UUID.', + path: '/databases/{uuid}/envs/{env_uuid}', + operationId: 'delete-env-by-database-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Databases'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the database.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + new OA\Parameter( + name: 'env_uuid', + in: 'path', + description: 'UUID of the environment variable.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Environment variable deleted.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Environment variable deleted.'], + ] + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function delete_env_by_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $database = queryDatabaseByUuidWithinTeam($request->route('uuid'), $teamId); + if (! $database) { + return response()->json(['message' => 'Database not found.'], 404); + } + + $this->authorize('manageEnvironment', $database); + + $env = EnvironmentVariable::where('uuid', $request->route('env_uuid')) + ->where('resourceable_type', get_class($database)) + ->where('resourceable_id', $database->id) + ->first(); + + if (! $env) { + return response()->json(['message' => 'Environment variable not found.'], 404); + } + + $env->forceDelete(); + + return response()->json(['message' => 'Environment variable deleted.']); + } } diff --git a/openapi.json b/openapi.json index 5477420ab..d119176a1 100644 --- a/openapi.json +++ b/openapi.json @@ -6132,6 +6132,387 @@ ] } }, + "\/databases\/{uuid}\/envs": { + "get": { + "tags": [ + "Databases" + ], + "summary": "List Envs", + "description": "List all envs by database UUID.", + "operationId": "list-envs-by-database-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Environment variables.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/EnvironmentVariable" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Databases" + ], + "summary": "Create Env", + "description": "Create env by database UUID.", + "operationId": "create-env-by-database-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Env created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "key": { + "type": "string", + "description": "The key of the environment variable." + }, + "value": { + "type": "string", + "description": "The value of the environment variable." + }, + "is_literal": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is a literal, nothing espaced." + }, + "is_multiline": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is multiline." + }, + "is_shown_once": { + "type": "boolean", + "description": "The flag to indicate if the environment variable's value is shown on the UI." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Environment variable created.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string", + "example": "nc0k04gk8g0cgsk440g0koko" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Databases" + ], + "summary": "Update Env", + "description": "Update env by database UUID.", + "operationId": "update-env-by-database-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Env updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string", + "description": "The key of the environment variable." + }, + "value": { + "type": "string", + "description": "The value of the environment variable." + }, + "is_literal": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is a literal, nothing espaced." + }, + "is_multiline": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is multiline." + }, + "is_shown_once": { + "type": "boolean", + "description": "The flag to indicate if the environment variable's value is shown on the UI." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Environment variable updated.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/EnvironmentVariable" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/{uuid}\/envs\/bulk": { + "patch": { + "tags": [ + "Databases" + ], + "summary": "Update Envs (Bulk)", + "description": "Update multiple envs by database UUID.", + "operationId": "update-envs-by-database-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Bulk envs updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "key": { + "type": "string", + "description": "The key of the environment variable." + }, + "value": { + "type": "string", + "description": "The value of the environment variable." + }, + "is_literal": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is a literal, nothing espaced." + }, + "is_multiline": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is multiline." + }, + "is_shown_once": { + "type": "boolean", + "description": "The flag to indicate if the environment variable's value is shown on the UI." + } + }, + "type": "object" + } + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Environment variables updated.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/EnvironmentVariable" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/{uuid}\/envs\/{env_uuid}": { + "delete": { + "tags": [ + "Databases" + ], + "summary": "Delete Env", + "description": "Delete env by UUID.", + "operationId": "delete-env-by-database-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "env_uuid", + "in": "path", + "description": "UUID of the environment variable.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Environment variable deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Environment variable deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/deployments": { "get": { "tags": [ diff --git a/openapi.yaml b/openapi.yaml index dd03f9c42..7064be28a 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3973,6 +3973,242 @@ paths: security: - bearerAuth: [] + '/databases/{uuid}/envs': + get: + tags: + - Databases + summary: 'List Envs' + description: 'List all envs by database UUID.' + operationId: list-envs-by-database-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the database.' + required: true + schema: + type: string + responses: + '200': + description: 'Environment variables.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EnvironmentVariable' + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + post: + tags: + - Databases + summary: 'Create Env' + description: 'Create env by database UUID.' + operationId: create-env-by-database-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the database.' + required: true + schema: + type: string + requestBody: + description: 'Env created.' + required: true + content: + application/json: + schema: + properties: + key: + type: string + description: 'The key of the environment variable.' + value: + type: string + description: 'The value of the environment variable.' + is_literal: + type: boolean + description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' + is_multiline: + type: boolean + description: 'The flag to indicate if the environment variable is multiline.' + is_shown_once: + type: boolean + description: "The flag to indicate if the environment variable's value is shown on the UI." + type: object + responses: + '201': + description: 'Environment variable created.' + content: + application/json: + schema: + properties: + uuid: { type: string, example: nc0k04gk8g0cgsk440g0koko } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] + patch: + tags: + - Databases + summary: 'Update Env' + description: 'Update env by database UUID.' + operationId: update-env-by-database-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the database.' + required: true + schema: + type: string + requestBody: + description: 'Env updated.' + required: true + content: + application/json: + schema: + required: + - key + - value + properties: + key: + type: string + description: 'The key of the environment variable.' + value: + type: string + description: 'The value of the environment variable.' + is_literal: + type: boolean + description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' + is_multiline: + type: boolean + description: 'The flag to indicate if the environment variable is multiline.' + is_shown_once: + type: boolean + description: "The flag to indicate if the environment variable's value is shown on the UI." + type: object + responses: + '201': + description: 'Environment variable updated.' + content: + application/json: + schema: + $ref: '#/components/schemas/EnvironmentVariable' + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] + '/databases/{uuid}/envs/bulk': + patch: + tags: + - Databases + summary: 'Update Envs (Bulk)' + description: 'Update multiple envs by database UUID.' + operationId: update-envs-by-database-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the database.' + required: true + schema: + type: string + requestBody: + description: 'Bulk envs updated.' + required: true + content: + application/json: + schema: + required: + - data + properties: + data: + type: array + items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: "The flag to indicate if the environment variable's value is shown on the UI." } }, type: object } + type: object + responses: + '201': + description: 'Environment variables updated.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EnvironmentVariable' + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] + '/databases/{uuid}/envs/{env_uuid}': + delete: + tags: + - Databases + summary: 'Delete Env' + description: 'Delete env by UUID.' + operationId: delete-env-by-database-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the database.' + required: true + schema: + type: string + - + name: env_uuid + in: path + description: 'UUID of the environment variable.' + required: true + schema: + type: string + responses: + '200': + description: 'Environment variable deleted.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Environment variable deleted.' } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] /deployments: get: tags: diff --git a/routes/api.php b/routes/api.php index b02682a5b..1de365c49 100644 --- a/routes/api.php +++ b/routes/api.php @@ -154,6 +154,12 @@ Route::delete('/databases/{uuid}/backups/{scheduled_backup_uuid}', [DatabasesController::class, 'delete_backup_by_uuid'])->middleware(['api.ability:write']); Route::delete('/databases/{uuid}/backups/{scheduled_backup_uuid}/executions/{execution_uuid}', [DatabasesController::class, 'delete_execution_by_uuid'])->middleware(['api.ability:write']); + Route::get('/databases/{uuid}/envs', [DatabasesController::class, 'envs'])->middleware(['api.ability:read']); + Route::post('/databases/{uuid}/envs', [DatabasesController::class, 'create_env'])->middleware(['api.ability:write']); + Route::patch('/databases/{uuid}/envs/bulk', [DatabasesController::class, 'create_bulk_envs'])->middleware(['api.ability:write']); + Route::patch('/databases/{uuid}/envs', [DatabasesController::class, 'update_env_by_uuid'])->middleware(['api.ability:write']); + Route::delete('/databases/{uuid}/envs/{env_uuid}', [DatabasesController::class, 'delete_env_by_uuid'])->middleware(['api.ability:write']); + Route::match(['get', 'post'], '/databases/{uuid}/start', [DatabasesController::class, 'action_deploy'])->middleware(['api.ability:deploy']); Route::match(['get', 'post'], '/databases/{uuid}/restart', [DatabasesController::class, 'action_restart'])->middleware(['api.ability:deploy']); Route::match(['get', 'post'], '/databases/{uuid}/stop', [DatabasesController::class, 'action_stop'])->middleware(['api.ability:deploy']); diff --git a/tests/Feature/DatabaseEnvironmentVariableApiTest.php b/tests/Feature/DatabaseEnvironmentVariableApiTest.php new file mode 100644 index 000000000..f3297cf17 --- /dev/null +++ b/tests/Feature/DatabaseEnvironmentVariableApiTest.php @@ -0,0 +1,346 @@ + 0]); + + $this->team = Team::factory()->create(); + $this->user = User::factory()->create(); + $this->team->members()->attach($this->user->id, ['role' => 'owner']); + + session(['currentTeam' => $this->team]); + + $this->token = $this->user->createToken('test-token', ['*']); + $this->bearerToken = $this->token->plainTextToken; + + $this->server = Server::factory()->create(['team_id' => $this->team->id]); + $this->destination = StandaloneDocker::where('server_id', $this->server->id)->first(); + $this->project = Project::factory()->create(['team_id' => $this->team->id]); + $this->environment = Environment::factory()->create(['project_id' => $this->project->id]); +}); + +function createDatabase($context): StandalonePostgresql +{ + return StandalonePostgresql::create([ + 'name' => 'test-postgres', + 'image' => 'postgres:15-alpine', + 'postgres_user' => 'postgres', + 'postgres_password' => 'password', + 'postgres_db' => 'postgres', + 'environment_id' => $context->environment->id, + 'destination_id' => $context->destination->id, + 'destination_type' => $context->destination->getMorphClass(), + ]); +} + +describe('GET /api/v1/databases/{uuid}/envs', function () { + test('lists environment variables for a database', function () { + $database = createDatabase($this); + + EnvironmentVariable::create([ + 'key' => 'CUSTOM_VAR', + 'value' => 'custom_value', + 'resourceable_type' => StandalonePostgresql::class, + 'resourceable_id' => $database->id, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->getJson("/api/v1/databases/{$database->uuid}/envs"); + + $response->assertStatus(200); + $response->assertJsonFragment(['key' => 'CUSTOM_VAR']); + }); + + test('returns empty array when no environment variables exist', function () { + $database = createDatabase($this); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->getJson("/api/v1/databases/{$database->uuid}/envs"); + + $response->assertStatus(200); + $response->assertJson([]); + }); + + test('returns 404 for non-existent database', function () { + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->getJson('/api/v1/databases/non-existent-uuid/envs'); + + $response->assertStatus(404); + }); +}); + +describe('POST /api/v1/databases/{uuid}/envs', function () { + test('creates an environment variable', function () { + $database = createDatabase($this); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->postJson("/api/v1/databases/{$database->uuid}/envs", [ + 'key' => 'NEW_VAR', + 'value' => 'new_value', + ]); + + $response->assertStatus(201); + + $env = EnvironmentVariable::where('key', 'NEW_VAR') + ->where('resourceable_id', $database->id) + ->where('resourceable_type', StandalonePostgresql::class) + ->first(); + + expect($env)->not->toBeNull(); + expect($env->value)->toBe('new_value'); + }); + + test('creates an environment variable with comment', function () { + $database = createDatabase($this); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->postJson("/api/v1/databases/{$database->uuid}/envs", [ + 'key' => 'COMMENTED_VAR', + 'value' => 'some_value', + 'comment' => 'This is a test comment', + ]); + + $response->assertStatus(201); + + $env = EnvironmentVariable::where('key', 'COMMENTED_VAR') + ->where('resourceable_id', $database->id) + ->first(); + + expect($env->comment)->toBe('This is a test comment'); + }); + + test('returns 409 when environment variable already exists', function () { + $database = createDatabase($this); + + EnvironmentVariable::create([ + 'key' => 'EXISTING_VAR', + 'value' => 'existing_value', + 'resourceable_type' => StandalonePostgresql::class, + 'resourceable_id' => $database->id, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->postJson("/api/v1/databases/{$database->uuid}/envs", [ + 'key' => 'EXISTING_VAR', + 'value' => 'new_value', + ]); + + $response->assertStatus(409); + }); + + test('returns 422 when key is missing', function () { + $database = createDatabase($this); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->postJson("/api/v1/databases/{$database->uuid}/envs", [ + 'value' => 'some_value', + ]); + + $response->assertStatus(422); + }); +}); + +describe('PATCH /api/v1/databases/{uuid}/envs', function () { + test('updates an environment variable', function () { + $database = createDatabase($this); + + EnvironmentVariable::create([ + 'key' => 'UPDATE_ME', + 'value' => 'old_value', + 'resourceable_type' => StandalonePostgresql::class, + 'resourceable_id' => $database->id, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/databases/{$database->uuid}/envs", [ + 'key' => 'UPDATE_ME', + 'value' => 'new_value', + ]); + + $response->assertStatus(201); + + $env = EnvironmentVariable::where('key', 'UPDATE_ME') + ->where('resourceable_id', $database->id) + ->first(); + + expect($env->value)->toBe('new_value'); + }); + + test('returns 404 when environment variable does not exist', function () { + $database = createDatabase($this); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/databases/{$database->uuid}/envs", [ + 'key' => 'NONEXISTENT', + 'value' => 'value', + ]); + + $response->assertStatus(404); + }); +}); + +describe('PATCH /api/v1/databases/{uuid}/envs/bulk', function () { + test('creates environment variables with comments', function () { + $database = createDatabase($this); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/databases/{$database->uuid}/envs/bulk", [ + 'data' => [ + [ + 'key' => 'DB_HOST', + 'value' => 'localhost', + 'comment' => 'Database host', + ], + [ + 'key' => 'DB_PORT', + 'value' => '5432', + ], + ], + ]); + + $response->assertStatus(201); + + $envWithComment = EnvironmentVariable::where('key', 'DB_HOST') + ->where('resourceable_id', $database->id) + ->where('resourceable_type', StandalonePostgresql::class) + ->first(); + + $envWithoutComment = EnvironmentVariable::where('key', 'DB_PORT') + ->where('resourceable_id', $database->id) + ->where('resourceable_type', StandalonePostgresql::class) + ->first(); + + expect($envWithComment->comment)->toBe('Database host'); + expect($envWithoutComment->comment)->toBeNull(); + }); + + test('updates existing environment variables via bulk', function () { + $database = createDatabase($this); + + EnvironmentVariable::create([ + 'key' => 'BULK_VAR', + 'value' => 'old_value', + 'comment' => 'Old comment', + 'resourceable_type' => StandalonePostgresql::class, + 'resourceable_id' => $database->id, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/databases/{$database->uuid}/envs/bulk", [ + 'data' => [ + [ + 'key' => 'BULK_VAR', + 'value' => 'new_value', + 'comment' => 'Updated comment', + ], + ], + ]); + + $response->assertStatus(201); + + $env = EnvironmentVariable::where('key', 'BULK_VAR') + ->where('resourceable_id', $database->id) + ->first(); + + expect($env->value)->toBe('new_value'); + expect($env->comment)->toBe('Updated comment'); + }); + + test('rejects comment exceeding 256 characters', function () { + $database = createDatabase($this); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/databases/{$database->uuid}/envs/bulk", [ + 'data' => [ + [ + 'key' => 'TEST_VAR', + 'value' => 'value', + 'comment' => str_repeat('a', 257), + ], + ], + ]); + + $response->assertStatus(422); + }); + + test('returns 400 when data is missing', function () { + $database = createDatabase($this); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->patchJson("/api/v1/databases/{$database->uuid}/envs/bulk", []); + + $response->assertStatus(400); + }); +}); + +describe('DELETE /api/v1/databases/{uuid}/envs/{env_uuid}', function () { + test('deletes an environment variable', function () { + $database = createDatabase($this); + + $env = EnvironmentVariable::create([ + 'key' => 'DELETE_ME', + 'value' => 'to_delete', + 'resourceable_type' => StandalonePostgresql::class, + 'resourceable_id' => $database->id, + ]); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->deleteJson("/api/v1/databases/{$database->uuid}/envs/{$env->uuid}"); + + $response->assertStatus(200); + $response->assertJson(['message' => 'Environment variable deleted.']); + + expect(EnvironmentVariable::where('uuid', $env->uuid)->first())->toBeNull(); + }); + + test('returns 404 for non-existent environment variable', function () { + $database = createDatabase($this); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->deleteJson("/api/v1/databases/{$database->uuid}/envs/non-existent-uuid"); + + $response->assertStatus(404); + }); +}); From 65ed407ec82389408fb4f4b210c38eb9f08890fe Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 19 Mar 2026 23:42:11 +0100 Subject: [PATCH 087/602] fix(deployment): disable build server during restart operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The just_restart() method doesn't need the build server—disabling it ensures the helper container is created on the deployment server with the correct network configuration and flags. The build server setting is restored before should_skip_build() is called in case it triggers a full rebuild that requires it. --- app/Jobs/ApplicationDeploymentJob.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 7adb938c5..7075fd8a3 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1103,10 +1103,21 @@ private function generate_image_names() private function just_restart() { $this->application_deployment_queue->addLogEntry("Restarting {$this->customRepository}:{$this->application->git_branch} on {$this->server->name}."); + + // Restart doesn't need the build server — disable it so the helper container + // is created on the deployment server with the correct network/flags. + $originalUseBuildServer = $this->use_build_server; + $this->use_build_server = false; + $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->generate_image_names(); $this->check_image_locally_or_remotely(); + + // Restore before should_skip_build() — it may re-enter decide_what_to_do() + // for a full rebuild which needs the build server. + $this->use_build_server = $originalUseBuildServer; + $this->should_skip_build(); $this->completeDeployment(); } From a7f07f66e3adf808f95c4ab5eed320bd640df462 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:50:11 +0000 Subject: [PATCH 088/602] build(deps): bump league/commonmark from 2.8.1 to 2.8.2 Bumps [league/commonmark](https://github.com/thephpleague/commonmark) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/thephpleague/commonmark/releases) - [Changelog](https://github.com/thephpleague/commonmark/blob/2.8/CHANGELOG.md) - [Commits](https://github.com/thephpleague/commonmark/compare/2.8.1...2.8.2) --- updated-dependencies: - dependency-name: league/commonmark dependency-version: 2.8.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 993835a42..534b418fe 100644 --- a/composer.lock +++ b/composer.lock @@ -2663,16 +2663,16 @@ }, { "name": "league/commonmark", - "version": "2.8.1", + "version": "2.8.2", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "84b1ca48347efdbe775426f108622a42735a6579" + "reference": "59fb075d2101740c337c7216e3f32b36c204218b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579", - "reference": "84b1ca48347efdbe775426f108622a42735a6579", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b", "shasum": "" }, "require": { @@ -2766,7 +2766,7 @@ "type": "tidelift" } ], - "time": "2026-03-05T21:37:03+00:00" + "time": "2026-03-19T13:16:38+00:00" }, { "name": "league/config", From e65ad22b42133b1941ffd75ecbbb9f19b6de972f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:02:18 +0100 Subject: [PATCH 089/602] refactor(breadcrumb): optimize queries and simplify state management - Add column selection to breadcrumb queries for better performance - Remove unused Alpine.js state (activeRes, activeMenuEnv, resPositions, menuPositions) - Simplify dropdown logic by removing duplicate state handling in index view - Change database relationship eager loading to use explicit column selection --- app/Livewire/Project/Resource/Index.php | 127 +++-- .../resources/breadcrumbs.blade.php | 531 ++---------------- .../livewire/project/resource/index.blade.php | 331 ++--------- 3 files changed, 167 insertions(+), 822 deletions(-) diff --git a/app/Livewire/Project/Resource/Index.php b/app/Livewire/Project/Resource/Index.php index be6e3e98f..094b61b28 100644 --- a/app/Livewire/Project/Resource/Index.php +++ b/app/Livewire/Project/Resource/Index.php @@ -13,33 +13,33 @@ class Index extends Component public Environment $environment; - public Collection $applications; - - public Collection $postgresqls; - - public Collection $redis; - - public Collection $mongodbs; - - public Collection $mysqls; - - public Collection $mariadbs; - - public Collection $keydbs; - - public Collection $dragonflies; - - public Collection $clickhouses; - - public Collection $services; - public Collection $allProjects; public Collection $allEnvironments; public array $parameters; - public function mount() + protected Collection $applications; + + protected Collection $postgresqls; + + protected Collection $redis; + + protected Collection $mongodbs; + + protected Collection $mysqls; + + protected Collection $mariadbs; + + protected Collection $keydbs; + + protected Collection $dragonflies; + + protected Collection $clickhouses; + + protected Collection $services; + + public function mount(): void { $this->applications = $this->postgresqls = $this->redis = $this->mongodbs = $this->mysqls = $this->mariadbs = $this->keydbs = $this->dragonflies = $this->clickhouses = $this->services = collect(); $this->parameters = get_route_parameters(); @@ -55,31 +55,23 @@ public function mount() $this->project = $project; - // Load projects and environments for breadcrumb navigation (avoids inline queries in view) + // Load projects and environments for breadcrumb navigation $this->allProjects = Project::ownedByCurrentTeamCached(); $this->allEnvironments = $project->environments() + ->select('id', 'uuid', 'name', 'project_id') ->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(); + 'applications:id,uuid,name,environment_id', + 'services:id,uuid,name,environment_id', + 'postgresqls:id,uuid,name,environment_id', + 'redis:id,uuid,name,environment_id', + 'mongodbs:id,uuid,name,environment_id', + 'mysqls:id,uuid,name,environment_id', + 'mariadbs:id,uuid,name,environment_id', + 'keydbs:id,uuid,name,environment_id', + 'dragonflies:id,uuid,name,environment_id', + 'clickhouses:id,uuid,name,environment_id', + ]) + ->get(); $this->environment = $environment->loadCount([ 'applications', @@ -94,11 +86,9 @@ public function mount() 'services', ]); - // Eager load all relationships for applications including nested ones + // Eager load relationships for applications $this->applications = $this->environment->applications()->with([ 'tags', - 'additional_servers.settings', - 'additional_networks', 'destination.server.settings', 'settings', ])->get()->sortBy('name'); @@ -160,6 +150,49 @@ public function mount() public function render() { - return view('livewire.project.resource.index'); + return view('livewire.project.resource.index', [ + 'applications' => $this->applications, + 'postgresqls' => $this->postgresqls, + 'redis' => $this->redis, + 'mongodbs' => $this->mongodbs, + 'mysqls' => $this->mysqls, + 'mariadbs' => $this->mariadbs, + 'keydbs' => $this->keydbs, + 'dragonflies' => $this->dragonflies, + 'clickhouses' => $this->clickhouses, + 'services' => $this->services, + 'applicationsJs' => $this->toSearchableArray($this->applications), + 'postgresqlsJs' => $this->toSearchableArray($this->postgresqls), + 'redisJs' => $this->toSearchableArray($this->redis), + 'mongodbsJs' => $this->toSearchableArray($this->mongodbs), + 'mysqlsJs' => $this->toSearchableArray($this->mysqls), + 'mariadbsJs' => $this->toSearchableArray($this->mariadbs), + 'keydbsJs' => $this->toSearchableArray($this->keydbs), + 'dragonfliesJs' => $this->toSearchableArray($this->dragonflies), + 'clickhousesJs' => $this->toSearchableArray($this->clickhouses), + 'servicesJs' => $this->toSearchableArray($this->services), + ]); + } + + private function toSearchableArray(Collection $items): array + { + return $items->map(fn ($item) => [ + 'uuid' => $item->uuid, + 'name' => $item->name, + 'fqdn' => $item->fqdn ?? null, + 'description' => $item->description ?? null, + 'status' => $item->status ?? '', + 'server_status' => $item->server_status ?? null, + 'hrefLink' => $item->hrefLink ?? '', + 'destination' => [ + 'server' => [ + 'name' => $item->destination?->server?->name ?? 'Unknown', + ], + ], + 'tags' => $item->tags->map(fn ($tag) => [ + 'id' => $tag->id, + 'name' => $tag->name, + ])->values()->toArray(), + ])->values()->toArray(); } } diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php index 135cad3a7..300a8d6e2 100644 --- a/resources/views/components/resources/breadcrumbs.blade.php +++ b/resources/views/components/resources/breadcrumbs.blade.php @@ -12,17 +12,18 @@ $projects = $projects ?? Project::ownedByCurrentTeamCached(); $environments = $environments ?? $resource->environment->project ->environments() + ->select('id', 'uuid', 'name', 'project_id') ->with([ - 'applications', - 'services', - 'postgresqls', - 'redis', - 'mongodbs', - 'mysqls', - 'mariadbs', - 'keydbs', - 'dragonflies', - 'clickhouses', + 'applications:id,uuid,name,environment_id', + 'services:id,uuid,name,environment_id', + 'postgresqls:id,uuid,name,environment_id', + 'redis:id,uuid,name,environment_id', + 'mongodbs:id,uuid,name,environment_id', + 'mysqls:id,uuid,name,environment_id', + 'mariadbs:id,uuid,name,environment_id', + 'keydbs:id,uuid,name,environment_id', + 'dragonflies:id,uuid,name,environment_id', + 'clickhouses:id,uuid,name,environment_id', ]) ->get(); $currentProjectUuid = data_get($resource, 'environment.project.uuid'); @@ -63,7 +64,7 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg -
  • +
  • + x-transition:leave-end="opacity-0 scale-95" + class="absolute z-20 top-full mt-1 left-0 sm:left-auto max-w-[calc(100vw-1rem)]" + x-init="$nextTick(() => { const rect = $el.getBoundingClientRect(); if (rect.right > window.innerWidth) { $el.style.left = 'auto'; $el.style.right = '0'; } })">
    @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()) @@ -101,26 +103,17 @@ class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 bor ->merge($environment->dragonflies ?? collect()) ->merge($environment->clickhouses ?? collect()); $envResources = collect() - ->merge( - $environment->applications->map( - fn($app) => ['type' => 'application', 'resource' => $app], - ), - ) - ->merge( - $envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db]), - ) - ->merge( - $environment->services->map( - fn($svc) => ['type' => 'service', 'resource' => $svc], - ), - ); + ->merge($environment->applications->map(fn($app) => ['type' => 'application', 'resource' => $app])) + ->merge($envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db])) + ->merge($environment->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc])) + ->sortBy(fn($item) => strtolower($item['resource']->name)); @endphp
    $environment->uuid, + 'project_uuid' => $currentProjectUuid, + ]) }}" {{ wireNavigate() }} class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200 {{ $environment->uuid === $currentEnvironmentUuid ? 'dark:text-warning font-semibold' : '' }}" title="{{ $environment->name }}"> {{ $environment->name }} @@ -150,31 +143,29 @@ class="flex items-center gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover @foreach ($environments as $environment) @php + $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( - fn($app) => ['type' => 'application', 'resource' => $app], - ), - ) - ->merge( - $environment - ->databases() - ->map(fn($db) => ['type' => 'database', 'resource' => $db]), - ) - ->merge( - $environment->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc]), - ); + ->merge($environment->applications->map(fn($app) => ['type' => 'application', 'resource' => $app])) + ->merge($envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db])) + ->merge($environment->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc])); @endphp @if ($envResources->count() > 0)
    - - - @foreach ($envResources as $envResource) - @php - $resType = $envResource['type']; - $res = $envResource['resource']; - $resParams = [ - 'project_uuid' => $currentProjectUuid, - 'environment_uuid' => $environment->uuid, - ]; - if ($resType === 'application') { - $resParams['application_uuid'] = $res->uuid; - } elseif ($resType === 'service') { - $resParams['service_uuid'] = $res->uuid; - } else { - $resParams['database_uuid'] = $res->uuid; - } - $resKey = $environment->uuid . '-' . $res->uuid; - @endphp -
    - -
    - @if ($resType === 'application') - - Deployments - Logs - @can('canAccessTerminal') - Terminal - @endcan - @elseif ($resType === 'service') - - Logs - @can('canAccessTerminal') - Terminal - @endcan - @else - - Logs - @can('canAccessTerminal') - Terminal - @endcan - @if ( - $res->getMorphClass() === 'App\Models\StandalonePostgresql' || - $res->getMorphClass() === 'App\Models\StandaloneMongodb' || - $res->getMorphClass() === 'App\Models\StandaloneMysql' || - $res->getMorphClass() === 'App\Models\StandaloneMariadb') - Backups - @endif - @endif -
    - - - -
    - @endforeach
    @endif @endforeach @@ -431,7 +210,6 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg $isApplication = $resourceType === 'App\Models\Application'; $isService = $resourceType === 'App\Models\Service'; $isDatabase = str_contains($resourceType, 'Database') || str_contains($resourceType, 'Standalone'); - // Use loaded relation count if available, otherwise check additional_servers_count attribute $hasMultipleServers = $isApplication && method_exists($resource, 'additional_servers') && ($resource->relationLoaded('additional_servers') ? $resource->additional_servers->count() > 0 : ($resource->additional_servers_count ?? 0) > 0); $serverName = $hasMultipleServers ? null : data_get($resource, 'destination.server.name'); @@ -447,221 +225,16 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg $routeParams['database_uuid'] = $resourceUuid; } @endphp -
  • -
    - - {{ data_get($resource, 'name') }}@if($serverName) ({{ $serverName }})@endif - - - - -
    - -
    - @if ($isApplication) - - - - Deployments - - - Logs - - @can('canAccessTerminal') - - Terminal - - @endcan - @elseif ($isService) - - - - Logs - - @can('canAccessTerminal') - - Terminal - - @endcan - @else - - - - Logs - - @can('canAccessTerminal') - - Terminal - - @endcan - @if ( - $resourceType === 'App\Models\StandalonePostgresql' || - $resourceType === 'App\Models\StandaloneMongodb' || - $resourceType === 'App\Models\StandaloneMysql' || - $resourceType === 'App\Models\StandaloneMariadb') - - Backups - - @endif - @endif -
    - - - -
    -
    +
  • + + {{ data_get($resource, 'name') }}@if($serverName) ({{ $serverName }})@endif +
  • diff --git a/resources/views/livewire/project/resource/index.blade.php b/resources/views/livewire/project/resource/index.blade.php index f18df5061..a38a04043 100644 --- a/resources/views/livewire/project/resource/index.blade.php +++ b/resources/views/livewire/project/resource/index.blade.php @@ -60,36 +60,13 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg
    -
  • +
  • {{ $environment->name }} - -
    @foreach ($allEnvironments as $env) @php + $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( - fn($app) => ['type' => 'application', 'resource' => $app], - ), - ) - ->merge( - $env - ->databases() - ->map(fn($db) => ['type' => 'database', 'resource' => $db]), - ) - ->merge( - $env->services->map( - fn($svc) => ['type' => 'service', 'resource' => $svc], - ), - ); + ->merge($env->applications->map(fn($app) => ['type' => 'application', 'resource' => $app])) + ->merge($envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db])) + ->merge($env->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc])) + ->sortBy(fn($item) => strtolower($item['resource']->name)); @endphp
    {{ $env->name }} @@ -153,7 +129,6 @@ class="flex items-center gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover @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()) @@ -164,28 +139,19 @@ class="flex items-center gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover ->merge($env->dragonflies ?? collect()) ->merge($env->clickhouses ?? collect()); $envResources = collect() - ->merge( - $env->applications->map( - fn($app) => ['type' => 'application', 'resource' => $app], - ), - ) - ->merge( - $envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db]), - ) - ->merge( - $env->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc]), - ); + ->merge($env->applications->map(fn($app) => ['type' => 'application', 'resource' => $app])) + ->merge($envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db])) + ->merge($env->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc])); @endphp @if ($envResources->count() > 0)
    - - - @foreach ($envResources as $envResource) - @php - $resType = $envResource['type']; - $res = $envResource['resource']; - $resParams = [ - 'project_uuid' => $project->uuid, - 'environment_uuid' => $env->uuid, - ]; - if ($resType === 'application') { - $resParams['application_uuid'] = $res->uuid; - } elseif ($resType === 'service') { - $resParams['service_uuid'] = $res->uuid; - } else { - $resParams['database_uuid'] = $res->uuid; - } - $resKey = $env->uuid . '-' . $res->uuid; - @endphp -
    - -
    - @if ($resType === 'application') - - Deployments - Logs - @can('canAccessTerminal') - Terminal - @endcan - @elseif ($resType === 'service') - - Logs - @can('canAccessTerminal') - Terminal - @endcan - @else - - Logs - @can('canAccessTerminal') - Terminal - @endcan - @if ( - $res->getMorphClass() === 'App\Models\StandalonePostgresql' || - $res->getMorphClass() === 'App\Models\StandaloneMongodb' || - $res->getMorphClass() === 'App\Models\StandaloneMysql' || - $res->getMorphClass() === 'App\Models\StandaloneMariadb') - Backups - @endif - @endif -
    - - - -
    - @endforeach
    @endif @endforeach @@ -656,16 +395,16 @@ function sortFn(a, b) { function searchComponent() { return { search: '', - applications: @js($applications), - postgresqls: @js($postgresqls), - redis: @js($redis), - mongodbs: @js($mongodbs), - mysqls: @js($mysqls), - mariadbs: @js($mariadbs), - keydbs: @js($keydbs), - dragonflies: @js($dragonflies), - clickhouses: @js($clickhouses), - services: @js($services), + applications: @js($applicationsJs), + postgresqls: @js($postgresqlsJs), + redis: @js($redisJs), + mongodbs: @js($mongodbsJs), + mysqls: @js($mysqlsJs), + mariadbs: @js($mariadbsJs), + keydbs: @js($keydbsJs), + dragonflies: @js($dragonfliesJs), + clickhouses: @js($clickhousesJs), + services: @js($servicesJs), filterAndSort(items) { if (this.search === '') { return Object.values(items).sort(sortFn); From 6aa618e57fe031b5b0b5834e6b711f94cf2d54d7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:44:10 +0100 Subject: [PATCH 090/602] feat(jobs): add cache-based deduplication for delayed cron execution Implements getPreviousRunDate() + cache-based tracking in shouldRunNow() to prevent duplicate dispatch of scheduled jobs when queue delays push execution past the cron minute. This resilience ensures jobs catch missed windows without double-dispatching within the same cron window. Updated scheduled job dispatches to include dedupKey parameter: - Docker cleanup operations - Server connection checks - Sentinel restart checks - Server storage checks - Server patch checks DockerCleanupJob now dispatches on the 'high' queue for faster processing. Includes comprehensive test coverage for dedup behavior across different cron schedules and delay scenarios. --- app/Jobs/DockerCleanupJob.php | 4 +- app/Jobs/ScheduledJobManager.php | 4 +- app/Jobs/ServerManagerJob.php | 45 ++++++-- .../ScheduledJobManagerShouldRunNowTest.php | 49 +++++++++ .../ServerManagerJobShouldRunNowTest.php | 102 ++++++++++++++++++ 5 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 tests/Feature/ServerManagerJobShouldRunNowTest.php diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 78ef7f3a2..a8a3cb159 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -39,7 +39,9 @@ public function __construct( public bool $manualCleanup = false, public bool $deleteUnusedVolumes = false, public bool $deleteUnusedNetworks = false - ) {} + ) { + $this->onQueue('high'); + } public function handle(): void { diff --git a/app/Jobs/ScheduledJobManager.php b/app/Jobs/ScheduledJobManager.php index e68e3b613..ebcd229ed 100644 --- a/app/Jobs/ScheduledJobManager.php +++ b/app/Jobs/ScheduledJobManager.php @@ -350,7 +350,7 @@ private function shouldRunNow(string $frequency, string $timezone, ?string $dedu $baseTime = $this->executionTime ?? Carbon::now(); $executionTime = $baseTime->copy()->setTimezone($timezone); - // No dedup key → simple isDue check (used by docker cleanups) + // No dedup key → simple isDue check if ($dedupKey === null) { return $cron->isDue($executionTime); } @@ -411,7 +411,7 @@ private function processDockerCleanups(): void } // Use the frozen execution time for consistent evaluation - if ($this->shouldRunNow($frequency, $serverTimezone)) { + if ($this->shouldRunNow($frequency, $serverTimezone, "docker-cleanup:{$server->id}")) { DockerCleanupJob::dispatch( $server, false, diff --git a/app/Jobs/ServerManagerJob.php b/app/Jobs/ServerManagerJob.php index 730ce547d..d56ff0a8c 100644 --- a/app/Jobs/ServerManagerJob.php +++ b/app/Jobs/ServerManagerJob.php @@ -14,6 +14,7 @@ use Illuminate\Queue\SerializesModels; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; class ServerManagerJob implements ShouldBeEncrypted, ShouldQueue @@ -80,7 +81,7 @@ private function getServers(): Collection private function dispatchConnectionChecks(Collection $servers): void { - if ($this->shouldRunNow($this->checkFrequency)) { + if ($this->shouldRunNow($this->checkFrequency, dedupKey: 'server-connection-checks')) { $servers->each(function (Server $server) { try { // Skip SSH connection check if Sentinel is healthy — its heartbeat already proves connectivity @@ -129,13 +130,13 @@ private function processServerTasks(Server $server): void if ($sentinelOutOfSync) { // Dispatch ServerCheckJob if Sentinel is out of sync - if ($this->shouldRunNow($this->checkFrequency, $serverTimezone)) { + if ($this->shouldRunNow($this->checkFrequency, $serverTimezone, "server-check:{$server->id}")) { ServerCheckJob::dispatch($server); } } $isSentinelEnabled = $server->isSentinelEnabled(); - $shouldRestartSentinel = $isSentinelEnabled && $this->shouldRunNow('0 0 * * *', $serverTimezone); + $shouldRestartSentinel = $isSentinelEnabled && $this->shouldRunNow('0 0 * * *', $serverTimezone, "sentinel-restart:{$server->id}"); // Dispatch Sentinel restart if due (daily for Sentinel-enabled servers) if ($shouldRestartSentinel) { @@ -149,7 +150,7 @@ private function processServerTasks(Server $server): void if (isset(VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency])) { $serverDiskUsageCheckFrequency = VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency]; } - $shouldRunStorageCheck = $this->shouldRunNow($serverDiskUsageCheckFrequency, $serverTimezone); + $shouldRunStorageCheck = $this->shouldRunNow($serverDiskUsageCheckFrequency, $serverTimezone, "server-storage-check:{$server->id}"); if ($shouldRunStorageCheck) { ServerStorageCheckJob::dispatch($server); @@ -157,7 +158,7 @@ private function processServerTasks(Server $server): void } // Dispatch ServerPatchCheckJob if due (weekly) - $shouldRunPatchCheck = $this->shouldRunNow('0 0 * * 0', $serverTimezone); + $shouldRunPatchCheck = $this->shouldRunNow('0 0 * * 0', $serverTimezone, "server-patch-check:{$server->id}"); if ($shouldRunPatchCheck) { // Weekly on Sunday at midnight ServerPatchCheckJob::dispatch($server); @@ -167,7 +168,14 @@ private function processServerTasks(Server $server): void // Crash recovery is handled by sentinelOutOfSync → ServerCheckJob → CheckAndStartSentinelJob. } - private function shouldRunNow(string $frequency, ?string $timezone = null): bool + /** + * Determine if a cron schedule should run now. + * + * When a dedupKey is provided, uses getPreviousRunDate() + last-dispatch tracking + * instead of isDue(). This is resilient to queue delays — even if the job is delayed + * by minutes, it still catches the missed cron window. + */ + private function shouldRunNow(string $frequency, ?string $timezone = null, ?string $dedupKey = null): bool { $cron = new CronExpression($frequency); @@ -175,6 +183,29 @@ private function shouldRunNow(string $frequency, ?string $timezone = null): bool $baseTime = $this->executionTime ?? Carbon::now(); $executionTime = $baseTime->copy()->setTimezone($timezone ?? config('app.timezone')); - return $cron->isDue($executionTime); + if ($dedupKey === null) { + return $cron->isDue($executionTime); + } + + $previousDue = Carbon::instance($cron->getPreviousRunDate($executionTime, allowCurrentDate: true)); + + $lastDispatched = Cache::get($dedupKey); + + if ($lastDispatched === null) { + $isDue = $cron->isDue($executionTime); + if ($isDue) { + Cache::put($dedupKey, $executionTime->toIso8601String(), 86400); + } + + return $isDue; + } + + if ($previousDue->gt(Carbon::parse($lastDispatched))) { + Cache::put($dedupKey, $executionTime->toIso8601String(), 86400); + + return true; + } + + return false; } } diff --git a/tests/Feature/ScheduledJobManagerShouldRunNowTest.php b/tests/Feature/ScheduledJobManagerShouldRunNowTest.php index f820c3777..e445e9908 100644 --- a/tests/Feature/ScheduledJobManagerShouldRunNowTest.php +++ b/tests/Feature/ScheduledJobManagerShouldRunNowTest.php @@ -194,6 +194,55 @@ expect($result2)->toBeFalse(); }); +it('catches delayed docker cleanup when job runs past the cron minute', function () { + // Simulate a previous dispatch at :10 + Cache::put('docker-cleanup:42', Carbon::create(2026, 2, 28, 10, 10, 0, 'UTC')->toIso8601String(), 86400); + + // Freeze time at :22 — job was delayed 2 minutes past the :20 cron window + Carbon::setTestNow(Carbon::create(2026, 2, 28, 10, 22, 0, 'UTC')); + + $job = new ScheduledJobManager; + $reflection = new ReflectionClass($job); + + $executionTimeProp = $reflection->getProperty('executionTime'); + $executionTimeProp->setAccessible(true); + $executionTimeProp->setValue($job, Carbon::now()); + + $method = $reflection->getMethod('shouldRunNow'); + $method->setAccessible(true); + + // isDue() would return false at :22, but getPreviousRunDate() = :20 + // lastDispatched = :10 → :20 > :10 → fires + $result = $method->invoke($job, '*/10 * * * *', 'UTC', 'docker-cleanup:42'); + + expect($result)->toBeTrue(); +}); + +it('does not double-dispatch docker cleanup within same cron window', function () { + // First dispatch at :10 + Carbon::setTestNow(Carbon::create(2026, 2, 28, 10, 10, 0, 'UTC')); + + $job = new ScheduledJobManager; + $reflection = new ReflectionClass($job); + + $executionTimeProp = $reflection->getProperty('executionTime'); + $executionTimeProp->setAccessible(true); + $executionTimeProp->setValue($job, Carbon::now()); + + $method = $reflection->getMethod('shouldRunNow'); + $method->setAccessible(true); + + $first = $method->invoke($job, '*/10 * * * *', 'UTC', 'docker-cleanup:99'); + expect($first)->toBeTrue(); + + // Second run at :11 — should NOT dispatch (previousDue=:10, lastDispatched=:10) + Carbon::setTestNow(Carbon::create(2026, 2, 28, 10, 11, 0, 'UTC')); + $executionTimeProp->setValue($job, Carbon::now()); + + $second = $method->invoke($job, '*/10 * * * *', 'UTC', 'docker-cleanup:99'); + expect($second)->toBeFalse(); +}); + it('respects server timezone for cron evaluation', function () { // UTC time is 22:00 Feb 28, which is 06:00 Mar 1 in Asia/Singapore (+8) Carbon::setTestNow(Carbon::create(2026, 2, 28, 22, 0, 0, 'UTC')); diff --git a/tests/Feature/ServerManagerJobShouldRunNowTest.php b/tests/Feature/ServerManagerJobShouldRunNowTest.php new file mode 100644 index 000000000..518f05c9c --- /dev/null +++ b/tests/Feature/ServerManagerJobShouldRunNowTest.php @@ -0,0 +1,102 @@ +toIso8601String(), 86400); + + // Job runs 3 minutes late at 00:03 + Carbon::setTestNow(Carbon::create(2026, 2, 28, 0, 3, 0, 'UTC')); + + $job = new ServerManagerJob; + $reflection = new ReflectionClass($job); + + $executionTimeProp = $reflection->getProperty('executionTime'); + $executionTimeProp->setAccessible(true); + $executionTimeProp->setValue($job, Carbon::now()); + + $method = $reflection->getMethod('shouldRunNow'); + $method->setAccessible(true); + + // isDue() would return false at 00:03, but getPreviousRunDate() = 00:00 today + // lastDispatched = yesterday 00:00 → today 00:00 > yesterday → fires + $result = $method->invoke($job, '0 0 * * *', 'UTC', 'sentinel-restart:1'); + + expect($result)->toBeTrue(); +}); + +it('catches delayed weekly patch check when job runs past the cron minute', function () { + // Simulate previous dispatch last Sunday at midnight + Cache::put('server-patch-check:1', Carbon::create(2026, 2, 22, 0, 0, 0, 'UTC')->toIso8601String(), 86400); + + // This Sunday at 00:02 — job was delayed 2 minutes + // 2026-03-01 is a Sunday + Carbon::setTestNow(Carbon::create(2026, 3, 1, 0, 2, 0, 'UTC')); + + $job = new ServerManagerJob; + $reflection = new ReflectionClass($job); + + $executionTimeProp = $reflection->getProperty('executionTime'); + $executionTimeProp->setAccessible(true); + $executionTimeProp->setValue($job, Carbon::now()); + + $method = $reflection->getMethod('shouldRunNow'); + $method->setAccessible(true); + + $result = $method->invoke($job, '0 0 * * 0', 'UTC', 'server-patch-check:1'); + + expect($result)->toBeTrue(); +}); + +it('catches delayed storage check when job runs past the cron minute', function () { + // Simulate previous dispatch yesterday at 23:00 + Cache::put('server-storage-check:5', Carbon::create(2026, 2, 27, 23, 0, 0, 'UTC')->toIso8601String(), 86400); + + // Today at 23:04 — job was delayed 4 minutes + Carbon::setTestNow(Carbon::create(2026, 2, 28, 23, 4, 0, 'UTC')); + + $job = new ServerManagerJob; + $reflection = new ReflectionClass($job); + + $executionTimeProp = $reflection->getProperty('executionTime'); + $executionTimeProp->setAccessible(true); + $executionTimeProp->setValue($job, Carbon::now()); + + $method = $reflection->getMethod('shouldRunNow'); + $method->setAccessible(true); + + $result = $method->invoke($job, '0 23 * * *', 'UTC', 'server-storage-check:5'); + + expect($result)->toBeTrue(); +}); + +it('does not double-dispatch within same cron window', function () { + Carbon::setTestNow(Carbon::create(2026, 2, 28, 0, 0, 0, 'UTC')); + + $job = new ServerManagerJob; + $reflection = new ReflectionClass($job); + + $executionTimeProp = $reflection->getProperty('executionTime'); + $executionTimeProp->setAccessible(true); + $executionTimeProp->setValue($job, Carbon::now()); + + $method = $reflection->getMethod('shouldRunNow'); + $method->setAccessible(true); + + $first = $method->invoke($job, '0 0 * * *', 'UTC', 'sentinel-restart:10'); + expect($first)->toBeTrue(); + + // Next minute — should NOT dispatch again + Carbon::setTestNow(Carbon::create(2026, 2, 28, 0, 1, 0, 'UTC')); + $executionTimeProp->setValue($job, Carbon::now()); + + $second = $method->invoke($job, '0 0 * * *', 'UTC', 'sentinel-restart:10'); + expect($second)->toBeFalse(); +}); From fef8e0b622706b5d7bacbbecc696d9c9eb783cdd Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:57:26 +0100 Subject: [PATCH 091/602] refactor: remove verbose logging and use explicit exception types - Remove verbose warning/debug logs from ServerConnectionCheckJob and ContainerStatusAggregator - Silently ignore expected errors (e.g., deleted Hetzner servers) - Replace generic RuntimeException with DeploymentException for deployment command failures - Catch both RuntimeException and DeploymentException in command retry logic --- app/Jobs/ServerConnectionCheckJob.php | 11 ++--------- app/Services/ContainerStatusAggregator.php | 14 -------------- app/Traits/ExecuteRemoteCommand.php | 5 +++-- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/app/Jobs/ServerConnectionCheckJob.php b/app/Jobs/ServerConnectionCheckJob.php index d4a499865..2c73ae43e 100644 --- a/app/Jobs/ServerConnectionCheckJob.php +++ b/app/Jobs/ServerConnectionCheckJob.php @@ -108,10 +108,6 @@ public function handle() public function failed(?\Throwable $exception): void { if ($exception instanceof \Illuminate\Queue\TimeoutExceededException) { - Log::warning('ServerConnectionCheckJob timed out', [ - 'server_id' => $this->server->id, - 'server_name' => $this->server->name, - ]); $this->server->settings->update([ 'is_reachable' => false, 'is_usable' => false, @@ -131,11 +127,8 @@ private function checkHetznerStatus(): void $serverData = $hetznerService->getServer($this->server->hetzner_server_id); $status = $serverData['status'] ?? null; - } catch (\Throwable $e) { - Log::debug('ServerConnectionCheck: Hetzner status check failed', [ - 'server_id' => $this->server->id, - 'error' => $e->getMessage(), - ]); + } catch (\Throwable) { + // Silently ignore — server may have been deleted from Hetzner. } if ($this->server->hetzner_server_status !== $status) { $this->server->update(['hetzner_server_status' => $status]); diff --git a/app/Services/ContainerStatusAggregator.php b/app/Services/ContainerStatusAggregator.php index 2be36d905..8859a9980 100644 --- a/app/Services/ContainerStatusAggregator.php +++ b/app/Services/ContainerStatusAggregator.php @@ -54,13 +54,6 @@ public function aggregateFromStrings(Collection $containerStatuses, int $maxRest $maxRestartCount = 0; } - if ($maxRestartCount > 1000) { - Log::warning('High maxRestartCount detected', [ - 'maxRestartCount' => $maxRestartCount, - 'containers' => $containerStatuses->count(), - ]); - } - if ($containerStatuses->isEmpty()) { return 'exited'; } @@ -138,13 +131,6 @@ public function aggregateFromContainers(Collection $containers, int $maxRestartC $maxRestartCount = 0; } - if ($maxRestartCount > 1000) { - Log::warning('High maxRestartCount detected', [ - 'maxRestartCount' => $maxRestartCount, - 'containers' => $containers->count(), - ]); - } - if ($containers->isEmpty()) { return 'exited'; } diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php index a4ea6abe5..72e0adde8 100644 --- a/app/Traits/ExecuteRemoteCommand.php +++ b/app/Traits/ExecuteRemoteCommand.php @@ -3,6 +3,7 @@ namespace App\Traits; use App\Enums\ApplicationDeploymentStatus; +use App\Exceptions\DeploymentException; use App\Helpers\SshMultiplexingHelper; use App\Models\Server; use Carbon\Carbon; @@ -103,7 +104,7 @@ public function execute_remote_command(...$commands) try { $this->executeCommandWithProcess($command, $hidden, $customType, $append, $ignore_errors); $commandExecuted = true; - } catch (\RuntimeException $e) { + } catch (\RuntimeException|DeploymentException $e) { $lastError = $e; $errorMessage = $e->getMessage(); // Only retry if it's an SSH connection error and we haven't exhausted retries @@ -233,7 +234,7 @@ private function executeCommandWithProcess($command, $hidden, $customType, $appe $error = $process_result->output() ?: 'Command failed with no error output'; } $redactedCommand = $this->redact_sensitive_info($command); - throw new \RuntimeException("Command execution failed (exit code {$process_result->exitCode()}): {$redactedCommand}\nError: {$error}"); + throw new DeploymentException("Command execution failed (exit code {$process_result->exitCode()}): {$redactedCommand}\nError: {$error}"); } } } From 1511797e0a03a29349b1d71e9015ea00a713feff Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:59:23 +0100 Subject: [PATCH 092/602] fix(docker): skip cleanup stale warning on cloud instances --- resources/views/livewire/server/docker-cleanup.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/server/docker-cleanup.blade.php b/resources/views/livewire/server/docker-cleanup.blade.php index 2fd8fc2ab..ac48651ae 100644 --- a/resources/views/livewire/server/docker-cleanup.blade.php +++ b/resources/views/livewire/server/docker-cleanup.blade.php @@ -27,7 +27,7 @@
    Configure Docker cleanup settings for your server.
    - @if ($this->isCleanupStale) + @if (!isCloud() && $this->isCleanupStale)

    The last Docker cleanup ran {{ $this->lastExecutionTime ?? 'unknown time' }} ago, From 949cce10978b874204d9ba0fcc274e6ddd06679d Mon Sep 17 00:00:00 2001 From: Xidik Date: Sun, 22 Mar 2026 22:02:13 +0700 Subject: [PATCH 093/602] fix(service): allow overriding GOTRUE_SITE_URL in Supabase template The Supabase template hardcoded GOTRUE_SITE_URL to the internal Supabase Kong URL, which caused OAuth redirects to go to the Supabase API domain instead of the user's frontend domain. This broke Google OAuth, magic links, and other redirect-based auth flows. Allow users to set GOTRUE_SITE_URL in the Coolify UI to their frontend domain while keeping the Supabase URL as a sensible default. Fixes #5581 --- templates/compose/supabase.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/supabase.yaml b/templates/compose/supabase.yaml index fad059a08..496bade26 100644 --- a/templates/compose/supabase.yaml +++ b/templates/compose/supabase.yaml @@ -975,7 +975,7 @@ services: - GOTRUE_DB_DRIVER=postgres - GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} # The base URL your site is located at. Currently used in combination with other settings to construct URLs used in emails. - - GOTRUE_SITE_URL=${SERVICE_URL_SUPABASEKONG} + - GOTRUE_SITE_URL=${GOTRUE_SITE_URL:-${SERVICE_URL_SUPABASEKONG}} # A comma separated list of URIs (e.g. "https://foo.example.com,https://*.foo.example.com,https://bar.example.com") which are permitted as valid redirect_to destinations. - GOTRUE_URI_ALLOW_LIST=${ADDITIONAL_REDIRECT_URLS} - GOTRUE_DISABLE_SIGNUP=${DISABLE_SIGNUP:-false} From 95351eba8939d321e26139fa0636693730fe1e69 Mon Sep 17 00:00:00 2001 From: Xidik Date: Sun, 22 Mar 2026 22:04:22 +0700 Subject: [PATCH 094/602] fix(service): use FQDN instead of URL for Grafana GF_SERVER_DOMAIN GF_SERVER_DOMAIN expects a bare hostname (e.g. grafana.example.com) but was set to SERVICE_URL_GRAFANA which includes the protocol (https://grafana.example.com). This mismatch can cause Grafana to fail to load its application files when deployed behind Coolify's proxy. Changed to SERVICE_FQDN_GRAFANA which provides just the hostname. Applied the fix to both grafana.yaml and grafana-with-postgresql.yaml templates. Fixes #5307 --- templates/compose/grafana-with-postgresql.yaml | 2 +- templates/compose/grafana.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/grafana-with-postgresql.yaml b/templates/compose/grafana-with-postgresql.yaml index 25add4cc2..6c5dda659 100644 --- a/templates/compose/grafana-with-postgresql.yaml +++ b/templates/compose/grafana-with-postgresql.yaml @@ -11,7 +11,7 @@ services: environment: - SERVICE_URL_GRAFANA_3000 - GF_SERVER_ROOT_URL=${SERVICE_URL_GRAFANA} - - GF_SERVER_DOMAIN=${SERVICE_URL_GRAFANA} + - GF_SERVER_DOMAIN=${SERVICE_FQDN_GRAFANA} - GF_SECURITY_ADMIN_PASSWORD=${SERVICE_PASSWORD_GRAFANA} - GF_DATABASE_TYPE=postgres - GF_DATABASE_HOST=postgresql diff --git a/templates/compose/grafana.yaml b/templates/compose/grafana.yaml index a570c6c79..ed1689f58 100644 --- a/templates/compose/grafana.yaml +++ b/templates/compose/grafana.yaml @@ -11,7 +11,7 @@ services: environment: - SERVICE_URL_GRAFANA_3000 - GF_SERVER_ROOT_URL=${SERVICE_URL_GRAFANA} - - GF_SERVER_DOMAIN=${SERVICE_URL_GRAFANA} + - GF_SERVER_DOMAIN=${SERVICE_FQDN_GRAFANA} - GF_SECURITY_ADMIN_PASSWORD=${SERVICE_PASSWORD_GRAFANA} volumes: - grafana-data:/var/lib/grafana From 9ca65313eab1a6391b9b30a8d4a3cbf83b407e61 Mon Sep 17 00:00:00 2001 From: Xidik Date: Sun, 22 Mar 2026 22:06:48 +0700 Subject: [PATCH 095/602] fix(service): add CORS defaults to Directus templates The Directus service templates were missing CORS configuration, causing preflight OPTIONS requests to fail when connecting from frontend apps. Users had to manually edit the compose file to add CORS variables. Add sensible CORS defaults (enabled with dynamic origin matching) to both directus.yaml and directus-with-postgresql.yaml templates. All values are user-overridable via the Coolify UI. Fixes #5024 --- templates/compose/directus-with-postgresql.yaml | 5 +++++ templates/compose/directus.yaml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/templates/compose/directus-with-postgresql.yaml b/templates/compose/directus-with-postgresql.yaml index c35e411fd..3aaa8f139 100644 --- a/templates/compose/directus-with-postgresql.yaml +++ b/templates/compose/directus-with-postgresql.yaml @@ -27,6 +27,11 @@ services: - REDIS_HOST=redis - REDIS_PORT=6379 - WEBSOCKETS_ENABLED=true + - CORS_ENABLED=${CORS_ENABLED:-true} + - CORS_ORIGIN=${CORS_ORIGIN:-true} + - CORS_METHODS=${CORS_METHODS:-GET,POST,PATCH,DELETE,OPTIONS} + - CORS_ALLOWED_HEADERS=${CORS_ALLOWED_HEADERS:-Content-Type,Authorization} + - CORS_CREDENTIALS=${CORS_CREDENTIALS:-true} healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8055/admin/login"] interval: 5s diff --git a/templates/compose/directus.yaml b/templates/compose/directus.yaml index 36589c72a..5c02ab1a3 100644 --- a/templates/compose/directus.yaml +++ b/templates/compose/directus.yaml @@ -22,6 +22,11 @@ services: - DB_CLIENT=sqlite3 - DB_FILENAME=/directus/database/data.db - WEBSOCKETS_ENABLED=true + - CORS_ENABLED=${CORS_ENABLED:-true} + - CORS_ORIGIN=${CORS_ORIGIN:-true} + - CORS_METHODS=${CORS_METHODS:-GET,POST,PATCH,DELETE,OPTIONS} + - CORS_ALLOWED_HEADERS=${CORS_ALLOWED_HEADERS:-Content-Type,Authorization} + - CORS_CREDENTIALS=${CORS_CREDENTIALS:-true} healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8055/admin/login"] From 820ee1c03cbadcf5a5e269be7a37e8a71d5e2022 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:34:58 +0100 Subject: [PATCH 096/602] docs(sponsors): update Brand.dev to Context.dev --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b7aefe16a..73af2a18c 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ ### Big Sponsors * [Arcjet](https://arcjet.com?ref=coolify.io) - Advanced web security and performance solutions * [BC Direct](https://bc.direct?ref=coolify.io) - Your trusted technology consulting partner * [Blacksmith](https://blacksmith.sh?ref=coolify.io) - Infrastructure automation platform -* [Brand.dev](https://brand.dev?ref=coolify.io) - API to personalize your product with logos, colors, and company info from any domain +* [Context.dev](https://context.dev?ref=coolify.io) - API to personalize your product with logos, colors, and company info from any domain * [ByteBase](https://www.bytebase.com?ref=coolify.io) - Database CI/CD and Security at Scale * [CodeRabbit](https://coderabbit.ai?ref=coolify.io) - Cut Code Review Time & Bugs in Half * [COMIT](https://comit.international?ref=coolify.io) - New York Times award–winning contractor From 069bf4cc82c1d534d0ef0df3d3d4679c1f827a36 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:35:16 +0100 Subject: [PATCH 097/602] chore(versions): bump coolify, sentinel, and traefik versions --- other/nightly/versions.json | 8 ++++---- versions.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 7564f625e..57bb21869 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,7 +1,7 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.469" + "version": "4.0.0-beta.470" }, "nightly": { "version": "4.0.0" @@ -13,17 +13,17 @@ "version": "1.0.11" }, "sentinel": { - "version": "0.0.19" + "version": "0.0.20" } }, "traefik": { - "v3.6": "3.6.5", + "v3.6": "3.6.11", "v3.5": "3.5.6", "v3.4": "3.4.5", "v3.3": "3.3.7", "v3.2": "3.2.5", "v3.1": "3.1.7", "v3.0": "3.0.4", - "v2.11": "2.11.32" + "v2.11": "2.11.40" } } diff --git a/versions.json b/versions.json index 7564f625e..57bb21869 100644 --- a/versions.json +++ b/versions.json @@ -1,7 +1,7 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.469" + "version": "4.0.0-beta.470" }, "nightly": { "version": "4.0.0" @@ -13,17 +13,17 @@ "version": "1.0.11" }, "sentinel": { - "version": "0.0.19" + "version": "0.0.20" } }, "traefik": { - "v3.6": "3.6.5", + "v3.6": "3.6.11", "v3.5": "3.5.6", "v3.4": "3.4.5", "v3.3": "3.3.7", "v3.2": "3.2.5", "v3.1": "3.1.7", "v3.0": "3.0.4", - "v2.11": "2.11.32" + "v2.11": "2.11.40" } } From f0ed05b399bdfa9697423f4cfbab5b8722529519 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:35:47 +0100 Subject: [PATCH 098/602] fix(docker): log failed cleanup attempts when server is not functional --- app/Jobs/DockerCleanupJob.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index a8a3cb159..16f3d88ad 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -46,14 +46,20 @@ public function __construct( public function handle(): void { try { - if (! $this->server->isFunctional()) { - return; - } - $this->execution_log = DockerCleanupExecution::create([ 'server_id' => $this->server->id, ]); + if (! $this->server->isFunctional()) { + $this->execution_log->update([ + 'status' => 'failed', + 'message' => 'Server is not functional (unreachable, unusable, or disabled)', + 'finished_at' => Carbon::now()->toImmutable(), + ]); + + return; + } + $this->usageBefore = $this->server->getDiskUsage(); if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) { From 89f2b83104cce1b4117b28539c5abb57d9b3522d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:36:08 +0100 Subject: [PATCH 099/602] style(modal-confirmation): improve mobile responsiveness Make modal full-screen on mobile devices with responsive padding, border radius, and dimensions. Modal is now full-screen on small screens and constrained to max-width/max-height on larger screens. --- resources/views/components/modal-confirmation.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index 615512b94..c1e8a3e54 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -190,7 +190,7 @@ class="relative w-auto h-auto"> @endif