From 678b264688f7a111fa9c3a694c063bef9d400c8a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 24 Oct 2023 11:08:05 +0200 Subject: [PATCH 1/7] fix: make sure coolfiy network exists on install --- docker-compose.yml | 1 + scripts/upgrade.sh | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index ecd3f8b7c..001b1b212 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,3 +28,4 @@ networks: coolify: name: coolify driver: bridge + external: true diff --git a/scripts/upgrade.sh b/scripts/upgrade.sh index 01405e873..bb3adcf35 100644 --- a/scripts/upgrade.sh +++ b/scripts/upgrade.sh @@ -15,4 +15,7 @@ curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production # Merge .env and .env.production. New values will be added to .env sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' > /data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env +# Make sure coolify network exists +docker network create coolify 2>/dev/null + docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate" From 393c334b129b4ad884b485265ada3e57916e3c5c Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 24 Oct 2023 11:08:11 +0200 Subject: [PATCH 2/7] version++ --- config/sentry.php | 2 +- config/version.php | 2 +- versions.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/sentry.php b/config/sentry.php index b8cd87fa3..25d04008a 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.99', + 'release' => '4.0.0-beta.100', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 63857a361..042813982 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ Date: Tue, 24 Oct 2023 11:08:15 +0200 Subject: [PATCH 3/7] fix: syncbunny command --- app/Console/Commands/SyncBunny.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/SyncBunny.php b/app/Console/Commands/SyncBunny.php index 509614f4c..f3ddaaffe 100644 --- a/app/Console/Commands/SyncBunny.php +++ b/app/Console/Commands/SyncBunny.php @@ -16,7 +16,7 @@ class SyncBunny extends Command * * @var string */ - protected $signature = 'sync:bunny {templates?} {release?}'; + protected $signature = 'sync:bunny {--templates} {--release}'; /** * The console command description. @@ -31,8 +31,8 @@ class SyncBunny extends Command public function handle() { $that = $this; - $only_template = $this->argument('templates'); - $only_version = $this->argument('release'); + $only_template = $this->option('templates'); + $only_version = $this->option('release'); $bunny_cdn = "https://cdn.coollabs.io"; $bunny_cdn_path = "coolify"; $bunny_cdn_storage_name = "coolcdn"; From c82e02218f4114eba035c1b3a451a3686e481f12 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 24 Oct 2023 11:08:59 +0200 Subject: [PATCH 4/7] version++ --- scripts/install.sh | 2 +- scripts/upgrade.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 9ea40c8c2..293c54e27 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -5,7 +5,7 @@ ## Always run "php artisan app:sync-to-bunny-cdn --env=secrets" or "scripts/run sync-bunny" if you update this file. ########### -VERSION="1.0.0" +VERSION="1.0.1" DOCKER_VERSION="24.0" CDN="https://cdn.coollabs.io/coolify" diff --git a/scripts/upgrade.sh b/scripts/upgrade.sh index bb3adcf35..3c44308a0 100644 --- a/scripts/upgrade.sh +++ b/scripts/upgrade.sh @@ -5,7 +5,7 @@ ## Always run "php artisan app:sync-to-bunny-cdn --env=secrets" if you update this file. ########### -VERSION="1.0.0" +VERSION="1.0.1" CDN="https://cdn.coollabs.io/coolify" curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml From b2d111e49a7ab06d414bd8e8d2e4395d1874f12f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 24 Oct 2023 12:33:49 +0200 Subject: [PATCH 5/7] feat: simple search functionality --- ...viceTemplates.php => ServicesGenerate.php} | 11 +- app/Http/Livewire/Project/New/Select.php | 39 +++-- .../livewire/project/new/select.blade.php | 11 +- templates/compose/appsmith.yaml | 1 + templates/compose/appwrite.yaml | 1 + templates/compose/babybuddy.yaml | 1 + templates/compose/code-server.yaml | 1 + templates/compose/dokuwiki.yaml | 1 + templates/compose/fider.yaml | 1 + templates/compose/ghost.yaml | 1 + templates/compose/heimdall.yaml | 1 + templates/compose/metube.yaml | 1 + templates/compose/minio.yaml | 1 + templates/compose/pairdrop.yaml | 1 + templates/compose/plausible.yaml | 1 + templates/compose/snapdrop.yaml | 1 + templates/compose/umami.yaml | 1 + templates/compose/uptime-kuma.yaml | 1 + templates/compose/wordpress-with-mariadb.yaml | 1 + templates/compose/wordpress-with-mysql.yaml | 1 + .../compose/wordpress-without-database.yaml | 1 + templates/service-templates.json | 140 ++++++++++++++++-- 22 files changed, 189 insertions(+), 30 deletions(-) rename app/Console/Commands/{GenerateServiceTemplates.php => ServicesGenerate.php} (88%) diff --git a/app/Console/Commands/GenerateServiceTemplates.php b/app/Console/Commands/ServicesGenerate.php similarity index 88% rename from app/Console/Commands/GenerateServiceTemplates.php rename to app/Console/Commands/ServicesGenerate.php index bf271e8ad..530dd259a 100644 --- a/app/Console/Commands/GenerateServiceTemplates.php +++ b/app/Console/Commands/ServicesGenerate.php @@ -5,7 +5,7 @@ namespace App\Console\Commands; use Illuminate\Console\Command; use Symfony\Component\Yaml\Yaml; -class GenerateServiceTemplates extends Command +class ServicesGenerate extends Command { /** * The name and signature of the console command. @@ -80,6 +80,14 @@ class GenerateServiceTemplates extends Command $env_file = null; } + $tags = collect(preg_grep('/^# tags:/', explode("\n", $content)))->values(); + if ($tags->count() > 0) { + $tags = str($tags[0])->after('# tags:')->trim()->explode(',')->map(function ($tag) { + return str($tag)->trim()->lower()->value(); + })->values(); + } else { + $tags = null; + } $json = Yaml::parse($content); $yaml = base64_encode(Yaml::dump($json, 10, 2)); $payload = [ @@ -87,6 +95,7 @@ class GenerateServiceTemplates extends Command 'documentation' => $documentation, 'slogan' => $slogan, 'compose' => $yaml, + 'tags' => $tags, ]; if ($env_file) { $env_file_content = file_get_contents(base_path("templates/compose/$env_file")); diff --git a/app/Http/Livewire/Project/New/Select.php b/app/Http/Livewire/Project/New/Select.php index 8f1271c35..a4b80ce69 100644 --- a/app/Http/Livewire/Project/New/Select.php +++ b/app/Http/Livewire/Project/New/Select.php @@ -21,14 +21,18 @@ class Select extends Component public Collection|array $swarmDockers = []; public array $parameters; public Collection|array $services = []; + public Collection|array $allServices = []; + public bool $loadingServices = true; public bool $loading = false; public $environments = []; public ?string $selectedEnvironment = null; public ?string $existingPostgresqlUrl = null; + public ?string $search = null; protected $queryString = [ 'server', + 'search' ]; public function mount() @@ -41,6 +45,11 @@ class Select extends Component $this->environments = Project::whereUuid($projectUuid)->first()->environments; $this->selectedEnvironment = data_get($this->parameters, 'environment_name'); } + public function render() + { + $this->loadServices(); + return view('livewire.project.new.select'); + } public function updatedSelectedEnvironment() { @@ -49,6 +58,7 @@ class Select extends Component 'environment_name' => $this->selectedEnvironment, ]); } + // public function addExistingPostgresql() // { // try { @@ -59,19 +69,28 @@ class Select extends Component // } // } - public function loadThings() - { - $this->loadServices(); - $this->loadServers(); - } - public function loadServices(bool $forceReload = false) + public function loadServices(bool $force = false) { try { - if ($forceReload) { - Cache::forget('services'); + if (count($this->allServices) > 0 && !$force) { + if (!$this->search) { + $this->services = $this->allServices; + return; + } + $this->services = $this->allServices->filter(function ($service, $key) { + $tags = collect(data_get($service, 'tags', [])); + return str_contains(strtolower($key), strtolower($this->search)) || $tags->contains(function ($tag) { + return str_contains(strtolower($tag), strtolower($this->search)); + }); + }); + } else { + $this->search = null; + $this->allServices = getServiceTemplates(); + $this->services = $this->allServices->filter(function ($service, $key) { + return str_contains(strtolower($key), strtolower($this->search)); + });; + $this->emit('success', 'Successfully loaded services.'); } - $this->services = getServiceTemplates(); - $this->emit('success', 'Successfully loaded services.'); } catch (\Throwable $e) { return handleError($e, $this); } finally { diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index ab5b0b894..6ebefda41 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -1,4 +1,4 @@ -
+

New Resource

@@ -128,12 +128,15 @@

Services

Reload Services List +
@if ($loadingServices) @else - @foreach ($services as $serviceName => $service) + @forelse ($services as $serviceName => $service) @if (data_get($service, 'disabled')) @endif - @endforeach + @empty +
No service found.
+ @endforelse @endif
Trademarks Policy: The respective trademarks mentioned here are owned by the diff --git a/templates/compose/appsmith.yaml b/templates/compose/appsmith.yaml index ce15f4e45..a30f4c4e5 100644 --- a/templates/compose/appsmith.yaml +++ b/templates/compose/appsmith.yaml @@ -1,5 +1,6 @@ # documentation: https://docs.appsmith.com # slogan: Appsmith is an open-source, self-hosted application development platform that enables you to build powerful web applications with ease. +# tags: lowcode,nocode,no,low,platform services: appsmith: diff --git a/templates/compose/appwrite.yaml b/templates/compose/appwrite.yaml index aa4e9da4a..ff2efb78a 100644 --- a/templates/compose/appwrite.yaml +++ b/templates/compose/appwrite.yaml @@ -1,6 +1,7 @@ # documentation: https://appwrite.io/docs # slogan: Appwrite is a self-hosted backend-as-a-service platform that simplifies the development of web and mobile applications by providing a range of features and APIs. # env_file: appwrite.env +# tags: backend-as-a-service, platform x-logging: &x-logging diff --git a/templates/compose/babybuddy.yaml b/templates/compose/babybuddy.yaml index e73f01c08..19b17037f 100644 --- a/templates/compose/babybuddy.yaml +++ b/templates/compose/babybuddy.yaml @@ -1,5 +1,6 @@ # documentation: https://docs.baby-buddy.net # slogan: Baby Buddy is an open-source web application that helps parents track their baby's daily activities, growth, and health with ease. +# tags: baby, parents, health, growth, activities services: babybuddy: diff --git a/templates/compose/code-server.yaml b/templates/compose/code-server.yaml index 612439035..19858601a 100644 --- a/templates/compose/code-server.yaml +++ b/templates/compose/code-server.yaml @@ -1,5 +1,6 @@ # documentation: https://coder.com/docs/code-server/latest/guide # slogan: Code-Server is a self-hosted, web-based code editor that enables remote coding and collaboration from any device, anywhere. +# tags: code, editor, remote, collaboration services: code-server: diff --git a/templates/compose/dokuwiki.yaml b/templates/compose/dokuwiki.yaml index 9ae0972ab..81f317c42 100644 --- a/templates/compose/dokuwiki.yaml +++ b/templates/compose/dokuwiki.yaml @@ -1,5 +1,6 @@ # documentation: https://www.dokuwiki.org/faq # slogan: A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility. +# tags: wiki, documentation, knowledge, base services: dokuwiki: diff --git a/templates/compose/fider.yaml b/templates/compose/fider.yaml index 71bfa0330..e2bf00910 100644 --- a/templates/compose/fider.yaml +++ b/templates/compose/fider.yaml @@ -1,5 +1,6 @@ # documentation: https://fider.io/doc # slogan: Fider is an open-source feedback platform for collecting and managing user feedback, helping you prioritize improvements to your products and services. +# tags: feedback, user-feedback services: fider: diff --git a/templates/compose/ghost.yaml b/templates/compose/ghost.yaml index fd1aca678..1ed34911b 100644 --- a/templates/compose/ghost.yaml +++ b/templates/compose/ghost.yaml @@ -1,5 +1,6 @@ # documentation: https://ghost.org/docs # slogan: Ghost is a popular open-source content management system (CMS) and blogging platform, known for its simplicity and focus on content creation. +# tags: cms, blog, content, management, system services: ghost: diff --git a/templates/compose/heimdall.yaml b/templates/compose/heimdall.yaml index 8216f0ea2..dcae08feb 100644 --- a/templates/compose/heimdall.yaml +++ b/templates/compose/heimdall.yaml @@ -1,5 +1,6 @@ # documentation: https://github.com/linuxserver/Heimdall # slogan: Heimdall is a self-hosted dashboard for managing and organizing your server applications, providing a centralized and efficient interface. +# tags: dashboard, server, applications, interface services: heimdall: diff --git a/templates/compose/metube.yaml b/templates/compose/metube.yaml index 4bebefbb9..9a39e4959 100644 --- a/templates/compose/metube.yaml +++ b/templates/compose/metube.yaml @@ -1,5 +1,6 @@ # documentation: https://github.com/alexta69/metube # slogan: A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites. +# tags: youtube, download, videos, playlist services: metube: diff --git a/templates/compose/minio.yaml b/templates/compose/minio.yaml index 372c928c0..6923af788 100644 --- a/templates/compose/minio.yaml +++ b/templates/compose/minio.yaml @@ -1,5 +1,6 @@ # documentation: https://docs.min.io/docs/minio-docker-quickstart-guide.html # slogan: MinIO is a high performance object storage server compatible with Amazon S3 APIs. +# tags: object, storage, server, s3, api services: minio: diff --git a/templates/compose/pairdrop.yaml b/templates/compose/pairdrop.yaml index c84d50434..924dae0d8 100644 --- a/templates/compose/pairdrop.yaml +++ b/templates/compose/pairdrop.yaml @@ -1,5 +1,6 @@ # documentation: https://github.com/schlagmichdoch/PairDrop/blob/master/docs/faq.md # slogan: Pairdrop is a self-hosted file sharing and collaboration platform, offering secure file sharing and collaboration capabilities for efficient teamwork. +# tags: file, sharing, collaboration, teamwork services: pairdrop: diff --git a/templates/compose/plausible.yaml b/templates/compose/plausible.yaml index b64f9c6ef..a08324b0b 100644 --- a/templates/compose/plausible.yaml +++ b/templates/compose/plausible.yaml @@ -1,6 +1,7 @@ # ignore: true # documentation: https://plausible.io/docs/self-hosting # slogan: "Plausible Analytics is a simple, open-source, lightweight (< 1 KB) and privacy-friendly web analytics alternative to Google Analytics." +# tags: analytics, privacy, google, alternative version: "3.3" services: diff --git a/templates/compose/snapdrop.yaml b/templates/compose/snapdrop.yaml index 345486859..066fb2013 100644 --- a/templates/compose/snapdrop.yaml +++ b/templates/compose/snapdrop.yaml @@ -1,5 +1,6 @@ # documentation: https://github.com/RobinLinus/snapdrop/blob/master/docs/faq.md # slogan: A self-hosted file-sharing service for secure and convenient file transfers, whether on a local network or the internet. +# tags: file, sharing, transfer, local, network, internet services: snapdrop: diff --git a/templates/compose/umami.yaml b/templates/compose/umami.yaml index d443279a1..83ac2ba26 100644 --- a/templates/compose/umami.yaml +++ b/templates/compose/umami.yaml @@ -1,5 +1,6 @@ # documentation: https://umami.is/docs/getting-started # slogan: Umami is a lightweight, self-hosted web analytics platform designed to provide website owners with insights into visitor behavior without compromising user privacy. +# tags: analytics, insights, privacy services: umami: diff --git a/templates/compose/uptime-kuma.yaml b/templates/compose/uptime-kuma.yaml index d64a0d05c..a1a02f91f 100644 --- a/templates/compose/uptime-kuma.yaml +++ b/templates/compose/uptime-kuma.yaml @@ -1,5 +1,6 @@ # documentation: https://github.com/louislam/uptime-kuma/wiki # slogan: Uptime Kuma is a free, self-hosted monitoring tool for tracking the status and performance of your web services and applications in real-time. +# tags: monitoring, status, performance, web, services, applications, real-time services: uptime-kuma: diff --git a/templates/compose/wordpress-with-mariadb.yaml b/templates/compose/wordpress-with-mariadb.yaml index ccd7f0c70..4d59daf32 100644 --- a/templates/compose/wordpress-with-mariadb.yaml +++ b/templates/compose/wordpress-with-mariadb.yaml @@ -1,5 +1,6 @@ # documentation: https://wordpress.org/documentation/ # slogan: "WordPress is open source software you can use to create a beautiful website, blog, or app." +# tags: cms, blog, content, management, mariadb services: wordpress: diff --git a/templates/compose/wordpress-with-mysql.yaml b/templates/compose/wordpress-with-mysql.yaml index b796db3ec..c264ecbf3 100644 --- a/templates/compose/wordpress-with-mysql.yaml +++ b/templates/compose/wordpress-with-mysql.yaml @@ -1,5 +1,6 @@ # documentation: https://wordpress.org/documentation/ # slogan: "WordPress is open source software you can use to create a beautiful website, blog, or app." +# tags: cms, blog, content, management, mysql services: wordpress: diff --git a/templates/compose/wordpress-without-database.yaml b/templates/compose/wordpress-without-database.yaml index 0a0745a24..ddcefe90d 100644 --- a/templates/compose/wordpress-without-database.yaml +++ b/templates/compose/wordpress-without-database.yaml @@ -1,5 +1,6 @@ # documentation: https://wordpress.org/documentation/ # slogan: "WordPress is open source software you can use to create a beautiful website, blog, or app." +# tags: cms, blog, content, management services: wordpress: diff --git a/templates/service-templates.json b/templates/service-templates.json index 33576e92b..d5af2b37c 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -2,87 +2,195 @@ "appsmith": { "documentation": "https:\/\/docs.appsmith.com", "slogan": "Appsmith is an open-source, self-hosted application development platform that enables you to build powerful web applications with ease.", - "compose": "c2VydmljZXM6CiAgYXBwc21pdGg6CiAgICBpbWFnZTogJ2luZGV4LmRvY2tlci5pby9hcHBzbWl0aC9hcHBzbWl0aC1jZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE4KICAgICAgLSBBUFBTTUlUSF9NQUlMX0VOQUJMRUQ9ZmFsc2UKICAgICAgLSBBUFBTTUlUSF9ESVNBQkxFX1RFTEVNRVRSWT10cnVlCiAgICAgIC0gQVBQU01JVEhfRElTQUJMRV9JTlRFUkNPTT10cnVlCiAgICAgIC0gQVBQU01JVEhfU0VOVFJZX0RTTj0KICAgICAgLSBBUFBTTUlUSF9TTUFSVF9MT09LX0lEPQogICAgdm9sdW1lczoKICAgICAgLSAnc3RhY2tzLWRhdGE6L2FwcHNtaXRoLXN0YWNrcycK" + "compose": "c2VydmljZXM6CiAgYXBwc21pdGg6CiAgICBpbWFnZTogJ2luZGV4LmRvY2tlci5pby9hcHBzbWl0aC9hcHBzbWl0aC1jZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE4KICAgICAgLSBBUFBTTUlUSF9NQUlMX0VOQUJMRUQ9ZmFsc2UKICAgICAgLSBBUFBTTUlUSF9ESVNBQkxFX1RFTEVNRVRSWT10cnVlCiAgICAgIC0gQVBQU01JVEhfRElTQUJMRV9JTlRFUkNPTT10cnVlCiAgICAgIC0gQVBQU01JVEhfU0VOVFJZX0RTTj0KICAgICAgLSBBUFBTTUlUSF9TTUFSVF9MT09LX0lEPQogICAgdm9sdW1lczoKICAgICAgLSAnc3RhY2tzLWRhdGE6L2FwcHNtaXRoLXN0YWNrcycK", + "tags": [ + "lowcode", + "nocode", + "no", + "low", + "platform" + ] }, "appwrite": { "documentation": "https:\/\/appwrite.io\/docs", "slogan": "Appwrite is a self-hosted backend-as-a-service platform that simplifies the development of web and mobile applications by providing a range of features and APIs.", "compose": "eC1sb2dnaW5nOgogIGxvZ2dpbmc6CiAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgb3B0aW9uczoKICAgICAgbWF4LWZpbGU6ICc1JwogICAgICBtYXgtc2l6ZTogMTBtCnZlcnNpb246ICczJwpzZXJ2aWNlczoKICBhcHB3cml0ZToKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS40JwogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlCiAgICBsb2dnaW5nOgogICAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgICBvcHRpb25zOgogICAgICAgIG1heC1maWxlOiAnNScKICAgICAgICBtYXgtc2l6ZTogMTBtCiAgICBsYWJlbHM6CiAgICAgIC0gdHJhZWZpay5jb25zdHJhaW50LWxhYmVsLXN0YWNrPWFwcHdyaXRlCiAgICAgIC0gdHJhZWZpay5kb2NrZXIubmV0d29yaz1hcHB3cml0ZQogICAgICAtIHRyYWVmaWsuaHR0cC5zZXJ2aWNlcy5hcHB3cml0ZV9hcGkubG9hZGJhbGFuY2VyLnNlcnZlci5wb3J0PTgwCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfYXBpX2h0dHAuZW50cnlwb2ludHM9d2ViCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfYXBpX2h0dHAucnVsZT1QYXRoUHJlZml4KGAvYCkKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9hcGlfaHR0cC5zZXJ2aWNlPWFwcHdyaXRlX2FwaQogICAgICAtIHRyYWVmaWsuaHR0cC5yb3V0ZXJzLmFwcHdyaXRlX2FwaV9odHRwcy5lbnRyeXBvaW50cz13ZWJzZWN1cmUKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9hcGlfaHR0cHMucnVsZT1QYXRoUHJlZml4KGAvYCkKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9hcGlfaHR0cHMuc2VydmljZT1hcHB3cml0ZV9hcGkKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9hcGlfaHR0cHMudGxzPXRydWUKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2FwcHdyaXRlLXVwbG9hZHM6L3N0b3JhZ2UvdXBsb2FkczpydycKICAgICAgLSAnYXBwd3JpdGUtY2FjaGU6L3N0b3JhZ2UvY2FjaGU6cncnCiAgICAgIC0gJ2FwcHdyaXRlLWNvbmZpZzovc3RvcmFnZS9jb25maWc6cncnCiAgICAgIC0gJ2FwcHdyaXRlLWNlcnRpZmljYXRlczovc3RvcmFnZS9jZXJ0aWZpY2F0ZXM6cncnCiAgICAgIC0gJ2FwcHdyaXRlLWZ1bmN0aW9uczovc3RvcmFnZS9mdW5jdGlvbnM6cncnCiAgICBkZXBlbmRzX29uOgogICAgICAtIG1hcmlhZGIKICAgICAgLSByZWRpcwogICAgICAtIGluZmx1eGRiCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVBQV1JJVEU9LwogICAgICAtIF9BUFBfRU5WCiAgICAgIC0gX0FQUF9XT1JLRVJfUEVSX0NPUkUKICAgICAgLSBfQVBQX0xPQ0FMRQogICAgICAtIF9BUFBfQ09OU09MRV9XSElURUxJU1RfUk9PVAogICAgICAtIF9BUFBfQ09OU09MRV9XSElURUxJU1RfRU1BSUxTCiAgICAgIC0gX0FQUF9DT05TT0xFX1dISVRFTElTVF9JUFMKICAgICAgLSBfQVBQX1NZU1RFTV9FTUFJTF9OQU1FCiAgICAgIC0gX0FQUF9TWVNURU1fRU1BSUxfQUREUkVTUwogICAgICAtIF9BUFBfU1lTVEVNX1NFQ1VSSVRZX0VNQUlMX0FERFJFU1MKICAgICAgLSBfQVBQX1NZU1RFTV9SRVNQT05TRV9GT1JNQVQKICAgICAgLSBfQVBQX09QVElPTlNfQUJVU0UKICAgICAgLSBfQVBQX09QVElPTlNfRk9SQ0VfSFRUUFMKICAgICAgLSBfQVBQX09QRU5TU0xfS0VZX1YxCiAgICAgIC0gX0FQUF9ET01BSU4KICAgICAgLSBfQVBQX0RPTUFJTl9UQVJHRVQKICAgICAgLSBfQVBQX0RPTUFJTl9GVU5DVElPTlMKICAgICAgLSBfQVBQX1JFRElTX0hPU1QKICAgICAgLSBfQVBQX1JFRElTX1BPUlQKICAgICAgLSBfQVBQX1JFRElTX1VTRVIKICAgICAgLSBfQVBQX1JFRElTX1BBU1MKICAgICAgLSBfQVBQX0RCX0hPU1QKICAgICAgLSBfQVBQX0RCX1BPUlQKICAgICAgLSBfQVBQX0RCX1NDSEVNQQogICAgICAtIF9BUFBfREJfVVNFUgogICAgICAtIF9BUFBfREJfUEFTUwogICAgICAtIF9BUFBfU01UUF9IT1NUCiAgICAgIC0gX0FQUF9TTVRQX1BPUlQKICAgICAgLSBfQVBQX1NNVFBfU0VDVVJFCiAgICAgIC0gX0FQUF9TTVRQX1VTRVJOQU1FCiAgICAgIC0gX0FQUF9TTVRQX1BBU1NXT1JECiAgICAgIC0gX0FQUF9VU0FHRV9TVEFUUwogICAgICAtIF9BUFBfSU5GTFVYREJfSE9TVAogICAgICAtIF9BUFBfSU5GTFVYREJfUE9SVAogICAgICAtIF9BUFBfU1RPUkFHRV9MSU1JVAogICAgICAtIF9BUFBfU1RPUkFHRV9QUkVWSUVXX0xJTUlUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0FOVElWSVJVUwogICAgICAtIF9BUFBfU1RPUkFHRV9BTlRJVklSVVNfSE9TVAogICAgICAtIF9BUFBfU1RPUkFHRV9BTlRJVklSVVNfUE9SVAogICAgICAtIF9BUFBfU1RPUkFHRV9ERVZJQ0UKICAgICAgLSBfQVBQX1NUT1JBR0VfUzNfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9TM19TRUNSRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfUzNfUkVHSU9OCiAgICAgIC0gX0FQUF9TVE9SQUdFX1MzX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0JBQ0tCTEFaRV9SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9MSU5PREVfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9MSU5PREVfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0xJTk9ERV9SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfTElOT0RFX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9XQVNBQklfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9XQVNBQklfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX1dBU0FCSV9SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfV0FTQUJJX0JVQ0tFVAogICAgICAtIF9BUFBfRlVOQ1RJT05TX1NJWkVfTElNSVQKICAgICAgLSBfQVBQX0ZVTkNUSU9OU19USU1FT1VUCiAgICAgIC0gX0FQUF9GVU5DVElPTlNfQlVJTERfVElNRU9VVAogICAgICAtIF9BUFBfRlVOQ1RJT05TX0NQVVMKICAgICAgLSBfQVBQX0ZVTkNUSU9OU19NRU1PUlkKICAgICAgLSBfQVBQX0ZVTkNUSU9OU19SVU5USU1FUwogICAgICAtIF9BUFBfRVhFQ1VUT1JfU0VDUkVUCiAgICAgIC0gX0FQUF9FWEVDVVRPUl9IT1NUCiAgICAgIC0gX0FQUF9MT0dHSU5HX1BST1ZJREVSCiAgICAgIC0gX0FQUF9MT0dHSU5HX0NPTkZJRwogICAgICAtIF9BUFBfU1RBVFNEX0hPU1QKICAgICAgLSBfQVBQX1NUQVRTRF9QT1JUCiAgICAgIC0gX0FQUF9NQUlOVEVOQU5DRV9JTlRFUlZBTAogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX0VYRUNVVElPTgogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX0NBQ0hFCiAgICAgIC0gX0FQUF9NQUlOVEVOQU5DRV9SRVRFTlRJT05fQUJVU0UKICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9BVURJVAogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX1VTQUdFX0hPVVJMWQogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX1NDSEVEVUxFUwogICAgICAtIF9BUFBfU01TX1BST1ZJREVSCiAgICAgIC0gX0FQUF9TTVNfRlJPTQogICAgICAtIF9BUFBfR1JBUEhRTF9NQVhfQkFUQ0hfU0laRQogICAgICAtIF9BUFBfR1JBUEhRTF9NQVhfQ09NUExFWElUWQogICAgICAtIF9BUFBfR1JBUEhRTF9NQVhfREVQVEgKICAgICAgLSBfQVBQX1ZDU19HSVRIVUJfQVBQX05BTUUKICAgICAgLSBfQVBQX1ZDU19HSVRIVUJfUFJJVkFURV9LRVkKICAgICAgLSBfQVBQX1ZDU19HSVRIVUJfQVBQX0lECiAgICAgIC0gX0FQUF9WQ1NfR0lUSFVCX1dFQkhPT0tfU0VDUkVUCiAgICAgIC0gX0FQUF9WQ1NfR0lUSFVCX0NMSUVOVF9TRUNSRVQKICAgICAgLSBfQVBQX1ZDU19HSVRIVUJfQ0xJRU5UX0lECiAgICAgIC0gX0FQUF9NSUdSQVRJT05TX0ZJUkVCQVNFX0NMSUVOVF9JRAogICAgICAtIF9BUFBfTUlHUkFUSU9OU19GSVJFQkFTRV9DTElFTlRfU0VDUkVUCiAgICAgIC0gX0FQUF9BU1NJU1RBTlRfT1BFTkFJX0FQSV9LRVkKICBhcHB3cml0ZS1yZWFsdGltZToKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMnCiAgICBlbnRyeXBvaW50OiByZWFsdGltZQogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLXJlYWx0aW1lCiAgICBsb2dnaW5nOgogICAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgICBvcHRpb25zOgogICAgICAgIG1heC1maWxlOiAnNScKICAgICAgICBtYXgtc2l6ZTogMTBtCiAgICBsYWJlbHM6CiAgICAgIC0gdHJhZWZpay5jb25zdHJhaW50LWxhYmVsLXN0YWNrPWFwcHdyaXRlCiAgICAgIC0gdHJhZWZpay5kb2NrZXIubmV0d29yaz1hcHB3cml0ZQogICAgICAtIHRyYWVmaWsuaHR0cC5zZXJ2aWNlcy5hcHB3cml0ZV9yZWFsdGltZS5sb2FkYmFsYW5jZXIuc2VydmVyLnBvcnQ9ODAKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9yZWFsdGltZV93cy5lbnRyeXBvaW50cz13ZWIKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9yZWFsdGltZV93cy5ydWxlPVBhdGhQcmVmaXgoYC92MS9yZWFsdGltZWApCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfcmVhbHRpbWVfd3Muc2VydmljZT1hcHB3cml0ZV9yZWFsdGltZQogICAgICAtIHRyYWVmaWsuaHR0cC5yb3V0ZXJzLmFwcHdyaXRlX3JlYWx0aW1lX3dzcy5lbnRyeXBvaW50cz13ZWJzZWN1cmUKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9yZWFsdGltZV93c3MucnVsZT1QYXRoUHJlZml4KGAvdjEvcmVhbHRpbWVgKQogICAgICAtIHRyYWVmaWsuaHR0cC5yb3V0ZXJzLmFwcHdyaXRlX3JlYWx0aW1lX3dzcy5zZXJ2aWNlPWFwcHdyaXRlX3JlYWx0aW1lCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfcmVhbHRpbWVfd3NzLnRscz10cnVlCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfcmVhbHRpbWVfd3NzLnRscy5jZXJ0cmVzb2x2ZXI9ZG5zCiAgICBuZXR3b3JrczoKICAgICAgLSBhcHB3cml0ZQogICAgZGVwZW5kc19vbjoKICAgICAgLSBtYXJpYWRiCiAgICAgIC0gcmVkaXMKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9BUFBXUklURT0vdjEvcmVhbHRpbWUKICAgICAgLSBfQVBQX0VOVgogICAgICAtIF9BUFBfV09SS0VSX1BFUl9DT1JFCiAgICAgIC0gX0FQUF9PUFRJT05TX0FCVVNFCiAgICAgIC0gX0FQUF9PUEVOU1NMX0tFWV9WMQogICAgICAtIF9BUFBfUkVESVNfSE9TVAogICAgICAtIF9BUFBfUkVESVNfUE9SVAogICAgICAtIF9BUFBfUkVESVNfVVNFUgogICAgICAtIF9BUFBfUkVESVNfUEFTUwogICAgICAtIF9BUFBfREJfSE9TVAogICAgICAtIF9BUFBfREJfUE9SVAogICAgICAtIF9BUFBfREJfU0NIRU1BCiAgICAgIC0gX0FQUF9EQl9VU0VSCiAgICAgIC0gX0FQUF9EQl9QQVNTCiAgICAgIC0gX0FQUF9VU0FHRV9TVEFUUwogICAgICAtIF9BUFBfTE9HR0lOR19QUk9WSURFUgogICAgICAtIF9BUFBfTE9HR0lOR19DT05GSUcKICBhcHB3cml0ZS13b3JrZXItYXVkaXRzOgogICAgaW1hZ2U6ICdhcHB3cml0ZS9hcHB3cml0ZToxLjQuMycKICAgIGVudHJ5cG9pbnQ6IHdvcmtlci1hdWRpdHMKICAgIGxvZ2dpbmc6CiAgICAgIGRyaXZlcjoganNvbi1maWxlCiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LWZpbGU6ICc1JwogICAgICAgIG1heC1zaXplOiAxMG0KICAgIGNvbnRhaW5lcl9uYW1lOiBhcHB3cml0ZS13b3JrZXItYXVkaXRzCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgbmV0d29ya3M6CiAgICAgIC0gYXBwd3JpdGUKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcmVkaXMKICAgICAgLSBtYXJpYWRiCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBfQVBQX0VOVgogICAgICAtIF9BUFBfV09SS0VSX1BFUl9DT1JFCiAgICAgIC0gX0FQUF9PUEVOU1NMX0tFWV9WMQogICAgICAtIF9BUFBfUkVESVNfSE9TVAogICAgICAtIF9BUFBfUkVESVNfUE9SVAogICAgICAtIF9BUFBfUkVESVNfVVNFUgogICAgICAtIF9BUFBfUkVESVNfUEFTUwogICAgICAtIF9BUFBfREJfSE9TVAogICAgICAtIF9BUFBfREJfUE9SVAogICAgICAtIF9BUFBfREJfU0NIRU1BCiAgICAgIC0gX0FQUF9EQl9VU0VSCiAgICAgIC0gX0FQUF9EQl9QQVNTCiAgICAgIC0gX0FQUF9MT0dHSU5HX1BST1ZJREVSCiAgICAgIC0gX0FQUF9MT0dHSU5HX0NPTkZJRwogIGFwcHdyaXRlLXdvcmtlci13ZWJob29rczoKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMnCiAgICBlbnRyeXBvaW50OiB3b3JrZXItd2ViaG9va3MKICAgIGxvZ2dpbmc6CiAgICAgIGRyaXZlcjoganNvbi1maWxlCiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LWZpbGU6ICc1JwogICAgICAgIG1heC1zaXplOiAxMG0KICAgIGNvbnRhaW5lcl9uYW1lOiBhcHB3cml0ZS13b3JrZXItd2ViaG9va3MKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBuZXR3b3JrczoKICAgICAgLSBhcHB3cml0ZQogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgICAtIG1hcmlhZGIKICAgIGVudmlyb25tZW50OgogICAgICAtIF9BUFBfRU5WCiAgICAgIC0gX0FQUF9XT1JLRVJfUEVSX0NPUkUKICAgICAgLSBfQVBQX09QRU5TU0xfS0VZX1YxCiAgICAgIC0gX0FQUF9TWVNURU1fU0VDVVJJVFlfRU1BSUxfQUREUkVTUwogICAgICAtIF9BUFBfUkVESVNfSE9TVAogICAgICAtIF9BUFBfUkVESVNfUE9SVAogICAgICAtIF9BUFBfUkVESVNfVVNFUgogICAgICAtIF9BUFBfUkVESVNfUEFTUwogICAgICAtIF9BUFBfTE9HR0lOR19QUk9WSURFUgogICAgICAtIF9BUFBfTE9HR0lOR19DT05GSUcKICBhcHB3cml0ZS13b3JrZXItZGVsZXRlczoKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMnCiAgICBlbnRyeXBvaW50OiB3b3JrZXItZGVsZXRlcwogICAgbG9nZ2luZzoKICAgICAgZHJpdmVyOiBqc29uLWZpbGUKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtZmlsZTogJzUnCiAgICAgICAgbWF4LXNpemU6IDEwbQogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLXdvcmtlci1kZWxldGVzCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgbmV0d29ya3M6CiAgICAgIC0gYXBwd3JpdGUKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcmVkaXMKICAgICAgLSBtYXJpYWRiCiAgICB2b2x1bWVzOgogICAgICAtICdhcHB3cml0ZS11cGxvYWRzOi9zdG9yYWdlL3VwbG9hZHM6cncnCiAgICAgIC0gJ2FwcHdyaXRlLWNhY2hlOi9zdG9yYWdlL2NhY2hlOnJ3JwogICAgICAtICdhcHB3cml0ZS1mdW5jdGlvbnM6L3N0b3JhZ2UvZnVuY3Rpb25zOnJ3JwogICAgICAtICdhcHB3cml0ZS1idWlsZHM6L3N0b3JhZ2UvYnVpbGRzOnJ3JwogICAgICAtICdhcHB3cml0ZS1jZXJ0aWZpY2F0ZXM6L3N0b3JhZ2UvY2VydGlmaWNhdGVzOnJ3JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gX0FQUF9FTlYKICAgICAgLSBfQVBQX1dPUktFUl9QRVJfQ09SRQogICAgICAtIF9BUFBfT1BFTlNTTF9LRVlfVjEKICAgICAgLSBfQVBQX1JFRElTX0hPU1QKICAgICAgLSBfQVBQX1JFRElTX1BPUlQKICAgICAgLSBfQVBQX1JFRElTX1VTRVIKICAgICAgLSBfQVBQX1JFRElTX1BBU1MKICAgICAgLSBfQVBQX0RCX0hPU1QKICAgICAgLSBfQVBQX0RCX1BPUlQKICAgICAgLSBfQVBQX0RCX1NDSEVNQQogICAgICAtIF9BUFBfREJfVVNFUgogICAgICAtIF9BUFBfREJfUEFTUwogICAgICAtIF9BUFBfU1RPUkFHRV9ERVZJQ0UKICAgICAgLSBfQVBQX1NUT1JBR0VfUzNfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9TM19TRUNSRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfUzNfUkVHSU9OCiAgICAgIC0gX0FQUF9TVE9SQUdFX1MzX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0JBQ0tCTEFaRV9SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9MSU5PREVfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9MSU5PREVfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX0xJTk9ERV9SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfTElOT0RFX0JVQ0tFVAogICAgICAtIF9BUFBfU1RPUkFHRV9XQVNBQklfQUNDRVNTX0tFWQogICAgICAtIF9BUFBfU1RPUkFHRV9XQVNBQklfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX1dBU0FCSV9SRUdJT04KICAgICAgLSBfQVBQX1NUT1JBR0VfV0FTQUJJX0JVQ0tFVAogICAgICAtIF9BUFBfTE9HR0lOR19QUk9WSURFUgogICAgICAtIF9BUFBfTE9HR0lOR19DT05GSUcKICAgICAgLSBfQVBQX0VYRUNVVE9SX1NFQ1JFVAogICAgICAtIF9BUFBfRVhFQ1VUT1JfSE9TVAogIGFwcHdyaXRlLXdvcmtlci1kYXRhYmFzZXM6CiAgICBpbWFnZTogJ2FwcHdyaXRlL2FwcHdyaXRlOjEuNC4zJwogICAgZW50cnlwb2ludDogd29ya2VyLWRhdGFiYXNlcwogICAgbG9nZ2luZzoKICAgICAgZHJpdmVyOiBqc29uLWZpbGUKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtZmlsZTogJzUnCiAgICAgICAgbWF4LXNpemU6IDEwbQogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLXdvcmtlci1kYXRhYmFzZXMKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBuZXR3b3JrczoKICAgICAgLSBhcHB3cml0ZQogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgICAtIG1hcmlhZGIKICAgIGVudmlyb25tZW50OgogICAgICAtIF9BUFBfRU5WCiAgICAgIC0gX0FQUF9XT1JLRVJfUEVSX0NPUkUKICAgICAgLSBfQVBQX09QRU5TU0xfS0VZX1YxCiAgICAgIC0gX0FQUF9SRURJU19IT1NUCiAgICAgIC0gX0FQUF9SRURJU19QT1JUCiAgICAgIC0gX0FQUF9SRURJU19VU0VSCiAgICAgIC0gX0FQUF9SRURJU19QQVNTCiAgICAgIC0gX0FQUF9EQl9IT1NUCiAgICAgIC0gX0FQUF9EQl9QT1JUCiAgICAgIC0gX0FQUF9EQl9TQ0hFTUEKICAgICAgLSBfQVBQX0RCX1VTRVIKICAgICAgLSBfQVBQX0RCX1BBU1MKICAgICAgLSBfQVBQX0xPR0dJTkdfUFJPVklERVIKICAgICAgLSBfQVBQX0xPR0dJTkdfQ09ORklHCiAgYXBwd3JpdGUtd29ya2VyLWJ1aWxkczoKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMnCiAgICBlbnRyeXBvaW50OiB3b3JrZXItYnVpbGRzCiAgICBsb2dnaW5nOgogICAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgICBvcHRpb25zOgogICAgICAgIG1heC1maWxlOiAnNScKICAgICAgICBtYXgtc2l6ZTogMTBtCiAgICBjb250YWluZXJfbmFtZTogYXBwd3JpdGUtd29ya2VyLWJ1aWxkcwogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIG5ldHdvcmtzOgogICAgICAtIGFwcHdyaXRlCiAgICBkZXBlbmRzX29uOgogICAgICAtIHJlZGlzCiAgICAgIC0gbWFyaWFkYgogICAgdm9sdW1lczoKICAgICAgLSAnYXBwd3JpdGUtZnVuY3Rpb25zOi9zdG9yYWdlL2Z1bmN0aW9uczpydycKICAgICAgLSAnYXBwd3JpdGUtYnVpbGRzOi9zdG9yYWdlL2J1aWxkczpydycKICAgIGVudmlyb25tZW50OgogICAgICAtIF9BUFBfRU5WCiAgICAgIC0gX0FQUF9XT1JLRVJfUEVSX0NPUkUKICAgICAgLSBfQVBQX09QRU5TU0xfS0VZX1YxCiAgICAgIC0gX0FQUF9FWEVDVVRPUl9TRUNSRVQKICAgICAgLSBfQVBQX0VYRUNVVE9SX0hPU1QKICAgICAgLSBfQVBQX1JFRElTX0hPU1QKICAgICAgLSBfQVBQX1JFRElTX1BPUlQKICAgICAgLSBfQVBQX1JFRElTX1VTRVIKICAgICAgLSBfQVBQX1JFRElTX1BBU1MKICAgICAgLSBfQVBQX0RCX0hPU1QKICAgICAgLSBfQVBQX0RCX1BPUlQKICAgICAgLSBfQVBQX0RCX1NDSEVNQQogICAgICAtIF9BUFBfREJfVVNFUgogICAgICAtIF9BUFBfREJfUEFTUwogICAgICAtIF9BUFBfTE9HR0lOR19QUk9WSURFUgogICAgICAtIF9BUFBfTE9HR0lOR19DT05GSUcKICAgICAgLSBfQVBQX1ZDU19HSVRIVUJfQVBQX05BTUUKICAgICAgLSBfQVBQX1ZDU19HSVRIVUJfUFJJVkFURV9LRVkKICAgICAgLSBfQVBQX1ZDU19HSVRIVUJfQVBQX0lECiAgICAgIC0gX0FQUF9GVU5DVElPTlNfVElNRU9VVAogICAgICAtIF9BUFBfRlVOQ1RJT05TX0JVSUxEX1RJTUVPVVQKICAgICAgLSBfQVBQX0ZVTkNUSU9OU19DUFVTCiAgICAgIC0gX0FQUF9GVU5DVElPTlNfTUVNT1JZCiAgICAgIC0gX0FQUF9PUFRJT05TX0ZPUkNFX0hUVFBTCiAgICAgIC0gX0FQUF9ET01BSU4KICAgICAgLSBfQVBQX1NUT1JBR0VfREVWSUNFCiAgICAgIC0gX0FQUF9TVE9SQUdFX1MzX0FDQ0VTU19LRVkKICAgICAgLSBfQVBQX1NUT1JBR0VfUzNfU0VDUkVUCiAgICAgIC0gX0FQUF9TVE9SQUdFX1MzX1JFR0lPTgogICAgICAtIF9BUFBfU1RPUkFHRV9TM19CVUNLRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX0FDQ0VTU19LRVkKICAgICAgLSBfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX1NFQ1JFVAogICAgICAtIF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfUkVHSU9OCiAgICAgIC0gX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19CVUNLRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX0FDQ0VTU19LRVkKICAgICAgLSBfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX1NFQ1JFVAogICAgICAtIF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfUkVHSU9OCiAgICAgIC0gX0FQUF9TVE9SQUdFX0JBQ0tCTEFaRV9CVUNLRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfTElOT0RFX0FDQ0VTU19LRVkKICAgICAgLSBfQVBQX1NUT1JBR0VfTElOT0RFX1NFQ1JFVAogICAgICAtIF9BUFBfU1RPUkFHRV9MSU5PREVfUkVHSU9OCiAgICAgIC0gX0FQUF9TVE9SQUdFX0xJTk9ERV9CVUNLRVQKICAgICAgLSBfQVBQX1NUT1JBR0VfV0FTQUJJX0FDQ0VTU19LRVkKICAgICAgLSBfQVBQX1NUT1JBR0VfV0FTQUJJX1NFQ1JFVAogICAgICAtIF9BUFBfU1RPUkFHRV9XQVNBQklfUkVHSU9OCiAgICAgIC0gX0FQUF9TVE9SQUdFX1dBU0FCSV9CVUNLRVQKICBhcHB3cml0ZS13b3JrZXItY2VydGlmaWNhdGVzOgogICAgaW1hZ2U6ICdhcHB3cml0ZS9hcHB3cml0ZToxLjQuMycKICAgIGVudHJ5cG9pbnQ6IHdvcmtlci1jZXJ0aWZpY2F0ZXMKICAgIGxvZ2dpbmc6CiAgICAgIGRyaXZlcjoganNvbi1maWxlCiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LWZpbGU6ICc1JwogICAgICAgIG1heC1zaXplOiAxMG0KICAgIGNvbnRhaW5lcl9uYW1lOiBhcHB3cml0ZS13b3JrZXItY2VydGlmaWNhdGVzCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgbmV0d29ya3M6CiAgICAgIC0gYXBwd3JpdGUKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcmVkaXMKICAgICAgLSBtYXJpYWRiCiAgICB2b2x1bWVzOgogICAgICAtICdhcHB3cml0ZS1jb25maWc6L3N0b3JhZ2UvY29uZmlnOnJ3JwogICAgICAtICdhcHB3cml0ZS1jZXJ0aWZpY2F0ZXM6L3N0b3JhZ2UvY2VydGlmaWNhdGVzOnJ3JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gX0FQUF9FTlYKICAgICAgLSBfQVBQX1dPUktFUl9QRVJfQ09SRQogICAgICAtIF9BUFBfT1BFTlNTTF9LRVlfVjEKICAgICAgLSBfQVBQX0RPTUFJTgogICAgICAtIF9BUFBfRE9NQUlOX1RBUkdFVAogICAgICAtIF9BUFBfRE9NQUlOX0ZVTkNUSU9OUwogICAgICAtIF9BUFBfU1lTVEVNX1NFQ1VSSVRZX0VNQUlMX0FERFJFU1MKICAgICAgLSBfQVBQX1JFRElTX0hPU1QKICAgICAgLSBfQVBQX1JFRElTX1BPUlQKICAgICAgLSBfQVBQX1JFRElTX1VTRVIKICAgICAgLSBfQVBQX1JFRElTX1BBU1MKICAgICAgLSBfQVBQX0RCX0hPU1QKICAgICAgLSBfQVBQX0RCX1BPUlQKICAgICAgLSBfQVBQX0RCX1NDSEVNQQogICAgICAtIF9BUFBfREJfVVNFUgogICAgICAtIF9BUFBfREJfUEFTUwogICAgICAtIF9BUFBfTE9HR0lOR19QUk9WSURFUgogICAgICAtIF9BUFBfTE9HR0lOR19DT05GSUcKICBhcHB3cml0ZS13b3JrZXItZnVuY3Rpb25zOgogICAgaW1hZ2U6ICdhcHB3cml0ZS9hcHB3cml0ZToxLjQuMycKICAgIGVudHJ5cG9pbnQ6IHdvcmtlci1mdW5jdGlvbnMKICAgIGxvZ2dpbmc6CiAgICAgIGRyaXZlcjoganNvbi1maWxlCiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LWZpbGU6ICc1JwogICAgICAgIG1heC1zaXplOiAxMG0KICAgIGNvbnRhaW5lcl9uYW1lOiBhcHB3cml0ZS13b3JrZXItZnVuY3Rpb25zCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgbmV0d29ya3M6CiAgICAgIC0gYXBwd3JpdGUKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcmVkaXMKICAgICAgLSBtYXJpYWRiCiAgICAgIC0gb3BlbnJ1bnRpbWVzLWV4ZWN1dG9yCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBfQVBQX0VOVgogICAgICAtIF9BUFBfV09SS0VSX1BFUl9DT1JFCiAgICAgIC0gX0FQUF9PUEVOU1NMX0tFWV9WMQogICAgICAtIF9BUFBfUkVESVNfSE9TVAogICAgICAtIF9BUFBfUkVESVNfUE9SVAogICAgICAtIF9BUFBfUkVESVNfVVNFUgogICAgICAtIF9BUFBfUkVESVNfUEFTUwogICAgICAtIF9BUFBfREJfSE9TVAogICAgICAtIF9BUFBfREJfUE9SVAogICAgICAtIF9BUFBfREJfU0NIRU1BCiAgICAgIC0gX0FQUF9EQl9VU0VSCiAgICAgIC0gX0FQUF9EQl9QQVNTCiAgICAgIC0gX0FQUF9GVU5DVElPTlNfVElNRU9VVAogICAgICAtIF9BUFBfRlVOQ1RJT05TX0JVSUxEX1RJTUVPVVQKICAgICAgLSBfQVBQX0ZVTkNUSU9OU19DUFVTCiAgICAgIC0gX0FQUF9GVU5DVElPTlNfTUVNT1JZCiAgICAgIC0gX0FQUF9FWEVDVVRPUl9TRUNSRVQKICAgICAgLSBfQVBQX0VYRUNVVE9SX0hPU1QKICAgICAgLSBfQVBQX1VTQUdFX1NUQVRTCiAgICAgIC0gX0FQUF9ET0NLRVJfSFVCX1VTRVJOQU1FCiAgICAgIC0gX0FQUF9ET0NLRVJfSFVCX1BBU1NXT1JECiAgICAgIC0gX0FQUF9MT0dHSU5HX0NPTkZJRwogICAgICAtIF9BUFBfTE9HR0lOR19QUk9WSURFUgogIGFwcHdyaXRlLXdvcmtlci1tYWlsczoKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMnCiAgICBlbnRyeXBvaW50OiB3b3JrZXItbWFpbHMKICAgIGxvZ2dpbmc6CiAgICAgIGRyaXZlcjoganNvbi1maWxlCiAgICAgIG9wdGlvbnM6CiAgICAgICAgbWF4LWZpbGU6ICc1JwogICAgICAgIG1heC1zaXplOiAxMG0KICAgIGNvbnRhaW5lcl9uYW1lOiBhcHB3cml0ZS13b3JrZXItbWFpbHMKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBuZXR3b3JrczoKICAgICAgLSBhcHB3cml0ZQogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gX0FQUF9FTlYKICAgICAgLSBfQVBQX1dPUktFUl9QRVJfQ09SRQogICAgICAtIF9BUFBfT1BFTlNTTF9LRVlfVjEKICAgICAgLSBfQVBQX1NZU1RFTV9FTUFJTF9OQU1FCiAgICAgIC0gX0FQUF9TWVNURU1fRU1BSUxfQUREUkVTUwogICAgICAtIF9BUFBfUkVESVNfSE9TVAogICAgICAtIF9BUFBfUkVESVNfUE9SVAogICAgICAtIF9BUFBfUkVESVNfVVNFUgogICAgICAtIF9BUFBfUkVESVNfUEFTUwogICAgICAtIF9BUFBfU01UUF9IT1NUCiAgICAgIC0gX0FQUF9TTVRQX1BPUlQKICAgICAgLSBfQVBQX1NNVFBfU0VDVVJFCiAgICAgIC0gX0FQUF9TTVRQX1VTRVJOQU1FCiAgICAgIC0gX0FQUF9TTVRQX1BBU1NXT1JECiAgICAgIC0gX0FQUF9MT0dHSU5HX1BST1ZJREVSCiAgICAgIC0gX0FQUF9MT0dHSU5HX0NPTkZJRwogIGFwcHdyaXRlLXdvcmtlci1tZXNzYWdpbmc6CiAgICBpbWFnZTogJ2FwcHdyaXRlL2FwcHdyaXRlOjEuNC4zJwogICAgZW50cnlwb2ludDogd29ya2VyLW1lc3NhZ2luZwogICAgbG9nZ2luZzoKICAgICAgZHJpdmVyOiBqc29uLWZpbGUKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtZmlsZTogJzUnCiAgICAgICAgbWF4LXNpemU6IDEwbQogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLXdvcmtlci1tZXNzYWdpbmcKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBuZXR3b3JrczoKICAgICAgLSBhcHB3cml0ZQogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gX0FQUF9FTlYKICAgICAgLSBfQVBQX1dPUktFUl9QRVJfQ09SRQogICAgICAtIF9BUFBfUkVESVNfSE9TVAogICAgICAtIF9BUFBfUkVESVNfUE9SVAogICAgICAtIF9BUFBfUkVESVNfVVNFUgogICAgICAtIF9BUFBfUkVESVNfUEFTUwogICAgICAtIF9BUFBfU01TX1BST1ZJREVSCiAgICAgIC0gX0FQUF9TTVNfRlJPTQogICAgICAtIF9BUFBfTE9HR0lOR19QUk9WSURFUgogICAgICAtIF9BUFBfTE9HR0lOR19DT05GSUcKICBhcHB3cml0ZS13b3JrZXItbWlncmF0aW9uczoKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMnCiAgICBlbnRyeXBvaW50OiB3b3JrZXItbWlncmF0aW9ucwogICAgbG9nZ2luZzoKICAgICAgZHJpdmVyOiBqc29uLWZpbGUKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtZmlsZTogJzUnCiAgICAgICAgbWF4LXNpemU6IDEwbQogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLXdvcmtlci1taWdyYXRpb25zCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgbmV0d29ya3M6CiAgICAgIC0gYXBwd3JpdGUKICAgIGRlcGVuZHNfb246CiAgICAgIC0gbWFyaWFkYgogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gX0FQUF9FTlYKICAgICAgLSBfQVBQX1dPUktFUl9QRVJfQ09SRQogICAgICAtIF9BUFBfT1BFTlNTTF9LRVlfVjEKICAgICAgLSBfQVBQX0RPTUFJTgogICAgICAtIF9BUFBfRE9NQUlOX1RBUkdFVAogICAgICAtIF9BUFBfU1lTVEVNX1NFQ1VSSVRZX0VNQUlMX0FERFJFU1MKICAgICAgLSBfQVBQX1JFRElTX0hPU1QKICAgICAgLSBfQVBQX1JFRElTX1BPUlQKICAgICAgLSBfQVBQX1JFRElTX1VTRVIKICAgICAgLSBfQVBQX1JFRElTX1BBU1MKICAgICAgLSBfQVBQX0RCX0hPU1QKICAgICAgLSBfQVBQX0RCX1BPUlQKICAgICAgLSBfQVBQX0RCX1NDSEVNQQogICAgICAtIF9BUFBfREJfVVNFUgogICAgICAtIF9BUFBfREJfUEFTUwogICAgICAtIF9BUFBfTE9HR0lOR19QUk9WSURFUgogICAgICAtIF9BUFBfTE9HR0lOR19DT05GSUcKICAgICAgLSBfQVBQX01JR1JBVElPTlNfRklSRUJBU0VfQ0xJRU5UX0lECiAgICAgIC0gX0FQUF9NSUdSQVRJT05TX0ZJUkVCQVNFX0NMSUVOVF9TRUNSRVQKICBhcHB3cml0ZS1tYWludGVuYW5jZToKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMnCiAgICBlbnRyeXBvaW50OiBtYWludGVuYW5jZQogICAgbG9nZ2luZzoKICAgICAgZHJpdmVyOiBqc29uLWZpbGUKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtZmlsZTogJzUnCiAgICAgICAgbWF4LXNpemU6IDEwbQogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLW1haW50ZW5hbmNlCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgbmV0d29ya3M6CiAgICAgIC0gYXBwd3JpdGUKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcmVkaXMKICAgIGVudmlyb25tZW50OgogICAgICAtIF9BUFBfRU5WCiAgICAgIC0gX0FQUF9XT1JLRVJfUEVSX0NPUkUKICAgICAgLSBfQVBQX0RPTUFJTgogICAgICAtIF9BUFBfRE9NQUlOX1RBUkdFVAogICAgICAtIF9BUFBfRE9NQUlOX0ZVTkNUSU9OUwogICAgICAtIF9BUFBfT1BFTlNTTF9LRVlfVjEKICAgICAgLSBfQVBQX1JFRElTX0hPU1QKICAgICAgLSBfQVBQX1JFRElTX1BPUlQKICAgICAgLSBfQVBQX1JFRElTX1VTRVIKICAgICAgLSBfQVBQX1JFRElTX1BBU1MKICAgICAgLSBfQVBQX0RCX0hPU1QKICAgICAgLSBfQVBQX0RCX1BPUlQKICAgICAgLSBfQVBQX0RCX1NDSEVNQQogICAgICAtIF9BUFBfREJfVVNFUgogICAgICAtIF9BUFBfREJfUEFTUwogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfSU5URVJWQUwKICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9FWEVDVVRJT04KICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9DQUNIRQogICAgICAtIF9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX0FCVVNFCiAgICAgIC0gX0FQUF9NQUlOVEVOQU5DRV9SRVRFTlRJT05fQVVESVQKICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9VU0FHRV9IT1VSTFkKICAgICAgLSBfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9TQ0hFRFVMRVMKICBhcHB3cml0ZS11c2FnZToKICAgIGltYWdlOiAnYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMnCiAgICBlbnRyeXBvaW50OiB1c2FnZQogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLXVzYWdlCiAgICBsb2dnaW5nOgogICAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgICBvcHRpb25zOgogICAgICAgIG1heC1maWxlOiAnNScKICAgICAgICBtYXgtc2l6ZTogMTBtCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgbmV0d29ya3M6CiAgICAgIC0gYXBwd3JpdGUKICAgIGRlcGVuZHNfb246CiAgICAgIC0gaW5mbHV4ZGIKICAgICAgLSBtYXJpYWRiCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBfQVBQX0VOVgogICAgICAtIF9BUFBfV09SS0VSX1BFUl9DT1JFCiAgICAgIC0gX0FQUF9PUEVOU1NMX0tFWV9WMQogICAgICAtIF9BUFBfREJfSE9TVAogICAgICAtIF9BUFBfREJfUE9SVAogICAgICAtIF9BUFBfREJfU0NIRU1BCiAgICAgIC0gX0FQUF9EQl9VU0VSCiAgICAgIC0gX0FQUF9EQl9QQVNTCiAgICAgIC0gX0FQUF9JTkZMVVhEQl9IT1NUCiAgICAgIC0gX0FQUF9JTkZMVVhEQl9QT1JUCiAgICAgIC0gX0FQUF9VU0FHRV9BR0dSRUdBVElPTl9JTlRFUlZBTAogICAgICAtIF9BUFBfUkVESVNfSE9TVAogICAgICAtIF9BUFBfUkVESVNfUE9SVAogICAgICAtIF9BUFBfUkVESVNfVVNFUgogICAgICAtIF9BUFBfUkVESVNfUEFTUwogICAgICAtIF9BUFBfVVNBR0VfU1RBVFMKICAgICAgLSBfQVBQX0xPR0dJTkdfUFJPVklERVIKICAgICAgLSBfQVBQX0xPR0dJTkdfQ09ORklHCiAgYXBwd3JpdGUtc2NoZWR1bGU6CiAgICBpbWFnZTogJ2FwcHdyaXRlL2FwcHdyaXRlOjEuNC4zJwogICAgZW50cnlwb2ludDogc2NoZWR1bGUKICAgIGNvbnRhaW5lcl9uYW1lOiBhcHB3cml0ZS1zY2hlZHVsZQogICAgbG9nZ2luZzoKICAgICAgZHJpdmVyOiBqc29uLWZpbGUKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtZmlsZTogJzUnCiAgICAgICAgbWF4LXNpemU6IDEwbQogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIG5ldHdvcmtzOgogICAgICAtIGFwcHdyaXRlCiAgICBkZXBlbmRzX29uOgogICAgICAtIG1hcmlhZGIKICAgICAgLSByZWRpcwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gX0FQUF9FTlYKICAgICAgLSBfQVBQX1dPUktFUl9QRVJfQ09SRQogICAgICAtIF9BUFBfT1BFTlNTTF9LRVlfVjEKICAgICAgLSBfQVBQX1JFRElTX0hPU1QKICAgICAgLSBfQVBQX1JFRElTX1BPUlQKICAgICAgLSBfQVBQX1JFRElTX1VTRVIKICAgICAgLSBfQVBQX1JFRElTX1BBU1MKICAgICAgLSBfQVBQX0RCX0hPU1QKICAgICAgLSBfQVBQX0RCX1BPUlQKICAgICAgLSBfQVBQX0RCX1NDSEVNQQogICAgICAtIF9BUFBfREJfVVNFUgogICAgICAtIF9BUFBfREJfUEFTUwogIGFwcHdyaXRlLWFzc2lzdGFudDoKICAgIGltYWdlOiAnYXBwd3JpdGUvYXNzaXN0YW50OjAuMi4xJwogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLWFzc2lzdGFudAogICAgbG9nZ2luZzoKICAgICAgZHJpdmVyOiBqc29uLWZpbGUKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtZmlsZTogJzUnCiAgICAgICAgbWF4LXNpemU6IDEwbQogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIG5ldHdvcmtzOgogICAgICAtIGFwcHdyaXRlCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBfQVBQX0FTU0lTVEFOVF9PUEVOQUlfQVBJX0tFWQogIG9wZW5ydW50aW1lcy1leGVjdXRvcjoKICAgIGNvbnRhaW5lcl9uYW1lOiBvcGVucnVudGltZXMtZXhlY3V0b3IKICAgIGhvc3RuYW1lOiBhcHB3cml0ZS1leGVjdXRvcgogICAgbG9nZ2luZzoKICAgICAgZHJpdmVyOiBqc29uLWZpbGUKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtZmlsZTogJzUnCiAgICAgICAgbWF4LXNpemU6IDEwbQogICAgc3RvcF9zaWduYWw6IFNJR0lOVAogICAgaW1hZ2U6ICdvcGVucnVudGltZXMvZXhlY3V0b3I6MC40LjEnCiAgICBuZXR3b3JrczoKICAgICAgLSBhcHB3cml0ZQogICAgICAtIHJ1bnRpbWVzCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnYXBwd3JpdGUtYnVpbGRzOi9zdG9yYWdlL2J1aWxkczpydycKICAgICAgLSAnYXBwd3JpdGUtZnVuY3Rpb25zOi9zdG9yYWdlL2Z1bmN0aW9uczpydycKICAgICAgLSAnL3RtcDovdG1wOnJ3JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gT1BSX0VYRUNVVE9SX0lOQUNUSVZFX1RSRVNIT0xEPSRfQVBQX0ZVTkNUSU9OU19JTkFDVElWRV9USFJFU0hPTEQKICAgICAgLSBPUFJfRVhFQ1VUT1JfTUFJTlRFTkFOQ0VfSU5URVJWQUw9JF9BUFBfRlVOQ1RJT05TX01BSU5URU5BTkNFX0lOVEVSVkFMCiAgICAgIC0gT1BSX0VYRUNVVE9SX05FVFdPUks9JF9BUFBfRlVOQ1RJT05TX1JVTlRJTUVTX05FVFdPUksKICAgICAgLSBPUFJfRVhFQ1VUT1JfRE9DS0VSX0hVQl9VU0VSTkFNRT0kX0FQUF9ET0NLRVJfSFVCX1VTRVJOQU1FCiAgICAgIC0gT1BSX0VYRUNVVE9SX0RPQ0tFUl9IVUJfUEFTU1dPUkQ9JF9BUFBfRE9DS0VSX0hVQl9QQVNTV09SRAogICAgICAtIE9QUl9FWEVDVVRPUl9FTlY9JF9BUFBfRU5WCiAgICAgIC0gT1BSX0VYRUNVVE9SX1JVTlRJTUVTPSRfQVBQX0ZVTkNUSU9OU19SVU5USU1FUwogICAgICAtIE9QUl9FWEVDVVRPUl9TRUNSRVQ9JF9BUFBfRVhFQ1VUT1JfU0VDUkVUCiAgICAgIC0gT1BSX0VYRUNVVE9SX0xPR0dJTkdfUFJPVklERVI9JF9BUFBfTE9HR0lOR19QUk9WSURFUgogICAgICAtIE9QUl9FWEVDVVRPUl9MT0dHSU5HX0NPTkZJRz0kX0FQUF9MT0dHSU5HX0NPTkZJRwogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX0RFVklDRT0kX0FQUF9TVE9SQUdFX0RFVklDRQogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX1MzX0FDQ0VTU19LRVk9JF9BUFBfU1RPUkFHRV9TM19BQ0NFU1NfS0VZCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfUzNfU0VDUkVUPSRfQVBQX1NUT1JBR0VfUzNfU0VDUkVUCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfUzNfUkVHSU9OPSRfQVBQX1NUT1JBR0VfUzNfUkVHSU9OCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfUzNfQlVDS0VUPSRfQVBQX1NUT1JBR0VfUzNfQlVDS0VUCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfRE9fU1BBQ0VTX0FDQ0VTU19LRVk9JF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfQUNDRVNTX0tFWQogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX0RPX1NQQUNFU19TRUNSRVQ9JF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfU0VDUkVUCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfRE9fU1BBQ0VTX1JFR0lPTj0kX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19SRUdJT04KICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9ET19TUEFDRVNfQlVDS0VUPSRfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX0JVQ0tFVAogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX0JBQ0tCTEFaRV9BQ0NFU1NfS0VZPSRfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX0FDQ0VTU19LRVkKICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9CQUNLQkxBWkVfU0VDUkVUPSRfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX1NFQ1JFVAogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX0JBQ0tCTEFaRV9SRUdJT049JF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfUkVHSU9OCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfQkFDS0JMQVpFX0JVQ0tFVD0kX0FQUF9TVE9SQUdFX0JBQ0tCTEFaRV9CVUNLRVQKICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9MSU5PREVfQUNDRVNTX0tFWT0kX0FQUF9TVE9SQUdFX0xJTk9ERV9BQ0NFU1NfS0VZCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfTElOT0RFX1NFQ1JFVD0kX0FQUF9TVE9SQUdFX0xJTk9ERV9TRUNSRVQKICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9MSU5PREVfUkVHSU9OPSRfQVBQX1NUT1JBR0VfTElOT0RFX1JFR0lPTgogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX0xJTk9ERV9CVUNLRVQ9JF9BUFBfU1RPUkFHRV9MSU5PREVfQlVDS0VUCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfV0FTQUJJX0FDQ0VTU19LRVk9JF9BUFBfU1RPUkFHRV9XQVNBQklfQUNDRVNTX0tFWQogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX1dBU0FCSV9TRUNSRVQ9JF9BUFBfU1RPUkFHRV9XQVNBQklfU0VDUkVUCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfV0FTQUJJX1JFR0lPTj0kX0FQUF9TVE9SQUdFX1dBU0FCSV9SRUdJT04KICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9XQVNBQklfQlVDS0VUPSRfQVBQX1NUT1JBR0VfV0FTQUJJX0JVQ0tFVAogIG1hcmlhZGI6CiAgICBpbWFnZTogJ21hcmlhZGI6MTAuNycKICAgIGNvbnRhaW5lcl9uYW1lOiBhcHB3cml0ZS1tYXJpYWRiCiAgICBsb2dnaW5nOgogICAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgICBvcHRpb25zOgogICAgICAgIG1heC1maWxlOiAnNScKICAgICAgICBtYXgtc2l6ZTogMTBtCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgbmV0d29ya3M6CiAgICAgIC0gYXBwd3JpdGUKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2FwcHdyaXRlLW1hcmlhZGI6L3Zhci9saWIvbXlzcWw6cncnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke19BUFBfREJfUk9PVF9QQVNTfScKICAgICAgLSAnTVlTUUxfREFUQUJBU0U9JHtfQVBQX0RCX1NDSEVNQX0nCiAgICAgIC0gJ01ZU1FMX1VTRVI9JHtfQVBQX0RCX1VTRVJ9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke19BUFBfREJfUEFTU30nCiAgICBjb21tYW5kOiAnbXlzcWxkIC0taW5ub2RiLWZsdXNoLW1ldGhvZD1mc3luYycKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6Ny4wLjQtYWxwaW5lJwogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLXJlZGlzCiAgICBsb2dnaW5nOgogICAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgICBvcHRpb25zOgogICAgICAgIG1heC1maWxlOiAnNScKICAgICAgICBtYXgtc2l6ZTogMTBtCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogInJlZGlzLXNlcnZlciAtLW1heG1lbW9yeSAgICAgICAgICAgIDUxMm1iIC0tbWF4bWVtb3J5LXBvbGljeSAgICAgYWxsa2V5cy1scnUgLS1tYXhtZW1vcnktc2FtcGxlcyAgICA1XG4iCiAgICBuZXR3b3JrczoKICAgICAgLSBhcHB3cml0ZQogICAgdm9sdW1lczoKICAgICAgLSAnYXBwd3JpdGUtcmVkaXM6L2RhdGE6cncnCiAgaW5mbHV4ZGI6CiAgICBpbWFnZTogJ2FwcHdyaXRlL2luZmx1eGRiOjEuNS4wJwogICAgY29udGFpbmVyX25hbWU6IGFwcHdyaXRlLWluZmx1eGRiCiAgICBsb2dnaW5nOgogICAgICBkcml2ZXI6IGpzb24tZmlsZQogICAgICBvcHRpb25zOgogICAgICAgIG1heC1maWxlOiAnNScKICAgICAgICBtYXgtc2l6ZTogMTBtCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgbmV0d29ya3M6CiAgICAgIC0gYXBwd3JpdGUKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2FwcHdyaXRlLWluZmx1eGRiOi92YXIvbGliL2luZmx1eGRiOnJ3JwogIHRlbGVncmFmOgogICAgaW1hZ2U6ICdhcHB3cml0ZS90ZWxlZ3JhZjoxLjQuMCcKICAgIGNvbnRhaW5lcl9uYW1lOiBhcHB3cml0ZS10ZWxlZ3JhZgogICAgbG9nZ2luZzoKICAgICAgZHJpdmVyOiBqc29uLWZpbGUKICAgICAgb3B0aW9uczoKICAgICAgICBtYXgtZmlsZTogJzUnCiAgICAgICAgbWF4LXNpemU6IDEwbQogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIG5ldHdvcmtzOgogICAgICAtIGFwcHdyaXRlCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBfQVBQX0lORkxVWERCX0hPU1QKICAgICAgLSBfQVBQX0lORkxVWERCX1BPUlQKbmV0d29ya3M6CiAgZ2F0ZXdheToKICAgIG5hbWU6IGdhdGV3YXkKICBhcHB3cml0ZToKICAgIG5hbWU6IGFwcHdyaXRlCiAgcnVudGltZXM6CiAgICBuYW1lOiBydW50aW1lcwp2b2x1bWVzOgogIGFwcHdyaXRlLW1hcmlhZGI6IG51bGwKICBhcHB3cml0ZS1yZWRpczogbnVsbAogIGFwcHdyaXRlLWNhY2hlOiBudWxsCiAgYXBwd3JpdGUtdXBsb2FkczogbnVsbAogIGFwcHdyaXRlLWNlcnRpZmljYXRlczogbnVsbAogIGFwcHdyaXRlLWZ1bmN0aW9uczogbnVsbAogIGFwcHdyaXRlLWJ1aWxkczogbnVsbAogIGFwcHdyaXRlLWluZmx1eGRiOiBudWxsCiAgYXBwd3JpdGUtY29uZmlnOiBudWxsCg==", + "tags": [ + "backend-as-a-service", + "platform" + ], "envs": "X0FQUF9FTlY9cHJvZHVjdGlvbgpfQVBQX0xPQ0FMRT1lbgpfQVBQX09QVElPTlNfQUJVU0U9ZW5hYmxlZApfQVBQX09QVElPTlNfRk9SQ0VfSFRUUFM9ZGlzYWJsZWQKX0FQUF9PUEVOU1NMX0tFWV9WMT0KX0FQUF9ET01BSU5fRlVOQ1RJT05TPQpfQVBQX0NPTlNPTEVfV0hJVEVMSVNUX1JPT1Q9ZW5hYmxlZApfQVBQX0NPTlNPTEVfV0hJVEVMSVNUX0VNQUlMUz0KX0FQUF9DT05TT0xFX1dISVRFTElTVF9JUFM9Cl9BUFBfU1lTVEVNX0VNQUlMX05BTUU9QXBwd3JpdGUKX0FQUF9TWVNURU1fRU1BSUxfQUREUkVTUz10ZWFtQGFwcHdyaXRlLmlvCl9BUFBfU1lTVEVNX1JFU1BPTlNFX0ZPUk1BVD0KX0FQUF9TWVNURU1fU0VDVVJJVFlfRU1BSUxfQUREUkVTUz1jZXJ0c0BhcHB3cml0ZS5pbwpfQVBQX1VTQUdFX1NUQVRTPWVuYWJsZWQKX0FQUF9MT0dHSU5HX1BST1ZJREVSPQpfQVBQX0xPR0dJTkdfQ09ORklHPQpfQVBQX1VTQUdFX0FHR1JFR0FUSU9OX0lOVEVSVkFMPTMwCl9BUFBfVVNBR0VfVElNRVNFUklFU19JTlRFUlZBTD0zMApfQVBQX1VTQUdFX0RBVEFCQVNFX0lOVEVSVkFMPTkwMApfQVBQX1dPUktFUl9QRVJfQ09SRT02Cl9BUFBfUkVESVNfSE9TVD1yZWRpcwpfQVBQX1JFRElTX1BPUlQ9NjM3OQpfQVBQX1JFRElTX1VTRVI9Cl9BUFBfUkVESVNfUEFTUz0KX0FQUF9EQl9IT1NUPW1hcmlhZGIKX0FQUF9EQl9QT1JUPTMzMDYKX0FQUF9EQl9TQ0hFTUE9YXBwd3JpdGUKX0FQUF9EQl9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKX0FQUF9EQl9QQVNTPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCl9BUFBfREJfUk9PVF9QQVNTPSRTRVJWSUNFX1BBU1NXT1JEX1JPT1RNWVNRTApfQVBQX0lORkxVWERCX0hPU1Q9aW5mbHV4ZGIKX0FQUF9JTkZMVVhEQl9QT1JUPTgwODYKX0FQUF9TVEFUU0RfSE9TVD10ZWxlZ3JhZgpfQVBQX1NUQVRTRF9QT1JUPTgxMjUKX0FQUF9TTVRQX0hPU1Q9Cl9BUFBfU01UUF9QT1JUPQpfQVBQX1NNVFBfU0VDVVJFPQpfQVBQX1NNVFBfVVNFUk5BTUU9Cl9BUFBfU01UUF9QQVNTV09SRD0KX0FQUF9TTVNfUFJPVklERVI9Cl9BUFBfU01TX0ZST009Cl9BUFBfU1RPUkFHRV9MSU1JVD0zMDAwMDAwMApfQVBQX1NUT1JBR0VfUFJFVklFV19MSU1JVD0yMDAwMDAwMApfQVBQX1NUT1JBR0VfQU5USVZJUlVTPWRpc2FibGVkCl9BUFBfU1RPUkFHRV9BTlRJVklSVVNfSE9TVD1jbGFtYXYKX0FQUF9TVE9SQUdFX0FOVElWSVJVU19QT1JUPTMzMTAKX0FQUF9TVE9SQUdFX0RFVklDRT1sb2NhbApfQVBQX1NUT1JBR0VfUzNfQUNDRVNTX0tFWT0KX0FQUF9TVE9SQUdFX1MzX1NFQ1JFVD0KX0FQUF9TVE9SQUdFX1MzX1JFR0lPTj11cy1lYXN0LTEKX0FQUF9TVE9SQUdFX1MzX0JVQ0tFVD0KX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19BQ0NFU1NfS0VZPQpfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX1NFQ1JFVD0KX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19SRUdJT049dXMtZWFzdC0xCl9BUFBfU1RPUkFHRV9ET19TUEFDRVNfQlVDS0VUPQpfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX0FDQ0VTU19LRVk9Cl9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfU0VDUkVUPQpfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX1JFR0lPTj11cy13ZXN0LTAwNApfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX0JVQ0tFVD0KX0FQUF9TVE9SQUdFX0xJTk9ERV9BQ0NFU1NfS0VZPQpfQVBQX1NUT1JBR0VfTElOT0RFX1NFQ1JFVD0KX0FQUF9TVE9SQUdFX0xJTk9ERV9SRUdJT049ZXUtY2VudHJhbC0xCl9BUFBfU1RPUkFHRV9MSU5PREVfQlVDS0VUPQpfQVBQX1NUT1JBR0VfV0FTQUJJX0FDQ0VTU19LRVk9Cl9BUFBfU1RPUkFHRV9XQVNBQklfU0VDUkVUPQpfQVBQX1NUT1JBR0VfV0FTQUJJX1JFR0lPTj1ldS1jZW50cmFsLTEKX0FQUF9TVE9SQUdFX1dBU0FCSV9CVUNLRVQ9Cl9BUFBfRlVOQ1RJT05TX1NJWkVfTElNSVQ9MzAwMDAwMDAKX0FQUF9GVU5DVElPTlNfVElNRU9VVD05MDAKX0FQUF9GVU5DVElPTlNfQlVJTERfVElNRU9VVD05MDAKX0FQUF9GVU5DVElPTlNfQ09OVEFJTkVSUz0xMApfQVBQX0ZVTkNUSU9OU19DUFVTPTAKX0FQUF9GVU5DVElPTlNfTUVNT1JZPTAKX0FQUF9GVU5DVElPTlNfTUVNT1JZX1NXQVA9MApfQVBQX0ZVTkNUSU9OU19SVU5USU1FUz1ub2RlLTE2LjAscGhwLTguMCxweXRob24tMy45LHJ1YnktMy4wCl9BUFBfRVhFQ1VUT1JfU0VDUkVUPXlvdXItc2VjcmV0LWtleQpfQVBQX0VYRUNVVE9SX0hPU1Q9aHR0cDovL2FwcHdyaXRlLWV4ZWN1dG9yL3YxCl9BUFBfRVhFQ1VUT1JfUlVOVElNRV9ORVRXT1JLPWFwcHdyaXRlX3J1bnRpbWVzCl9BUFBfRlVOQ1RJT05TX0VOVlM9bm9kZS0xNi4wLHBocC03LjQscHl0aG9uLTMuOSxydWJ5LTMuMApfQVBQX0ZVTkNUSU9OU19JTkFDVElWRV9USFJFU0hPTEQ9NjAKRE9DS0VSSFVCX1BVTExfVVNFUk5BTUU9CkRPQ0tFUkhVQl9QVUxMX1BBU1NXT1JEPQpET0NLRVJIVUJfUFVMTF9FTUFJTD0KT1BFTl9SVU5USU1FU19ORVRXT1JLPWFwcHdyaXRlX3J1bnRpbWVzCl9BUFBfRlVOQ1RJT05TX1JVTlRJTUVTX05FVFdPUks9cnVudGltZXMKX0FQUF9ET0NLRVJfSFVCX1VTRVJOQU1FPQpfQVBQX0RPQ0tFUl9IVUJfUEFTU1dPUkQ9Cl9BUFBfRlVOQ1RJT05TX01BSU5URU5BTkNFX0lOVEVSVkFMPTM2MDAKX0FQUF9WQ1NfR0lUSFVCX0FQUF9OQU1FPQpfQVBQX1ZDU19HSVRIVUJfUFJJVkFURV9LRVk9Cl9BUFBfVkNTX0dJVEhVQl9BUFBfSUQ9Cl9BUFBfVkNTX0dJVEhVQl9DTElFTlRfSUQ9Cl9BUFBfVkNTX0dJVEhVQl9DTElFTlRfU0VDUkVUPQpfQVBQX1ZDU19HSVRIVUJfV0VCSE9PS19TRUNSRVQ9Cl9BUFBfTUFJTlRFTkFOQ0VfSU5URVJWQUw9ODY0MDAKX0FQUF9NQUlOVEVOQU5DRV9SRVRFTlRJT05fQ0FDSEU9MjU5MjAwMApfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9FWEVDVVRJT049MTIwOTYwMApfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9BVURJVD0xMjA5NjAwCl9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX0FCVVNFPTg2NDAwCl9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX1VTQUdFX0hPVVJMWT04NjQwMDAwCl9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX1NDSEVEVUxFUz04NjQwMApfQVBQX0dSQVBIUUxfTUFYX0JBVENIX1NJWkU9MTAKX0FQUF9HUkFQSFFMX01BWF9DT01QTEVYSVRZPTI1MApfQVBQX0dSQVBIUUxfTUFYX0RFUFRIPTMKX0FQUF9NSUdSQVRJT05TX0ZJUkVCQVNFX0NMSUVOVF9JRD0KX0FQUF9NSUdSQVRJT05TX0ZJUkVCQVNFX0NMSUVOVF9TRUNSRVQ9Cl9BUFBfQVNTSVNUQU5UX09QRU5BSV9BUElfS0VZPQo=" }, "babybuddy": { "documentation": "https:\/\/docs.baby-buddy.net", "slogan": "Baby Buddy is an open-source web application that helps parents track their baby's daily activities, growth, and health with ease.", - "compose": "c2VydmljZXM6CiAgYmFieWJ1ZGR5OgogICAgaW1hZ2U6ICdsc2NyLmlvL2xpbnV4c2VydmVyL2JhYnlidWRkeTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQkFCWUJVRERZCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgICAtIENTUkZfVFJVU1RFRF9PUklHSU5TPSRTRVJWSUNFX0ZRRE5fQkFCWUJVRERZCiAgICB2b2x1bWVzOgogICAgICAtICdiYWJ5YnVkZHktY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAwMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=" + "compose": "c2VydmljZXM6CiAgYmFieWJ1ZGR5OgogICAgaW1hZ2U6ICdsc2NyLmlvL2xpbnV4c2VydmVyL2JhYnlidWRkeTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQkFCWUJVRERZCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgICAtIENTUkZfVFJVU1RFRF9PUklHSU5TPSRTRVJWSUNFX0ZRRE5fQkFCWUJVRERZCiAgICB2b2x1bWVzOgogICAgICAtICdiYWJ5YnVkZHktY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAwMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=", + "tags": [ + "baby", + "parents", + "health", + "growth", + "activities" + ] }, "code-server": { "documentation": "https:\/\/coder.com\/docs\/code-server\/latest\/guide", "slogan": "Code-Server is a self-hosted, web-based code editor that enables remote coding and collaboration from any device, anywhere.", - "compose": "c2VydmljZXM6CiAgY29kZS1zZXJ2ZXI6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvY29kZS1zZXJ2ZXI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0NPREVTRVJWRVIKICAgICAgLSBQVUlEPTEwMDAKICAgICAgLSBQR0lEPTEwMDAKICAgICAgLSBUWj1FdXJvcGUvTWFkcmlkCiAgICAgIC0gUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfNjRfUEFTU1dPUkRDT0RFU0VSVkVSCiAgICAgIC0gU1VET19QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9TVURPQ09ERVNFUlZFUgogICAgICAtIERFRkFVTFRfV09SS1NQQUNFPS9jb25maWcvd29ya3NwYWNlCiAgICB2b2x1bWVzOgogICAgICAtICdjb2RlLXNlcnZlci1jb25maWc6L2NvbmZpZycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo4NDQzJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==" + "compose": "c2VydmljZXM6CiAgY29kZS1zZXJ2ZXI6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvY29kZS1zZXJ2ZXI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0NPREVTRVJWRVIKICAgICAgLSBQVUlEPTEwMDAKICAgICAgLSBQR0lEPTEwMDAKICAgICAgLSBUWj1FdXJvcGUvTWFkcmlkCiAgICAgIC0gUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfNjRfUEFTU1dPUkRDT0RFU0VSVkVSCiAgICAgIC0gU1VET19QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9TVURPQ09ERVNFUlZFUgogICAgICAtIERFRkFVTFRfV09SS1NQQUNFPS9jb25maWcvd29ya3NwYWNlCiAgICB2b2x1bWVzOgogICAgICAtICdjb2RlLXNlcnZlci1jb25maWc6L2NvbmZpZycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo4NDQzJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", + "tags": [ + "code", + "editor", + "remote", + "collaboration" + ] }, "dokuwiki": { "documentation": "https:\/\/www.dokuwiki.org\/faq", "slogan": "A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility.", - "compose": "c2VydmljZXM6CiAgZG9rdXdpa2k6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvZG9rdXdpa2k6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0RPS1VXSUtJCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgdm9sdW1lczoKICAgICAgLSAnZG9rdXdpa2ktY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK" + "compose": "c2VydmljZXM6CiAgZG9rdXdpa2k6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvZG9rdXdpa2k6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0RPS1VXSUtJCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgdm9sdW1lczoKICAgICAgLSAnZG9rdXdpa2ktY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "tags": [ + "wiki", + "documentation", + "knowledge", + "base" + ] }, "fider": { "documentation": "https:\/\/fider.io\/doc", "slogan": "Fider is an open-source feedback platform for collecting and managing user feedback, helping you prioritize improvements to your products and services.", - "compose": "c2VydmljZXM6CiAgZmlkZXI6CiAgICBpbWFnZTogJ2dldGZpZGVyL2ZpZGVyOnN0YWJsZScKICAgIGVudmlyb25tZW50OgogICAgICBCQVNFX1VSTDogJFNFUlZJQ0VfRlFETl9GSURFUgogICAgICBEQVRBQkFTRV9VUkw6ICdwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfTVlTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxAZGF0YWJhc2U6NTQzMi9maWRlcj9zc2xtb2RlPWRpc2FibGUnCiAgICAgIEpXVF9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0ZJREVSCiAgICAgIEVNQUlMX05PUkVQTFk6ICcke0VNQUlMX05PUkVQTFk6LW5vcmVwbHlAZXhhbXBsZS5jb219JwogICAgICBFTUFJTF9NQUlMR1VOX0FQSTogJEVNQUlMX01BSUxHVU5fQVBJCiAgICAgIEVNQUlMX01BSUxHVU5fRE9NQUlOOiAkRU1BSUxfTUFJTEdVTl9ET01BSU4KICAgICAgRU1BSUxfTUFJTEdVTl9SRUdJT046ICRFTUFJTF9NQUlMR1VOX1JFR0lPTgogICAgICBFTUFJTF9TTVRQX0hPU1Q6ICcke0VNQUlMX1NNVFBfSE9TVDotc210cC5tYWlsZ3VuLmNvbX0nCiAgICAgIEVNQUlMX1NNVFBfUE9SVDogJyR7RU1BSUxfU01UUF9QT1JUOi01ODd9JwogICAgICBFTUFJTF9TTVRQX1VTRVJOQU1FOiAnJHtFTUFJTF9TTVRQX1VTRVJOQU1FOi1wb3N0bWFzdGVyQG1haWxndW4uY29tfScKICAgICAgRU1BSUxfU01UUF9QQVNTV09SRDogJEVNQUlMX1NNVFBfUEFTU1dPUkQKICAgICAgRU1BSUxfU01UUF9FTkFCTEVfU1RBUlRUTFM6ICRFTUFJTF9TTVRQX0VOQUJMRV9TVEFSVFRMUwogICAgICBFTUFJTF9BV1NTRVNfUkVHSU9OOiAkRU1BSUxfQVdTU0VTX1JFR0lPTgogICAgICBFTUFJTF9BV1NTRVNfQUNDRVNTX0tFWV9JRDogJEVNQUlMX0FXU1NFU19BQ0NFU1NfS0VZX0lECiAgICAgIEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWTogJEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWQogIGRhdGFiYXNlOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxMicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BnX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIFBPU1RHUkVTX1VTRVI6ICRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIFBPU1RHUkVTX0RCOiAnJHtQT1NUR1JFU19EQjotZmlkZXJ9Jwo=" + "compose": "c2VydmljZXM6CiAgZmlkZXI6CiAgICBpbWFnZTogJ2dldGZpZGVyL2ZpZGVyOnN0YWJsZScKICAgIGVudmlyb25tZW50OgogICAgICBCQVNFX1VSTDogJFNFUlZJQ0VfRlFETl9GSURFUgogICAgICBEQVRBQkFTRV9VUkw6ICdwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfTVlTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxAZGF0YWJhc2U6NTQzMi9maWRlcj9zc2xtb2RlPWRpc2FibGUnCiAgICAgIEpXVF9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0ZJREVSCiAgICAgIEVNQUlMX05PUkVQTFk6ICcke0VNQUlMX05PUkVQTFk6LW5vcmVwbHlAZXhhbXBsZS5jb219JwogICAgICBFTUFJTF9NQUlMR1VOX0FQSTogJEVNQUlMX01BSUxHVU5fQVBJCiAgICAgIEVNQUlMX01BSUxHVU5fRE9NQUlOOiAkRU1BSUxfTUFJTEdVTl9ET01BSU4KICAgICAgRU1BSUxfTUFJTEdVTl9SRUdJT046ICRFTUFJTF9NQUlMR1VOX1JFR0lPTgogICAgICBFTUFJTF9TTVRQX0hPU1Q6ICcke0VNQUlMX1NNVFBfSE9TVDotc210cC5tYWlsZ3VuLmNvbX0nCiAgICAgIEVNQUlMX1NNVFBfUE9SVDogJyR7RU1BSUxfU01UUF9QT1JUOi01ODd9JwogICAgICBFTUFJTF9TTVRQX1VTRVJOQU1FOiAnJHtFTUFJTF9TTVRQX1VTRVJOQU1FOi1wb3N0bWFzdGVyQG1haWxndW4uY29tfScKICAgICAgRU1BSUxfU01UUF9QQVNTV09SRDogJEVNQUlMX1NNVFBfUEFTU1dPUkQKICAgICAgRU1BSUxfU01UUF9FTkFCTEVfU1RBUlRUTFM6ICRFTUFJTF9TTVRQX0VOQUJMRV9TVEFSVFRMUwogICAgICBFTUFJTF9BV1NTRVNfUkVHSU9OOiAkRU1BSUxfQVdTU0VTX1JFR0lPTgogICAgICBFTUFJTF9BV1NTRVNfQUNDRVNTX0tFWV9JRDogJEVNQUlMX0FXU1NFU19BQ0NFU1NfS0VZX0lECiAgICAgIEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWTogJEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWQogIGRhdGFiYXNlOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxMicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BnX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIFBPU1RHUkVTX1VTRVI6ICRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIFBPU1RHUkVTX0RCOiAnJHtQT1NUR1JFU19EQjotZmlkZXJ9Jwo=", + "tags": [ + "feedback", + "user-feedback" + ] }, "ghost": { "documentation": "https:\/\/ghost.org\/docs", "slogan": "Ghost is a popular open-source content management system (CMS) and blogging platform, known for its simplicity and focus on content creation.", - "compose": "c2VydmljZXM6CiAgZ2hvc3Q6CiAgICBpbWFnZTogJ2dob3N0OjUnCiAgICB2b2x1bWVzOgogICAgICAtICdnaG9zdC1jb250ZW50LWRhdGE6L3Zhci9saWIvZ2hvc3QvY29udGVudCcKICAgIGVudmlyb25tZW50OgogICAgICAtIHVybD0kU0VSVklDRV9GUUROX0dIT1NUCiAgICAgIC0gZGF0YWJhc2VfX2NsaWVudD1teXNxbAogICAgICAtIGRhdGFiYXNlX19jb25uZWN0aW9uX19ob3N0PW15c3FsCiAgICAgIC0gZGF0YWJhc2VfX2Nvbm5lY3Rpb25fX3VzZXI9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIGRhdGFiYXNlX19jb25uZWN0aW9uX19wYXNzd29yZD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgICAtICdkYXRhYmFzZV9fY29ubmVjdGlvbl9fZGF0YWJhc2U9JHtNWVNRTF9EQVRBQkFTRS1naG9zdH0nCiAgICBkZXBlbmRzX29uOgogICAgICBteXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIG15c3FsOgogICAgaW1hZ2U6ICdteXNxbDo4LjAnCiAgICB2b2x1bWVzOgogICAgICAtICdnaG9zdC1teXNxbC1kYXRhOi92YXIvbGliL215c3FsJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01ZU1FMX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUx9JwogICAgICAtICdNWVNRTF9EQVRBQkFTRT0ke01ZU1FMX0RBVEFCQVNFfScKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBteXNxbGFkbWluCiAgICAgICAgLSBwaW5nCiAgICAgICAgLSAnLWgnCiAgICAgICAgLSBsb2NhbGhvc3QKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCg==" + "compose": "c2VydmljZXM6CiAgZ2hvc3Q6CiAgICBpbWFnZTogJ2dob3N0OjUnCiAgICB2b2x1bWVzOgogICAgICAtICdnaG9zdC1jb250ZW50LWRhdGE6L3Zhci9saWIvZ2hvc3QvY29udGVudCcKICAgIGVudmlyb25tZW50OgogICAgICAtIHVybD0kU0VSVklDRV9GUUROX0dIT1NUCiAgICAgIC0gZGF0YWJhc2VfX2NsaWVudD1teXNxbAogICAgICAtIGRhdGFiYXNlX19jb25uZWN0aW9uX19ob3N0PW15c3FsCiAgICAgIC0gZGF0YWJhc2VfX2Nvbm5lY3Rpb25fX3VzZXI9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIGRhdGFiYXNlX19jb25uZWN0aW9uX19wYXNzd29yZD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgICAtICdkYXRhYmFzZV9fY29ubmVjdGlvbl9fZGF0YWJhc2U9JHtNWVNRTF9EQVRBQkFTRS1naG9zdH0nCiAgICBkZXBlbmRzX29uOgogICAgICBteXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIG15c3FsOgogICAgaW1hZ2U6ICdteXNxbDo4LjAnCiAgICB2b2x1bWVzOgogICAgICAtICdnaG9zdC1teXNxbC1kYXRhOi92YXIvbGliL215c3FsJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01ZU1FMX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUx9JwogICAgICAtICdNWVNRTF9EQVRBQkFTRT0ke01ZU1FMX0RBVEFCQVNFfScKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBteXNxbGFkbWluCiAgICAgICAgLSBwaW5nCiAgICAgICAgLSAnLWgnCiAgICAgICAgLSBsb2NhbGhvc3QKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCg==", + "tags": [ + "cms", + "blog", + "content", + "management", + "system" + ] }, "heimdall": { "documentation": "https:\/\/github.com\/linuxserver\/Heimdall", "slogan": "Heimdall is a self-hosted dashboard for managing and organizing your server applications, providing a centralized and efficient interface.", - "compose": "c2VydmljZXM6CiAgaGVpbWRhbGw6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvaGVpbWRhbGw6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0hFSU1EQUxMCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgdm9sdW1lczoKICAgICAgLSAnaGVpbWRhbGwtY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK" + "compose": "c2VydmljZXM6CiAgaGVpbWRhbGw6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvaGVpbWRhbGw6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0hFSU1EQUxMCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgdm9sdW1lczoKICAgICAgLSAnaGVpbWRhbGwtY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "tags": [ + "dashboard", + "server", + "applications", + "interface" + ] }, "metube": { "documentation": "https:\/\/github.com\/alexta69\/metube", "slogan": "A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.", - "compose": "c2VydmljZXM6CiAgbWV0dWJlOgogICAgaW1hZ2U6ICdnaGNyLmlvL2FsZXh0YTY5L21ldHViZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTUVUVUJFCiAgICAgIC0gVUlEPTEwMDAKICAgICAgLSBHSUQ9MTAwMAogICAgdm9sdW1lczoKICAgICAgLSAnbWV0dWJlLWRvd25sb2FkczovZG93bmxvYWRzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwODEnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK" + "compose": "c2VydmljZXM6CiAgbWV0dWJlOgogICAgaW1hZ2U6ICdnaGNyLmlvL2FsZXh0YTY5L21ldHViZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTUVUVUJFCiAgICAgIC0gVUlEPTEwMDAKICAgICAgLSBHSUQ9MTAwMAogICAgdm9sdW1lczoKICAgICAgLSAnbWV0dWJlLWRvd25sb2FkczovZG93bmxvYWRzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwODEnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "tags": [ + "youtube", + "download", + "videos", + "playlist" + ] }, "minio": { "documentation": "https:\/\/docs.min.io\/docs\/minio-docker-quickstart-guide.html", "slogan": "MinIO is a high performance object storage server compatible with Amazon S3 APIs.", - "compose": "c2VydmljZXM6CiAgbWluaW86CiAgICBpbWFnZTogJ3F1YXkuaW8vbWluaW8vbWluaW86bGF0ZXN0JwogICAgY29tbWFuZDogJ3NlcnZlciAvZGF0YSAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIFNFUlZJQ0VfRlFETl9NSU5JT185MDAwOiBudWxsCiAgICAgIFNFUlZJQ0VfRlFETl9DT05TT0xFXzkwMDE6IG51bGwKICAgICAgTUlOSU9fUk9PVF9VU0VSOiAkU0VSVklDRV9VU0VSX01JTklPCiAgICAgIE1JTklPX1JPT1RfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICB2b2x1bWVzOgogICAgICAtICdtaW5pby1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjkwMDAvbWluaW8vaGVhbHRoL2xpdmUnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK" + "compose": "c2VydmljZXM6CiAgbWluaW86CiAgICBpbWFnZTogJ3F1YXkuaW8vbWluaW8vbWluaW86bGF0ZXN0JwogICAgY29tbWFuZDogJ3NlcnZlciAvZGF0YSAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIFNFUlZJQ0VfRlFETl9NSU5JT185MDAwOiBudWxsCiAgICAgIFNFUlZJQ0VfRlFETl9DT05TT0xFXzkwMDE6IG51bGwKICAgICAgTUlOSU9fUk9PVF9VU0VSOiAkU0VSVklDRV9VU0VSX01JTklPCiAgICAgIE1JTklPX1JPT1RfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICB2b2x1bWVzOgogICAgICAtICdtaW5pby1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjkwMDAvbWluaW8vaGVhbHRoL2xpdmUnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "tags": [ + "object", + "storage", + "server", + "s3", + "api" + ] }, "pairdrop": { "documentation": "https:\/\/github.com\/schlagmichdoch\/PairDrop\/blob\/master\/docs\/faq.md", "slogan": "Pairdrop is a self-hosted file sharing and collaboration platform, offering secure file sharing and collaboration capabilities for efficient teamwork.", - "compose": "c2VydmljZXM6CiAgcGFpcmRyb3A6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvcGFpcmRyb3A6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1BBSVJEUk9QCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgICAtIERFQlVHX01PREU9ZmFsc2UKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDozMDAwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==" + "compose": "c2VydmljZXM6CiAgcGFpcmRyb3A6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvcGFpcmRyb3A6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1BBSVJEUk9QCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgICAtIERFQlVHX01PREU9ZmFsc2UKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDozMDAwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", + "tags": [ + "file", + "sharing", + "collaboration", + "teamwork" + ] }, "snapdrop": { "documentation": "https:\/\/github.com\/RobinLinus\/snapdrop\/blob\/master\/docs\/faq.md", "slogan": "A self-hosted file-sharing service for secure and convenient file transfers, whether on a local network or the internet.", - "compose": "c2VydmljZXM6CiAgc25hcGRyb3A6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvc25hcGRyb3A6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1NOQVBEUk9QCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgdm9sdW1lczoKICAgICAgLSAnc25hcGRyb3AtY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK" + "compose": "c2VydmljZXM6CiAgc25hcGRyb3A6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvc25hcGRyb3A6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1NOQVBEUk9QCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgdm9sdW1lczoKICAgICAgLSAnc25hcGRyb3AtY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "tags": [ + "file", + "sharing", + "transfer", + "local", + "network", + "internet" + ] }, "umami": { "documentation": "https:\/\/umami.is\/docs\/getting-started", "slogan": "Umami is a lightweight, self-hosted web analytics platform designed to provide website owners with insights into visitor behavior without compromising user privacy.", - "compose": "c2VydmljZXM6CiAgdW1hbWk6CiAgICBpbWFnZTogJ2doY3IuaW8vdW1hbWktc29mdHdhcmUvdW1hbWk6cG9zdGdyZXNxbC1sYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fVU1BTUkKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtIERBVEFCQVNFX1RZUEU9cG9zdGdyZXMKICAgICAgLSBBUFBfU0VDUkVUPSRTRVJWSUNFX1BBU1NXT1JEXzY0X1VNQU1JCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXVtYW1pfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAo=" + "compose": "c2VydmljZXM6CiAgdW1hbWk6CiAgICBpbWFnZTogJ2doY3IuaW8vdW1hbWktc29mdHdhcmUvdW1hbWk6cG9zdGdyZXNxbC1sYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fVU1BTUkKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlc3FsOjU0MzIvJFBPU1RHUkVTX0RCJwogICAgICAtIERBVEFCQVNFX1RZUEU9cG9zdGdyZXMKICAgICAgLSBBUFBfU0VDUkVUPSRTRVJWSUNFX1BBU1NXT1JEXzY0X1VNQU1JCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXVtYW1pfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAo=", + "tags": [ + "analytics", + "insights", + "privacy" + ] }, "uptime-kuma": { "documentation": "https:\/\/github.com\/louislam\/uptime-kuma\/wiki", "slogan": "Uptime Kuma is a free, self-hosted monitoring tool for tracking the status and performance of your web services and applications in real-time.", - "compose": "c2VydmljZXM6CiAgdXB0aW1lLWt1bWE6CiAgICBpbWFnZTogJ2xvdWlzbGFtL3VwdGltZS1rdW1hOjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE4KICAgIHZvbHVtZXM6CiAgICAgIC0gJ3VwdGltZS1rdW1hOi9hcHAvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSBleHRyYS9oZWFsdGhjaGVjawogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==" + "compose": "c2VydmljZXM6CiAgdXB0aW1lLWt1bWE6CiAgICBpbWFnZTogJ2xvdWlzbGFtL3VwdGltZS1rdW1hOjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE4KICAgIHZvbHVtZXM6CiAgICAgIC0gJ3VwdGltZS1rdW1hOi9hcHAvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSBleHRyYS9oZWFsdGhjaGVjawogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", + "tags": [ + "monitoring", + "status", + "performance", + "web", + "services", + "applications", + "real-time" + ] }, "wordpress-with-mariadb": { "documentation": "https:\/\/wordpress.org\/documentation\/", "slogan": "\"WordPress is open source software you can use to create a beautiful website, blog, or app.\"", - "compose": "c2VydmljZXM6CiAgd29yZHByZXNzOgogICAgaW1hZ2U6ICd3b3JkcHJlc3M6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAnd29yZHByZXNzLWZpbGVzOi92YXIvd3d3L2h0bWwnCiAgICBlbnZpcm9ubWVudDoKICAgICAgU0VSVklDRV9GUUROOiBudWxsCiAgICAgIFdPUkRQUkVTU19EQl9IT1NUOiBtYXJpYWRiCiAgICAgIFdPUkRQUkVTU19EQl9VU0VSOiAkU0VSVklDRV9VU0VSX1dPUkRQUkVTUwogICAgICBXT1JEUFJFU1NfREJfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1dPUkRQUkVTUwogICAgICBXT1JEUFJFU1NfREJfTkFNRTogd29yZHByZXNzCiAgICBkZXBlbmRzX29uOgogICAgICAtIG1hcmlhZGIKICBtYXJpYWRiOgogICAgaW1hZ2U6ICdtYXJpYWRiOjExJwogICAgdm9sdW1lczoKICAgICAgLSAnbWFyaWFkYi1kYXRhOi92YXIvbGliL215c3FsJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIE1ZU1FMX1JPT1RfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1JPT1QKICAgICAgTVlTUUxfREFUQUJBU0U6IHdvcmRwcmVzcwogICAgICBNWVNRTF9VU0VSOiAkU0VSVklDRV9VU0VSX1dPUkRQUkVTUwogICAgICBNWVNRTF9QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfV09SRFBSRVNTCg==" + "compose": "c2VydmljZXM6CiAgd29yZHByZXNzOgogICAgaW1hZ2U6ICd3b3JkcHJlc3M6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAnd29yZHByZXNzLWZpbGVzOi92YXIvd3d3L2h0bWwnCiAgICBlbnZpcm9ubWVudDoKICAgICAgU0VSVklDRV9GUUROOiBudWxsCiAgICAgIFdPUkRQUkVTU19EQl9IT1NUOiBtYXJpYWRiCiAgICAgIFdPUkRQUkVTU19EQl9VU0VSOiAkU0VSVklDRV9VU0VSX1dPUkRQUkVTUwogICAgICBXT1JEUFJFU1NfREJfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1dPUkRQUkVTUwogICAgICBXT1JEUFJFU1NfREJfTkFNRTogd29yZHByZXNzCiAgICBkZXBlbmRzX29uOgogICAgICAtIG1hcmlhZGIKICBtYXJpYWRiOgogICAgaW1hZ2U6ICdtYXJpYWRiOjExJwogICAgdm9sdW1lczoKICAgICAgLSAnbWFyaWFkYi1kYXRhOi92YXIvbGliL215c3FsJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIE1ZU1FMX1JPT1RfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1JPT1QKICAgICAgTVlTUUxfREFUQUJBU0U6IHdvcmRwcmVzcwogICAgICBNWVNRTF9VU0VSOiAkU0VSVklDRV9VU0VSX1dPUkRQUkVTUwogICAgICBNWVNRTF9QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfV09SRFBSRVNTCg==", + "tags": [ + "cms", + "blog", + "content", + "management", + "mariadb" + ] }, "wordpress-with-mysql": { "documentation": "https:\/\/wordpress.org\/documentation\/", "slogan": "\"WordPress is open source software you can use to create a beautiful website, blog, or app.\"", - "compose": "c2VydmljZXM6CiAgd29yZHByZXNzOgogICAgaW1hZ2U6ICd3b3JkcHJlc3M6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAnd29yZHByZXNzLWZpbGVzOi92YXIvd3d3L2h0bWwnCiAgICBlbnZpcm9ubWVudDoKICAgICAgU0VSVklDRV9GUUROOiBudWxsCiAgICAgIFdPUkRQUkVTU19EQl9IT1NUOiBteXNxbAogICAgICBXT1JEUFJFU1NfREJfVVNFUjogJFNFUlZJQ0VfVVNFUl9XT1JEUFJFU1MKICAgICAgV09SRFBSRVNTX0RCX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9XT1JEUFJFU1MKICAgICAgV09SRFBSRVNTX0RCX05BTUU6IHdvcmRwcmVzcwogICAgZGVwZW5kc19vbjoKICAgICAgLSBteXNxbAogIG15c3FsOgogICAgaW1hZ2U6ICdteXNxbDo1LjcnCiAgICB2b2x1bWVzOgogICAgICAtICdteXNxbC1kYXRhOi92YXIvbGliL215c3FsJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIE1ZU1FMX1JPT1RfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1JPT1QKICAgICAgTVlTUUxfREFUQUJBU0U6IHdvcmRwcmVzcwogICAgICBNWVNRTF9VU0VSOiAkU0VSVklDRV9VU0VSX1dPUkRQUkVTUwogICAgICBNWVNRTF9QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfV09SRFBSRVNTCg==" + "compose": "c2VydmljZXM6CiAgd29yZHByZXNzOgogICAgaW1hZ2U6ICd3b3JkcHJlc3M6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAnd29yZHByZXNzLWZpbGVzOi92YXIvd3d3L2h0bWwnCiAgICBlbnZpcm9ubWVudDoKICAgICAgU0VSVklDRV9GUUROOiBudWxsCiAgICAgIFdPUkRQUkVTU19EQl9IT1NUOiBteXNxbAogICAgICBXT1JEUFJFU1NfREJfVVNFUjogJFNFUlZJQ0VfVVNFUl9XT1JEUFJFU1MKICAgICAgV09SRFBSRVNTX0RCX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9XT1JEUFJFU1MKICAgICAgV09SRFBSRVNTX0RCX05BTUU6IHdvcmRwcmVzcwogICAgZGVwZW5kc19vbjoKICAgICAgLSBteXNxbAogIG15c3FsOgogICAgaW1hZ2U6ICdteXNxbDo1LjcnCiAgICB2b2x1bWVzOgogICAgICAtICdteXNxbC1kYXRhOi92YXIvbGliL215c3FsJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIE1ZU1FMX1JPT1RfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1JPT1QKICAgICAgTVlTUUxfREFUQUJBU0U6IHdvcmRwcmVzcwogICAgICBNWVNRTF9VU0VSOiAkU0VSVklDRV9VU0VSX1dPUkRQUkVTUwogICAgICBNWVNRTF9QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfV09SRFBSRVNTCg==", + "tags": [ + "cms", + "blog", + "content", + "management", + "mysql" + ] }, "wordpress-without-database": { "documentation": "https:\/\/wordpress.org\/documentation\/", "slogan": "\"WordPress is open source software you can use to create a beautiful website, blog, or app.\"", - "compose": "c2VydmljZXM6CiAgd29yZHByZXNzOgogICAgaW1hZ2U6ICd3b3JkcHJlc3M6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAnd29yZHByZXNzLWZpbGVzOi92YXIvd3d3L2h0bWwnCiAgICBlbnZpcm9ubWVudDoKICAgICAgU0VSVklDRV9GUUROOiBudWxsCg==" + "compose": "c2VydmljZXM6CiAgd29yZHByZXNzOgogICAgaW1hZ2U6ICd3b3JkcHJlc3M6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAnd29yZHByZXNzLWZpbGVzOi92YXIvd3d3L2h0bWwnCiAgICBlbnZpcm9ubWVudDoKICAgICAgU0VSVklDRV9GUUROOiBudWxsCg==", + "tags": [ + "cms", + "blog", + "content", + "management" + ] } } \ No newline at end of file From f801bb98cd11c1d43881ab3d23af5fd1226cca13 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 24 Oct 2023 14:31:28 +0200 Subject: [PATCH 6/7] feat: mysql, mariadb --- app/Actions/Database/StartDatabaseProxy.php | 8 +- app/Actions/Database/StartMariadb.php | 158 ++++++++++++++++++ app/Actions/Database/StartMysql.php | 158 ++++++++++++++++++ app/Actions/Database/StopDatabase.php | 4 +- app/Actions/Database/StopDatabaseProxy.php | 4 +- app/Http/Controllers/ProjectController.php | 8 +- app/Http/Livewire/Project/CloneProject.php | 4 + .../Project/Database/BackupExecution.php | 23 --- .../Project/Database/BackupExecutions.php | 15 +- .../Database/CreateScheduledBackup.php | 4 + .../Livewire/Project/Database/Heading.php | 14 +- .../Project/Database/Mariadb/General.php | 95 +++++++++++ .../Project/Database/Mongodb/General.php | 11 +- .../Project/Database/Mysql/General.php | 95 +++++++++++ .../Project/Database/Postgresql/General.php | 9 +- .../Project/Database/Redis/General.php | 11 +- .../Shared/EnvironmentVariable/All.php | 6 + app/Http/Livewire/Project/Shared/Logs.php | 13 +- app/Jobs/DatabaseBackupJob.php | 72 +++++++- app/Jobs/StopResourceJob.php | 10 +- app/Models/Environment.php | 12 +- app/Models/Project.php | 12 ++ app/Models/Server.php | 13 +- app/Models/StandaloneDocker.php | 18 ++ app/Models/StandaloneMariadb.php | 106 ++++++++++++ app/Models/StandaloneMysql.php | 106 ++++++++++++ app/Models/StandalonePostgresql.php | 2 - bootstrap/helpers/constants.php | 2 +- bootstrap/helpers/databases.php | 32 ++++ ..._103548_create_standalone_mysqls_table.php | 57 +++++++ ...20523_create_standalone_mariadbs_table.php | 57 +++++++ ...e_mysql_to_environment_variables_table.php | 30 ++++ .../components/databases/navbar.blade.php | 6 +- .../project/database/backup-edit.blade.php | 8 + .../database/backup-execution.blade.php | 8 - .../database/backup-executions.blade.php | 26 ++- .../database/mariadb/general.blade.php | 58 +++++++ .../project/database/mysql/general.blade.php | 58 +++++++ .../livewire/project/new/select.blade.php | 20 +++ .../database/backups/executions.blade.php | 2 +- .../project/database/configuration.blade.php | 42 +++-- 41 files changed, 1309 insertions(+), 88 deletions(-) create mode 100644 app/Actions/Database/StartMariadb.php create mode 100644 app/Actions/Database/StartMysql.php delete mode 100644 app/Http/Livewire/Project/Database/BackupExecution.php create mode 100644 app/Http/Livewire/Project/Database/Mariadb/General.php create mode 100644 app/Http/Livewire/Project/Database/Mysql/General.php create mode 100644 app/Models/StandaloneMariadb.php create mode 100644 app/Models/StandaloneMysql.php create mode 100644 database/migrations/2023_10_24_103548_create_standalone_mysqls_table.php create mode 100644 database/migrations/2023_10_24_120523_create_standalone_mariadbs_table.php create mode 100644 database/migrations/2023_10_24_120524_add_standalone_mysql_to_environment_variables_table.php delete mode 100644 resources/views/livewire/project/database/backup-execution.blade.php create mode 100644 resources/views/livewire/project/database/mariadb/general.blade.php create mode 100644 resources/views/livewire/project/database/mysql/general.blade.php diff --git a/app/Actions/Database/StartDatabaseProxy.php b/app/Actions/Database/StartDatabaseProxy.php index 15009019d..ffc91b86a 100644 --- a/app/Actions/Database/StartDatabaseProxy.php +++ b/app/Actions/Database/StartDatabaseProxy.php @@ -2,7 +2,9 @@ namespace App\Actions\Database; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Lorisleiva\Actions\Concerns\AsAction; @@ -12,7 +14,7 @@ class StartDatabaseProxy { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database) + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database) { $internalPort = null; if ($database->getMorphClass() === 'App\Models\StandaloneRedis') { @@ -21,6 +23,10 @@ class StartDatabaseProxy $internalPort = 5432; } else if ($database->getMorphClass() === 'App\Models\StandaloneMongodb') { $internalPort = 27017; + } else if ($database->getMorphClass() === 'App\Models\StandaloneMysql') { + $internalPort = 3306; + } else if ($database->getMorphClass() === 'App\Models\StandaloneMariadb') { + $internalPort = 3306; } $containerName = "{$database->uuid}-proxy"; $configuration_dir = database_proxy_dir($database->uuid); diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php new file mode 100644 index 000000000..75fd69adc --- /dev/null +++ b/app/Actions/Database/StartMariadb.php @@ -0,0 +1,158 @@ +database = $database; + + $container_name = $this->database->uuid; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; + + $this->commands = [ + "echo '####### Starting {$database->name}.'", + "mkdir -p $this->configuration_dir", + ]; + + $persistent_storages = $this->generate_local_persistent_volumes(); + $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); + $environment_variables = $this->generate_environment_variables(); + $this->add_custom_mysql(); + $docker_compose = [ + 'version' => '3.8', + 'services' => [ + $container_name => [ + 'image' => $this->database->image, + 'container_name' => $container_name, + 'environment' => $environment_variables, + 'restart' => RESTART_MODE, + 'networks' => [ + $this->database->destination->network, + ], + 'labels' => [ + 'coolify.managed' => 'true', + ], + 'healthcheck' => [ + 'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"], + 'interval' => '5s', + 'timeout' => '5s', + 'retries' => 10, + 'start_period' => '5s' + ], + 'mem_limit' => $this->database->limits_memory, + 'memswap_limit' => $this->database->limits_memory_swap, + 'mem_swappiness' => $this->database->limits_memory_swappiness, + 'mem_reservation' => $this->database->limits_memory_reservation, + 'cpus' => $this->database->limits_cpus, + 'cpuset' => $this->database->limits_cpuset, + 'cpu_shares' => $this->database->limits_cpu_shares, + ] + ], + 'networks' => [ + $this->database->destination->network => [ + 'external' => true, + 'name' => $this->database->destination->network, + 'attachable' => true, + ] + ] + ]; + if (count($this->database->ports_mappings_array) > 0) { + $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; + } + if (count($persistent_storages) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + } + if (count($volume_names) > 0) { + $docker_compose['volumes'] = $volume_names; + } + if (!is_null($this->database->mariadb_conf)) { + $docker_compose['services'][$container_name]['volumes'][] = [ + 'type' => 'bind', + 'source' => $this->configuration_dir . '/custom-config.cnf', + 'target' => '/etc/mysql/conf.d/custom-config.cnf', + 'read_only' => true, + ]; + } + $docker_compose = Yaml::dump($docker_compose, 10); + $docker_compose_base64 = base64_encode($docker_compose); + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $readme = generate_readme_file($this->database->name, now()); + $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; + $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + $this->commands[] = "echo '####### {$database->name} started.'"; + return remote_process($this->commands, $database->destination->server); + } + + private function generate_local_persistent_volumes() + { + $local_persistent_volumes = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + $volume_name = $persistentStorage->host_path ?? $persistentStorage->name; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; + } + return $local_persistent_volumes; + } + + private function generate_local_persistent_volumes_only_volume_names() + { + $local_persistent_volumes_names = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + if ($persistentStorage->host_path) { + continue; + } + $name = $persistentStorage->name; + $local_persistent_volumes_names[$name] = [ + 'name' => $name, + 'external' => false, + ]; + } + return $local_persistent_volumes_names; + } + + private function generate_environment_variables() + { + $environment_variables = collect(); + foreach ($this->database->runtime_environment_variables as $env) { + $environment_variables->push("$env->key=$env->value"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) { + $environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) { + $environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) { + $environment_variables->push("MARIADB_USER={$this->database->mariadb_user}"); + } + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) { + $environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}"); + } + return $environment_variables->all(); + } + private function add_custom_mysql() + { + if (is_null($this->database->mariadb_conf)) { + return; + } + $filename = 'custom-config.cnf'; + $content = $this->database->mariadb_conf; + $content_base64 = base64_encode($content); + $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; + } +} diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php new file mode 100644 index 000000000..8ee0db6e9 --- /dev/null +++ b/app/Actions/Database/StartMysql.php @@ -0,0 +1,158 @@ +database = $database; + + $container_name = $this->database->uuid; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; + + $this->commands = [ + "echo '####### Starting {$database->name}.'", + "mkdir -p $this->configuration_dir", + ]; + + $persistent_storages = $this->generate_local_persistent_volumes(); + $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); + $environment_variables = $this->generate_environment_variables(); + $this->add_custom_mysql(); + $docker_compose = [ + 'version' => '3.8', + 'services' => [ + $container_name => [ + 'image' => $this->database->image, + 'container_name' => $container_name, + 'environment' => $environment_variables, + 'restart' => RESTART_MODE, + 'networks' => [ + $this->database->destination->network, + ], + 'labels' => [ + 'coolify.managed' => 'true', + ], + 'healthcheck' => [ + 'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"], + 'interval' => '5s', + 'timeout' => '5s', + 'retries' => 10, + 'start_period' => '5s' + ], + 'mem_limit' => $this->database->limits_memory, + 'memswap_limit' => $this->database->limits_memory_swap, + 'mem_swappiness' => $this->database->limits_memory_swappiness, + 'mem_reservation' => $this->database->limits_memory_reservation, + 'cpus' => $this->database->limits_cpus, + 'cpuset' => $this->database->limits_cpuset, + 'cpu_shares' => $this->database->limits_cpu_shares, + ] + ], + 'networks' => [ + $this->database->destination->network => [ + 'external' => true, + 'name' => $this->database->destination->network, + 'attachable' => true, + ] + ] + ]; + if (count($this->database->ports_mappings_array) > 0) { + $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; + } + if (count($persistent_storages) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + } + if (count($volume_names) > 0) { + $docker_compose['volumes'] = $volume_names; + } + if (!is_null($this->database->mysql_conf)) { + $docker_compose['services'][$container_name]['volumes'][] = [ + 'type' => 'bind', + 'source' => $this->configuration_dir . '/custom-config.cnf', + 'target' => '/etc/mysql/conf.d/custom-config.cnf', + 'read_only' => true, + ]; + } + $docker_compose = Yaml::dump($docker_compose, 10); + $docker_compose_base64 = base64_encode($docker_compose); + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $readme = generate_readme_file($this->database->name, now()); + $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; + $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + $this->commands[] = "echo '####### {$database->name} started.'"; + return remote_process($this->commands, $database->destination->server); + } + + private function generate_local_persistent_volumes() + { + $local_persistent_volumes = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + $volume_name = $persistentStorage->host_path ?? $persistentStorage->name; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; + } + return $local_persistent_volumes; + } + + private function generate_local_persistent_volumes_only_volume_names() + { + $local_persistent_volumes_names = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + if ($persistentStorage->host_path) { + continue; + } + $name = $persistentStorage->name; + $local_persistent_volumes_names[$name] = [ + 'name' => $name, + 'external' => false, + ]; + } + return $local_persistent_volumes_names; + } + + private function generate_environment_variables() + { + $environment_variables = collect(); + foreach ($this->database->runtime_environment_variables as $env) { + $environment_variables->push("$env->key=$env->value"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) { + $environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) { + $environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) { + $environment_variables->push("MYSQL_USER={$this->database->mysql_user}"); + } + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) { + $environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}"); + } + return $environment_variables->all(); + } + private function add_custom_mysql() + { + if (is_null($this->database->mysql_conf)) { + return; + } + $filename = 'custom-config.cnf'; + $content = $this->database->mysql_conf; + $content_base64 = base64_encode($content); + $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; + } +} diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index 7e3f5f4c2..4f6a8c6c2 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -2,7 +2,9 @@ namespace App\Actions\Database; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Lorisleiva\Actions\Concerns\AsAction; @@ -11,7 +13,7 @@ class StopDatabase { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database) + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database) { $server = $database->destination->server; instant_remote_process( diff --git a/app/Actions/Database/StopDatabaseProxy.php b/app/Actions/Database/StopDatabaseProxy.php index 840e8ed56..d52d1961c 100644 --- a/app/Actions/Database/StopDatabaseProxy.php +++ b/app/Actions/Database/StopDatabaseProxy.php @@ -2,7 +2,9 @@ namespace App\Actions\Database; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Lorisleiva\Actions\Concerns\AsAction; @@ -11,7 +13,7 @@ class StopDatabaseProxy { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database) + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database) { instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server); $database->is_public = false; diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 1d1a5b14e..0e3983f7e 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -63,8 +63,12 @@ class ProjectController extends Controller $database = create_standalone_postgresql($environment->id, $destination_uuid); } else if ($type->value() === 'redis') { $database = create_standalone_redis($environment->id, $destination_uuid); - } else if ($type->value() === 'mongodb') { + } else if ($type->value() === 'mongodb') { $database = create_standalone_mongodb($environment->id, $destination_uuid); + } else if ($type->value() === 'mysql') { + $database = create_standalone_mysql($environment->id, $destination_uuid); + }else if ($type->value() === 'mariadb') { + $database = create_standalone_mariadb($environment->id, $destination_uuid); } return redirect()->route('project.database.configuration', [ 'project_uuid' => $project->uuid, @@ -72,7 +76,7 @@ class ProjectController extends Controller 'database_uuid' => $database->uuid, ]); } - if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) { + if ($type->startsWith('one-click-service-') && !is_null((int)$server_id)) { $oneClickServiceName = $type->after('one-click-service-')->value(); $oneClickService = data_get($services, "$oneClickServiceName.compose"); $oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null); diff --git a/app/Http/Livewire/Project/CloneProject.php b/app/Http/Livewire/Project/CloneProject.php index e12138d10..735bbc0da 100644 --- a/app/Http/Livewire/Project/CloneProject.php +++ b/app/Http/Livewire/Project/CloneProject.php @@ -117,6 +117,10 @@ class CloneProject extends Component $payload['standalone_redis_id'] = $newDatabase->id; } else if ($database->type() === 'standalone_mongodb') { $payload['standalone_mongodb_id'] = $newDatabase->id; + } else if ($database->type() === 'standalone_mysql') { + $payload['standalone_mysql_id'] = $newDatabase->id; + }else if ($database->type() === 'standalone_mariadb') { + $payload['standalone_mariadb_id'] = $newDatabase->id; } $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); $newEnvironmentVariable->save(); diff --git a/app/Http/Livewire/Project/Database/BackupExecution.php b/app/Http/Livewire/Project/Database/BackupExecution.php deleted file mode 100644 index 2f9d7dcb5..000000000 --- a/app/Http/Livewire/Project/Database/BackupExecution.php +++ /dev/null @@ -1,23 +0,0 @@ -execution->filename, $this->execution->scheduledDatabaseBackup->database->destination->server); - $this->execution->delete(); - $this->emit('success', 'Backup deleted successfully.'); - $this->emit('refreshBackupExecutions'); - } -} diff --git a/app/Http/Livewire/Project/Database/BackupExecutions.php b/app/Http/Livewire/Project/Database/BackupExecutions.php index 93da317f7..2f808d992 100644 --- a/app/Http/Livewire/Project/Database/BackupExecutions.php +++ b/app/Http/Livewire/Project/Database/BackupExecutions.php @@ -8,8 +8,21 @@ class BackupExecutions extends Component { public $backup; public $executions; - protected $listeners = ['refreshBackupExecutions']; + public $setDeletableBackup; + protected $listeners = ['refreshBackupExecutions', 'deleteBackup']; + public function deleteBackup($exeuctionId) + { + $execution = $this->backup->executions()->where('id', $exeuctionId)->first(); + if (is_null($execution)) { + $this->emit('error', 'Backup execution not found.'); + return; + } + delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server); + $execution->delete(); + $this->emit('success', 'Backup deleted successfully.'); + $this->emit('refreshBackupExecutions'); + } public function refreshBackupExecutions(): void { $this->executions = $this->backup->executions; diff --git a/app/Http/Livewire/Project/Database/CreateScheduledBackup.php b/app/Http/Livewire/Project/Database/CreateScheduledBackup.php index ac34e93bd..f804c389d 100644 --- a/app/Http/Livewire/Project/Database/CreateScheduledBackup.php +++ b/app/Http/Livewire/Project/Database/CreateScheduledBackup.php @@ -48,6 +48,10 @@ class CreateScheduledBackup extends Component ]; if ($this->database->type() === 'standalone-postgresql') { $payload['databases_to_backup'] = $this->database->postgres_db; + } else if ($this->database->type() === 'standalone-mysql') { + $payload['databases_to_backup'] = $this->database->mysql_database; + }else if ($this->database->type() === 'standalone-mariadb') { + $payload['databases_to_backup'] = $this->database->mariadb_database; } ScheduledDatabaseBackup::create($payload); $this->emit('refreshScheduledBackups'); diff --git a/app/Http/Livewire/Project/Database/Heading.php b/app/Http/Livewire/Project/Database/Heading.php index 6045e2b7f..7b14e5368 100644 --- a/app/Http/Livewire/Project/Database/Heading.php +++ b/app/Http/Livewire/Project/Database/Heading.php @@ -2,7 +2,9 @@ namespace App\Http\Livewire\Project\Database; +use App\Actions\Database\StartMariadb; use App\Actions\Database\StartMongodb; +use App\Actions\Database\StartMysql; use App\Actions\Database\StartPostgresql; use App\Actions\Database\StartRedis; use App\Actions\Database\StopDatabase; @@ -49,14 +51,18 @@ class Heading extends Component if ($this->database->type() === 'standalone-postgresql') { $activity = StartPostgresql::run($this->database); $this->emit('newMonitorActivity', $activity->id); - } - if ($this->database->type() === 'standalone-redis') { + } else if ($this->database->type() === 'standalone-redis') { $activity = StartRedis::run($this->database); $this->emit('newMonitorActivity', $activity->id); - } - if ($this->database->type() === 'standalone-mongodb') { + } else if ($this->database->type() === 'standalone-mongodb') { $activity = StartMongodb::run($this->database); $this->emit('newMonitorActivity', $activity->id); + } else if ($this->database->type() === 'standalone-mysql') { + $activity = StartMysql::run($this->database); + $this->emit('newMonitorActivity', $activity->id); + } else if ($this->database->type() === 'standalone-mariadb') { + $activity = StartMariadb::run($this->database); + $this->emit('newMonitorActivity', $activity->id); } } } diff --git a/app/Http/Livewire/Project/Database/Mariadb/General.php b/app/Http/Livewire/Project/Database/Mariadb/General.php new file mode 100644 index 000000000..4d04371d0 --- /dev/null +++ b/app/Http/Livewire/Project/Database/Mariadb/General.php @@ -0,0 +1,95 @@ + 'required', + 'database.description' => 'nullable', + 'database.mariadb_root_password' => 'required', + 'database.mariadb_user' => 'required', + 'database.mariadb_password' => 'required', + 'database.mariadb_database' => 'required', + 'database.mariadb_conf' => 'nullable', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', + ]; + protected $validationAttributes = [ + 'database.name' => 'Name', + 'database.description' => 'Description', + 'database.mariadb_root_password' => 'Root Password', + 'database.mariadb_user' => 'User', + 'database.mariadb_password' => 'Password', + 'database.mariadb_database' => 'Database', + 'database.mariadb_conf' => 'MariaDB Configuration', + 'database.image' => 'Image', + 'database.ports_mappings' => 'Port Mapping', + 'database.is_public' => 'Is Public', + 'database.public_port' => 'Public Port', + ]; + public function submit() + { + try { + $this->validate(); + $this->database->save(); + $this->emit('success', 'Database updated successfully.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + public function instantSave() + { + try { + if ($this->database->is_public && !$this->database->public_port) { + $this->emit('error', 'Public port is required.'); + $this->database->is_public = false; + return; + } + if ($this->database->is_public) { + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } + StartDatabaseProxy::run($this->database); + $this->emit('success', 'Database is now publicly accessible.'); + } else { + StopDatabaseProxy::run($this->database); + $this->emit('success', 'Database is no longer publicly accessible.'); + } + $this->db_url = $this->database->getDbUrl(); + $this->database->save(); + } catch (\Throwable $e) { + $this->database->is_public = !$this->database->is_public; + return handleError($e, $this); + } + } + public function refresh(): void + { + $this->database->refresh(); + } + + public function mount() + { + $this->db_url = $this->database->getDbUrl(); + } + + public function render() + { + return view('livewire.project.database.mariadb.general'); + } +} diff --git a/app/Http/Livewire/Project/Database/Mongodb/General.php b/app/Http/Livewire/Project/Database/Mongodb/General.php index e0fc3c277..2e6bed4a4 100644 --- a/app/Http/Livewire/Project/Database/Mongodb/General.php +++ b/app/Http/Livewire/Project/Database/Mongodb/General.php @@ -39,7 +39,8 @@ class General extends Component 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', ]; - public function submit() { + public function submit() + { try { $this->validate(); if ($this->database->mongo_conf === "") { @@ -60,7 +61,11 @@ class General extends Component return; } if ($this->database->is_public) { - $this->emit('success', 'Starting TCP proxy...'); + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } StartDatabaseProxy::run($this->database); $this->emit('success', 'Database is now publicly accessible.'); } else { @@ -69,7 +74,7 @@ class General extends Component } $this->db_url = $this->database->getDbUrl(); $this->database->save(); - } catch(\Throwable $e) { + } catch (\Throwable $e) { $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Http/Livewire/Project/Database/Mysql/General.php b/app/Http/Livewire/Project/Database/Mysql/General.php new file mode 100644 index 000000000..ca7eb6ebe --- /dev/null +++ b/app/Http/Livewire/Project/Database/Mysql/General.php @@ -0,0 +1,95 @@ + 'required', + 'database.description' => 'nullable', + 'database.mysql_root_password' => 'required', + 'database.mysql_user' => 'required', + 'database.mysql_password' => 'required', + 'database.mysql_database' => 'required', + 'database.mysql_conf' => 'nullable', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', + ]; + protected $validationAttributes = [ + 'database.name' => 'Name', + 'database.description' => 'Description', + 'database.mysql_root_password' => 'Root Password', + 'database.mysql_user' => 'User', + 'database.mysql_password' => 'Password', + 'database.mysql_database' => 'Database', + 'database.mysql_conf' => 'MySQL Configuration', + 'database.image' => 'Image', + 'database.ports_mappings' => 'Port Mapping', + 'database.is_public' => 'Is Public', + 'database.public_port' => 'Public Port', + ]; + public function submit() + { + try { + $this->validate(); + $this->database->save(); + $this->emit('success', 'Database updated successfully.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + public function instantSave() + { + try { + if ($this->database->is_public && !$this->database->public_port) { + $this->emit('error', 'Public port is required.'); + $this->database->is_public = false; + return; + } + if ($this->database->is_public) { + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } + StartDatabaseProxy::run($this->database); + $this->emit('success', 'Database is now publicly accessible.'); + } else { + StopDatabaseProxy::run($this->database); + $this->emit('success', 'Database is no longer publicly accessible.'); + } + $this->db_url = $this->database->getDbUrl(); + $this->database->save(); + } catch (\Throwable $e) { + $this->database->is_public = !$this->database->is_public; + return handleError($e, $this); + } + } + public function refresh(): void + { + $this->database->refresh(); + } + + public function mount() + { + $this->db_url = $this->database->getDbUrl(); + } + + public function render() + { + return view('livewire.project.database.mysql.general'); + } +} diff --git a/app/Http/Livewire/Project/Database/Postgresql/General.php b/app/Http/Livewire/Project/Database/Postgresql/General.php index df1f0da85..4e3bda418 100644 --- a/app/Http/Livewire/Project/Database/Postgresql/General.php +++ b/app/Http/Livewire/Project/Database/Postgresql/General.php @@ -60,7 +60,11 @@ class General extends Component return; } if ($this->database->is_public) { - $this->emit('success', 'Starting TCP proxy...'); + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } StartDatabaseProxy::run($this->database); $this->emit('success', 'Database is now publicly accessible.'); } else { @@ -69,11 +73,10 @@ class General extends Component } $this->db_url = $this->database->getDbUrl(); $this->database->save(); - } catch(\Throwable $e) { + } catch (\Throwable $e) { $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } - } public function save_init_script($script) { diff --git a/app/Http/Livewire/Project/Database/Redis/General.php b/app/Http/Livewire/Project/Database/Redis/General.php index 6f33ae30a..dd2e8151d 100644 --- a/app/Http/Livewire/Project/Database/Redis/General.php +++ b/app/Http/Livewire/Project/Database/Redis/General.php @@ -35,7 +35,8 @@ class General extends Component 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', ]; - public function submit() { + public function submit() + { try { $this->validate(); if ($this->database->redis_conf === "") { @@ -56,7 +57,11 @@ class General extends Component return; } if ($this->database->is_public) { - $this->emit('success', 'Starting TCP proxy...'); + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } StartDatabaseProxy::run($this->database); $this->emit('success', 'Database is now publicly accessible.'); } else { @@ -65,7 +70,7 @@ class General extends Component } $this->db_url = $this->database->getDbUrl(); $this->database->save(); - } catch(\Throwable $e) { + } catch (\Throwable $e) { $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php index f453b4bf3..9b714a590 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -81,6 +81,12 @@ class All extends Component case 'standalone-mongodb': $environment->standalone_mongodb_id = $this->resource->id; break; + case 'standalone-mysql': + $environment->standalone_mysql_id = $this->resource->id; + break; + case 'standalone-mariadb': + $environment->standalone_mariadb_id = $this->resource->id; + break; case 'service': $environment->service_id = $this->resource->id; break; diff --git a/app/Http/Livewire/Project/Shared/Logs.php b/app/Http/Livewire/Project/Shared/Logs.php index 80cdf82c4..2b0561800 100644 --- a/app/Http/Livewire/Project/Shared/Logs.php +++ b/app/Http/Livewire/Project/Shared/Logs.php @@ -5,7 +5,9 @@ namespace App\Http\Livewire\Project\Shared; use App\Models\Application; use App\Models\Server; use App\Models\Service; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Livewire\Component; @@ -13,7 +15,7 @@ use Livewire\Component; class Logs extends Component { public ?string $type = null; - public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb $resource; + public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource; public Server $server; public ?string $container = null; public $parameters; @@ -41,11 +43,16 @@ class Logs extends Component if (is_null($resource)) { $resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first(); if (is_null($resource)) { - abort(404); + $resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + abort(404); + } + } } } } - $this->resource = $resource; $this->status = $this->resource->status; $this->server = $this->resource->destination->server; diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index d73474018..da660c449 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -6,7 +6,9 @@ use App\Models\S3Storage; use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackupExecution; use App\Models\Server; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\Team; use App\Notifications\Database\BackupFailed; @@ -28,7 +30,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted public ?Team $team = null; public Server $server; public ScheduledDatabaseBackup $backup; - public StandalonePostgresql|StandaloneMongodb $database; + public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database; public ?string $container_name = null; public ?ScheduledDatabaseBackupExecution $backup_log = null; @@ -75,6 +77,10 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted $databasesToBackup = [$this->database->postgres_db]; } else if ($databaseType === 'standalone-mongodb') { $databasesToBackup = ['*']; + } else if ($databaseType === 'standalone-mysql') { + $databasesToBackup = [$this->database->mysql_database]; + } else if ($databaseType === 'standalone-mariadb') { + $databasesToBackup = [$this->database->mariadb_database]; } else { return; } @@ -88,6 +94,14 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted $databasesToBackup = explode('|', $databasesToBackup); $databasesToBackup = array_map('trim', $databasesToBackup); ray($databasesToBackup); + } else if ($databaseType === 'standalone-mysql') { + // Format: db1,db2,db3 + $databasesToBackup = explode(',', $databasesToBackup); + $databasesToBackup = array_map('trim', $databasesToBackup); + } else if ($databaseType === 'standalone-mariadb') { + // Format: db1,db2,db3 + $databasesToBackup = explode(',', $databasesToBackup); + $databasesToBackup = array_map('trim', $databasesToBackup); } else { return; } @@ -124,7 +138,6 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted } else { $databaseName = $database; } - ray($databaseName); } $this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz"; $this->backup_location = $this->backup_dir . $this->backup_file; @@ -134,6 +147,24 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted 'scheduled_database_backup_id' => $this->backup->id, ]); $this->backup_standalone_mongodb($database); + } else if ($databaseType === 'standalone-mysql') { + $this->backup_file = "/mysql-dump-$database-" . Carbon::now()->timestamp . ".dmp"; + $this->backup_location = $this->backup_dir . $this->backup_file; + $this->backup_log = ScheduledDatabaseBackupExecution::create([ + 'database_name' => $database, + 'filename' => $this->backup_location, + 'scheduled_database_backup_id' => $this->backup->id, + ]); + $this->backup_standalone_mysql($database); + } else if ($databaseType === 'standalone-mariadb') { + $this->backup_file = "/mariadb-dump-$database-" . Carbon::now()->timestamp . ".dmp"; + $this->backup_location = $this->backup_dir . $this->backup_file; + $this->backup_log = ScheduledDatabaseBackupExecution::create([ + 'database_name' => $database, + 'filename' => $this->backup_location, + 'scheduled_database_backup_id' => $this->backup->id, + ]); + $this->backup_standalone_mariadb($database); } else { throw new \Exception('Unsupported database type'); } @@ -218,7 +249,42 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted throw $e; } } - + private function backup_standalone_mysql(string $database): void + { + try { + $commands[] = "mkdir -p " . $this->backup_dir; + $commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location"; + ray($commands); + $this->backup_output = instant_remote_process($commands, $this->server); + $this->backup_output = trim($this->backup_output); + if ($this->backup_output === '') { + $this->backup_output = null; + } + ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location); + } catch (\Throwable $e) { + $this->add_to_backup_output($e->getMessage()); + ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); + throw $e; + } + } + private function backup_standalone_mariadb(string $database): void + { + try { + $commands[] = "mkdir -p " . $this->backup_dir; + $commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location"; + ray($commands); + $this->backup_output = instant_remote_process($commands, $this->server); + $this->backup_output = trim($this->backup_output); + if ($this->backup_output === '') { + $this->backup_output = null; + } + ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location); + } catch (\Throwable $e) { + $this->add_to_backup_output($e->getMessage()); + ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); + throw $e; + } + } private function add_to_backup_output($output): void { if ($this->backup_output) { diff --git a/app/Jobs/StopResourceJob.php b/app/Jobs/StopResourceJob.php index 721f7f698..76c5588b8 100644 --- a/app/Jobs/StopResourceJob.php +++ b/app/Jobs/StopResourceJob.php @@ -7,7 +7,9 @@ use App\Actions\Database\StopDatabase; use App\Actions\Service\StopService; use App\Models\Application; use App\Models\Service; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Illuminate\Bus\Queueable; @@ -21,7 +23,7 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb $resource) + public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource) { } @@ -45,6 +47,12 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted case 'standalone-mongodb': StopDatabase::run($this->resource); break; + case 'standalone-mysql': + StopDatabase::run($this->resource); + break; + case 'standalone-mariadb': + StopDatabase::run($this->resource); + break; case 'service': StopService::run($this->resource); break; diff --git a/app/Models/Environment.php b/app/Models/Environment.php index 55dbeee94..430a02cdb 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -34,13 +34,23 @@ class Environment extends Model { return $this->hasMany(StandaloneMongodb::class); } + public function mysqls() + { + return $this->hasMany(StandaloneMysql::class); + } + public function mariadbs() + { + return $this->hasMany(StandaloneMariadb::class); + } public function databases() { $postgresqls = $this->postgresqls; $redis = $this->redis; $mongodbs = $this->mongodbs; - return $postgresqls->concat($redis)->concat($mongodbs); + $mysqls = $this->mysqls; + $mariadbs = $this->mariadbs; + return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs); } public function project() diff --git a/app/Models/Project.php b/app/Models/Project.php index a910348b4..1668d4059 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -56,4 +56,16 @@ class Project extends BaseModel { return $this->hasManyThrough(StandaloneRedis::class, Environment::class); } + public function mongodbs() + { + return $this->hasManyThrough(StandaloneMongodb::class, Environment::class); + } + public function mysqls() + { + return $this->hasMany(StandaloneMysql::class, Environment::class); + } + public function mariadbs() + { + return $this->hasMany(StandaloneMariadb::class, Environment::class); + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 7ff517ef6..11be55764 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -122,10 +122,12 @@ class Server extends BaseModel public function databases() { return $this->destinations()->map(function ($standaloneDocker) { - $postgresqls = data_get($standaloneDocker,'postgresqls',collect([])); - $redis = data_get($standaloneDocker,'redis',collect([])); - $mongodbs = data_get($standaloneDocker,'mongodbs',collect([])); - return $postgresqls->concat($redis)->concat($mongodbs); + $postgresqls = data_get($standaloneDocker, 'postgresqls', collect([])); + $redis = data_get($standaloneDocker, 'redis', collect([])); + $mongodbs = data_get($standaloneDocker, 'mongodbs', collect([])); + $mysqls = data_get($standaloneDocker, 'mysqls', collect([])); + $mariadbs = data_get($standaloneDocker, 'mariadbs', collect([])); + return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs); })->flatten(); } public function applications() @@ -258,7 +260,8 @@ class Server extends BaseModel $this->settings->save(); return true; } - public function validateCoolifyNetwork() { + public function validateCoolifyNetwork() + { return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); } } diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index 9e70b7514..277a250c9 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -24,6 +24,14 @@ class StandaloneDocker extends BaseModel { return $this->morphMany(StandaloneMongodb::class, 'destination'); } + public function mysqls() + { + return $this->morphMany(StandaloneMysql::class, 'destination'); + } + public function mariadbs() + { + return $this->morphMany(StandaloneMariadb::class, 'destination'); + } public function server() { @@ -35,6 +43,16 @@ class StandaloneDocker extends BaseModel return $this->morphMany(Service::class, 'destination'); } + public function databases() + { + $postgresqls = $this->postgresqls; + $redis = $this->redis; + $mongodbs = $this->mongodbs; + $mysqls = $this->mysqls; + $mariadbs = $this->mariadbs; + return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs); + } + public function attachedTo() { return $this->applications?->count() > 0 || $this->databases?->count() > 0; diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php new file mode 100644 index 000000000..5e721857b --- /dev/null +++ b/app/Models/StandaloneMariadb.php @@ -0,0 +1,106 @@ + 'encrypted', + ]; + + protected static function booted() + { + static::created(function ($database) { + LocalPersistentVolume::create([ + 'name' => 'mariadb-data-' . $database->uuid, + 'mount_path' => '/var/lib/mysql', + 'host_path' => null, + 'resource_id' => $database->id, + 'resource_type' => $database->getMorphClass(), + 'is_readonly' => true + ]); + }); + static::deleting(function ($database) { + $storages = $database->persistentStorages()->get(); + foreach ($storages as $storage) { + instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false); + } + $database->scheduledBackups()->delete(); + $database->persistentStorages()->delete(); + $database->environment_variables()->delete(); + }); + } + public function type(): string + { + return 'standalone-mariadb'; + } + + public function portsMappings(): Attribute + { + return Attribute::make( + set: fn ($value) => $value === "" ? null : $value, + ); + } + + public function portsMappingsArray(): Attribute + { + return Attribute::make( + get: fn () => is_null($this->ports_mappings) + ? [] + : explode(',', $this->ports_mappings), + + ); + } + + public function getDbUrl(bool $useInternal = false): string + { + if ($this->is_public && !$useInternal) { + return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}"; + } else { + return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}"; + } + } + + public function environment() + { + return $this->belongsTo(Environment::class); + } + + public function fileStorages() + { + return $this->morphMany(LocalFileVolume::class, 'resource'); + } + + public function destination() + { + return $this->morphTo(); + } + + public function environment_variables(): HasMany + { + return $this->hasMany(EnvironmentVariable::class); + } + + public function runtime_environment_variables(): HasMany + { + return $this->hasMany(EnvironmentVariable::class); + } + + public function persistentStorages() + { + return $this->morphMany(LocalPersistentVolume::class, 'resource'); + } + + public function scheduledBackups() + { + return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); + } +} diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php new file mode 100644 index 000000000..53a797d49 --- /dev/null +++ b/app/Models/StandaloneMysql.php @@ -0,0 +1,106 @@ + 'encrypted', + 'mysql_root_password' => 'encrypted', + ]; + + protected static function booted() + { + static::created(function ($database) { + LocalPersistentVolume::create([ + 'name' => 'mysql-data-' . $database->uuid, + 'mount_path' => '/var/lib/mysql', + 'host_path' => null, + 'resource_id' => $database->id, + 'resource_type' => $database->getMorphClass(), + 'is_readonly' => true + ]); + }); + static::deleting(function ($database) { + $storages = $database->persistentStorages()->get(); + foreach ($storages as $storage) { + instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false); + } + $database->scheduledBackups()->delete(); + $database->persistentStorages()->delete(); + $database->environment_variables()->delete(); + }); + } + public function type(): string + { + return 'standalone-mysql'; + } + + public function portsMappings(): Attribute + { + return Attribute::make( + set: fn ($value) => $value === "" ? null : $value, + ); + } + + public function portsMappingsArray(): Attribute + { + return Attribute::make( + get: fn () => is_null($this->ports_mappings) + ? [] + : explode(',', $this->ports_mappings), + + ); + } + + public function getDbUrl(bool $useInternal = false): string + { + if ($this->is_public && !$useInternal) { + return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}"; + } else { + return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}"; + } + } + + public function environment() + { + return $this->belongsTo(Environment::class); + } + + public function fileStorages() + { + return $this->morphMany(LocalFileVolume::class, 'resource'); + } + + public function destination() + { + return $this->morphTo(); + } + + public function environment_variables(): HasMany + { + return $this->hasMany(EnvironmentVariable::class); + } + + public function runtime_environment_variables(): HasMany + { + return $this->hasMany(EnvironmentVariable::class); + } + + public function persistentStorages() + { + return $this->morphMany(LocalPersistentVolume::class, 'resource'); + } + + public function scheduledBackups() + { + return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); + } +} diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 3a0432180..bbfabbf67 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -46,8 +46,6 @@ class StandalonePostgresql extends BaseModel ); } - // Normal Deployments - public function portsMappingsArray(): Attribute { return Attribute::make( diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index 586ba531d..10c1353d3 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -1,6 +1,6 @@ '* * * * *', 'hourly' => '0 * * * *', diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php index 0c5c8898e..007c414bd 100644 --- a/bootstrap/helpers/databases.php +++ b/bootstrap/helpers/databases.php @@ -2,7 +2,9 @@ use App\Models\Server; use App\Models\StandaloneDocker; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Visus\Cuid2\Cuid2; @@ -58,6 +60,36 @@ function create_standalone_mongodb($environment_id, $destination_uuid): Standalo 'destination_type' => $destination->getMorphClass(), ]); } +function create_standalone_mysql($environment_id, $destination_uuid): StandaloneMysql +{ + $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); + if (!$destination) { + throw new Exception('Destination not found'); + } + return StandaloneMysql::create([ + 'name' => generate_database_name('mysql'), + 'mysql_root_password' => \Illuminate\Support\Str::password(symbols: false), + 'mysql_password' => \Illuminate\Support\Str::password(symbols: false), + 'environment_id' => $environment_id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + ]); +} +function create_standalone_mariadb($environment_id, $destination_uuid): StandaloneMariadb +{ + $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); + if (!$destination) { + throw new Exception('Destination not found'); + } + return StandaloneMariadb::create([ + 'name' => generate_database_name('mariadb'), + 'mariadb_root_password' => \Illuminate\Support\Str::password(symbols: false), + 'mariadb_password' => \Illuminate\Support\Str::password(symbols: false), + 'environment_id' => $environment_id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + ]); +} /** * Delete file locally on the filesystem. diff --git a/database/migrations/2023_10_24_103548_create_standalone_mysqls_table.php b/database/migrations/2023_10_24_103548_create_standalone_mysqls_table.php new file mode 100644 index 000000000..2b069424a --- /dev/null +++ b/database/migrations/2023_10_24_103548_create_standalone_mysqls_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name'); + $table->string('description')->nullable(); + + $table->text('mysql_root_password'); + $table->string('mysql_user')->default('mysql'); + $table->text('mysql_password'); + $table->string('mysql_database')->default('default'); + $table->longText('mysql_conf')->nullable(); + + $table->string('status')->default('exited'); + + $table->string('image')->default('mysql:8'); + $table->boolean('is_public')->default(false); + $table->integer('public_port')->nullable(); + $table->text('ports_mappings')->nullable(); + + $table->string('limits_memory')->default("0"); + $table->string('limits_memory_swap')->default("0"); + $table->integer('limits_memory_swappiness')->default(60); + $table->string('limits_memory_reservation')->default("0"); + + $table->string('limits_cpus')->default("0"); + $table->string('limits_cpuset')->nullable()->default("0"); + $table->integer('limits_cpu_shares')->default(1024); + + $table->timestamp('started_at')->nullable(); + $table->morphs('destination'); + + $table->foreignId('environment_id')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('standalone_mysqls'); + } +}; diff --git a/database/migrations/2023_10_24_120523_create_standalone_mariadbs_table.php b/database/migrations/2023_10_24_120523_create_standalone_mariadbs_table.php new file mode 100644 index 000000000..4d7b89f4c --- /dev/null +++ b/database/migrations/2023_10_24_120523_create_standalone_mariadbs_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name'); + $table->string('description')->nullable(); + + $table->text('mariadb_root_password'); + $table->string('mariadb_user')->default('mariadb'); + $table->text('mariadb_password'); + $table->string('mariadb_database')->default('default'); + $table->longText('mariadb_conf')->nullable(); + + $table->string('status')->default('exited'); + + $table->string('image')->default('mariadb:11'); + $table->boolean('is_public')->default(false); + $table->integer('public_port')->nullable(); + $table->text('ports_mappings')->nullable(); + + $table->string('limits_memory')->default("0"); + $table->string('limits_memory_swap')->default("0"); + $table->integer('limits_memory_swappiness')->default(60); + $table->string('limits_memory_reservation')->default("0"); + + $table->string('limits_cpus')->default("0"); + $table->string('limits_cpuset')->nullable()->default("0"); + $table->integer('limits_cpu_shares')->default(1024); + + $table->timestamp('started_at')->nullable(); + $table->morphs('destination'); + + $table->foreignId('environment_id')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('standalone_mariadbs'); + } +}; diff --git a/database/migrations/2023_10_24_120524_add_standalone_mysql_to_environment_variables_table.php b/database/migrations/2023_10_24_120524_add_standalone_mysql_to_environment_variables_table.php new file mode 100644 index 000000000..a511e9b21 --- /dev/null +++ b/database/migrations/2023_10_24_120524_add_standalone_mysql_to_environment_variables_table.php @@ -0,0 +1,30 @@ +foreignId('standalone_mysql_id')->nullable(); + $table->foreignId('standalone_mariadb_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('environment_variables', function (Blueprint $table) { + $table->dropColumn('standalone_mysql_id'); + $table->dropColumn('standalone_mariadb_id'); + }); + } +}; diff --git a/resources/views/components/databases/navbar.blade.php b/resources/views/components/databases/navbar.blade.php index 64dc1d288..ecf4ca573 100644 --- a/resources/views/components/databases/navbar.blade.php +++ b/resources/views/components/databases/navbar.blade.php @@ -7,7 +7,11 @@ href="{{ route('project.database.logs', $parameters) }}"> - @if ($database->getMorphClass() === 'App\Models\StandalonePostgresql' || $database->getMorphClass() === 'App\Models\StandaloneMongodb') + @if ( + $database->getMorphClass() === 'App\Models\StandalonePostgresql' || + $database->getMorphClass() === 'App\Models\StandaloneMongodb' || + $database->getMorphClass() === 'App\Models\StandaloneMysql' || + $database->getMorphClass() === 'App\Models\StandaloneMariadb') diff --git a/resources/views/livewire/project/database/backup-edit.blade.php b/resources/views/livewire/project/database/backup-edit.blade.php index 7d634a37b..0542ee087 100644 --- a/resources/views/livewire/project/database/backup-edit.blade.php +++ b/resources/views/livewire/project/database/backup-edit.blade.php @@ -35,6 +35,14 @@ + @elseif($backup->database_type === 'App\Models\StandaloneMysql') + + @elseif($backup->database_type === 'App\Models\StandaloneMariadb') + @endif
diff --git a/resources/views/livewire/project/database/backup-execution.blade.php b/resources/views/livewire/project/database/backup-execution.blade.php deleted file mode 100644 index cf306858d..000000000 --- a/resources/views/livewire/project/database/backup-execution.blade.php +++ /dev/null @@ -1,8 +0,0 @@ -
-
- - {{-- @if (data_get($execution, 'status') !== 'failed') --}} - {{-- Download --}} - {{-- @endif --}} - Delete -
diff --git a/resources/views/livewire/project/database/backup-executions.blade.php b/resources/views/livewire/project/database/backup-executions.blade.php index 30af9e93e..7b0a2ca51 100644 --- a/resources/views/livewire/project/database/backup-executions.blade.php +++ b/resources/views/livewire/project/database/backup-executions.blade.php @@ -1,9 +1,10 @@
@forelse($executions as $execution) -
data_get($execution, 'status') === 'success', - 'border-red-500' => data_get($execution, 'status') === 'failed', - ])> + data_get($execution, 'status') === 'success', + 'border-red-500' => data_get($execution, 'status') === 'failed', + ])>
Database: {{ data_get($execution, 'database_name', 'N/A') }}
Status: {{ data_get($execution, 'status') }}
Started At: {{ data_get($execution, 'created_at') }}
@@ -14,9 +15,24 @@ kB / {{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
Location: {{ data_get($execution, 'filename', 'N/A') }}
- +
+
+ + {{-- @if (data_get($execution, 'status') !== 'failed') --}} + {{-- Download --}} + {{-- @endif --}} + Delete +
@empty
No executions found.
@endforelse +
diff --git a/resources/views/livewire/project/database/mariadb/general.blade.php b/resources/views/livewire/project/database/mariadb/general.blade.php new file mode 100644 index 000000000..6b0205cbe --- /dev/null +++ b/resources/views/livewire/project/database/mariadb/general.blade.php @@ -0,0 +1,58 @@ +
+
+
+

General

+ + Save + +
+
+ + + +
+ @if ($database->started_at) +
+ + + + +
+ @else +
Please verify these values. You can only modify them before the initial + start. After that, you need to modify it in the database. +
+
+ + + + +
+ @endif +
+

Network

+
+ + + +
+ +
+ + +
diff --git a/resources/views/livewire/project/database/mysql/general.blade.php b/resources/views/livewire/project/database/mysql/general.blade.php new file mode 100644 index 000000000..09a23a694 --- /dev/null +++ b/resources/views/livewire/project/database/mysql/general.blade.php @@ -0,0 +1,58 @@ +
+
+
+

General

+ + Save + +
+
+ + + +
+ @if ($database->started_at) +
+ + + + +
+ @else +
Please verify these values. You can only modify them before the initial + start. After that, you need to modify it in the database. +
+
+ + + + +
+ @endif +
+

Network

+
+ + + +
+ +
+ + +
diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index 6ebefda41..a18d00fc5 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -114,6 +114,26 @@
+
+
+
+ New MySQL +
+
+ MySQL +
+
+
+
+
+
+ New Mariadb +
+
+ MySQL +
+
+
{{--
diff --git a/resources/views/project/database/backups/executions.blade.php b/resources/views/project/database/backups/executions.blade.php index 26d4aabd0..8c4625dcd 100644 --- a/resources/views/project/database/backups/executions.blade.php +++ b/resources/views/project/database/backups/executions.blade.php @@ -12,7 +12,7 @@
- +

Executions

diff --git a/resources/views/project/database/configuration.blade.php b/resources/views/project/database/configuration.blade.php index 8fbfa8f0c..3ad6e6df5 100644 --- a/resources/views/project/database/configuration.blade.php +++ b/resources/views/project/database/configuration.blade.php @@ -13,36 +13,48 @@
@if ($database->type() === 'standalone-postgresql') - @endif - @if ($database->type() === 'standalone-redis') + @elseif ($database->type() === 'standalone-redis') + @elseif ($database->type() === 'standalone-mongodb') + + @elseif ($database->type() === 'standalone-mysql') + + @elseif ($database->type() === 'standalone-mariadb') + @endif - @if ($database->type() === 'standalone-mongodb') - - @endif
From 72421d692befed5d32f77e1427c06d4bc8983074 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 24 Oct 2023 14:36:43 +0200 Subject: [PATCH 7/7] add slogans --- resources/views/livewire/project/new/select.blade.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index a18d00fc5..145e7a152 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -90,7 +90,7 @@ New PostgreSQL
- The most loved relational database in the world. + PostgreSQL is an open-source, object-relational database management system known for its robustness, advanced features, and strong standards compliance.
@@ -100,7 +100,7 @@ New Redis
- The open source, in-memory data store for cache, streaming engine, and message broker. + Redis is an open-source, in-memory data structure store used as a database, cache, and message broker, known for its high performance, flexibility, and rich data structures.
@@ -110,7 +110,7 @@ New MongoDB
- MongoDB is a source-available cross-platform document-oriented database program. + MongoDB is a source-available, NoSQL database program that uses JSON-like documents with optional schemas, known for its flexibility, scalability, and wide range of application use cases.
@@ -120,7 +120,7 @@ New MySQL
- MySQL + MySQL is an open-source relational database management system known for its speed, reliability, and flexibility in managing and accessing data.
@@ -130,7 +130,7 @@ New Mariadb
- MySQL + MariaDB is an open-source relational database management system that serves as a drop-in replacement for MySQL, offering more robust, scalable, and reliable SQL server capabilities.